blob: 7ecdc59576a3915b8fee84e596004de161ee952b [file] [log] [blame]
cristy6e0b3bc2014-10-19 17:51:42 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
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 %
11% %
12% %
13% MagickCore Computer Vision Methods %
14% %
15% Software Design %
16% Cristy %
17% September 2014 %
18% %
19% %
20% Copyright 1999-2014 ImageMagick Studio LLC, a non-profit organization %
21% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
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. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37*/
38
39#include "MagickCore/studio.h"
cristy016b7642014-10-25 13:09:57 +000040#include "MagickCore/artifact.h"
cristy6e0b3bc2014-10-19 17:51:42 +000041#include "MagickCore/blob.h"
42#include "MagickCore/cache-view.h"
43#include "MagickCore/color.h"
44#include "MagickCore/color-private.h"
45#include "MagickCore/colorspace.h"
46#include "MagickCore/constitute.h"
47#include "MagickCore/decorate.h"
48#include "MagickCore/distort.h"
49#include "MagickCore/draw.h"
50#include "MagickCore/enhance.h"
51#include "MagickCore/exception.h"
52#include "MagickCore/exception-private.h"
53#include "MagickCore/effect.h"
54#include "MagickCore/gem.h"
55#include "MagickCore/geometry.h"
56#include "MagickCore/image-private.h"
57#include "MagickCore/list.h"
58#include "MagickCore/log.h"
59#include "MagickCore/matrix.h"
60#include "MagickCore/memory_.h"
61#include "MagickCore/memory-private.h"
62#include "MagickCore/monitor.h"
63#include "MagickCore/monitor-private.h"
64#include "MagickCore/montage.h"
65#include "MagickCore/morphology.h"
66#include "MagickCore/morphology-private.h"
67#include "MagickCore/opencl-private.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/pixel-private.h"
71#include "MagickCore/property.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/resource_.h"
74#include "MagickCore/signature-private.h"
75#include "MagickCore/string_.h"
76#include "MagickCore/thread-private.h"
cristy016b7642014-10-25 13:09:57 +000077#include "MagickCore/token.h"
cristy6e0b3bc2014-10-19 17:51:42 +000078#include "MagickCore/vision.h"
79
80/*
81%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82% %
83% %
84% %
85% C o n n e c t e d C o m p o n e n t s I m a g e %
86% %
87% %
88% %
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90%
91% ConnectedComponentsImage() returns the connected-components of the image
92% uniquely labeled. Choose from 4 or 8-way connectivity.
93%
94% The format of the ConnectedComponentsImage method is:
95%
96% Image *ConnectedComponentsImage(const Image *image,
97% const size_t connectivity,ExceptionInfo *exception)
98%
99% A description of each parameter follows:
100%
101% o image: the image.
102%
cristy016b7642014-10-25 13:09:57 +0000103% o connectivity: how many neighbors to visit, choose from 4 or 8.
cristy6e0b3bc2014-10-19 17:51:42 +0000104%
105% o exception: return any errors or warnings in this structure.
106%
107*/
cristy016b7642014-10-25 13:09:57 +0000108
109typedef struct _CCObject
110{
111 ssize_t
112 id;
113
114 RectangleInfo
115 bounding_box;
116
117 PointInfo
118 centroid;
119
120 ssize_t
121 area;
122} CCObject;
123
124static int CCObjectCompare(const void *x,const void *y)
125{
126 CCObject
127 *p,
128 *q;
129
130 p=(CCObject *) x;
131 q=(CCObject *) y;
132 return((int) (q->area-(ssize_t) p->area));
133}
134
135static MagickBooleanType ConnectedComponentsStatistics(const Image *image,
136 const size_t number_objects,ExceptionInfo *exception)
137{
138 CacheView
139 *image_view;
140
141 CCObject
142 *object;
143
144 MagickBooleanType
145 status;
146
147 register ssize_t
148 i;
149
150 ssize_t
151 y;
152
153 object=(CCObject *) AcquireQuantumMemory(number_objects,sizeof(*object));
154 if (object == (CCObject *) NULL)
155 {
156 (void) ThrowMagickException(exception,GetMagickModule(),
157 ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
158 return(MagickFalse);
159 }
160 (void) ResetMagickMemory(object,0,number_objects*sizeof(*object));
161 for (i=0; i < (ssize_t) number_objects; i++)
162 {
163 object[i].id=i;
164 object[i].bounding_box.x=(ssize_t) image->columns;
165 object[i].bounding_box.y=(ssize_t) image->rows;
166 }
167 (void) fprintf(stdout,"Objects (id: bounding-box centroid area):\n");
168 status=MagickTrue;
169 image_view=AcquireVirtualCacheView(image,exception);
170 for (y=0; y < (ssize_t) image->rows; y++)
171 {
172 register const Quantum
173 *restrict p;
174
175 register ssize_t
176 x;
177
178 if (status == MagickFalse)
179 continue;
180 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
181 if (p == (const Quantum *) NULL)
182 {
183 status=MagickFalse;
184 continue;
185 }
186 for (x=0; x < (ssize_t) image->columns; x++)
187 {
188 i=(ssize_t) *p;
189 if (x < object[i].bounding_box.x)
190 object[i].bounding_box.x=x;
191 if (x > (ssize_t) object[i].bounding_box.width)
192 object[i].bounding_box.width=(size_t) x;
193 if (y < object[i].bounding_box.y)
194 object[i].bounding_box.y=y;
195 if (y > (ssize_t) object[i].bounding_box.height)
196 object[i].bounding_box.height=(size_t) y;
cristyc75924c2014-10-25 14:58:34 +0000197 object[i].centroid.x+=x;
198 object[i].centroid.y+=y;
cristy016b7642014-10-25 13:09:57 +0000199 object[i].area++;
200 p+=GetPixelChannels(image);
201 }
202 }
203 for (i=0; i < (ssize_t) number_objects; i++)
204 {
205 object[i].bounding_box.width-=(object[i].bounding_box.x-1);
206 object[i].bounding_box.height-=(object[i].bounding_box.y-1);
cristyc75924c2014-10-25 14:58:34 +0000207 object[i].centroid.x=object[i].centroid.x/object[i].area;
208 object[i].centroid.y=object[i].centroid.y/object[i].area;
cristy016b7642014-10-25 13:09:57 +0000209 }
210 image_view=DestroyCacheView(image_view);
211 qsort((void *) object,number_objects,sizeof(*object),CCObjectCompare);
212 for (i=0; i < (ssize_t) number_objects; i++)
213 {
214 if (status == MagickFalse)
215 break;
216 (void) fprintf(stdout,
217 " %.20g: %.20gx%.20g%+.20g%+.20g %+.1f,%+.1f %.20g\n",(double)
218 object[i].id,(double) object[i].bounding_box.width,(double)
219 object[i].bounding_box.height,(double) object[i].bounding_box.x,
220 (double) object[i].bounding_box.y,object[i].centroid.x,
221 object[i].centroid.y,(double) object[i].area);
222 }
223 object=(CCObject *) RelinquishMagickMemory(object);
224 return(status);
225}
226
cristy6e0b3bc2014-10-19 17:51:42 +0000227MagickExport Image *ConnectedComponentsImage(const Image *image,
228 const size_t connectivity,ExceptionInfo *exception)
229{
230#define ConnectedComponentsImageTag "ConnectedComponents/Image"
231
232 CacheView
233 *image_view,
234 *component_view;
235
cristy016b7642014-10-25 13:09:57 +0000236 const char
237 *artifact;
238
cristy6e0b3bc2014-10-19 17:51:42 +0000239 Image
240 *component_image;
241
242 MagickBooleanType
243 status;
244
245 MagickOffsetType
246 progress;
247
cristy016b7642014-10-25 13:09:57 +0000248 MatrixInfo
249 *equivalences;
250
251 size_t
252 size;
253
cristy6e0b3bc2014-10-19 17:51:42 +0000254 ssize_t
cristy016b7642014-10-25 13:09:57 +0000255 n,
cristy6e0b3bc2014-10-19 17:51:42 +0000256 y;
257
258 /*
259 Initialize connected components image attributes.
260 */
261 assert(image != (Image *) NULL);
262 assert(image->signature == MagickSignature);
263 if (image->debug != MagickFalse)
264 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
265 assert(exception != (ExceptionInfo *) NULL);
266 assert(exception->signature == MagickSignature);
267 component_image=CloneImage(image,image->columns,image->rows,MagickTrue,
268 exception);
269 if (component_image == (Image *) NULL)
270 return((Image *) NULL);
cristy016b7642014-10-25 13:09:57 +0000271 component_image->depth=MAGICKCORE_QUANTUM_DEPTH;
272 component_image->colorspace=GRAYColorspace;
cristy6e0b3bc2014-10-19 17:51:42 +0000273 if (SetImageStorageClass(component_image,DirectClass,exception) == MagickFalse)
274 {
275 component_image=DestroyImage(component_image);
276 return((Image *) NULL);
277 }
278 /*
cristy016b7642014-10-25 13:09:57 +0000279 Initialize connected components equivalences.
280 */
281 size=image->columns*image->rows;
282 if (image->columns != (size/image->rows))
283 {
284 component_image=DestroyImage(component_image);
285 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
286 }
287 equivalences=AcquireMatrixInfo(size,1,sizeof(ssize_t),exception);
288 if (equivalences == (MatrixInfo *) NULL)
289 {
290 component_image=DestroyImage(component_image);
291 return((Image *) NULL);
292 }
293 for (n=0; n < (ssize_t) (image->columns*image->rows); n++)
294 status=SetMatrixElement(equivalences,n,0,&n);
295 /*
296 Find connected components.
cristy6e0b3bc2014-10-19 17:51:42 +0000297 */
298 status=MagickTrue;
299 progress=0;
300 image_view=AcquireVirtualCacheView(image,exception);
cristy016b7642014-10-25 13:09:57 +0000301 for (n=0; n < (ssize_t) (connectivity > 4 ? 4 : 2); n++)
302 {
303 ssize_t
304 connect4[2][2] = { { -1, 0 }, { 0, -1 } },
305 connect8[4][2] = { { -1, -1 }, { -1, 0 }, { -1, 1 }, { 0, -1 } },
306 dx,
307 dy;
308
309 if (status == MagickFalse)
310 continue;
311 dy=connectivity > 4 ? connect8[n][0] : connect4[n][0];
312 dx=connectivity > 4 ? connect8[n][1] : connect4[n][1];
313 for (y=0; y < (ssize_t) image->rows; y++)
314 {
315 register const Quantum
316 *restrict p;
317
318 register ssize_t
319 x;
320
321 if (status == MagickFalse)
322 continue;
323 p=GetCacheViewVirtualPixels(image_view,0,y-1,image->columns,3,exception);
324 if (p == (const Quantum *) NULL)
325 {
326 status=MagickFalse;
327 continue;
328 }
cristy00f8eb42014-10-25 13:46:10 +0000329 p+=image->columns*GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000330 for (x=0; x < (ssize_t) image->columns; x++)
331 {
332 PixelInfo
333 pixel,
334 target;
335
336 ssize_t
337 neighbor_offset,
338 object,
cristy00f8eb42014-10-25 13:46:10 +0000339 offset,
cristy016b7642014-10-25 13:09:57 +0000340 ox,
341 oy,
cristy016b7642014-10-25 13:09:57 +0000342 root;
343
344 /*
345 Is neighbor an authentic pixel and a different color than the pixel?
346 */
cristy00f8eb42014-10-25 13:46:10 +0000347 GetPixelInfoPixel(image,p,&pixel);
cristy016b7642014-10-25 13:09:57 +0000348 neighbor_offset=dy*(image->columns*GetPixelChannels(image))+dx*
349 GetPixelChannels(image);
cristy016b7642014-10-25 13:09:57 +0000350 GetPixelInfoPixel(image,p+neighbor_offset,&target);
351 if (((x+dx) < 0) || ((x+dx) >= (ssize_t) image->columns) ||
352 ((y+dy) < 0) || ((y+dy) >= (ssize_t) image->rows) ||
353 (IsFuzzyEquivalencePixelInfo(&pixel,&target) == MagickFalse))
354 {
355 p+=GetPixelChannels(image);
356 continue;
357 }
358 /*
359 Resolve this equivalence.
360 */
cristy00f8eb42014-10-25 13:46:10 +0000361 offset=y*image->columns+x;
362 neighbor_offset=dy*image->columns+dx;
363 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000364 status=GetMatrixElement(equivalences,ox,0,&object);
365 while (object != ox) {
366 ox=object;
367 status=GetMatrixElement(equivalences,ox,0,&object);
368 }
cristy00f8eb42014-10-25 13:46:10 +0000369 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000370 status=GetMatrixElement(equivalences,oy,0,&object);
371 while (object != oy) {
372 oy=object;
373 status=GetMatrixElement(equivalences,oy,0,&object);
374 }
375 if (ox < oy)
376 {
377 status=SetMatrixElement(equivalences,oy,0,&ox);
378 root=ox;
379 }
380 else
381 {
382 status=SetMatrixElement(equivalences,ox,0,&oy);
383 root=oy;
384 }
cristy00f8eb42014-10-25 13:46:10 +0000385 ox=offset;
cristy016b7642014-10-25 13:09:57 +0000386 status=GetMatrixElement(equivalences,ox,0,&object);
387 while (object != root) {
388 status=GetMatrixElement(equivalences,ox,0,&object);
389 status=SetMatrixElement(equivalences,ox,0,&root);
390 }
cristy00f8eb42014-10-25 13:46:10 +0000391 oy=offset+neighbor_offset;
cristy016b7642014-10-25 13:09:57 +0000392 status=GetMatrixElement(equivalences,oy,0,&object);
393 while (object != root) {
394 status=GetMatrixElement(equivalences,oy,0,&object);
395 status=SetMatrixElement(equivalences,oy,0,&root);
396 }
397 status=SetMatrixElement(equivalences,y*image->columns+x,0,&root);
398 p+=GetPixelChannels(image);
399 }
400 }
401 }
402 image_view=DestroyCacheView(image_view);
403 /*
404 Label connected components.
405 */
406 n=0;
cristy6e0b3bc2014-10-19 17:51:42 +0000407 component_view=AcquireAuthenticCacheView(component_image,exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000408 for (y=0; y < (ssize_t) component_image->rows; y++)
409 {
410 register Quantum
411 *restrict q;
412
413 register ssize_t
414 x;
415
416 if (status == MagickFalse)
417 continue;
418 q=QueueCacheViewAuthenticPixels(component_view,0,y,component_image->columns,
419 1,exception);
420 if (q == (Quantum *) NULL)
421 {
422 status=MagickFalse;
423 continue;
424 }
425 for (x=0; x < (ssize_t) component_image->columns; x++)
426 {
cristy016b7642014-10-25 13:09:57 +0000427 ssize_t
428 object,
429 offset;
430
431 offset=y*image->columns+x;
432 status=GetMatrixElement(equivalences,offset,0,&object);
433 if (object == offset)
434 {
435 object=n++;
436 status=SetMatrixElement(equivalences,offset,0,&object);
437 }
438 else
439 {
440 status=GetMatrixElement(equivalences,object,0,&object);
441 status=SetMatrixElement(equivalences,offset,0,&object);
442 }
443 *q=(Quantum) (object > (ssize_t) QuantumRange ? (ssize_t) QuantumRange :
444 object);
445 q+=GetPixelChannels(component_image);
cristy6e0b3bc2014-10-19 17:51:42 +0000446 }
447 if (SyncCacheViewAuthenticPixels(component_view,exception) == MagickFalse)
448 status=MagickFalse;
449 if (image->progress_monitor != (MagickProgressMonitor) NULL)
450 {
451 MagickBooleanType
452 proceed;
453
cristy6e0b3bc2014-10-19 17:51:42 +0000454 proceed=SetImageProgress(image,ConnectedComponentsImageTag,progress++,
455 image->rows);
456 if (proceed == MagickFalse)
457 status=MagickFalse;
458 }
459 }
460 component_view=DestroyCacheView(component_view);
cristy016b7642014-10-25 13:09:57 +0000461 equivalences=DestroyMatrixInfo(equivalences);
462 artifact=GetImageArtifact(image,"connected-components:verbose");
463 if (IsStringTrue(artifact))
464 status=ConnectedComponentsStatistics(component_image,(size_t) n,exception);
cristy6e0b3bc2014-10-19 17:51:42 +0000465 if (status == MagickFalse)
466 component_image=DestroyImage(component_image);
467 return(component_image);
468}