]> granicus.if.org Git - imagemagick/commitdiff
New morphology kernels
authoranthony <anthony@git.imagemagick.org>
Thu, 13 May 2010 12:57:32 +0000 (12:57 +0000)
committeranthony <anthony@git.imagemagick.org>
Thu, 13 May 2010 12:57:32 +0000 (12:57 +0000)
ChangeLog
magick/morphology.c
magick/morphology.h
magick/option.c

index bbbea70334ffb5bb7f8c98b9d578606cf7e5bfc6..934201a108fe3b690e399bde6b4b095ad42dc675 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -8,7 +8,9 @@
   * Parsing of Multiple Kernel Strings, generating a list of kernels
   * Union of results from Multiple Kernels for HitAndMiss method
   * Interated Multiple Kernels for all other morphology methods
-  * Added Constant Kernels  Sobel:{angle} and   Laplacian:{type}
+  * Added many new kernels for HitAndMiss and Convolution
+    Sobel, Laplacian, Cross, Peaks, Corners, LineEnds, LineJunctions,
+    ConvexHull (for Thicken), Skeleton (for Thinning)
 
 2010-05-08  6.6.1-8 Glenn Randers-Pehrson <glennrp@image...>
   * Use proper background.index when writing indexed PNG, if the background
index 07afc17427fcd4966cc8ab65f09431dced4f666f..97273e244464e0244d624fc8258b154ff5c098b4 100644 (file)
@@ -110,6 +110,16 @@ static void
   ExpandKernelInfo(KernelInfo *, double),
   RotateKernelInfo(KernelInfo *, double);
 \f
+
+/* Quick function to find last kernel in a kernel list */
+static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
+{
+  while (kernel->next != (KernelInfo *) NULL)
+    kernel = kernel->next;
+  return(kernel);
+}
+
+
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %                                                                             %
@@ -399,6 +409,7 @@ static KernelInfo *ParseNamedKernel(const char *kernel_string)
     case DiamondKernel:
     case DiskKernel:
     case PlusKernel:
+    case CrossKernel:
       /* If no scale given (a 0 scale is valid! - set it to 1.0 */
       if ( (flags & HeightValue) == 0 )
         args.sigma = 1.0;
@@ -461,12 +472,11 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
       }
 
       /* initialise or append the kernel list */
-      if ( last_kernel == (KernelInfo *) NULL )
-        kernel = last_kernel = new_kernel;
-      else {
+      if ( kernel == (KernelInfo *) NULL )
+        kernel = new_kernel;
+      else
         last_kernel->next = new_kernel;
-        last_kernel = new_kernel;
-      }
+      last_kernel = LastKernelInfo(new_kernel);
     }
 
     /* look for the next kernel in list */
@@ -544,13 +554,6 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %
 %    # Still to be implemented...
 %    #
-%    # Sharpen:{radius},{sigma}
-%    #    Negated Gaussian (center zeroed and re-normalized),
-%    #    with a 2 unit positive peak.   -- Check On line documentation
-%    #
-%    # LOG:{radius},{sigma1},{sigma2}
-%    #    Laplacian of Gaussian
-%    #
 %    # DOG:{radius},{sigma1},{sigma2}
 %    #    Difference of two Gaussians
 %    #
@@ -576,7 +579,7 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %        Type 1            3x3: central 4 edge -1 corner 0
 %        Type 2            3x3: central 4 edge 1 corner -2
 %        Type 3   a  5x5 laplacian
-%        Type 5   a  7x7 laplacian
+%        Type 4   a  7x7 laplacian
 %
 %  Boolean Kernels
 %
@@ -619,23 +622,39 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %       larger radius is preferred over iterating the morphological operation.
 %
 %    Plus:[{radius}[,{scale}]]
-%       Generate a kernel in the shape of a 'plus' sign. The length of each
-%       arm is also the radius, which defaults to 2.
+%    Cross:[{radius}[,{scale}]]
+%       Generate a kernel in the shape of a 'plus' or a cross. The length of
+%       each arm is also the radius, which defaults to 2.
+%
+%       NOTE: "plus:1" is equivelent to a "Diamond" kernel.
 %
 %       This kernel is not a good general morphological kernel, but is used
 %       more for highlighting and marking any single pixels in an image using,
 %       a "Dilate" or "Erode" method as appropriate.
 %
-%       NOTE: "plus:1" is equivelent to a "Diamond" kernel.
+%       For the same reasons iterating these kernels does not produce the
+%       same result as using a larger radius for the symbol.
+%
+%  Hit and Miss Kernels
 %
-%       Note that unlike other kernels iterating a plus does not produce the
-%       same result as using a larger radius for the cross.
+%    Peak:radius1,radius2
+%       Find a foreground inside a background ring of the given radii.
+%    Corners
+%       Find corners of a binary shape
+%    LineEnds
+%       Find end points of lines (for pruning a skeletion)
+%    LineJunctions
+%       Find three line junctions (in a skeletion)
+%    ConvexHull
+%       Octagonal thicken kernel, to generate convex hulls of 45 degrees
+%    Skeleton
+%       Thinning kernel, which leaves behind a skeletion of a shape
 %
 %  Distance Measuring Kernels
 %
-%    Chebyshev "[{radius}][x{scale}[%!]]"
-%    Manhatten "[{radius}][x{scale}[%!]]"
-%    Euclidean "[{radius}][x{scale}[%!]]"
+%    Chebyshev:[{radius}][x{scale}[%!]]
+%    Manhatten:[{radius}][x{scale}[%!]]
+%    Euclidean:[{radius}][x{scale}[%!]]
 %
 %       Different types of distance measuring methods, which are used with the
 %       a 'Distance' morphology method for generating a gradient based on
@@ -678,10 +697,6 @@ MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
 %       See the 'Distance' Morphological Method, for information of how it is
 %       applied.
 %
-%  # Hit-n-Miss Kernel-Lists -- Still to be implemented
-%  #
-%  # specifically for   Pruning,  Thinning,  Thickening
-%  #
 */
 
 MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
@@ -738,6 +753,11 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         kernel->maximum = kernel->values[
                          kernel->y*kernel->width+kernel->x ];
 
+        /* Normalize the 2D Gaussian Kernel
+        **
+        ** As it is normalized the divisor in the above kernel generator is
+        ** not needed, so is not done above.
+        */
         ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
 
         break;
@@ -797,7 +817,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
 
         /* Normalize the 1D Gaussian Kernel
         **
-        ** Because of this the divisor in the above kernel generator is
+        ** As it is normalized the divisor in the above kernel generator is
         ** not needed, so is not done above.
         */
         ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
@@ -826,7 +846,8 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
 
         /* A comet blur is half a gaussian curve, so that the object is
         ** blurred in one direction only.  This may not be quite the right
-        ** curve so may change in the future. The function must be normalised.
+        ** curve to use so may change in the future. The function must be
+        ** normalised after generation, which also resolves any clipping.
         */
 #if 1
 #define KernelRank 3
@@ -868,21 +889,21 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
     case LaplacianKernel:
       { kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
         switch ( (int) args->rho ) {
+          case 0:
+          default: /* default laplacian 'edge' filter */
+            kernel=ParseKernelArray("3x3: -1,-1,-1  -1,8,-1  -1,-1,-1");
+            break;
           case 1:
             kernel=ParseKernelArray("3x3:  0,-1,0  -1,4,-1  0,-1,0");
             break;
           case 2:
             kernel=ParseKernelArray("3x3: -2,1,-2  1,4,1  -2,1,-2");
             break;
-          case 3:
-          default: /* default laplacian 'edge' filter */
-            kernel=ParseKernelArray("3x3: -1,-1,-1  -1,8,-1  -1,-1,-1");
-            break;
-          case 4:   /* a 5x5 laplacian */
+          case 3:   /* a 5x5 laplacian */
             kernel=ParseKernelArray(
               "5x5: -4,-1,0,-1,-4 -1,2,3,2,-1  0,3,4,3,0 -1,2,3,2,-1  -4,-1,0,-1,-4");
             break;
-          case 5:   /* a 7x7 laplacian */
+          case 4:   /* a 7x7 laplacian */
             kernel=ParseKernelArray(
               "7x7:-10,-5,-2,-1,-2,-5,-10 -5,0,3,4,3,0,-5 -2,3,6,7,6,3,-2 -1,4,7,8,7,4,-1 -2,3,6,7,6,3,-2 -5,0,3,4,3,0,-5 -10,-5,-2,-1,-2,-5,-10" );
             break;
@@ -924,7 +945,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         if (kernel->values == (double *) NULL)
           return(DestroyKernelInfo(kernel));
 
-        /* set all kernel values to 1.0 */
+        /* set all kernel values to scale given */
         u=(long) kernel->width*kernel->height;
         for ( i=0; i < u; i++)
             kernel->values[i] = scale;
@@ -972,8 +993,8 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         if (kernel->values == (double *) NULL)
           return(DestroyKernelInfo(kernel));
 
-        /* set all kernel values within disk area to 1.0 */
-        for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
+        /* set all kernel values within disk area to scale given */
+        for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
           for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
             if ((u*u+v*v) <= limit)
               kernel->positive_range += kernel->values[i] = args->sigma;
@@ -995,7 +1016,7 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         if (kernel->values == (double *) NULL)
           return(DestroyKernelInfo(kernel));
 
-        /* set all kernel values along axises to 1.0 */
+        /* set all kernel values along axises to given scale */
         for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
           for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
             kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
@@ -1003,6 +1024,157 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
         break;
       }
+    case CrossKernel:
+      {
+        if (args->rho < 1.0)
+          kernel->width = kernel->height = 5;  /* default radius 2 */
+        else
+           kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
+        kernel->x = kernel->y = (long) (kernel->width-1)/2;
+
+        kernel->values=(double *) AcquireQuantumMemory(kernel->width,
+                              kernel->height*sizeof(double));
+        if (kernel->values == (double *) NULL)
+          return(DestroyKernelInfo(kernel));
+
+        /* set all kernel values along axises to given scale */
+        for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
+          for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
+            kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
+        kernel->minimum = kernel->maximum = args->sigma;   /* a flat shape */
+        kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
+        break;
+      }
+    /* HitAndMiss Kernels */
+    case PeaksKernel:
+      {
+        long
+          limit1,
+          limit2;
+
+        if (args->rho < args->sigma)
+          {
+            kernel->width = ((unsigned long)args->sigma)*2+1;
+            limit1 = (long)args->rho*args->rho;
+            limit2 = (long)args->sigma*args->sigma;
+          }
+        else
+          {
+            kernel->width = ((unsigned long)args->rho)*2+1;
+            limit1 = (long)args->sigma*args->sigma;
+            limit2 = (long)args->rho*args->rho;
+          }
+        if ( limit2 <= 0 )       /* default outer radius approx 3.5 */
+          kernel->width = 7L, limit2 = 11L;
+        kernel->height = kernel->width;
+        kernel->x = kernel->y = (long) (kernel->width-1)/2;
+        kernel->values=(double *) AcquireQuantumMemory(kernel->width,
+                              kernel->height*sizeof(double));
+        if (kernel->values == (double *) NULL)
+          return(DestroyKernelInfo(kernel));
+
+        /* set a ring of background points */
+        for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
+          for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
+            { long radius=u*u+v*v;
+              if ( limit1 <= radius && radius <= limit2)
+                kernel->values[i] = 0.0;
+              else
+                kernel->values[i] = nan;
+            }
+        /* central point is always foreground */
+        kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
+        kernel->positive_range = 1.0;
+        kernel->maximum = 1.0;
+        break;
+      }
+    case CornersKernel:
+      {
+        kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
+        kernel=ParseKernelArray("3x3: 0,0,-  0,1,1  -,1,-");
+        if (kernel == (KernelInfo *) NULL)
+          return(kernel);
+        kernel->type = type;
+        ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
+        break;
+      }
+    case LineEndsKernel:
+      {
+        kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
+        kernel=ParseKernelArray("3x3: 0,-,-  0,1,0  0,0,0");
+        if (kernel == (KernelInfo *) NULL)
+          return(kernel);
+        kernel->type = type;
+        ExpandKernelInfo(kernel, 45.0); /* Create a list of 8 rotated kernels */
+        break;
+      }
+    case LineJunctionsKernel:
+      {
+        KernelInfo
+          *new_kernel;
+        kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
+        /* first set of 4 kernels */
+        kernel=ParseKernelArray("3x3: -,1,-  -,1,-  1,-,1");
+        if (kernel == (KernelInfo *) NULL)
+          return(kernel);
+        kernel->type = type;
+        ExpandKernelInfo(kernel, 90.0);
+        /* append second set of 4 kernels */
+        new_kernel=ParseKernelArray("3x3: 1,-,-  -,1,-  1,-,1");
+        if (new_kernel == (KernelInfo *) NULL)
+          return(DestroyKernelInfo(kernel));
+        kernel->type = type;
+        ExpandKernelInfo(new_kernel, 90.0);
+        LastKernelInfo(kernel)->next = new_kernel;
+        /* append Thrid set of 4 kernels */
+        new_kernel=ParseKernelArray("3x3: -,1,-  -,1,1  1,-,-");
+        if (new_kernel == (KernelInfo *) NULL)
+          return(DestroyKernelInfo(kernel));
+        kernel->type = type;
+        ExpandKernelInfo(new_kernel, 90.0);
+        LastKernelInfo(kernel)->next = new_kernel;
+        break;
+      }
+    case ConvexHullKernel:
+      {
+        KernelInfo
+          *new_kernel;
+        kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
+        /* first set of 4 kernels */
+        kernel=ParseKernelArray("3x3: 1,1,-  1,0,-  1,-,0");
+        if (kernel == (KernelInfo *) NULL)
+          return(kernel);
+        kernel->type = type;
+        ExpandKernelInfo(kernel, 90.0);
+        /* append second set of 4 kernels */
+        new_kernel=ParseKernelArray("3x3: -,1,1  -,0,1  0,-,1");
+        if (new_kernel == (KernelInfo *) NULL)
+          return(DestroyKernelInfo(kernel));
+        kernel->type = type;
+        ExpandKernelInfo(new_kernel, 90.0);
+        LastKernelInfo(kernel)->next = new_kernel;
+        break;
+      }
+    case SkeletonKernel:
+      {
+        KernelInfo
+          *new_kernel;
+        kernel=DestroyKernelInfo(kernel); /* default kernel is not needed */
+        /* first set of 4 kernels - corners */
+        kernel=ParseKernelArray("3x3: 0,0,-  0,1,1  -,1,-");
+        if (kernel == (KernelInfo *) NULL)
+          return(kernel);
+        kernel->type = type;
+        ExpandKernelInfo(kernel, 90);
+        /* append second set of 4 kernels - edge middles */
+        new_kernel=ParseKernelArray("3x3: 0,0,0  -,1,-  1,1,1");
+        if (new_kernel == (KernelInfo *) NULL)
+          return(DestroyKernelInfo(kernel));
+        kernel->type = type;
+        ExpandKernelInfo(new_kernel, 90);
+        LastKernelInfo(kernel)->next = new_kernel;
+        break;
+      }
     /* Distance Measuring Kernels */
     case ChebyshevKernel:
       {
@@ -1065,7 +1237,6 @@ MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
         break;
       }
     /* Undefined Kernels */
-    case LOGKernel:
     case DOGKernel:
       perror("Kernel Type has not been defined yet\n");
       /* FALL THRU */
@@ -2122,6 +2293,11 @@ MagickExport Image *MorphologyImageChannel(const Image *image,
         this_kernel = this_kernel->next;
         kernel_number++;
       }
+      if ( kernel_number > 1 )
+        if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
+          fprintf(stderr, "Morphology %s:%lu ===> Total Changed %lu\n",
+                MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
+                count, total_changed);
       if ( total_changed == 0 )
         break;  /* no changes after processing all kernels - ABORT */
       /* prepare for next loop */
@@ -2132,7 +2308,7 @@ MagickExport Image *MorphologyImageChannel(const Image *image,
     old_image=DestroyImage(old_image);
   }
 
-  /* finished with kernel - destary any copy that was made */
+  /* finished with kernel - destroy any copy that was made */
   if ( curr_kernel != kernel )
     curr_kernel=DestroyKernelInfo(curr_kernel);
 
@@ -2226,13 +2402,14 @@ static void RotateKernelInfo(KernelInfo *kernel, double angle)
   if ( 337.5 < angle || angle <= 22.5 )
     return;   /* no change! - At least at this time */
 
+  /* Handle special cases */
   switch (kernel->type) {
     /* These built-in kernels are cylindrical kernels, rotating is useless */
     case GaussianKernel:
-    case LaplacianKernel:
-    case LOGKernel:
     case DOGKernel:
     case DiskKernel:
+    case PeaksKernel:
+    case LaplacianKernel:
     case ChebyshevKernel:
     case ManhattenKernel:
     case EuclideanKernel:
@@ -2243,6 +2420,7 @@ static void RotateKernelInfo(KernelInfo *kernel, double angle)
     case SquareKernel:
     case DiamondKernel:
     case PlusKernel:
+    case CrossKernel:
       return;
 
     /* These only allows a +/-90 degree rotation (by transpose) */
@@ -2255,11 +2433,7 @@ static void RotateKernelInfo(KernelInfo *kernel, double angle)
         angle -= 180;
       break;
 
-    /* these are freely rotatable in 90 degree units */
-    case SobelKernel:
-    case CometKernel:
-    case UndefinedKernel:
-    case UserDefinedKernel:
+    default:
       break;
   }
   /* Attempt rotations by 45 degrees */
index 33a8a30f5a09c5704dce5b8398254aced0fbd15e..542199184eb6c22af5c1e445f180ca6f449f1341 100644 (file)
@@ -30,19 +30,25 @@ typedef enum
   GaussianKernel,     /* Convolution Kernels */
   BlurKernel,
   CometKernel,
-  LaplacianKernel,
-  LOGKernel,
   DOGKernel,
+  SobelKernel,        /* Named Constant Convolution Kernels */
+  LaplacianKernel,
   RectangleKernel,    /* Shape Kernels */
   SquareKernel,
   DiamondKernel,
   DiskKernel,
   PlusKernel,
+  CrossKernel,
+  PeaksKernel,         /* Hit And Miss Kernels */
+  CornersKernel,
+  LineEndsKernel,
+  LineJunctionsKernel,
+  ConvexHullKernel,
+  SkeletonKernel,
   ChebyshevKernel,    /* Distance Measuring Kernels */
   ManhattenKernel,
   EuclideanKernel,
-  SobelKernel,
-  UserDefinedKernel   /* user specified kernel values */
+  UserDefinedKernel   /* User Specified Kernel Array */
 } KernelInfoType;
 
 typedef enum
index 89a736824655286baae0f30eb660c85423df3c68..8718b379715c8c32e9c3e65c1a8c5be36f3b151d 100644 (file)
@@ -1055,14 +1055,21 @@ static const OptionInfo
     { "Gaussian", (long) GaussianKernel, MagickFalse },
     { "Blur", (long) BlurKernel, MagickFalse },
     { "Comet", (long) CometKernel, MagickFalse },
-    { "Laplacian", (long) LaplacianKernel, MagickTrue }, /* not implemented */
     { "DOG", (long) DOGKernel, MagickTrue },             /* not implemented */
-    { "LOG", (long) LOGKernel, MagickTrue },             /* not implemented */
+    { "Sobel", (long) SobelKernel, MagickFalse },
+    { "Laplacian", (long) LaplacianKernel, MagickFalse },
     { "Rectangle", (long) RectangleKernel, MagickFalse },
     { "Square", (long) SquareKernel, MagickFalse },
     { "Diamond", (long) DiamondKernel, MagickFalse },
     { "Disk", (long) DiskKernel, MagickFalse },
     { "Plus", (long) PlusKernel, MagickFalse },
+    { "Cross", (long) CrossKernel, MagickFalse },
+    { "Peaks", (long) PeaksKernel,MagickFalse },
+    { "Corners", (long) CornersKernel,MagickFalse },
+    { "LineEnds", (long) LineEndsKernel,MagickFalse },
+    { "LineJunctions", (long) LineJunctionsKernel,MagickFalse },
+    { "ConvexHull", (long) ConvexHullKernel,MagickFalse },
+    { "Skeleton", (long) SkeletonKernel,MagickFalse },
     { "Chebyshev", (long) ChebyshevKernel, MagickFalse },
     { "Manhatten", (long) ManhattenKernel, MagickFalse },
     { "Euclidean", (long) EuclideanKernel, MagickFalse },