% The format of the EdgeImage method is:
%
% Image *CannyEdgeImage(const Image *image,const double radius,
-% const double sigma,const double low_percent,const double high_percent,
-% const size_t threshold,ExceptionInfo *exception)
+% const double sigma,const double lower_precent,
+% const double upper_percent,ExceptionInfo *exception)
%
% A description of each parameter follows:
%
%
% o sigma: the sigma of the gaussian smoothing filter.
%
-% o low_percent: percentage of pixels in the low threshold.
+% o lower_precent: percentage of edge pixels in the lower threshold.
%
-% o high_percent: percentage of pixels in the high threshold.
+% o upper_percent: percentage of edge pixels in the upper threshold.
%
% o exception: return any errors or warnings in this structure.
%
typedef struct _CannyInfo
{
double
- Dx,
- Dy,
magnitude,
intensity;
+
+ int
+ orientation;
+
+ ssize_t
+ x,
+ y;
} CannyInfo;
-static MagickBooleanType IsAuthenticPixel(const Image *image,const ssize_t x,
- const ssize_t y)
+static inline MagickBooleanType IsAuthenticPixel(const Image *image,
+ const ssize_t x,const ssize_t y)
{
if ((x < 0) || (x >= (ssize_t) image->columns))
return(MagickFalse);
return(MagickTrue);
}
-static MagickBooleanType TraceEdge(Image *edge_image,CacheView *edge_view,
+static MagickBooleanType TraceEdges(Image *edge_image,CacheView *trace_view,
MatrixInfo *pixel_cache,const ssize_t x,const ssize_t y,
- const double threshold,ExceptionInfo *exception)
+ const double lower_threshold,ExceptionInfo *exception)
{
CannyInfo
pixel;
- Quantum
- *q;
+ size_t
+ number_edges;
- MagickBooleanType
- status;
+ if (GetMatrixElement(pixel_cache,0,0,&pixel) == MagickFalse)
+ return(MagickFalse);
+ pixel.x=x;
+ pixel.y=y;
+ if (SetMatrixElement(pixel_cache,0,0,&pixel) == MagickFalse)
+ return(MagickFalse);
+ number_edges=1;
+ do
+ {
+ MagickBooleanType
+ status;
+
+ ssize_t
+ v,
+ x_offset,
+ y_offset;
- q=GetCacheViewAuthenticPixels(edge_view,x,y,1,1,exception);
- if ((q != (Quantum *) NULL) && (GetPixelIntensity(edge_image,q) == 0))
+ number_edges--;
+ status=GetMatrixElement(pixel_cache,(ssize_t) number_edges,0,&pixel);
+ if (status == MagickFalse)
+ return(MagickFalse);
+ x_offset=pixel.x;
+ y_offset=pixel.y;
+ for (v=(-1); v <= 1; v++)
{
ssize_t
- v;
+ u;
- *q=QuantumRange;
- status=SyncCacheViewAuthenticPixels(edge_view,exception);
- if (status != MagickFalse)
- {
- for (v=(-1); v <= 1; v++)
- {
- ssize_t
- u;
+ for (u=(-1); u <= 1; u++)
+ {
+ Quantum
+ *q;
- for (u=(-1); u <= 1; u++)
- {
- if (((y != 0) || (u != 0)) &&
- (IsAuthenticPixel(edge_image,x+u,y+v) != MagickFalse))
- {
- (void) GetMatrixElement(pixel_cache,x+u,y+v,&pixel);
- if (pixel.intensity >= threshold)
- {
- status=TraceEdge(edge_image,edge_view,pixel_cache,x+u,y+v,
- threshold,exception);
- if (status != MagickFalse)
- return(MagickTrue);
- }
- }
- }
+ if ((u == 0) && (v == 0))
+ continue;
+ if (IsAuthenticPixel(edge_image,x_offset+u,y_offset+v) == MagickFalse)
+ continue;
+ /*
+ Not an edge if gradient value is below the lower threshold.
+ */
+ q=GetCacheViewAuthenticPixels(trace_view,x_offset+u,y_offset+v,1,1,
+ exception);
+ if (q == (Quantum *) NULL)
+ return(MagickFalse);
+ status=GetMatrixElement(pixel_cache,x_offset+u,y_offset+v,&pixel);
+ if (status == MagickFalse)
+ return(MagickFalse);
+ if ((pixel.intensity >= lower_threshold) &&
+ (GetPixelIntensity(edge_image,q) == 0))
+ {
+ *q=QuantumRange;
+ status=SyncCacheViewAuthenticPixels(trace_view,exception);
+ if (status == MagickFalse)
+ return(MagickFalse);
+ status=GetMatrixElement(pixel_cache,(ssize_t) number_edges,0,
+ &pixel);
+ if (status == MagickFalse)
+ return(MagickFalse);
+ pixel.x=x_offset+u;
+ pixel.y=y_offset+v;
+ status=SetMatrixElement(pixel_cache,(ssize_t) number_edges,0,
+ &pixel);
+ if (status == MagickFalse)
+ return(MagickFalse);
+ number_edges++;
}
- return(MagickTrue);
- }
+ }
}
- return(MagickFalse);
+ } while (number_edges != 0);
+ return(MagickTrue);
}
+
MagickExport Image *CannyEdgeImage(const Image *image,const double radius,
- const double sigma,const double low_percent,const double high_percent,
+ const double sigma,const double lower_precent,const double upper_percent,
ExceptionInfo *exception)
{
CacheView
- *edge_view;
+ *edge_view,
+ *trace_view;
+
+ CannyInfo
+ pixel;
char
geometry[MaxTextExtent];
double
- high_threshold,
- low_threshold;
+ lower_threshold,
+ upper_threshold;
Image
*edge_image;
assert(exception != (ExceptionInfo *) NULL);
assert(exception->signature == MagickSignature);
/*
- Filter out noise before trying to locate and detect any edges.
+ Filter out noise.
*/
(void) FormatLocaleString(geometry,MaxTextExtent,
"blur:%.20gx%.20g;blur:%.20gx%.20g+90",radius,sigma,radius,sigma);
return((Image *) NULL);
}
/*
- Find the edge strength by taking the gradient of the image.
+ Find the intensity gradient of the image.
*/
pixel_cache=AcquireMatrixInfo(edge_image->columns,edge_image->rows,
sizeof(CannyInfo),exception);
if (status == MagickFalse)
continue;
- p=GetCacheViewVirtualPixels(edge_view,-1,y-1,edge_image->columns+2,3,
+ p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns+1,2,
exception);
if (p == (const Quantum *) NULL)
{
CannyInfo
pixel;
+ double
+ dx,
+ dy;
+
+ int
+ orientation;
+
register const Quantum
*restrict kernel_pixels;
v;
static double
- Gx[3][3] =
+ Gx[2][2] =
{
- { -1.0, 0.0, +1.0 },
- { -2.0, 0.0, +2.0 },
- { -1.0, 0.0, +1.0 }
+ { -1.0, +1.0 },
+ { -1.0, +1.0 }
},
- Gy[3][3] =
+ Gy[2][2] =
{
- { +1.0, +2.0, +1.0 },
- { 0.0, 0.0, 0.0 },
- { -1.0, -2.0, -1.0 }
+ { +1.0, +1.0 },
+ { -1.0, -1.0 }
};
(void) ResetMagickMemory(&pixel,0,sizeof(pixel));
+ dx=0.0;
+ dy=0.0;
kernel_pixels=p;
- for (v=0; v < 3; v++)
+ for (v=0; v < 2; v++)
{
ssize_t
u;
- for (u=0; u < 3; u++)
+ for (u=0; u < 2; u++)
{
double
intensity;
intensity=GetPixelIntensity(edge_image,kernel_pixels+u);
- pixel.Dx+=Gx[v][u]*intensity;
- pixel.Dy+=Gy[v][u]*intensity;
+ dx+=0.5*Gx[v][u]*intensity;
+ dy+=0.5*Gy[v][u]*intensity;
}
- kernel_pixels+=edge_image->columns+2;
+ kernel_pixels+=edge_image->columns+1;
}
- pixel.magnitude=sqrt(pixel.Dx*pixel.Dx+pixel.Dy*pixel.Dy);
+ pixel.magnitude=sqrt(dx*dx+dy*dy);
+ pixel.orientation=2;
+ if (fabs(dx) > MagickEpsilon)
+ {
+ double
+ theta;
+
+ theta=dy/dx;
+ if (theta < 0.0)
+ {
+ if (theta < -2.41421356237)
+ pixel.orientation=2;
+ else
+ if (theta < -0.414213562373)
+ pixel.orientation=1;
+ else
+ pixel.orientation=0;
+ }
+ else
+ {
+ if (theta > 2.41421356237)
+ pixel.orientation=2;
+ else
+ if (theta > 0.414213562373)
+ pixel.orientation=3;
+ else
+ pixel.orientation=0;
+ }
+ }
if (SetMatrixElement(pixel_cache,x,y,&pixel) == MagickFalse)
continue;
p+=GetPixelChannels(edge_image);
}
edge_view=DestroyCacheView(edge_view);
/*
- Non-maxima suppression; reset edge image.
+ Non-maxima suppression, remove pixels that are not considered to be part
+ of an edge.
*/
- histogram=(size_t *) AcquireQuantumMemory(65536,sizeof(*histogram));
- if (histogram == (size_t *) NULL)
- {
- pixel_cache=DestroyMatrixInfo(pixel_cache);
- edge_image=DestroyImage(edge_image);
- return((Image *) NULL);
- }
- (void) ResetMagickMemory(histogram,0,65536*sizeof(*histogram));
+ (void) GetMatrixElement(pixel_cache,0,0,&pixel);
+ max=pixel.intensity;
+ min=pixel.intensity;
edge_view=AcquireAuthenticCacheView(edge_image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
beta_pixel,
pixel;
- ssize_t
- direction;
-
(void) GetMatrixElement(pixel_cache,x,y,&pixel);
- direction=2;
- if (pixel.Dx != 0.0)
- {
- double
- sector;
-
- sector=pixel.Dy/pixel.Dx;
- if (sector < 0.0)
- {
- if (sector < -2.41421356237)
- direction=0;
- else
- if (sector < -0.414213562373)
- direction=1;
- else
- direction=2;
- }
- else
- {
- if (sector > 2.41421356237)
- direction=0;
- else
- if (sector > 0.414213562373)
- direction=3;
- else
- direction=2;
- }
- }
- switch (direction)
+ switch (pixel.orientation)
{
case 0:
{
/*
0 degrees.
*/
- (void) GetMatrixElement(pixel_cache,x,y-1,&alpha_pixel);
- (void) GetMatrixElement(pixel_cache,x,y+1,&beta_pixel);
+ (void) GetMatrixElement(pixel_cache,x-1,y,&alpha_pixel);
+ (void) GetMatrixElement(pixel_cache,x+1,y,&beta_pixel);
break;
}
case 1:
/*
90 degrees.
*/
- (void) GetMatrixElement(pixel_cache,x-1,y,&alpha_pixel);
- (void) GetMatrixElement(pixel_cache,x+1,y,&beta_pixel);
+ (void) GetMatrixElement(pixel_cache,x,y-1,&alpha_pixel);
+ (void) GetMatrixElement(pixel_cache,x,y+1,&beta_pixel);
+ break;
}
case 3:
{
/*
135 degrees.
*/
- (void) GetMatrixElement(pixel_cache,x+1,y-1,&alpha_pixel);
- (void) GetMatrixElement(pixel_cache,x-1,y+1,&beta_pixel);
+ (void) GetMatrixElement(pixel_cache,x-1,y+1,&alpha_pixel);
+ (void) GetMatrixElement(pixel_cache,x+1,y-1,&beta_pixel);
+ break;
}
}
pixel.intensity=pixel.magnitude;
if ((pixel.magnitude < alpha_pixel.magnitude) ||
(pixel.magnitude < beta_pixel.magnitude))
pixel.intensity=0;
- else
- if (pixel.magnitude > QuantumRange)
- pixel.intensity=QuantumRange;
(void) SetMatrixElement(pixel_cache,x,y,&pixel);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp critical (MagickCore_CannyEdgeImage)
#endif
- histogram[ScaleQuantumToShort(ClampToQuantum(pixel.intensity))]++;
+ {
+ if (pixel.intensity < min)
+ min=pixel.intensity;
+ if (pixel.intensity > max)
+ max=pixel.intensity;
+ }
*q=0;
q+=GetPixelChannels(edge_image);
}
/*
Estimate hysteresis threshold.
*/
- number_pixels=(size_t) (low_percent*(image->columns*image->rows-
- histogram[0]));
- count=0;
- for (i=65535; count < (ssize_t) number_pixels; i--)
- count+=histogram[i];
- high_threshold=(double) ScaleShortToQuantum((unsigned short) i);
- for (i=0; histogram[i] == 0; i++) ;
- low_threshold=high_percent*(high_threshold+
- ScaleShortToQuantum((unsigned short) i));
- histogram=(size_t *) RelinquishMagickMemory(histogram);
+ lower_threshold=lower_percent*(max-min)+min;
+ upper_threshold=upper_percent*(max-min)+min;
/*
- Hysteresis thresholding.
+ Hysteresis threshold.
*/
edge_view=AcquireAuthenticCacheView(edge_image,exception);
+ trace_view=AcquireAuthenticCacheView(edge_image,exception);
#if defined(MAGICKCORE_OPENMP_SUPPORT)
#pragma omp parallel for schedule(static,4) shared(status) \
magick_threads(edge_image,edge_image,edge_image->rows,1)
#endif
for (y=0; y < (ssize_t) edge_image->rows; y++)
{
- register Quantum
- *restrict q;
-
register ssize_t
x;
if (status == MagickFalse)
continue;
- q=GetCacheViewAuthenticPixels(edge_view,0,y,edge_image->columns,1,
- exception);
- if (q == (Quantum *) NULL)
- {
- status=MagickFalse;
- continue;
- }
for (x=0; x < (ssize_t) edge_image->columns; x++)
{
CannyInfo
pixel;
- (void) GetMatrixElement(pixel_cache,x,y,&pixel);
- if (pixel.intensity >= high_threshold)
- (void) TraceEdge(edge_image,edge_view,pixel_cache,x,y,low_threshold,
- exception);
+ register Quantum
+ *restrict q;
+
+ /*
+ Edge if pixel gradient higher than upper threshold.
+ */
+ status=GetMatrixElement(pixel_cache,x,y,&pixel);
+ if (status == MagickFalse)
+ break;
+ q=GetCacheViewAuthenticPixels(edge_view,x,y,1,1,exception);
+ if (q == (PixelPacket *) NULL)
+ {
+ status=MagickFalse;
+ continue;
+ }
+ if ((pixel.intensity >= upper_threshold) &&
+ (GetPixelIntensity(edge_image,q) == 0))
+ {
+ *q=QuantumRange;
+ status=SyncCacheViewAuthenticPixels(edge_view,exception);
+ if (status == MagickFalse)
+ continue;
+ status=TraceEdges(edge_image,trace_view,pixel_cache,x,y,
+ lower_threshold,exception);
+ if (status == MagickFalse)
+ continue;
+ }
}
if (SyncCacheViewAuthenticPixels(edge_view,exception) == MagickFalse)
status=MagickFalse;
}
+ trace_view=DestroyCacheView(trace_view);
edge_view=DestroyCacheView(edge_view);
pixel_cache=DestroyMatrixInfo(pixel_cache);
return(edge_image);