%
% Sobel:{angle}
% Sobel 'Edge' convolution kernel (3x3)
-% -1, 0, 1
-% -2, 0,-2
-% -1, 0, 1
+% | -1, 0, 1 |
+% | -2, 0,-2 |
+% | -1, 0, 1 |
+%
+% Sobel:{type},{angle}
+% Type 0: default un-nomalized version shown above.
+%
+% Type 1: As default but pre-normalized
+% | 1, 0, -1 |
+% | 2, 0, -2 | / 4
+% | 1, 0, -1 |
+%
+% Type 2: Diagonal version with same normalization as 1
+% | 1, 0, -1 |
+% | 2, 0, -2 | / 4
+% | 1, 0, -1 |
%
% Roberts:{angle}
% Roberts convolution kernel (3x3)
-% 0, 0, 0
-% -1, 1, 0
-% 0, 0, 0
+% | 0, 0, 0 |
+% | -1, 1, 0 |
+% | 0, 0, 0 |
+%
% Prewitt:{angle}
% Prewitt Edge convolution kernel (3x3)
-% -1, 0, 1
-% -1, 0, 1
-% -1, 0, 1
+% | -1, 0, 1 |
+% | -1, 0, 1 |
+% | -1, 0, 1 |
+%
% Compass:{angle}
% Prewitt's "Compass" convolution kernel (3x3)
-% -1, 1, 1
-% -1,-2, 1
-% -1, 1, 1
+% | -1, 1, 1 |
+% | -1,-2, 1 |
+% | -1, 1, 1 |
+%
% Kirsch:{angle}
% Kirsch's "Compass" convolution kernel (3x3)
-% -3,-3, 5
-% -3, 0, 5
-% -3,-3, 5
+% | -3,-3, 5 |
+% | -3, 0, 5 |
+% | -3,-3, 5 |
%
-% FreiChen:{type},{angle}
+% FreiChen:{angle}
% Frei-Chen Edge Detector is based on a kernel that is similar to
% the Sobel Kernel, but is designed to be isotropic. That is it takes
% into account the distance of the diagonal in the kernel.
%
-% Type 0: | 1, 0, -1 |
-% | sqrt(2), 0, -sqrt(2) |
-% | 1, 0, -1 |
+% | 1, 0, -1 |
+% | sqrt(2), 0, -sqrt(2) |
+% | 1, 0, -1 |
+%
+% FreiChen:{type},{angle}
+%
+% Frei-Chen Pre-weighted kernels...
+%
+% Type 0: default un-nomalized version shown above.
+%
+% Type 1: Orthogonal Kernel (same as type 11 below)
+% | 1, 0, -1 |
+% | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
+% | 1, 0, -1 |
+%
+% Type 2: Diagonal form of Kernel...
+% | 1, sqrt(2), 0 |
+% | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
+% | 0, -sqrt(2) -1 |
%
% However this kernel is als at the heart of the FreiChen Edge Detection
% Process which uses a set of 9 specially weighted kernel. These 9
% from each other, both the direction and the strength of the edge can be
% determined.
%
-% Type 1: | 1, 0, -1 |
-% | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
-% | 1, 0, -1 |
+% Type 10: All 9 of the following pre-weighted kernels...
%
-% Type 2: | 1, sqrt(2), 1 |
-% | 0, 0, 0 | / 2*sqrt(2)
-% | 1, sqrt(2), 1 |
+% Type 11: | 1, 0, -1 |
+% | sqrt(2), 0, -sqrt(2) | / 2*sqrt(2)
+% | 1, 0, -1 |
%
-% Type 3: | sqrt(2), -1, 0 |
-% | -1, 0, 1 | / 2*sqrt(2)
-% | 0, 1, -sqrt(2) |
+% Type 12: | 1, sqrt(2), 1 |
+% | 0, 0, 0 | / 2*sqrt(2)
+% | 1, sqrt(2), 1 |
%
-% Type 4: | 0, 1, -sqrt(2) |
-% | -1, 0, 1 | / 2*sqrt(2)
-% | sqrt(2), -1, 0 |
+% Type 13: | sqrt(2), -1, 0 |
+% | -1, 0, 1 | / 2*sqrt(2)
+% | 0, 1, -sqrt(2) |
%
-% Type 5: | 0, -1, 0 |
-% | 1, 0, 1 | / 2
-% | 0, -1, 0 |
+% Type 14: | 0, 1, -sqrt(2) |
+% | -1, 0, 1 | / 2*sqrt(2)
+% | sqrt(2), -1, 0 |
%
-% Type 6: | 1, 0, -1 |
-% | 0, 0, 0 | / 2
-% | -1, 0, 1 |
+% Type 15: | 0, -1, 0 |
+% | 1, 0, 1 | / 2
+% | 0, -1, 0 |
%
-% Type 7: | 1, -2, 1 |
-% | -2, 4, -2 | / 6
-% | -1, -2, 1 |
+% Type 16: | 1, 0, -1 |
+% | 0, 0, 0 | / 2
+% | -1, 0, 1 |
%
-% Type 8: | -2, 1, -2 |
-% | 1, 4, 1 | / 6
-% | -2, 1, -2 |
+% Type 17: | 1, -2, 1 |
+% | -2, 4, -2 | / 6
+% | -1, -2, 1 |
%
-% Type 9: | 1, 1, 1 |
-% | 1, 1, 1 | / 3
-% | 1, 1, 1 |
+% Type 18: | -2, 1, -2 |
+% | 1, 4, 1 | / 6
+% | -2, 1, -2 |
+%
+% Type 19: | 1, 1, 1 |
+% | 1, 1, 1 | / 3
+% | 1, 1, 1 |
%
% The first 4 are for edge detection, the next 4 are for line detection
% and the last is to add a average component to the results.
% Find any peak larger than the pixels the fall between the two radii.
% The default ring of pixels is as per "Ring".
% Edges
-% Find edges of a binary shape
+% Find flat orthogonal edges of a binary shape
% Corners
-% Find corners of a binary shape
-% Ridges
-% Find single pixel ridges or thin lines
-% Ridges2
-% Find 2 pixel thick ridges or lines
-% Ridges3
-% Find 2 pixel thick diagonal ridges (experimental)
-% LineEnds
+% Find 90 degree corners of a binary shape
+% LineEnds:type
% Find end points of lines (for pruning a skeletion)
+% Two types of lines ends (default to both) can be searched for
+% Type 0: All line ends
+% Type 1: single kernel for 4-conneected line ends
+% Type 2: single kernel for simple line ends
% LineJunctions
% Find three line junctions (within a skeletion)
+% Type 0: all line junctions
+% Type 1: Y Junction kernel
+% Type 2: Diagonal T Junction kernel
+% Type 3: Orthogonal T Junction kernel
+% Type 4: Diagonal X Junction kernel
+% Type 5: Orthogonal + Junction kernel
+% Ridges:type
+% Find single pixel ridges or thin lines
+% Type 1: Fine single pixel thick lines and ridges
+% Type 2: Find two pixel thick lines and ridges
% ConvexHull
% Octagonal thicken kernel, to generate convex hulls of 45 degrees
-% Skeleton
-% Thinning kernel, which leaves behind a skeletion of a shape
+% Skeleton:type
+% Traditional skeleton generating kernels.
+% Type 1: Tradional Skeleton kernel (4 connected skeleton)
+% Type 2: HIPR2 Skeleton kernel (8 connected skeleton)
+% Type 3: Experimental Variation to try to present left-right symmetry
+% Type 4: Experimental Variation to preserve left-right symmetry
%
% Distance Measuring Kernels
%
case CompassKernel:
case KirschKernel:
case FreiChenKernel:
- case CornersKernel: /* Hit and Miss kernels */
+ case EdgesKernel: /* Hit and Miss kernels */
+ case CornersKernel:
case LineEndsKernel:
case LineJunctionsKernel:
- case EdgesKernel:
case RidgesKernel:
- case Ridges2Kernel:
case ConvexHullKernel:
case SkeletonKernel:
- case MatKernel:
- /* A pre-generated kernel is not needed */
- break;
-#if 0 /* set to 1 to do a compile-time check that we haven't missed anything */
+ break; /* A pre-generated kernel is not needed */
+#if 0
+ /* set to 1 to do a compile-time check that we haven't missed anything */
case GaussianKernel:
case DoGKernel:
case LoGKernel:
break;
}
case SobelKernel:
- {
+#if 0
+ { /* Sobel with optional 'sub-types' */
+ switch ( (int) args->rho ) {
+ default:
+ case 0:
+ kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ case 1:
+ kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ ScaleKernelInfo(kernel, 0.25, NoValue);
+ break;
+ case 2:
+ kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ ScaleKernelInfo(kernel, 0.25, NoValue);
+ break;
+ }
+ if ( fabs(args->sigma) > MagickEpsilon )
+ /* Rotate by correctly supplied 'angle' */
+ RotateKernelInfo(kernel, args->sigma);
+ else if ( args->rho > 30.0 || args->rho < -30.0 )
+ /* Rotate by out of bounds 'type' */
+ RotateKernelInfo(kernel, args->rho);
+ break;
+ }
+#else
+ { /* Simple Sobel Kernel */
kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = type;
- RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
+ RotateKernelInfo(kernel, args->rho);
break;
}
+#endif
case RobertsKernel:
{
kernel=ParseKernelArray("3: 0,0,0 1,-1,0 0,0,0");
kernel->values[5] = -MagickSQ2;
CalcKernelMetaData(kernel); /* recalculate meta-data */
break;
+ case 2:
+ kernel=ParseKernelArray("3: 1,2,0 2,0,-2 0,-2,-1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ kernel->values[1] = kernel->values[3] = +MagickSQ2;
+ kernel->values[5] = kernel->values[7] = -MagickSQ2;
+ CalcKernelMetaData(kernel); /* recalculate meta-data */
+ ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
+ break;
+ case 10:
+ kernel=AcquireKernelInfo("FreiChen:11;FreiChen:12;FreiChen:13;FreiChen:14;FreiChen:15;FreiChen:16;FreiChen:17;FreiChen:18;FreiChen:19");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ break;
case 1:
+ case 11:
kernel=ParseKernelArray("3: 1,0,-1 2,0,-2 1,0,-1");
if (kernel == (KernelInfo *) NULL)
return(kernel);
CalcKernelMetaData(kernel); /* recalculate meta-data */
ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
break;
- case 2:
+ case 12:
kernel=ParseKernelArray("3: 1,2,1 0,0,0 1,2,1");
if (kernel == (KernelInfo *) NULL)
return(kernel);
CalcKernelMetaData(kernel);
ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
break;
- case 3:
+ case 13:
kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
if (kernel == (KernelInfo *) NULL)
return(kernel);
CalcKernelMetaData(kernel);
ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
break;
- case 4:
+ case 14:
kernel=ParseKernelArray("3: 0,1,-2 -1,0,1 2,-1,0");
if (kernel == (KernelInfo *) NULL)
return(kernel);
CalcKernelMetaData(kernel);
ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
break;
- case 5:
+ case 15:
kernel=ParseKernelArray("3: 0,-1,0 1,0,1 0,-1,0");
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = type;
ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
break;
- case 6:
+ case 16:
kernel=ParseKernelArray("3: 1,0,-1 0,0,0 -1,0,1");
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = type;
ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
break;
- case 7:
+ case 17:
kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 -1,-2,1");
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = type;
ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
break;
- case 8:
+ case 18:
kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = type;
ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
break;
- case 9:
+ case 19:
kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = type;
ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
break;
- case -1:
- kernel=AcquireKernelInfo("FreiChen:1;FreiChen:2;FreiChen:3;FreiChen:4;FreiChen:5;FreiChen:6;FreiChen:7;FreiChen:8;FreiChen:9");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- break;
}
if ( fabs(args->sigma) > MagickEpsilon )
/* Rotate by correctly supplied 'angle' */
ExpandRotateKernelInfo(kernel, 90.0); /* Expand 90 degree rotations */
break;
}
- case RidgesKernel:
- {
- kernel=ParseKernelArray("3x1:0,1,0");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- kernel->type = type;
- ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
- break;
- }
- case Ridges2Kernel:
- {
- KernelInfo
- *new_kernel;
- kernel=ParseKernelArray("4x1:0,1,1,0");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- kernel->type = type;
- ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
-#if 0
- /* 2 pixel diagonaly thick - 4 rotates - not needed? */
- new_kernel=ParseKernelArray("4x4>:0,-,-,- -,1,-,- -,-,1,- -,-,-,0'");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- ExpandRotateKernelInfo(new_kernel, 90.0); /* 4 rotated kernels */
- LastKernelInfo(kernel)->next = new_kernel;
-#endif
- /* kernels to find a stepped 'thick' line, 4 rotates * mirror */
- /* Unfortunatally we can not yet rotate a non-square kernel */
- /* But then we can't flip a non-symetrical kernel either */
- new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- break;
- }
case LineEndsKernel:
- {
- KernelInfo
- *new_kernel;
- kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- kernel->type = type;
- ExpandRotateKernelInfo(kernel, 90.0);
- /* append second set of 4 kernels */
- new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- ExpandRotateKernelInfo(new_kernel, 90.0);
- LastKernelInfo(kernel)->next = new_kernel;
+ { /* Kernels for finding the end of thin lines */
+ switch ( (int) args->rho ) {
+ case 0:
+ default:
+ /* set of kernels to find all end of lines */
+ kernel=AcquireKernelInfo("LineEnds:1>;LineEnds:2>");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ break;
+ case 1:
+ /* kernel for 4-connected line ends - no rotation */
+ kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ case 2:
+ /* kernel to add for 8-connected lines - no rotation */
+ kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ }
break;
}
case LineJunctionsKernel:
- {
+ { /* kernels for finding the junctions of multiple lines */
+ switch ( (int) args->rho ) {
+ case 0:
+ default:
+ /* set of kernels to find all line junctions */
+ kernel=AcquireKernelInfo("LineJunctions:1@;LineJunctions:2>");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ break;
+ case 1:
+ /* Y Junction */
+ kernel=ParseKernelArray("3: 1,-,1 -,1,- -,1,-");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ case 2:
+ /* Diagonal T Junctions */
+ kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ case 3:
+ /* Orthogonal T Junctions */
+ kernel=ParseKernelArray("3: -,-,- 1,1,1 -,1,-");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ case 4:
+ /* Diagonal X Junctions */
+ kernel=ParseKernelArray("3: 1,-,1 -,1,- 1,-,1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ case 5:
+ /* Orthogonal X Junctions - minimal diamond kernel */
+ kernel=ParseKernelArray("3: -,1,- 1,1,1 -,1,-");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ break;
+ }
+ break;
+ }
+ case RidgesKernel:
+ { /* Ridges - Ridge finding kernels */
KernelInfo
*new_kernel;
- /* first set of 4 kernels */
- kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- kernel->type = type;
- ExpandRotateKernelInfo(kernel, 45.0);
- /* append second set of 4 kernels */
- new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- ExpandRotateKernelInfo(new_kernel, 90.0);
- LastKernelInfo(kernel)->next = new_kernel;
+ switch ( (int) args->rho ) {
+ case 1:
+ default:
+ kernel=ParseKernelArray("3x1:0,1,0");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
+ break;
+ case 2:
+ kernel=ParseKernelArray("4x1:0,1,1,0");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotated kernels */
+
+ /* Kernels to find a stepped 'thick' line, 4 rotates + mirrors */
+ /* Unfortunatally we can not yet rotate a non-square kernel */
+ /* But then we can't flip a non-symetrical kernel either */
+ new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("4x3+2+1:0,1,1,- -,1,1,- -,1,1,0");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("4x3+1+1:-,1,1,0 -,1,1,- 0,1,1,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("4x3+2+1:-,1,1,0 -,1,1,- 0,1,1,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3x4+1+1:0,-,- 1,1,1 1,1,1 -,-,0");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3x4+1+2:0,-,- 1,1,1 1,1,1 -,-,0");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3x4+1+1:-,-,0 1,1,1 1,1,1 0,-,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3x4+1+2:-,-,0 1,1,1 1,1,1 0,-,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ break;
+ }
break;
}
case ConvexHullKernel:
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = type;
- ExpandRotateKernelInfo(kernel, 45.0);
- /* append the mirror versions too */
+ ExpandRotateKernelInfo(kernel, 90.0);
+ /* append the mirror versions too - no flip function yet */
new_kernel=ParseKernelArray("3: 1,1,1 1,0,- -,-,0");
if (new_kernel == (KernelInfo *) NULL)
return(DestroyKernelInfo(kernel));
new_kernel->type = type;
- ExpandRotateKernelInfo(new_kernel, 45.0);
+ ExpandRotateKernelInfo(new_kernel, 90.0);
LastKernelInfo(kernel)->next = new_kernel;
break;
}
case SkeletonKernel:
- { /* what is the best form for skeletonization by thinning? */
-#if 0
- /* Use a edge/corner pruning method to generate a skeleton.
- ** This actually works, but tends to generate slightly thick
- ** diagonals. Later thinning of those diagonals results in
- ** asymetrically thining.
- */
- kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- kernel->type = type;
- ExpandRotateKernelInfo(kernel, 45);
- break;
- }
-#endif
-#if 1
- /* This is like simple 'Edge' thinning, but with a extra two
- ** kernels (3 x 4 rotates => 12) to finish off the pruning
- ** of the diagonal lines.
- */
- KernelInfo
- *new_kernel;
- kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- kernel->type = type;
- new_kernel=ParseKernelArray("3: 0,0,0 0,1,1 1,1,-");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- new_kernel=ParseKernelArray("3: 0,0,0 1,1,0 -,1,1");
- if (new_kernel == (KernelInfo *) NULL)
- return(DestroyKernelInfo(kernel));
- new_kernel->type = type;
- LastKernelInfo(kernel)->next = new_kernel;
- ExpandMirrorKernelInfo(kernel);
- break;
-#endif
- }
- case MatKernel: /* experimental - MAT from a Distance Gradient */
{
KernelInfo
*new_kernel;
- /* Ridge Kernel but without the diagonal */
- kernel=ParseKernelArray("3x1: 0,1,0");
- if (kernel == (KernelInfo *) NULL)
- return(kernel);
- kernel->type = RidgesKernel;
- ExpandRotateKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
- /* Plus the 2 pixel ridges kernel - no diagonal */
- new_kernel=AcquireKernelBuiltIn(Ridges2Kernel,args);
- if (new_kernel == (KernelInfo *) NULL)
- return(kernel);
- LastKernelInfo(kernel)->next = new_kernel;
+ switch ( (int) args->rho ) {
+ case 1:
+ default:
+ /* Traditional Skeleton...
+ ** A cyclically rotated single kernel
+ */
+ kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ ExpandRotateKernelInfo(kernel, 45.0); /* 8 rotations */
+ break;
+ case 2:
+ /* HIPR Variation of the cyclic skeleton
+ ** Corners of the traditional method made more forgiving,
+ ** but the retain the same cyclic order.
+ */
+ kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ new_kernel=ParseKernelArray("3: -,0,0 1,1,0 -,1,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ ExpandRotateKernelInfo(kernel, 90.0); /* 4 rotations of the 2 kernels */
+ break;
+ case 3:
+ /* Jittered Skeleton: do top, then bottom, then each sides */
+ /* Do top edge */
+ kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ new_kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3: -,0,0 1,1,0 -,1,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ /* Do Bottom edge */
+ new_kernel=ParseKernelArray("3: 1,1,1 -,1,- 0,0,0");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3: -,1,- 1,1,0 -,0,0");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3: -,1,- 0,1,1 0,0,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ /* Last the two sides */
+ new_kernel=ParseKernelArray("3: 0,-,1 0,1,1 0,-,1");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3: 1,-,0 1,1,0 1,-,0");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(new_kernel);
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ break;
+ case 4:
+ /* Just a simple 'Edge' kernel, but with a extra two kernels
+ ** to finish off diagonal lines, top then bottom then sides.
+ ** Works well for test case but fails for general case.
+ */
+ kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
+ if (kernel == (KernelInfo *) NULL)
+ return(kernel);
+ kernel->type = type;
+ new_kernel=ParseKernelArray("3: 0,0,0 0,1,1 1,1,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ new_kernel=ParseKernelArray("3: 0,0,0 1,1,0 -,1,1");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ LastKernelInfo(kernel)->next = new_kernel;
+ ExpandMirrorKernelInfo(kernel);
+ /* Append a set of corner kernels */
+ new_kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
+ if (new_kernel == (KernelInfo *) NULL)
+ return(DestroyKernelInfo(kernel));
+ new_kernel->type = type;
+ ExpandRotateKernelInfo(new_kernel, 90.0);
+ LastKernelInfo(kernel)->next = new_kernel;
+ break;
+ }
break;
}
/* Distance Measuring Kernels */
case UnityKernel:
default:
{
- /* Unity or No-Op Kernel - 3x3 with 1 in center */
- kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
+ /* Unity or No-Op Kernel - Basically just a single pixel on its own */
+ kernel=ParseKernelArray("1:1");
if (kernel == (KernelInfo *) NULL)
return(kernel);
kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
case HitAndMissMorphology:
case ThinningMorphology:
case ThickenMorphology:
- /* kernel is user as is, without reflection */
+ /* kernel is used as is, without reflection */
break;
default:
assert("Not a Primitive Morphology Method" != (char *) NULL);
** neighbourhoods, 0.0 for background, and 1.0 for foreground
** with either Nan or 0.5 values for don't care.
**
- ** Note that this can produce negative results, though really
- ** only a positive match has any real value.
+ ** Note that this will never produce a meaningless negative
+ ** result. Such results can cause Thinning/Thicken to not work
+ ** correctly when used against a greyscale image.
*/
k = kernel->values;
k_pixels = p;
k_pixels += image->columns+kernel->width;
k_indexes += image->columns+kernel->width;
}
- /* Pattern Match only if min fg larger than min bg pixels */
+ /* Pattern Match if difference is positive */
min.red -= max.red; Maximize( min.red, 0.0 );
min.green -= max.green; Maximize( min.green, 0.0 );
min.blue -= max.blue; Maximize( min.blue, 0.0 );
result.index -= min.index;
break;
case ThickenMorphology:
- /* Union with original image (maximize) - or should this be + */
- Maximize( result.red, min.red );
- Maximize( result.green, min.green );
- Maximize( result.blue, min.blue );
- Maximize( result.opacity, min.opacity );
- Maximize( result.index, min.index );
+ /* Add the pattern matchs to the original */
+ result.red += min.red;
+ result.green += min.green;
+ result.blue += min.blue;
+ result.opacity += min.opacity;
+ result.index += min.index;
break;
default:
/* result directly calculated or assigned */
stage_limit = 2;
break;
case HitAndMissMorphology:
- kernel_limit = 1; /* no method or kernel iteration */
rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
- break;
+ /* FALL THUR */
case ThinningMorphology:
case ThickenMorphology:
- method_limit = kernel_limit; /* iterate method with each kernel */
+ method_limit = kernel_limit; /* iterate the whole method */
kernel_limit = 1; /* do not do kernel iteration */
- case DistanceMorphology:
- rslt_compose = NoCompositeOp; /* Re-iterate with multiple kernels */
break;
default:
break;
** below ensures the methematical compose method is applied in a
** purely mathematical way, and only to the selected channels.
** Turn off SVG composition 'alpha blending'.
+ **
+ ** The compose image order is specifically so that the new image can
+ ** be subtarcted 'Minus' from the collected result, to allow you to
+ ** convert a HitAndMiss methd into a Thinning method.
*/
if ( verbose == MagickTrue )
fprintf(stderr, " (compose \"%s\")",
MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
- (void) CompositeImageChannel(rslt_image,
+ (void) CompositeImageChannel(curr_image,
(ChannelType) (channel & ~SyncChannels), rslt_compose,
- curr_image, 0, 0);
+ rslt_image, 0, 0);
+ rslt_image = DestroyImage(rslt_image);
+ rslt_image = curr_image;
curr_image = (Image *) image; /* continue with original image */
}
if ( verbose == MagickTrue )
if ( artifact != (const char *) NULL)
ShowKernelInfo(curr_kernel);
- /* override the default handling of multi-kernel morphology results
- * if 'Undefined' use the default method
- * if 'None' (default for 'Convolve') re-iterate previous result
- * otherwise merge resulting images using compose method given
+ /* Override the default handling of multi-kernel morphology results
+ * If 'Undefined' use the default method
+ * If 'None' (default for 'Convolve') re-iterate previous result
+ * Otherwise merge resulting images using compose method given.
+ * Default for 'HitAndMiss' is 'Lighten'.
*/
compose = UndefinedCompositeOp; /* use default for method */
artifact = GetImageArtifact(image,"morphology:compose");