blob: 0bbae0c0ac89dd450f79fd26b94c6e8a8fc56202 [file] [log] [blame]
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% 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-2015 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 %
% %
% http://www.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/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/quantum-private.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. Choose from 4 or 8-way connectivity.
%
% The format of the ConnectedComponentsImage method is:
%
% Image *ConnectedComponentsImage(const Image *image,
% const size_t connectivity,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 exception: return any errors or warnings in this structure.
%
*/
typedef struct _CCObject
{
ssize_t
id;
RectangleInfo
bounding_box;
PixelInfo
color;
PointInfo
centroid;
double
area,
census;
} CCObject;
static int CCObjectCompare(const void *x,const void *y)
{
CCObject
*p,
*q;
p=(CCObject *) x;
q=(CCObject *) y;
return((int) (q->area-(ssize_t) p->area));
}
static MagickBooleanType MergeConnectedComponents(Image *image,
const size_t number_objects,const double area_threshold,
ExceptionInfo *exception)
{
CacheView
*image_view;
CCObject
*object;
MagickBooleanType
status;
register ssize_t
i;
ssize_t
y;
/*
Collect statistics on unique objects.
*/
object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
if (object == (CCObject *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
return(MagickFalse);
}
(void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
for (i=0; i < (ssize_t) number_objects; i++)
{
object[i].id=i;
object[i].bounding_box.x=(ssize_t) image->columns;
object[i].bounding_box.y=(ssize_t) image->rows;
}
status=MagickTrue;
image_view=AcquireVirtualCacheView(image,exception);
for (y=0; y < (ssize_t) image->rows; y++)
{
register const Quantum
*restrict p;
register ssize_t
x;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
if (p == (const Quantum *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
i=(ssize_t) GetPixelIntensity(image,p);
if (x < object[i].bounding_box.x)
object[i].bounding_box.x=x;
if (x > (ssize_t) object[i].bounding_box.width)
object[i].bounding_box.width=(size_t) x;
if (y < object[i].bounding_box.y)
object[i].bounding_box.y=y;
if (y > (ssize_t) object[i].bounding_box.height)
object[i].bounding_box.height=(size_t) y;
object[i].area++;
p+=GetPixelChannels(image);
}
}
image_view=DestroyCacheView(image_view);
for (i=0; i < (ssize_t) number_objects; i++)
{
object[i].bounding_box.width-=(object[i].bounding_box.x-1);
object[i].bounding_box.height-=(object[i].bounding_box.y-1);
}
/*
Merge objects below area threshold.
*/
image_view=AcquireAuthenticCacheView(image,exception);
for (i=0; i < (ssize_t) number_objects; i++)
{
double
census;
RectangleInfo
bounding_box;
register ssize_t
j;
size_t
id;
if (status == MagickFalse)
continue;
if ((double) object[i].area >= area_threshold)
continue;
for (j=0; j < (ssize_t) number_objects; j++)
object[j].census=0;
bounding_box=object[i].bounding_box;
for (y=0; y < (ssize_t) bounding_box.height+2; y++)
{
register const Quantum
*restrict p;
register ssize_t
x;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,bounding_box.x-1,bounding_box.y+y-
1,bounding_box.width+2,1,exception);
if (p == (const Quantum *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) bounding_box.width+2; x++)
{
j=(ssize_t) GetPixelIntensity(image,p);
if (j != i)
object[j].census++;
p+=GetPixelChannels(image);
}
}
census=0;
id=0;
for (j=0; j < (ssize_t) number_objects; j++)
if (census < object[j].census)
{
census=object[j].census;
id=(size_t) j;
}
object[id].area+=object[i].area;
for (y=0; y < (ssize_t) bounding_box.height; y++)
{
register Quantum
*restrict q;
register ssize_t
x;
if (status == MagickFalse)
continue;
q=GetCacheViewAuthenticPixels(image_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) GetPixelIntensity(image,q) == i)
*q=(Quantum) id;
q+=GetPixelChannels(image);
}
if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
status=MagickFalse;
}
}
image_view=DestroyCacheView(image_view);
object=(CCObject *) RelinquishMagickMemory(object);
return(status);
}
static MagickBooleanType StatisticsComponentsStatistics(const Image *image,
const Image *component_image,const size_t number_objects,
ExceptionInfo *exception)
{
CacheView
*component_view,
*image_view;
CCObject
*object;
MagickBooleanType
status;
register ssize_t
i;
ssize_t
y;
/*
Collect statistics on unique objects.
*/
object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
if (object == (CCObject *) NULL)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
return(MagickFalse);
}
(void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
for (i=0; i < (ssize_t) number_objects; i++)
{
object[i].id=i;
object[i].bounding_box.x=(ssize_t) component_image->columns;
object[i].bounding_box.y=(ssize_t) component_image->rows;
GetPixelInfo(image,&object[i].color);
}
status=MagickTrue;
image_view=AcquireVirtualCacheView(image,exception);
component_view=AcquireVirtualCacheView(component_image,exception);
for (y=0; y < (ssize_t) image->rows; y++)
{
register const Quantum
*restrict p,
*restrict q;
register ssize_t
x;
if (status == MagickFalse)
continue;
p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
q=GetCacheViewVirtualPixels(component_view,0,y,component_image->columns,1,
exception);
if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) image->columns; x++)
{
i=(ssize_t) GetPixelIntensity(image,q);
if (x < object[i].bounding_box.x)
object[i].bounding_box.x=x;
if (x > (ssize_t) object[i].bounding_box.width)
object[i].bounding_box.width=(size_t) x;
if (y < object[i].bounding_box.y)
object[i].bounding_box.y=y;
if (y > (ssize_t) object[i].bounding_box.height)
object[i].bounding_box.height=(size_t) y;
object[i].color.red+=GetPixelRed(image,p);
object[i].color.green+=GetPixelGreen(image,p);
object[i].color.blue+=GetPixelBlue(image,p);
object[i].color.alpha+=GetPixelAlpha(image,p);
object[i].color.black+=GetPixelBlack(image,p);
object[i].centroid.x+=x;
object[i].centroid.y+=y;
object[i].area++;
p+=GetPixelChannels(image);
q+=GetPixelChannels(component_image);
}
}
for (i=0; i < (ssize_t) number_objects; 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=object[i].color.red/object[i].area;
object[i].color.green=object[i].color.green/object[i].area;
object[i].color.blue=object[i].color.blue/object[i].area;
object[i].color.alpha=object[i].color.alpha/object[i].area;
object[i].color.black=object[i].color.black/object[i].area;
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);
image_view=DestroyCacheView(image_view);
/*
Report statistics on unique objects.
*/
qsort((void *) object,number_objects,sizeof(*object),CCObjectCompare);
(void) fprintf(stdout,
"Objects (id: bounding-box centroid area mean-color):\n");
for (i=0; i < (ssize_t) number_objects; i++)
{
char
mean_color[MagickPathExtent];
if (status == MagickFalse)
break;
if (object[i].area < MagickEpsilon)
continue;
GetColorTuple(&object[i].color,MagickFalse,mean_color);
(void) fprintf(stdout,
" %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
object[i].id,(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,(double) object[i].area,mean_color);
}
object=(CCObject *) RelinquishMagickMemory(object);
return(status);
}
MagickExport Image *ConnectedComponentsImage(const Image *image,
const size_t connectivity,ExceptionInfo *exception)
{
#define ConnectedComponentsImageTag "ConnectedComponents/Image"
CacheView
*image_view,
*component_view;
const char
*artifact;
double
area_threshold;
Image
*component_image;
MagickBooleanType
status;
MagickOffsetType
progress;
MatrixInfo
*equivalences;
size_t
size;
ssize_t
n,
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);
component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
exception);
if (component_image == (Image *) NULL)
return((Image *) NULL);
component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
component_image->colorspace=GRAYColorspace;
status=SetImageStorageClass(component_image,DirectClass,exception);
if (status == MagickFalse)
{
component_image=DestroyImage(component_image);
return((Image *) NULL);
}
/*
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++)
status=SetMatrixElement(equivalences,n,0,&n);
/*
Find connected components.
*/
status=MagickTrue;
progress=0;
image_view=AcquireVirtualCacheView(image,exception);
for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
{
ssize_t
connect4[2][2] = { { -1, 0 }, { 0, -1 } },
connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
dx,
dy;
if (status == MagickFalse)
continue;
dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
for (y=0; y < (ssize_t) image->rows; y++)
{
register const Quantum
*restrict p;
register 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,
object,
offset,
ox,
oy,
root;
/*
Is neighbor an authentic pixel and a different color than the pixel?
*/
GetPixelInfoPixel(image,p,&pixel);
neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
GetPixelChannels(image);
GetPixelInfoPixel(image,p+neighbor_offset,&target);
if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows) ||
(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,&object);
while (object != ox)
{
ox=object;
status=GetMatrixElement(equivalences,ox,0,&object);
}
oy=offset+neighbor_offset;
status=GetMatrixElement(equivalences,oy,0,&object);
while (object != oy)
{
oy=object;
status=GetMatrixElement(equivalences,oy,0,&object);
}
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,&object);
while (object != root)
{
status=GetMatrixElement(equivalences,ox,0,&object);
status=SetMatrixElement(equivalences,ox,0,&root);
}
oy=offset+neighbor_offset;
status=GetMatrixElement(equivalences,oy,0,&object);
while (object != root)
{
status=GetMatrixElement(equivalences,oy,0,&object);
status=SetMatrixElement(equivalences,oy,0,&root);
}
status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
p+=GetPixelChannels(image);
}
}
}
image_view=DestroyCacheView(image_view);
/*
Label connected components.
*/
n=0;
component_view=AcquireAuthenticCacheView(component_image,exception);
for (y=0; y < (ssize_t) component_image->rows; y++)
{
register Quantum
*restrict q;
register ssize_t
x;
if (status == MagickFalse)
continue;
q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
1,exception);
if (q == (Quantum *) NULL)
{
status=MagickFalse;
continue;
}
for (x=0; x < (ssize_t) component_image->columns; x++)
{
ssize_t
object,
offset;
offset=y*image->columns+x;
status=GetMatrixElement(equivalences,offset,0,&object);
if (object == offset)
{
object=n++;
status=SetMatrixElement(equivalences,offset,0,&object);
}
else
{
status=GetMatrixElement(equivalences,object,0,&object);
status=SetMatrixElement(equivalences,offset,0,&object);
}
*q=(Quantum) (object > (ssize_t) QuantumRange ? (ssize_t) QuantumRange :
object);
q+=GetPixelChannels(component_image);
}
if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
status=MagickFalse;
if (image->progress_monitor != (MagickProgressMonitor) NULL)
{
MagickBooleanType
proceed;
proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
image->rows);
if (proceed == MagickFalse)
status=MagickFalse;
}
}
component_view=DestroyCacheView(component_view);
equivalences=DestroyMatrixInfo(equivalences);
if (n > (ssize_t) QuantumRange)
{
component_image=DestroyImage(component_image);
ThrowImageException(ResourceLimitError,"TooManyObjects");
}
artifact=GetImageArtifact(image,"connected-components:area-threshold");
area_threshold=0.0;
if (artifact != (const char *) NULL)
area_threshold=StringToDouble(artifact,(char **) NULL);
if (area_threshold > 0.0)
status=MergeConnectedComponents(component_image,(size_t) n,area_threshold,
exception);
artifact=GetImageArtifact(image,"connected-components:verbose");
if (IsStringTrue(artifact) != MagickFalse)
status=StatisticsComponentsStatistics(image,component_image,(size_t) n,
exception);
if (status == MagickFalse)
component_image=DestroyImage(component_image);
return(component_image);
}