2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % V V IIIII SSSSS IIIII OOO N N %
7 % V V I SS I O O NN N %
8 % V V I SSS I O O N N N %
9 % V V I SS I O O N NN %
10 % V IIIII SSSSS IIIII OOO N N %
13 % MagickCore Computer Vision Methods %
20 % Copyright 1999-2017 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % https://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
39 #include "MagickCore/studio.h"
40 #include "MagickCore/artifact.h"
41 #include "MagickCore/blob.h"
42 #include "MagickCore/cache-view.h"
43 #include "MagickCore/color.h"
44 #include "MagickCore/color-private.h"
45 #include "MagickCore/colormap.h"
46 #include "MagickCore/colorspace.h"
47 #include "MagickCore/constitute.h"
48 #include "MagickCore/decorate.h"
49 #include "MagickCore/distort.h"
50 #include "MagickCore/draw.h"
51 #include "MagickCore/enhance.h"
52 #include "MagickCore/exception.h"
53 #include "MagickCore/exception-private.h"
54 #include "MagickCore/effect.h"
55 #include "MagickCore/gem.h"
56 #include "MagickCore/geometry.h"
57 #include "MagickCore/image-private.h"
58 #include "MagickCore/list.h"
59 #include "MagickCore/log.h"
60 #include "MagickCore/matrix.h"
61 #include "MagickCore/memory_.h"
62 #include "MagickCore/memory-private.h"
63 #include "MagickCore/monitor.h"
64 #include "MagickCore/monitor-private.h"
65 #include "MagickCore/montage.h"
66 #include "MagickCore/morphology.h"
67 #include "MagickCore/morphology-private.h"
68 #include "MagickCore/opencl-private.h"
69 #include "MagickCore/paint.h"
70 #include "MagickCore/pixel-accessor.h"
71 #include "MagickCore/pixel-private.h"
72 #include "MagickCore/property.h"
73 #include "MagickCore/quantum.h"
74 #include "MagickCore/resource_.h"
75 #include "MagickCore/signature-private.h"
76 #include "MagickCore/string_.h"
77 #include "MagickCore/string-private.h"
78 #include "MagickCore/thread-private.h"
79 #include "MagickCore/token.h"
80 #include "MagickCore/vision.h"
83 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
87 % C o n n e c t e d C o m p o n e n t s I m a g e %
91 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93 % ConnectedComponentsImage() returns the connected-components of the image
94 % uniquely labeled. The returned connected components image colors member
95 % defines the number of unique objects. Choose from 4 or 8-way connectivity.
97 % You are responsible for freeing the connected components objects resources
98 % with this statement;
100 % objects = (CCObjectInfo *) RelinquishMagickMemory(objects);
102 % The format of the ConnectedComponentsImage method is:
104 % Image *ConnectedComponentsImage(const Image *image,
105 % const size_t connectivity,CCObjectInfo **objects,
106 % ExceptionInfo *exception)
108 % A description of each parameter follows:
110 % o image: the image.
112 % o connectivity: how many neighbors to visit, choose from 4 or 8.
114 % o objects: return the attributes of each unique object.
116 % o exception: return any errors or warnings in this structure.
120 static int CCObjectInfoCompare(const void *x,const void *y)
126 p=(CCObjectInfo *) x;
127 q=(CCObjectInfo *) y;
128 return((int) (q->area-(ssize_t) p->area));
131 MagickExport Image *ConnectedComponentsImage(const Image *image,
132 const size_t connectivity,CCObjectInfo **objects,ExceptionInfo *exception)
134 #define ConnectedComponentsImageTag "ConnectedComponents/Image"
178 Initialize connected components image attributes.
180 assert(image != (Image *) NULL);
181 assert(image->signature == MagickCoreSignature);
182 if (image->debug != MagickFalse)
183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
184 assert(exception != (ExceptionInfo *) NULL);
185 assert(exception->signature == MagickCoreSignature);
186 if (objects != (CCObjectInfo **) NULL)
187 *objects=(CCObjectInfo *) NULL;
188 component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
190 if (component_image == (Image *) NULL)
191 return((Image *) NULL);
192 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
193 if (AcquireImageColormap(component_image,MaxColormapSize,exception) == MagickFalse)
195 component_image=DestroyImage(component_image);
196 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
199 Initialize connected components equivalences.
201 size=image->columns*image->rows;
202 if (image->columns != (size/image->rows))
204 component_image=DestroyImage(component_image);
205 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
207 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
208 if (equivalences == (MatrixInfo *) NULL)
210 component_image=DestroyImage(component_image);
211 return((Image *) NULL);
213 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
214 (void) SetMatrixElement(equivalences,n,0,&n);
215 object=(CCObjectInfo *) AcquireQuantumMemory(MaxColormapSize,sizeof(*object));
216 if (object == (CCObjectInfo *) NULL)
218 equivalences=DestroyMatrixInfo(equivalences);
219 component_image=DestroyImage(component_image);
220 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
222 (void) ResetMagickMemory(object,0,MaxColormapSize*sizeof(*object));
223 for (i=0; i < (ssize_t) MaxColormapSize; i++)
226 object[i].bounding_box.x=(ssize_t) image->columns;
227 object[i].bounding_box.y=(ssize_t) image->rows;
228 GetPixelInfo(image,&object[i].color);
231 Find connected components.
235 image_view=AcquireVirtualCacheView(image,exception);
236 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
239 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
240 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
244 if (status == MagickFalse)
246 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
247 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
248 for (y=0; y < (ssize_t) image->rows; y++)
250 register const Quantum
256 if (status == MagickFalse)
258 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
259 if (p == (const Quantum *) NULL)
264 p+=GetPixelChannels(image)*image->columns;
265 for (x=0; x < (ssize_t) image->columns; x++)
280 Is neighbor an authentic pixel and a different color than the pixel?
282 GetPixelInfoPixel(image,p,&pixel);
283 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
284 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows))
286 p+=GetPixelChannels(image);
289 neighbor_offset=dy*(GetPixelChannels(image)*image->columns)+dx*
290 GetPixelChannels(image);
291 GetPixelInfoPixel(image,p+neighbor_offset,&target);
292 if (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse)
294 p+=GetPixelChannels(image);
298 Resolve this equivalence.
300 offset=y*image->columns+x;
301 neighbor_offset=dy*image->columns+dx;
303 status=GetMatrixElement(equivalences,ox,0,&obj);
307 status=GetMatrixElement(equivalences,ox,0,&obj);
309 oy=offset+neighbor_offset;
310 status=GetMatrixElement(equivalences,oy,0,&obj);
314 status=GetMatrixElement(equivalences,oy,0,&obj);
318 status=SetMatrixElement(equivalences,oy,0,&ox);
323 status=SetMatrixElement(equivalences,ox,0,&oy);
327 status=GetMatrixElement(equivalences,ox,0,&obj);
330 status=GetMatrixElement(equivalences,ox,0,&obj);
331 status=SetMatrixElement(equivalences,ox,0,&root);
333 oy=offset+neighbor_offset;
334 status=GetMatrixElement(equivalences,oy,0,&obj);
337 status=GetMatrixElement(equivalences,oy,0,&obj);
338 status=SetMatrixElement(equivalences,oy,0,&root);
340 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
341 p+=GetPixelChannels(image);
345 image_view=DestroyCacheView(image_view);
347 Label connected components.
350 image_view=AcquireVirtualCacheView(image,exception);
351 component_view=AcquireAuthenticCacheView(component_image,exception);
352 for (y=0; y < (ssize_t) component_image->rows; y++)
354 register const Quantum
363 if (status == MagickFalse)
365 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
366 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
368 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
373 for (x=0; x < (ssize_t) component_image->columns; x++)
379 offset=y*image->columns+x;
380 status=GetMatrixElement(equivalences,offset,0,&id);
382 status=GetMatrixElement(equivalences,id,0,&id);
386 if (id >= (ssize_t) MaxColormapSize)
389 status=SetMatrixElement(equivalences,offset,0,&id);
390 if (x < object[id].bounding_box.x)
391 object[id].bounding_box.x=x;
392 if (x >= (ssize_t) object[id].bounding_box.width)
393 object[id].bounding_box.width=(size_t) x;
394 if (y < object[id].bounding_box.y)
395 object[id].bounding_box.y=y;
396 if (y >= (ssize_t) object[id].bounding_box.height)
397 object[id].bounding_box.height=(size_t) y;
398 object[id].color.red+=QuantumScale*GetPixelRed(image,p);
399 object[id].color.green+=QuantumScale*GetPixelGreen(image,p);
400 object[id].color.blue+=QuantumScale*GetPixelBlue(image,p);
401 if (image->alpha_trait != UndefinedPixelTrait)
402 object[id].color.alpha+=QuantumScale*GetPixelAlpha(image,p);
403 if (image->colorspace == CMYKColorspace)
404 object[id].color.black+=QuantumScale*GetPixelBlack(image,p);
405 object[id].centroid.x+=x;
406 object[id].centroid.y+=y;
408 SetPixelIndex(component_image,(Quantum) id,q);
409 p+=GetPixelChannels(image);
410 q+=GetPixelChannels(component_image);
412 if (n > (ssize_t) MaxColormapSize)
414 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
416 if (image->progress_monitor != (MagickProgressMonitor) NULL)
421 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
423 if (proceed == MagickFalse)
427 component_view=DestroyCacheView(component_view);
428 image_view=DestroyCacheView(image_view);
429 equivalences=DestroyMatrixInfo(equivalences);
430 if (n > (ssize_t) MaxColormapSize)
432 object=(CCObjectInfo *) RelinquishMagickMemory(object);
433 component_image=DestroyImage(component_image);
434 ThrowImageException(ResourceLimitError,"TooManyObjects");
436 component_image->colors=(size_t) n;
437 for (i=0; i < (ssize_t) component_image->colors; i++)
439 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
440 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
441 object[i].color.red=QuantumRange*(object[i].color.red/object[i].area);
442 object[i].color.green=QuantumRange*(object[i].color.green/object[i].area);
443 object[i].color.blue=QuantumRange*(object[i].color.blue/object[i].area);
444 if (image->alpha_trait != UndefinedPixelTrait)
445 object[i].color.alpha=QuantumRange*(object[i].color.alpha/object[i].area);
446 if (image->colorspace == CMYKColorspace)
447 object[i].color.black=QuantumRange*(object[i].color.black/object[i].area);
448 object[i].centroid.x=object[i].centroid.x/object[i].area;
449 object[i].centroid.y=object[i].centroid.y/object[i].area;
451 artifact=GetImageArtifact(image,"connected-components:area-threshold");
453 if (artifact != (const char *) NULL)
454 area_threshold=StringToDouble(artifact,(char **) NULL);
455 if (area_threshold > 0.0)
458 Merge object below area threshold.
460 component_view=AcquireAuthenticCacheView(component_image,exception);
461 for (i=0; i < (ssize_t) component_image->colors; i++)
475 if (status == MagickFalse)
477 if ((double) object[i].area >= area_threshold)
479 for (j=0; j < (ssize_t) component_image->colors; j++)
481 bounding_box=object[i].bounding_box;
482 for (y=0; y < (ssize_t) bounding_box.height+2; y++)
484 register const Quantum
490 if (status == MagickFalse)
492 p=GetCacheViewVirtualPixels(component_view,bounding_box.x-1,
493 bounding_box.y+y-1,bounding_box.width+2,1,exception);
494 if (p == (const Quantum *) NULL)
499 for (x=0; x < (ssize_t) bounding_box.width+2; x++)
501 j=(ssize_t) GetPixelIndex(component_image,p);
504 p+=GetPixelChannels(component_image);
509 for (j=0; j < (ssize_t) component_image->colors; j++)
510 if (census < object[j].census)
512 census=object[j].census;
515 object[id].area+=object[i].area;
516 for (y=0; y < (ssize_t) bounding_box.height; y++)
524 if (status == MagickFalse)
526 q=GetCacheViewAuthenticPixels(component_view,bounding_box.x,
527 bounding_box.y+y,bounding_box.width,1,exception);
528 if (q == (Quantum *) NULL)
533 for (x=0; x < (ssize_t) bounding_box.width; x++)
535 if ((ssize_t) GetPixelIndex(component_image,q) == i)
536 SetPixelIndex(component_image,(Quantum) id,q);
537 q+=GetPixelChannels(component_image);
539 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
543 component_view=DestroyCacheView(component_view);
544 (void) SyncImage(component_image,exception);
546 artifact=GetImageArtifact(image,"connected-components:mean-color");
547 if (IsStringTrue(artifact) != MagickFalse)
550 Replace object with mean color.
552 for (i=0; i < (ssize_t) component_image->colors; i++)
553 component_image->colormap[i]=object[i].color;
555 artifact=GetImageArtifact(image,"connected-components:keep");
556 if (artifact != (const char *) NULL)
559 Keep these object (make others transparent).
561 for (i=0; i < (ssize_t) component_image->colors; i++)
563 for (c=(char *) artifact; *c != '\0';)
565 while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
567 first=strtol(c,&c,10);
569 first+=(long) component_image->colors;
571 while (isspace((int) ((unsigned char) *c)) != 0)
575 last=strtol(c+1,&c,10);
577 last+=(long) component_image->colors;
579 for (step=first > last ? -1 : 1; first != (last+step); first+=step)
580 object[first].census++;
582 for (i=0; i < (ssize_t) component_image->colors; i++)
584 if (object[i].census != 0)
586 component_image->alpha_trait=BlendPixelTrait;
587 component_image->colormap[i].alpha=TransparentAlpha;
590 artifact=GetImageArtifact(image,"connected-components:remove");
591 if (artifact != (const char *) NULL)
594 Remove these object (make them transparent).
596 for (c=(char *) artifact; *c != '\0';)
598 while ((isspace((int) ((unsigned char) *c)) != 0) || (*c == ','))
600 first=strtol(c,&c,10);
602 first+=(long) component_image->colors;
604 while (isspace((int) ((unsigned char) *c)) != 0)
608 last=strtol(c+1,&c,10);
610 last+=(long) component_image->colors;
612 for (step=first > last ? -1 : 1; first != (last+step); first+=step)
614 component_image->alpha_trait=BlendPixelTrait;
615 component_image->colormap[first].alpha=TransparentAlpha;
619 (void) SyncImage(component_image,exception);
620 artifact=GetImageArtifact(image,"connected-components:verbose");
621 if ((IsStringTrue(artifact) != MagickFalse) ||
622 (objects != (CCObjectInfo **) NULL))
625 Report statistics on unique object.
627 for (i=0; i < (ssize_t) component_image->colors; i++)
629 object[i].bounding_box.width=0;
630 object[i].bounding_box.height=0;
631 object[i].bounding_box.x=(ssize_t) component_image->columns;
632 object[i].bounding_box.y=(ssize_t) component_image->rows;
633 object[i].centroid.x=0;
634 object[i].centroid.y=0;
637 component_view=AcquireVirtualCacheView(component_image,exception);
638 for (y=0; y < (ssize_t) component_image->rows; y++)
640 register const Quantum
646 if (status == MagickFalse)
648 p=GetCacheViewVirtualPixels(component_view,0,y,
649 component_image->columns,1,exception);
650 if (p == (const Quantum *) NULL)
655 for (x=0; x < (ssize_t) component_image->columns; x++)
660 id=GetPixelIndex(component_image,p);
661 if (x < object[id].bounding_box.x)
662 object[id].bounding_box.x=x;
663 if (x > (ssize_t) object[id].bounding_box.width)
664 object[id].bounding_box.width=(size_t) x;
665 if (y < object[id].bounding_box.y)
666 object[id].bounding_box.y=y;
667 if (y > (ssize_t) object[id].bounding_box.height)
668 object[id].bounding_box.height=(size_t) y;
669 object[id].centroid.x+=x;
670 object[id].centroid.y+=y;
672 p+=GetPixelChannels(component_image);
675 for (i=0; i < (ssize_t) component_image->colors; i++)
677 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
678 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
679 object[i].centroid.x=object[i].centroid.x/object[i].area;
680 object[i].centroid.y=object[i].centroid.y/object[i].area;
682 component_view=DestroyCacheView(component_view);
683 qsort((void *) object,component_image->colors,sizeof(*object),
684 CCObjectInfoCompare);
685 if (objects == (CCObjectInfo **) NULL)
687 (void) fprintf(stdout,
688 "Objects (id: bounding-box centroid area mean-color):\n");
689 for (i=0; i < (ssize_t) component_image->colors; i++)
692 mean_color[MagickPathExtent];
694 if (status == MagickFalse)
696 if (object[i].area <= area_threshold)
698 GetColorTuple(&object[i].color,MagickFalse,mean_color);
699 (void) fprintf(stdout,
700 " %.20g: %.20gx%.20g%+.20g%+.20g %.1f,%.1f %.20g %s\n",(double)
701 object[i].id,(double) object[i].bounding_box.width,(double)
702 object[i].bounding_box.height,(double) object[i].bounding_box.x,
703 (double) object[i].bounding_box.y,object[i].centroid.x,
704 object[i].centroid.y,(double) object[i].area,mean_color);
708 if (objects == (CCObjectInfo **) NULL)
709 object=(CCObjectInfo *) RelinquishMagickMemory(object);
712 return(component_image);