1547 lines
50 KiB
C
1547 lines
50 KiB
C
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% V V IIIII SSSSS IIIII OOO N N %
|
||
% V V I SS I O O NN N %
|
||
% V V I SSS I O O N N N %
|
||
% V V I SS I O O N NN %
|
||
% V IIIII SSSSS IIIII OOO N N %
|
||
% %
|
||
% %
|
||
% MagickCore Computer Vision Methods %
|
||
% %
|
||
% Software Design %
|
||
% Cristy %
|
||
% September 2014 %
|
||
% %
|
||
% %
|
||
% Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
|
||
% dedicated to making software imaging solutions freely available. %
|
||
% %
|
||
% You may not use this file except in compliance with the License. You may %
|
||
% obtain a copy of the License at %
|
||
% %
|
||
% https://imagemagick.org/script/license.php %
|
||
% %
|
||
% Unless required by applicable law or agreed to in writing, software %
|
||
% distributed under the License is distributed on an "AS IS" BASIS, %
|
||
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
|
||
% See the License for the specific language governing permissions and %
|
||
% limitations under the License. %
|
||
% %
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%
|
||
%
|
||
*/
|
||
|
||
#include "MagickCore/studio.h"
|
||
#include "MagickCore/artifact.h"
|
||
#include "MagickCore/blob.h"
|
||
#include "MagickCore/cache-view.h"
|
||
#include "MagickCore/color.h"
|
||
#include "MagickCore/color-private.h"
|
||
#include "MagickCore/colormap.h"
|
||
#include "MagickCore/colorspace.h"
|
||
#include "MagickCore/constitute.h"
|
||
#include "MagickCore/decorate.h"
|
||
#include "MagickCore/distort.h"
|
||
#include "MagickCore/draw.h"
|
||
#include "MagickCore/enhance.h"
|
||
#include "MagickCore/exception.h"
|
||
#include "MagickCore/exception-private.h"
|
||
#include "MagickCore/effect.h"
|
||
#include "MagickCore/gem.h"
|
||
#include "MagickCore/geometry.h"
|
||
#include "MagickCore/image-private.h"
|
||
#include "MagickCore/list.h"
|
||
#include "MagickCore/log.h"
|
||
#include "MagickCore/matrix.h"
|
||
#include "MagickCore/memory_.h"
|
||
#include "MagickCore/memory-private.h"
|
||
#include "MagickCore/monitor.h"
|
||
#include "MagickCore/monitor-private.h"
|
||
#include "MagickCore/montage.h"
|
||
#include "MagickCore/morphology.h"
|
||
#include "MagickCore/morphology-private.h"
|
||
#include "MagickCore/opencl-private.h"
|
||
#include "MagickCore/paint.h"
|
||
#include "MagickCore/pixel-accessor.h"
|
||
#include "MagickCore/pixel-private.h"
|
||
#include "MagickCore/property.h"
|
||
#include "MagickCore/quantum.h"
|
||
#include "MagickCore/resource_.h"
|
||
#include "MagickCore/signature-private.h"
|
||
#include "MagickCore/string_.h"
|
||
#include "MagickCore/string-private.h"
|
||
#include "MagickCore/thread-private.h"
|
||
#include "MagickCore/token.h"
|
||
#include "MagickCore/vision.h"
|
||
|
||
/*
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
% %
|
||
% %
|
||
% %
|
||
% C o n n e c t e d C o m p o n e n t s I m a g e %
|
||
% %
|
||
% %
|
||
% %
|
||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
|
||
%
|
||
% ConnectedComponentsImage() returns the connected-components of the image
|
||
% uniquely labeled. The returned connected components image colors member
|
||
% defines the number of unique objects. Choose from 4 or 8-way connectivity.
|
||
%
|
||
% You are responsible for freeing the connected components objects resources
|
||
% with this statement;
|
||
%
|
||
% objects = (CCObjectInfo *) RelinquishMagickMemory(objects);
|
||
%
|
||
% The format of the ConnectedComponentsImage method is:
|
||
%
|
||
% Image *ConnectedComponentsImage(const Image *image,
|
||
% const size_t connectivity,CCObjectInfo **objects,
|
||
% ExceptionInfo *exception)
|
||
%
|
||
% A description of each parameter follows:
|
||
%
|
||
% o image: the image.
|
||
%
|
||
% o connectivity: how many neighbors to visit, choose from 4 or 8.
|
||
%
|
||
% o objects: return the attributes of each unique object.
|
||
%
|
||
% o exception: return any errors or warnings in this structure.
|
||
%
|
||
*/
|
||
|
||
static int CCObjectInfoCompare(const void *x,const void *y)
|
||
{
|
||
CCObjectInfo
|
||
*p,
|
||
*q;
|
||
|
||
p=(CCObjectInfo *) x;
|
||
q=(CCObjectInfo *) y;
|
||
return((int) (q->area-(ssize_t) p->area));
|
||
}
|
||
|
||
MagickExport Image *ConnectedComponentsImage(const Image *image,
|
||
const size_t connectivity,CCObjectInfo **objects,ExceptionInfo *exception)
|
||
{
|
||
#define ConnectedComponentsImageTag "ConnectedComponents/Image"
|
||
|
||
CacheView
|
||
*component_view,
|
||
*image_view,
|
||
*object_view;
|
||
|
||
CCObjectInfo
|
||
*object;
|
||
|
||
char
|
||
*c;
|
||
|
||
const char
|
||
*artifact,
|
||
*metrics[CCMaxMetrics];
|
||
|
||
double
|
||
max_threshold,
|
||
min_threshold;
|
||
|
||
Image
|
||
*component_image;
|
||
|
||
MagickBooleanType
|
||
status;
|
||
|
||
MagickOffsetType
|
||
progress;
|
||
|
||
MatrixInfo
|
||
*equivalences;
|
||
|
||
RectangleInfo
|
||
bounding_box;
|
||
|
||
ssize_t
|
||
i;
|
||
|
||
size_t
|
||
size;
|
||
|
||
ssize_t
|
||
background_id,
|
||
connect4[2][2] = { { -1, 0 }, { 0, -1 } },
|
||
connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
|
||
dx,
|
||
dy,
|
||
first,
|
||
last,
|
||
n,
|
||
step,
|
||
y;
|
||
|
||
/*
|
||
Initialize connected components image attributes.
|
||
*/
|
||
assert(image != (Image *) NULL);
|
||
assert(image->signature == MagickCoreSignature);
|
||
if (image->debug != MagickFalse)
|
||
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
|
||
assert(exception != (ExceptionInfo *) NULL);
|
||
assert(exception->signature == MagickCoreSignature);
|
||
if (objects != (CCObjectInfo **) NULL)
|
||
*objects=(CCObjectInfo *) NULL;
|
||
component_image=CloneImage(image,0,0,MagickTrue,exception);
|
||
if (component_image == (Image *) NULL)
|
||
return((Image *) NULL);
|
||
component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
|
||
if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
|
||
{
|
||
component_image=DestroyImage(component_image);
|
||
ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
|
||
}
|
||
/*
|
||
Initialize connected components equivalences.
|
||
*/
|
||
size=image->columns*image->rows;
|
||
if (image->columns != (size/image->rows))
|
||
{
|
||
component_image=DestroyImage(component_image);
|
||
ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
|
||
}
|
||
equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
|
||
if (equivalences == (MatrixInfo *) NULL)
|
||
{
|
||
component_image=DestroyImage(component_image);
|
||
return((Image *) NULL);
|
||
}
|
||
for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
|
||
(void) SetMatrixElement(equivalences,n,0,&n);
|
||
object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
|
||
if (object == (CCObjectInfo *) NULL)
|
||
{
|
||
equivalences=DestroyMatrixInfo(equivalences);
|
||
component_image=DestroyImage(component_image);
|
||
ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
|
||
}
|
||
(void) memset(object,0,MaxColormapSize*sizeof(*object));
|
||
for (i=0; i < (ssize_t) MaxColormapSize; i++)
|
||
{
|
||
object[i].id=i;
|
||
object[i].bounding_box.x=(ssize_t) image->columns;
|
||
object[i].bounding_box.y=(ssize_t) image->rows;
|
||
GetPixelInfo(image,&object[i].color);
|
||
}
|
||
/*
|
||
Find connected components.
|
||
*/
|
||
status=MagickTrue;
|
||
progress=0;
|
||
image_view=AcquireVirtualCacheView(image,exception);
|
||
for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
|
||
dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
|
||
for (y=0; y < (ssize_t) image->rows; y++)
|
||
{
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
continue;
|
||
}
|
||
p+=GetPixelChannels(image)*image->columns;
|
||
for (x=0; x < (ssize_t) image->columns; x++)
|
||
{
|
||
PixelInfo
|
||
pixel,
|
||
target;
|
||
|
||
ssize_t
|
||
neighbor_offset,
|
||
obj,
|
||
offset,
|
||
ox,
|
||
oy,
|
||
root;
|
||
|
||
/*
|
||
Is neighbor an authentic pixel and a different color than the pixel?
|
||
*/
|
||
GetPixelInfoPixel(image,p,&pixel);
|
||
if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
|
||
((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
|
||
{
|
||
p+=GetPixelChannels(image);
|
||
continue;
|
||
}
|
||
neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
|
||
GetPixelChannels(image);
|
||
GetPixelInfoPixel(image,p+neighbor_offset,&target);
|
||
if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
|
||
{
|
||
p+=GetPixelChannels(image);
|
||
continue;
|
||
}
|
||
/*
|
||
Resolve this equivalence.
|
||
*/
|
||
offset=y*image->columns+x;
|
||
neighbor_offset=dy*image->columns+dx;
|
||
ox=offset;
|
||
status=GetMatrixElement(equivalences,ox,0,&obj);
|
||
while (obj != ox)
|
||
{
|
||
ox=obj;
|
||
status=GetMatrixElement(equivalences,ox,0,&obj);
|
||
}
|
||
oy=offset+neighbor_offset;
|
||
status=GetMatrixElement(equivalences,oy,0,&obj);
|
||
while (obj != oy)
|
||
{
|
||
oy=obj;
|
||
status=GetMatrixElement(equivalences,oy,0,&obj);
|
||
}
|
||
if (ox < oy)
|
||
{
|
||
status=SetMatrixElement(equivalences,oy,0,&ox);
|
||
root=ox;
|
||
}
|
||
else
|
||
{
|
||
status=SetMatrixElement(equivalences,ox,0,&oy);
|
||
root=oy;
|
||
}
|
||
ox=offset;
|
||
status=GetMatrixElement(equivalences,ox,0,&obj);
|
||
while (obj != root)
|
||
{
|
||
status=GetMatrixElement(equivalences,ox,0,&obj);
|
||
status=SetMatrixElement(equivalences,ox,0,&root);
|
||
}
|
||
oy=offset+neighbor_offset;
|
||
status=GetMatrixElement(equivalences,oy,0,&obj);
|
||
while (obj != root)
|
||
{
|
||
status=GetMatrixElement(equivalences,oy,0,&obj);
|
||
status=SetMatrixElement(equivalences,oy,0,&root);
|
||
}
|
||
status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
|
||
p+=GetPixelChannels(image);
|
||
}
|
||
}
|
||
}
|
||
/*
|
||
Label connected components.
|
||
*/
|
||
n=0;
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
for (y=0; y < (ssize_t) component_image->rows; y++)
|
||
{
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
Quantum
|
||
*magick_restrict q;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
|
||
q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
|
||
1,exception);
|
||
if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
|
||
{
|
||
status=MagickFalse;
|
||
continue;
|
||
}
|
||
for (x=0; x < (ssize_t) component_image->columns; x++)
|
||
{
|
||
ssize_t
|
||
id,
|
||
offset;
|
||
|
||
offset=y*image->columns+x;
|
||
status=GetMatrixElement(equivalences,offset,0,&id);
|
||
if (id != offset)
|
||
status=GetMatrixElement(equivalences,id,0,&id);
|
||
else
|
||
{
|
||
id=n++;
|
||
if (id >= (ssize_t) MaxColormapSize)
|
||
break;
|
||
}
|
||
status=SetMatrixElement(equivalences,offset,0,&id);
|
||
if (x < object[id].bounding_box.x)
|
||
object[id].bounding_box.x=x;
|
||
if (x >= (ssize_t) object[id].bounding_box.width)
|
||
object[id].bounding_box.width=(size_t) x;
|
||
if (y < object[id].bounding_box.y)
|
||
object[id].bounding_box.y=y;
|
||
if (y >= (ssize_t) object[id].bounding_box.height)
|
||
object[id].bounding_box.height=(size_t) y;
|
||
object[id].color.red+=QuantumScale*GetPixelRed(image,p);
|
||
object[id].color.green+=QuantumScale*GetPixelGreen(image,p);
|
||
object[id].color.blue+=QuantumScale*GetPixelBlue(image,p);
|
||
if (image->alpha_trait != UndefinedPixelTrait)
|
||
object[id].color.alpha+=QuantumScale*GetPixelAlpha(image,p);
|
||
if (image->colorspace == CMYKColorspace)
|
||
object[id].color.black+=QuantumScale*GetPixelBlack(image,p);
|
||
object[id].centroid.x+=x;
|
||
object[id].centroid.y+=y;
|
||
object[id].area++;
|
||
SetPixelIndex(component_image,(Quantum) id,q);
|
||
p+=GetPixelChannels(image);
|
||
q+=GetPixelChannels(component_image);
|
||
}
|
||
if (n > (ssize_t) MaxColormapSize)
|
||
break;
|
||
if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
|
||
status=MagickFalse;
|
||
if (image->progress_monitor != (MagickProgressMonitor) NULL)
|
||
{
|
||
MagickBooleanType
|
||
proceed;
|
||
|
||
progress++;
|
||
proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress,
|
||
image->rows);
|
||
if (proceed == MagickFalse)
|
||
status=MagickFalse;
|
||
}
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
image_view=DestroyCacheView(image_view);
|
||
equivalences=DestroyMatrixInfo(equivalences);
|
||
if (n > (ssize_t) MaxColormapSize)
|
||
{
|
||
object=(CCObjectInfo *) RelinquishMagickMemory(object);
|
||
component_image=DestroyImage(component_image);
|
||
ThrowImageException(ResourceLimitError,"TooManyObjects");
|
||
}
|
||
background_id=0;
|
||
min_threshold=0.0;
|
||
max_threshold=0.0;
|
||
component_image->colors=(size_t) n;
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
object[i].bounding_box.width-=(object[i].bounding_box.x-1);
|
||
object[i].bounding_box.height-=(object[i].bounding_box.y-1);
|
||
object[i].color.red/=(QuantumScale*object[i].area);
|
||
object[i].color.green/=(QuantumScale*object[i].area);
|
||
object[i].color.blue/=(QuantumScale*object[i].area);
|
||
if (image->alpha_trait != UndefinedPixelTrait)
|
||
object[i].color.alpha/=(QuantumScale*object[i].area);
|
||
if (image->colorspace == CMYKColorspace)
|
||
object[i].color.black/=(QuantumScale*object[i].area);
|
||
object[i].centroid.x/=object[i].area;
|
||
object[i].centroid.y/=object[i].area;
|
||
max_threshold+=object[i].area;
|
||
if (object[i].area > object[background_id].area)
|
||
background_id=i;
|
||
}
|
||
max_threshold+=MagickEpsilon;
|
||
n=(-1);
|
||
artifact=GetImageArtifact(image,"connected-components:background-id");
|
||
if (artifact != (const char *) NULL)
|
||
background_id=(ssize_t) StringToLong(artifact);
|
||
artifact=GetImageArtifact(image,"connected-components:area-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max area threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (((object[i].area < min_threshold) ||
|
||
(object[i].area >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:keep-colors");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
const char
|
||
*p;
|
||
|
||
/*
|
||
Keep selected objects based on color, merge others.
|
||
*/
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
object[i].merge=MagickTrue;
|
||
for (p=artifact; ; )
|
||
{
|
||
char
|
||
color[MagickPathExtent];
|
||
|
||
PixelInfo
|
||
pixel;
|
||
|
||
const char
|
||
*q;
|
||
|
||
for (q=p; *q != '\0'; q++)
|
||
if (*q == ';')
|
||
break;
|
||
(void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
|
||
MagickPathExtent));
|
||
(void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
|
||
object[i].merge=MagickFalse;
|
||
if (*q == '\0')
|
||
break;
|
||
p=q+1;
|
||
}
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:keep-ids");
|
||
if (artifact == (const char *) NULL)
|
||
artifact=GetImageArtifact(image,"connected-components:keep");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Keep selected objects based on id, merge others.
|
||
*/
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
object[i].merge=MagickTrue;
|
||
for (c=(char *) artifact; *c != '\0'; )
|
||
{
|
||
while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
|
||
c++;
|
||
first=(ssize_t) strtol(c,&c,10);
|
||
if (first < 0)
|
||
first+=(ssize_t) component_image->colors;
|
||
last=first;
|
||
while (isspace((int) ((unsigned char) *c)) != 0)
|
||
c++;
|
||
if (*c == '-')
|
||
{
|
||
last=(ssize_t) strtol(c+1,&c,10);
|
||
if (last < 0)
|
||
last+=(ssize_t) component_image->colors;
|
||
}
|
||
step=(ssize_t) (first > last ? -1 : 1);
|
||
for ( ; first != (last+step); first+=step)
|
||
object[first].merge=MagickFalse;
|
||
}
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:keep-top");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
CCObjectInfo
|
||
*top_objects;
|
||
|
||
ssize_t
|
||
top_ids;
|
||
|
||
/*
|
||
Keep top objects.
|
||
*/
|
||
top_ids=(ssize_t) StringToLong(artifact);
|
||
top_objects=(CCObjectInfo *) AcquireQuantumMemory(component_image->colors,
|
||
sizeof(*top_objects));
|
||
if (top_objects == (CCObjectInfo *) NULL)
|
||
{
|
||
object=(CCObjectInfo *) RelinquishMagickMemory(object);
|
||
component_image=DestroyImage(component_image);
|
||
ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
|
||
}
|
||
(void) memcpy(top_objects,object,component_image->colors*sizeof(*object));
|
||
qsort((void *) top_objects,component_image->colors,sizeof(*top_objects),
|
||
CCObjectInfoCompare);
|
||
for (i=top_ids+1; i < (ssize_t) component_image->colors; i++)
|
||
object[top_objects[i].id].merge=MagickTrue;
|
||
top_objects=(CCObjectInfo *) RelinquishMagickMemory(top_objects);
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:remove-colors");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
const char
|
||
*p;
|
||
|
||
/*
|
||
Remove selected objects based on color, keep others.
|
||
*/
|
||
for (p=artifact; ; )
|
||
{
|
||
char
|
||
color[MagickPathExtent];
|
||
|
||
PixelInfo
|
||
pixel;
|
||
|
||
const char
|
||
*q;
|
||
|
||
for (q=p; *q != '\0'; q++)
|
||
if (*q == ';')
|
||
break;
|
||
(void) CopyMagickString(color,p,(size_t) MagickMin(q-p+1,
|
||
MagickPathExtent));
|
||
(void) QueryColorCompliance(color,AllCompliance,&pixel,exception);
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (IsFuzzyEquivalencePixelInfo(&object[i].color,&pixel) != MagickFalse)
|
||
object[i].merge=MagickTrue;
|
||
if (*q == '\0')
|
||
break;
|
||
p=q+1;
|
||
}
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:remove-ids");
|
||
if (artifact == (const char *) NULL)
|
||
artifact=GetImageArtifact(image,"connected-components:remove");
|
||
if (artifact != (const char *) NULL)
|
||
for (c=(char *) artifact; *c != '\0'; )
|
||
{
|
||
/*
|
||
Remove selected objects based on id, keep others.
|
||
*/
|
||
while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
|
||
c++;
|
||
first=(ssize_t) strtol(c,&c,10);
|
||
if (first < 0)
|
||
first+=(ssize_t) component_image->colors;
|
||
last=first;
|
||
while (isspace((int) ((unsigned char) *c)) != 0)
|
||
c++;
|
||
if (*c == '-')
|
||
{
|
||
last=(ssize_t) strtol(c+1,&c,10);
|
||
if (last < 0)
|
||
last+=(ssize_t) component_image->colors;
|
||
}
|
||
step=(ssize_t) (first > last ? -1 : 1);
|
||
for ( ; first != (last+step); first+=step)
|
||
object[first].merge=MagickTrue;
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:perimeter-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max perimeter threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
metrics[++n]="perimeter";
|
||
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
||
#pragma omp parallel for schedule(dynamic) shared(status) \
|
||
magick_number_threads(component_image,component_image,component_image->colors,1)
|
||
#endif
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
CacheView
|
||
*component_view;
|
||
|
||
RectangleInfo
|
||
bounding_box;
|
||
|
||
size_t
|
||
pattern[4] = { 1, 0, 0, 0 };
|
||
|
||
ssize_t
|
||
y;
|
||
|
||
/*
|
||
Compute perimeter of each object.
|
||
*/
|
||
if (status == MagickFalse)
|
||
continue;
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
bounding_box=object[i].bounding_box;
|
||
for (y=(-1); y < (ssize_t) bounding_box.height+1; y++)
|
||
{
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
|
||
bounding_box.y+y,bounding_box.width+2,2,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=(-1); x < (ssize_t) bounding_box.width+1; x++)
|
||
{
|
||
Quantum
|
||
pixels[4];
|
||
|
||
ssize_t
|
||
v;
|
||
|
||
size_t
|
||
foreground;
|
||
|
||
/*
|
||
An Algorithm for Calculating Objects’ Shape Features in Binary
|
||
Images, Lifeng He, Yuyan Chao.
|
||
*/
|
||
foreground=0;
|
||
for (v=0; v < 2; v++)
|
||
{
|
||
ssize_t
|
||
u;
|
||
|
||
for (u=0; u < 2; u++)
|
||
{
|
||
ssize_t
|
||
offset;
|
||
|
||
offset=v*(bounding_box.width+2)*
|
||
GetPixelChannels(component_image)+u*
|
||
GetPixelChannels(component_image);
|
||
pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
|
||
if ((ssize_t) pixels[2*v+u] == i)
|
||
foreground++;
|
||
}
|
||
}
|
||
if (foreground == 1)
|
||
pattern[1]++;
|
||
else
|
||
if (foreground == 2)
|
||
{
|
||
if ((((ssize_t) pixels[0] == i) &&
|
||
((ssize_t) pixels[3] == i)) ||
|
||
(((ssize_t) pixels[1] == i) &&
|
||
((ssize_t) pixels[2] == i)))
|
||
pattern[0]++; /* diagonal */
|
||
else
|
||
pattern[2]++;
|
||
}
|
||
else
|
||
if (foreground == 3)
|
||
pattern[3]++;
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
object[i].metric[n]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
|
||
MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (((object[i].metric[n] < min_threshold) ||
|
||
(object[i].metric[n] >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:circularity-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max circularity threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
metrics[++n]="circularity";
|
||
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
||
#pragma omp parallel for schedule(dynamic) shared(status) \
|
||
magick_number_threads(component_image,component_image,component_image->colors,1)
|
||
#endif
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
CacheView
|
||
*component_view;
|
||
|
||
RectangleInfo
|
||
bounding_box;
|
||
|
||
size_t
|
||
pattern[4] = { 1, 0, 0, 0 };
|
||
|
||
ssize_t
|
||
y;
|
||
|
||
/*
|
||
Compute perimeter of each object.
|
||
*/
|
||
if (status == MagickFalse)
|
||
continue;
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
bounding_box=object[i].bounding_box;
|
||
for (y=(-1); y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
|
||
bounding_box.y+y,bounding_box.width+2,2,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=(-1); x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
Quantum
|
||
pixels[4];
|
||
|
||
ssize_t
|
||
v;
|
||
|
||
size_t
|
||
foreground;
|
||
|
||
/*
|
||
An Algorithm for Calculating Objects’ Shape Features in Binary
|
||
Images, Lifeng He, Yuyan Chao.
|
||
*/
|
||
foreground=0;
|
||
for (v=0; v < 2; v++)
|
||
{
|
||
ssize_t
|
||
u;
|
||
|
||
for (u=0; u < 2; u++)
|
||
{
|
||
ssize_t
|
||
offset;
|
||
|
||
offset=v*(bounding_box.width+2)*
|
||
GetPixelChannels(component_image)+u*
|
||
GetPixelChannels(component_image);
|
||
pixels[2*v+u]=GetPixelIndex(component_image,p+offset);
|
||
if ((ssize_t) pixels[2*v+u] == i)
|
||
foreground++;
|
||
}
|
||
}
|
||
if (foreground == 1)
|
||
pattern[1]++;
|
||
else
|
||
if (foreground == 2)
|
||
{
|
||
if ((((ssize_t) pixels[0] == i) &&
|
||
((ssize_t) pixels[3] == i)) ||
|
||
(((ssize_t) pixels[1] == i) &&
|
||
((ssize_t) pixels[2] == i)))
|
||
pattern[0]++; /* diagonal */
|
||
else
|
||
pattern[2]++;
|
||
}
|
||
else
|
||
if (foreground == 3)
|
||
pattern[3]++;
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
object[i].metric[n]=ceil(MagickSQ1_2*pattern[1]+1.0*pattern[2]+
|
||
MagickSQ1_2*pattern[3]+MagickSQ2*pattern[0]-0.5);
|
||
object[i].metric[n]=4.0*MagickPI*object[i].area/(object[i].metric[n]*
|
||
object[i].metric[n]);
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (((object[i].metric[n] < min_threshold) ||
|
||
(object[i].metric[n] >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:diameter-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max diameter threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
metrics[++n]="diameter";
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
object[i].metric[n]=ceil(sqrt(4.0*object[i].area/MagickPI)-0.5);
|
||
if (((object[i].metric[n] < min_threshold) ||
|
||
(object[i].metric[n] >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:major-axis-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max ellipse major threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
metrics[++n]="major-axis";
|
||
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
||
#pragma omp parallel for schedule(dynamic) shared(status) \
|
||
magick_number_threads(component_image,component_image,component_image->colors,1)
|
||
#endif
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
CacheView
|
||
*component_view;
|
||
|
||
double
|
||
M00 = 0.0,
|
||
M01 = 0.0,
|
||
M02 = 0.0,
|
||
M10 = 0.0,
|
||
M11 = 0.0,
|
||
M20 = 0.0;
|
||
|
||
PointInfo
|
||
centroid = { 0.0, 0.0 };
|
||
|
||
RectangleInfo
|
||
bounding_box;
|
||
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
ssize_t
|
||
y;
|
||
|
||
/*
|
||
Compute ellipse major axis of each object.
|
||
*/
|
||
if (status == MagickFalse)
|
||
continue;
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
bounding_box=object[i].bounding_box;
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M00++;
|
||
M10+=x;
|
||
M01+=y;
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
centroid.x=M10*PerceptibleReciprocal(M00);
|
||
centroid.y=M01*PerceptibleReciprocal(M00);
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M11+=(x-centroid.x)*(y-centroid.y);
|
||
M20+=(x-centroid.x)*(x-centroid.x);
|
||
M02+=(y-centroid.y)*(y-centroid.y);
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
object[i].metric[n]=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)+
|
||
sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (((object[i].metric[n] < min_threshold) ||
|
||
(object[i].metric[n] >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:minor-axis-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max ellipse minor threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
metrics[++n]="minor-axis";
|
||
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
||
#pragma omp parallel for schedule(dynamic) shared(status) \
|
||
magick_number_threads(component_image,component_image,component_image->colors,1)
|
||
#endif
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
CacheView
|
||
*component_view;
|
||
|
||
double
|
||
M00 = 0.0,
|
||
M01 = 0.0,
|
||
M02 = 0.0,
|
||
M10 = 0.0,
|
||
M11 = 0.0,
|
||
M20 = 0.0;
|
||
|
||
PointInfo
|
||
centroid = { 0.0, 0.0 };
|
||
|
||
RectangleInfo
|
||
bounding_box;
|
||
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
ssize_t
|
||
y;
|
||
|
||
/*
|
||
Compute ellipse major axis of each object.
|
||
*/
|
||
if (status == MagickFalse)
|
||
continue;
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
bounding_box=object[i].bounding_box;
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M00++;
|
||
M10+=x;
|
||
M01+=y;
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
centroid.x=M10*PerceptibleReciprocal(M00);
|
||
centroid.y=M01*PerceptibleReciprocal(M00);
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M11+=(x-centroid.x)*(y-centroid.y);
|
||
M20+=(x-centroid.x)*(x-centroid.x);
|
||
M02+=(y-centroid.y)*(y-centroid.y);
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
object[i].metric[n]=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)-
|
||
sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (((object[i].metric[n] < min_threshold) ||
|
||
(object[i].metric[n] >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
artifact=GetImageArtifact(image,
|
||
"connected-components:eccentricity-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max eccentricity threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
metrics[++n]="eccentricy";
|
||
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
||
#pragma omp parallel for schedule(dynamic) shared(status) \
|
||
magick_number_threads(component_image,component_image,component_image->colors,1)
|
||
#endif
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
CacheView
|
||
*component_view;
|
||
|
||
double
|
||
M00 = 0.0,
|
||
M01 = 0.0,
|
||
M02 = 0.0,
|
||
M10 = 0.0,
|
||
M11 = 0.0,
|
||
M20 = 0.0;
|
||
|
||
PointInfo
|
||
centroid = { 0.0, 0.0 },
|
||
ellipse_axis = { 0.0, 0.0 };
|
||
|
||
RectangleInfo
|
||
bounding_box;
|
||
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
ssize_t
|
||
y;
|
||
|
||
/*
|
||
Compute eccentricity of each object.
|
||
*/
|
||
if (status == MagickFalse)
|
||
continue;
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
bounding_box=object[i].bounding_box;
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M00++;
|
||
M10+=x;
|
||
M01+=y;
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
centroid.x=M10*PerceptibleReciprocal(M00);
|
||
centroid.y=M01*PerceptibleReciprocal(M00);
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M11+=(x-centroid.x)*(y-centroid.y);
|
||
M20+=(x-centroid.x)*(x-centroid.x);
|
||
M02+=(y-centroid.y)*(y-centroid.y);
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
ellipse_axis.x=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)+
|
||
sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
|
||
ellipse_axis.y=sqrt((2.0*PerceptibleReciprocal(M00))*((M20+M02)-
|
||
sqrt(4.0*M11*M11+(M20-M02)*(M20-M02))));
|
||
object[i].metric[n]=sqrt(1.0-(ellipse_axis.y*ellipse_axis.y*
|
||
PerceptibleReciprocal(ellipse_axis.x*ellipse_axis.x)));
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (((object[i].metric[n] < min_threshold) ||
|
||
(object[i].metric[n] >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
artifact=GetImageArtifact(image,"connected-components:angle-threshold");
|
||
if (artifact != (const char *) NULL)
|
||
{
|
||
/*
|
||
Merge any object not within the min and max ellipse angle threshold.
|
||
*/
|
||
(void) sscanf(artifact,"%lf%*[ -]%lf",&min_threshold,&max_threshold);
|
||
metrics[++n]="angle";
|
||
#if defined(MAGICKCORE_OPENMP_SUPPORT)
|
||
#pragma omp parallel for schedule(dynamic) shared(status) \
|
||
magick_number_threads(component_image,component_image,component_image->colors,1)
|
||
#endif
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
CacheView
|
||
*component_view;
|
||
|
||
double
|
||
M00 = 0.0,
|
||
M01 = 0.0,
|
||
M02 = 0.0,
|
||
M10 = 0.0,
|
||
M11 = 0.0,
|
||
M20 = 0.0;
|
||
|
||
PointInfo
|
||
centroid = { 0.0, 0.0 };
|
||
|
||
RectangleInfo
|
||
bounding_box;
|
||
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
ssize_t
|
||
y;
|
||
|
||
/*
|
||
Compute ellipse angle of each object.
|
||
*/
|
||
if (status == MagickFalse)
|
||
continue;
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
bounding_box=object[i].bounding_box;
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M00++;
|
||
M10+=x;
|
||
M01+=y;
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
centroid.x=M10*PerceptibleReciprocal(M00);
|
||
centroid.y=M01*PerceptibleReciprocal(M00);
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,p) == i)
|
||
{
|
||
M11+=(x-centroid.x)*(y-centroid.y);
|
||
M20+=(x-centroid.x)*(x-centroid.x);
|
||
M02+=(y-centroid.y)*(y-centroid.y);
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
object[i].metric[n]=RadiansToDegrees(1.0/2.0*atan(2.0*M11*
|
||
PerceptibleReciprocal(M20-M02)));
|
||
if (fabs(M11) < 0.0)
|
||
{
|
||
if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
|
||
object[i].metric[n]+=90.0;
|
||
}
|
||
else
|
||
if (M11 < 0.0)
|
||
{
|
||
if (fabs(M20-M02) >= 0.0)
|
||
{
|
||
if ((M20-M02) < 0.0)
|
||
object[i].metric[n]+=90.0;
|
||
else
|
||
object[i].metric[n]+=180.0;
|
||
}
|
||
}
|
||
else
|
||
if ((fabs(M20-M02) >= 0.0) && ((M20-M02) < 0.0))
|
||
object[i].metric[n]+=90.0;
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (((object[i].metric[n] < min_threshold) ||
|
||
(object[i].metric[n] >= max_threshold)) && (i != background_id))
|
||
object[i].merge=MagickTrue;
|
||
}
|
||
/*
|
||
Merge any object not within the min and max area threshold.
|
||
*/
|
||
component_view=AcquireAuthenticCacheView(component_image,exception);
|
||
object_view=AcquireVirtualCacheView(component_image,exception);
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
ssize_t
|
||
j;
|
||
|
||
size_t
|
||
id;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
if ((object[i].merge == MagickFalse) || (i == background_id))
|
||
continue; /* keep object */
|
||
/*
|
||
Merge this object.
|
||
*/
|
||
for (j=0; j < (ssize_t) component_image->colors; j++)
|
||
object[j].census=0;
|
||
bounding_box=object[i].bounding_box;
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
continue;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
ssize_t
|
||
n;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
j=(ssize_t) GetPixelIndex(component_image,p);
|
||
if (j == i)
|
||
for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
|
||
{
|
||
const Quantum
|
||
*p;
|
||
|
||
/*
|
||
Compute area of adjacent objects.
|
||
*/
|
||
if (status == MagickFalse)
|
||
continue;
|
||
dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
|
||
dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
|
||
p=GetCacheViewVirtualPixels(object_view,bounding_box.x+x+dx,
|
||
bounding_box.y+y+dy,1,1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
break;
|
||
}
|
||
j=(ssize_t) GetPixelIndex(component_image,p);
|
||
if (j != i)
|
||
object[j].census++;
|
||
}
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
/*
|
||
Merge with object of greatest adjacent area.
|
||
*/
|
||
id=0;
|
||
for (j=1; j < (ssize_t) component_image->colors; j++)
|
||
if (object[j].census > object[id].census)
|
||
id=(size_t) j;
|
||
object[i].area=0.0;
|
||
for (y=0; y < (ssize_t) bounding_box.height; y++)
|
||
{
|
||
Quantum
|
||
*magick_restrict q;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
|
||
bounding_box.y+y,bounding_box.width,1,exception);
|
||
if (q == (Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
continue;
|
||
}
|
||
for (x=0; x < (ssize_t) bounding_box.width; x++)
|
||
{
|
||
if ((ssize_t) GetPixelIndex(component_image,q) == i)
|
||
SetPixelIndex(component_image,(Quantum) id,q);
|
||
q+=GetPixelChannels(component_image);
|
||
}
|
||
if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
|
||
status=MagickFalse;
|
||
}
|
||
}
|
||
object_view=DestroyCacheView(object_view);
|
||
component_view=DestroyCacheView(component_view);
|
||
artifact=GetImageArtifact(image,"connected-components:mean-color");
|
||
if (IsStringTrue(artifact) != MagickFalse)
|
||
{
|
||
/*
|
||
Replace object with mean color.
|
||
*/
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
component_image->colormap[i]=object[i].color;
|
||
}
|
||
(void) SyncImage(component_image,exception);
|
||
artifact=GetImageArtifact(image,"connected-components:verbose");
|
||
if ((IsStringTrue(artifact) != MagickFalse) ||
|
||
(objects != (CCObjectInfo **) NULL))
|
||
{
|
||
/*
|
||
Report statistics on each unique object.
|
||
*/
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
object[i].bounding_box.width=0;
|
||
object[i].bounding_box.height=0;
|
||
object[i].bounding_box.x=(ssize_t) component_image->columns;
|
||
object[i].bounding_box.y=(ssize_t) component_image->rows;
|
||
object[i].centroid.x=0;
|
||
object[i].centroid.y=0;
|
||
object[i].census=object[i].area == 0.0 ? 0.0 : 1.0;
|
||
object[i].area=0;
|
||
}
|
||
component_view=AcquireVirtualCacheView(component_image,exception);
|
||
for (y=0; y < (ssize_t) component_image->rows; y++)
|
||
{
|
||
const Quantum
|
||
*magick_restrict p;
|
||
|
||
ssize_t
|
||
x;
|
||
|
||
if (status == MagickFalse)
|
||
continue;
|
||
p=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,
|
||
1,exception);
|
||
if (p == (const Quantum *) NULL)
|
||
{
|
||
status=MagickFalse;
|
||
continue;
|
||
}
|
||
for (x=0; x < (ssize_t) component_image->columns; x++)
|
||
{
|
||
size_t
|
||
id;
|
||
|
||
id=(size_t) GetPixelIndex(component_image,p);
|
||
if (x < object[id].bounding_box.x)
|
||
object[id].bounding_box.x=x;
|
||
if (x > (ssize_t) object[id].bounding_box.width)
|
||
object[id].bounding_box.width=(size_t) x;
|
||
if (y < object[id].bounding_box.y)
|
||
object[id].bounding_box.y=y;
|
||
if (y > (ssize_t) object[id].bounding_box.height)
|
||
object[id].bounding_box.height=(size_t) y;
|
||
object[id].centroid.x+=x;
|
||
object[id].centroid.y+=y;
|
||
object[id].area++;
|
||
p+=GetPixelChannels(component_image);
|
||
}
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
{
|
||
object[i].bounding_box.width-=(object[i].bounding_box.x-1);
|
||
object[i].bounding_box.height-=(object[i].bounding_box.y-1);
|
||
object[i].centroid.x=object[i].centroid.x/object[i].area;
|
||
object[i].centroid.y=object[i].centroid.y/object[i].area;
|
||
}
|
||
component_view=DestroyCacheView(component_view);
|
||
qsort((void *) object,component_image->colors,sizeof(*object),
|
||
CCObjectInfoCompare);
|
||
if (objects == (CCObjectInfo **) NULL)
|
||
{
|
||
ssize_t
|
||
j;
|
||
|
||
artifact=GetImageArtifact(image,
|
||
"connected-components:exclude-header");
|
||
if (IsStringTrue(artifact) == MagickFalse)
|
||
{
|
||
(void) fprintf(stdout,"Objects (");
|
||
artifact=GetImageArtifact(image,
|
||
"connected-components:exclude-ids");
|
||
if (IsStringTrue(artifact) == MagickFalse)
|
||
(void) fprintf(stdout,"id: ");
|
||
(void) fprintf(stdout,"bounding-box centroid area mean-color");
|
||
for (j=0; j <= n; j++)
|
||
(void) fprintf(stdout," %s",metrics[j]);
|
||
(void) fprintf(stdout,"):\n");
|
||
}
|
||
for (i=0; i < (ssize_t) component_image->colors; i++)
|
||
if (object[i].census > 0.0)
|
||
{
|
||
char
|
||
mean_color[MagickPathExtent];
|
||
|
||
GetColorTuple(&object[i].color,MagickFalse,mean_color);
|
||
(void) fprintf(stdout," ");
|
||
artifact=GetImageArtifact(image,
|
||
"connected-components:exclude-ids");
|
||
if (IsStringTrue(artifact) == MagickFalse)
|
||
(void) fprintf(stdout,"%.20g: ",(double) object[i].id);
|
||
(void) fprintf(stdout,
|
||
"%.20gx%.20g%+.20g%+.20g %.1f,%.1f %.*g %s",(double)
|
||
object[i].bounding_box.width,(double)
|
||
object[i].bounding_box.height,(double)
|
||
object[i].bounding_box.x,(double) object[i].bounding_box.y,
|
||
object[i].centroid.x,object[i].centroid.y,
|
||
GetMagickPrecision(),(double) object[i].area,mean_color);
|
||
for (j=0; j <= n; j++)
|
||
(void) fprintf(stdout," %.*g",GetMagickPrecision(),
|
||
object[i].metric[j]);
|
||
(void) fprintf(stdout,"\n");
|
||
}
|
||
}
|
||
}
|
||
if (objects == (CCObjectInfo **) NULL)
|
||
object=(CCObjectInfo *) RelinquishMagickMemory(object);
|
||
else
|
||
*objects=object;
|
||
return(component_image);
|
||
}
|