/src/imagemagick/MagickCore/feature.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
3 | | % % |
4 | | % % |
5 | | % % |
6 | | % FFFFF EEEEE AAA TTTTT U U RRRR EEEEE % |
7 | | % F E A A T U U R R E % |
8 | | % FFF EEE AAAAA T U U RRRR EEE % |
9 | | % F E A A T U U R R E % |
10 | | % F EEEEE A A T UUU R R EEEEE % |
11 | | % % |
12 | | % % |
13 | | % MagickCore Image Feature Methods % |
14 | | % % |
15 | | % Software Design % |
16 | | % Cristy % |
17 | | % July 1992 % |
18 | | % % |
19 | | % % |
20 | | % Copyright @ 1999 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 | | % https://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 | | |
40 | | /* |
41 | | Include declarations. |
42 | | */ |
43 | | #include "MagickCore/studio.h" |
44 | | #include "MagickCore/animate.h" |
45 | | #include "MagickCore/artifact.h" |
46 | | #include "MagickCore/blob.h" |
47 | | #include "MagickCore/blob-private.h" |
48 | | #include "MagickCore/cache.h" |
49 | | #include "MagickCore/cache-private.h" |
50 | | #include "MagickCore/cache-view.h" |
51 | | #include "MagickCore/channel.h" |
52 | | #include "MagickCore/client.h" |
53 | | #include "MagickCore/color.h" |
54 | | #include "MagickCore/color-private.h" |
55 | | #include "MagickCore/colorspace.h" |
56 | | #include "MagickCore/colorspace-private.h" |
57 | | #include "MagickCore/composite.h" |
58 | | #include "MagickCore/composite-private.h" |
59 | | #include "MagickCore/compress.h" |
60 | | #include "MagickCore/constitute.h" |
61 | | #include "MagickCore/display.h" |
62 | | #include "MagickCore/draw.h" |
63 | | #include "MagickCore/enhance.h" |
64 | | #include "MagickCore/exception.h" |
65 | | #include "MagickCore/exception-private.h" |
66 | | #include "MagickCore/feature.h" |
67 | | #include "MagickCore/gem.h" |
68 | | #include "MagickCore/geometry.h" |
69 | | #include "MagickCore/list.h" |
70 | | #include "MagickCore/image-private.h" |
71 | | #include "MagickCore/magic.h" |
72 | | #include "MagickCore/magick.h" |
73 | | #include "MagickCore/matrix.h" |
74 | | #include "MagickCore/memory_.h" |
75 | | #include "MagickCore/module.h" |
76 | | #include "MagickCore/monitor.h" |
77 | | #include "MagickCore/monitor-private.h" |
78 | | #include "MagickCore/morphology-private.h" |
79 | | #include "MagickCore/nt-base-private.h" |
80 | | #include "MagickCore/option.h" |
81 | | #include "MagickCore/paint.h" |
82 | | #include "MagickCore/pixel-accessor.h" |
83 | | #include "MagickCore/profile.h" |
84 | | #include "MagickCore/property.h" |
85 | | #include "MagickCore/quantize.h" |
86 | | #include "MagickCore/quantum-private.h" |
87 | | #include "MagickCore/random_.h" |
88 | | #include "MagickCore/resource_.h" |
89 | | #include "MagickCore/segment.h" |
90 | | #include "MagickCore/semaphore.h" |
91 | | #include "MagickCore/signature-private.h" |
92 | | #include "MagickCore/statistic-private.h" |
93 | | #include "MagickCore/string_.h" |
94 | | #include "MagickCore/thread-private.h" |
95 | | #include "MagickCore/timer.h" |
96 | | #include "MagickCore/utility.h" |
97 | | #include "MagickCore/utility-private.h" |
98 | | #include "MagickCore/version.h" |
99 | | |
100 | | /* |
101 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
102 | | % % |
103 | | % % |
104 | | % % |
105 | | % C a n n y E d g e I m a g e % |
106 | | % % |
107 | | % % |
108 | | % % |
109 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
110 | | % |
111 | | % CannyEdgeImage() uses a multi-stage algorithm to detect a wide range of |
112 | | % edges in images. |
113 | | % |
114 | | % The format of the CannyEdgeImage method is: |
115 | | % |
116 | | % Image *CannyEdgeImage(const Image *image,const double radius, |
117 | | % const double sigma,const double lower_percent, |
118 | | % const double upper_percent,ExceptionInfo *exception) |
119 | | % |
120 | | % A description of each parameter follows: |
121 | | % |
122 | | % o image: the image. |
123 | | % |
124 | | % o radius: the radius of the gaussian smoothing filter. |
125 | | % |
126 | | % o sigma: the sigma of the gaussian smoothing filter. |
127 | | % |
128 | | % o lower_percent: percentage of edge pixels in the lower threshold. |
129 | | % |
130 | | % o upper_percent: percentage of edge pixels in the upper threshold. |
131 | | % |
132 | | % o exception: return any errors or warnings in this structure. |
133 | | % |
134 | | */ |
135 | | |
136 | | typedef struct _CannyInfo |
137 | | { |
138 | | double |
139 | | magnitude, |
140 | | intensity; |
141 | | |
142 | | int |
143 | | orientation; |
144 | | |
145 | | ssize_t |
146 | | x, |
147 | | y; |
148 | | } CannyInfo; |
149 | | |
150 | | static inline MagickBooleanType IsAuthenticPixel(const Image *image, |
151 | | const ssize_t x,const ssize_t y) |
152 | 0 | { |
153 | 0 | if ((x < 0) || (x >= (ssize_t) image->columns)) |
154 | 0 | return(MagickFalse); |
155 | 0 | if ((y < 0) || (y >= (ssize_t) image->rows)) |
156 | 0 | return(MagickFalse); |
157 | 0 | return(MagickTrue); |
158 | 0 | } |
159 | | |
160 | | static MagickBooleanType TraceEdges(Image *edge_image,CacheView *edge_view, |
161 | | MatrixInfo *canny_cache,const ssize_t x,const ssize_t y, |
162 | | const double lower_threshold,ExceptionInfo *exception) |
163 | 0 | { |
164 | 0 | CannyInfo |
165 | 0 | edge, |
166 | 0 | pixel; |
167 | |
|
168 | 0 | MagickBooleanType |
169 | 0 | status; |
170 | |
|
171 | 0 | Quantum |
172 | 0 | *q; |
173 | |
|
174 | 0 | ssize_t |
175 | 0 | i; |
176 | |
|
177 | 0 | q=GetCacheViewAuthenticPixels(edge_view,x,y,1,1,exception); |
178 | 0 | if (q == (Quantum *) NULL) |
179 | 0 | return(MagickFalse); |
180 | 0 | *q=QuantumRange; |
181 | 0 | status=SyncCacheViewAuthenticPixels(edge_view,exception); |
182 | 0 | if (status == MagickFalse) |
183 | 0 | return(MagickFalse); |
184 | 0 | if (GetMatrixElement(canny_cache,0,0,&edge) == MagickFalse) |
185 | 0 | return(MagickFalse); |
186 | 0 | edge.x=x; |
187 | 0 | edge.y=y; |
188 | 0 | if (SetMatrixElement(canny_cache,0,0,&edge) == MagickFalse) |
189 | 0 | return(MagickFalse); |
190 | 0 | for (i=1; i != 0; ) |
191 | 0 | { |
192 | 0 | ssize_t |
193 | 0 | v; |
194 | |
|
195 | 0 | i--; |
196 | 0 | status=GetMatrixElement(canny_cache,i,0,&edge); |
197 | 0 | if (status == MagickFalse) |
198 | 0 | return(MagickFalse); |
199 | 0 | for (v=(-1); v <= 1; v++) |
200 | 0 | { |
201 | 0 | ssize_t |
202 | 0 | u; |
203 | |
|
204 | 0 | for (u=(-1); u <= 1; u++) |
205 | 0 | { |
206 | 0 | if ((u == 0) && (v == 0)) |
207 | 0 | continue; |
208 | 0 | if (IsAuthenticPixel(edge_image,edge.x+u,edge.y+v) == MagickFalse) |
209 | 0 | continue; |
210 | | /* |
211 | | Not an edge if gradient value is below the lower threshold. |
212 | | */ |
213 | 0 | q=GetCacheViewAuthenticPixels(edge_view,edge.x+u,edge.y+v,1,1, |
214 | 0 | exception); |
215 | 0 | if (q == (Quantum *) NULL) |
216 | 0 | return(MagickFalse); |
217 | 0 | status=GetMatrixElement(canny_cache,edge.x+u,edge.y+v,&pixel); |
218 | 0 | if (status == MagickFalse) |
219 | 0 | return(MagickFalse); |
220 | 0 | if ((GetPixelIntensity(edge_image,q) == 0.0) && |
221 | 0 | (pixel.intensity >= lower_threshold)) |
222 | 0 | { |
223 | 0 | *q=QuantumRange; |
224 | 0 | status=SyncCacheViewAuthenticPixels(edge_view,exception); |
225 | 0 | if (status == MagickFalse) |
226 | 0 | return(MagickFalse); |
227 | 0 | edge.x+=u; |
228 | 0 | edge.y+=v; |
229 | 0 | status=SetMatrixElement(canny_cache,i,0,&edge); |
230 | 0 | if (status == MagickFalse) |
231 | 0 | return(MagickFalse); |
232 | 0 | i++; |
233 | 0 | } |
234 | 0 | } |
235 | 0 | } |
236 | 0 | } |
237 | 0 | return(MagickTrue); |
238 | 0 | } |
239 | | |
240 | | MagickExport Image *CannyEdgeImage(const Image *image,const double radius, |
241 | | const double sigma,const double lower_percent,const double upper_percent, |
242 | | ExceptionInfo *exception) |
243 | 0 | { |
244 | 0 | #define CannyEdgeImageTag "CannyEdge/Image" |
245 | |
|
246 | 0 | CacheView |
247 | 0 | *edge_view; |
248 | |
|
249 | 0 | CannyInfo |
250 | 0 | element; |
251 | |
|
252 | 0 | char |
253 | 0 | geometry[MagickPathExtent]; |
254 | |
|
255 | 0 | double |
256 | 0 | lower_threshold, |
257 | 0 | max, |
258 | 0 | min, |
259 | 0 | upper_threshold; |
260 | |
|
261 | 0 | Image |
262 | 0 | *edge_image; |
263 | |
|
264 | 0 | KernelInfo |
265 | 0 | *kernel_info; |
266 | |
|
267 | 0 | MagickBooleanType |
268 | 0 | status; |
269 | |
|
270 | 0 | MagickOffsetType |
271 | 0 | progress; |
272 | |
|
273 | 0 | MatrixInfo |
274 | 0 | *canny_cache; |
275 | |
|
276 | 0 | ssize_t |
277 | 0 | y; |
278 | |
|
279 | 0 | assert(image != (const Image *) NULL); |
280 | 0 | assert(image->signature == MagickCoreSignature); |
281 | 0 | assert(exception != (ExceptionInfo *) NULL); |
282 | 0 | assert(exception->signature == MagickCoreSignature); |
283 | 0 | if (IsEventLogging() != MagickFalse) |
284 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
285 | | /* |
286 | | Filter out noise. |
287 | | */ |
288 | 0 | (void) FormatLocaleString(geometry,MagickPathExtent, |
289 | 0 | "blur:%.20gx%.20g;blur:%.20gx%.20g+90",radius,sigma,radius,sigma); |
290 | 0 | kernel_info=AcquireKernelInfo(geometry,exception); |
291 | 0 | if (kernel_info == (KernelInfo *) NULL) |
292 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
293 | 0 | edge_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,exception); |
294 | 0 | kernel_info=DestroyKernelInfo(kernel_info); |
295 | 0 | if (edge_image == (Image *) NULL) |
296 | 0 | return((Image *) NULL); |
297 | 0 | if (TransformImageColorspace(edge_image,GRAYColorspace,exception) == MagickFalse) |
298 | 0 | { |
299 | 0 | edge_image=DestroyImage(edge_image); |
300 | 0 | return((Image *) NULL); |
301 | 0 | } |
302 | 0 | (void) SetImageAlphaChannel(edge_image,OffAlphaChannel,exception); |
303 | | /* |
304 | | Find the intensity gradient of the image. |
305 | | */ |
306 | 0 | canny_cache=AcquireMatrixInfo(edge_image->columns,edge_image->rows, |
307 | 0 | sizeof(CannyInfo),exception); |
308 | 0 | if (canny_cache == (MatrixInfo *) NULL) |
309 | 0 | { |
310 | 0 | edge_image=DestroyImage(edge_image); |
311 | 0 | return((Image *) NULL); |
312 | 0 | } |
313 | 0 | status=MagickTrue; |
314 | 0 | edge_view=AcquireVirtualCacheView(edge_image,exception); |
315 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
316 | | #pragma omp parallel for schedule(static) shared(status) \ |
317 | | magick_number_threads(edge_image,edge_image,edge_image->rows,1) |
318 | | #endif |
319 | 0 | for (y=0; y < (ssize_t) edge_image->rows; y++) |
320 | 0 | { |
321 | 0 | const Quantum |
322 | 0 | *magick_restrict p; |
323 | |
|
324 | 0 | ssize_t |
325 | 0 | x; |
326 | |
|
327 | 0 | if (status == MagickFalse) |
328 | 0 | continue; |
329 | 0 | p=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns+1,2, |
330 | 0 | exception); |
331 | 0 | if (p == (const Quantum *) NULL) |
332 | 0 | { |
333 | 0 | status=MagickFalse; |
334 | 0 | continue; |
335 | 0 | } |
336 | 0 | for (x=0; x < (ssize_t) edge_image->columns; x++) |
337 | 0 | { |
338 | 0 | CannyInfo |
339 | 0 | pixel; |
340 | |
|
341 | 0 | double |
342 | 0 | dx, |
343 | 0 | dy; |
344 | |
|
345 | 0 | const Quantum |
346 | 0 | *magick_restrict kernel_pixels; |
347 | |
|
348 | 0 | ssize_t |
349 | 0 | v; |
350 | |
|
351 | 0 | static double |
352 | 0 | Gx[2][2] = |
353 | 0 | { |
354 | 0 | { -1.0, +1.0 }, |
355 | 0 | { -1.0, +1.0 } |
356 | 0 | }, |
357 | 0 | Gy[2][2] = |
358 | 0 | { |
359 | 0 | { +1.0, +1.0 }, |
360 | 0 | { -1.0, -1.0 } |
361 | 0 | }; |
362 | |
|
363 | 0 | (void) memset(&pixel,0,sizeof(pixel)); |
364 | 0 | dx=0.0; |
365 | 0 | dy=0.0; |
366 | 0 | kernel_pixels=p; |
367 | 0 | for (v=0; v < 2; v++) |
368 | 0 | { |
369 | 0 | ssize_t |
370 | 0 | u; |
371 | |
|
372 | 0 | for (u=0; u < 2; u++) |
373 | 0 | { |
374 | 0 | double |
375 | 0 | intensity; |
376 | |
|
377 | 0 | intensity=GetPixelIntensity(edge_image,kernel_pixels+u); |
378 | 0 | dx+=0.5*Gx[v][u]*intensity; |
379 | 0 | dy+=0.5*Gy[v][u]*intensity; |
380 | 0 | } |
381 | 0 | kernel_pixels+=edge_image->columns+1; |
382 | 0 | } |
383 | 0 | pixel.magnitude=hypot(dx,dy); |
384 | 0 | pixel.orientation=0; |
385 | 0 | if (fabs(dx) > MagickEpsilon) |
386 | 0 | { |
387 | 0 | double |
388 | 0 | slope; |
389 | |
|
390 | 0 | slope=dy/dx; |
391 | 0 | if (slope < 0.0) |
392 | 0 | { |
393 | 0 | if (slope < -2.41421356237) |
394 | 0 | pixel.orientation=0; |
395 | 0 | else |
396 | 0 | if (slope < -0.414213562373) |
397 | 0 | pixel.orientation=1; |
398 | 0 | else |
399 | 0 | pixel.orientation=2; |
400 | 0 | } |
401 | 0 | else |
402 | 0 | { |
403 | 0 | if (slope > 2.41421356237) |
404 | 0 | pixel.orientation=0; |
405 | 0 | else |
406 | 0 | if (slope > 0.414213562373) |
407 | 0 | pixel.orientation=3; |
408 | 0 | else |
409 | 0 | pixel.orientation=2; |
410 | 0 | } |
411 | 0 | } |
412 | 0 | if (SetMatrixElement(canny_cache,x,y,&pixel) == MagickFalse) |
413 | 0 | continue; |
414 | 0 | p+=(ptrdiff_t) GetPixelChannels(edge_image); |
415 | 0 | } |
416 | 0 | } |
417 | 0 | edge_view=DestroyCacheView(edge_view); |
418 | | /* |
419 | | Non-maxima suppression, remove pixels that are not considered to be part |
420 | | of an edge. |
421 | | */ |
422 | 0 | progress=0; |
423 | 0 | (void) GetMatrixElement(canny_cache,0,0,&element); |
424 | 0 | max=element.intensity; |
425 | 0 | min=element.intensity; |
426 | 0 | edge_view=AcquireAuthenticCacheView(edge_image,exception); |
427 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
428 | | #pragma omp parallel for schedule(static) shared(status) \ |
429 | | magick_number_threads(edge_image,edge_image,edge_image->rows,1) |
430 | | #endif |
431 | 0 | for (y=0; y < (ssize_t) edge_image->rows; y++) |
432 | 0 | { |
433 | 0 | Quantum |
434 | 0 | *magick_restrict q; |
435 | |
|
436 | 0 | ssize_t |
437 | 0 | x; |
438 | |
|
439 | 0 | if (status == MagickFalse) |
440 | 0 | continue; |
441 | 0 | q=GetCacheViewAuthenticPixels(edge_view,0,y,edge_image->columns,1, |
442 | 0 | exception); |
443 | 0 | if (q == (Quantum *) NULL) |
444 | 0 | { |
445 | 0 | status=MagickFalse; |
446 | 0 | continue; |
447 | 0 | } |
448 | 0 | for (x=0; x < (ssize_t) edge_image->columns; x++) |
449 | 0 | { |
450 | 0 | CannyInfo |
451 | 0 | alpha_pixel, |
452 | 0 | beta_pixel, |
453 | 0 | pixel; |
454 | |
|
455 | 0 | (void) GetMatrixElement(canny_cache,x,y,&pixel); |
456 | 0 | switch (pixel.orientation) |
457 | 0 | { |
458 | 0 | case 0: |
459 | 0 | default: |
460 | 0 | { |
461 | | /* |
462 | | 0 degrees, north and south. |
463 | | */ |
464 | 0 | (void) GetMatrixElement(canny_cache,x,y-1,&alpha_pixel); |
465 | 0 | (void) GetMatrixElement(canny_cache,x,y+1,&beta_pixel); |
466 | 0 | break; |
467 | 0 | } |
468 | 0 | case 1: |
469 | 0 | { |
470 | | /* |
471 | | 45 degrees, northwest and southeast. |
472 | | */ |
473 | 0 | (void) GetMatrixElement(canny_cache,x-1,y-1,&alpha_pixel); |
474 | 0 | (void) GetMatrixElement(canny_cache,x+1,y+1,&beta_pixel); |
475 | 0 | break; |
476 | 0 | } |
477 | 0 | case 2: |
478 | 0 | { |
479 | | /* |
480 | | 90 degrees, east and west. |
481 | | */ |
482 | 0 | (void) GetMatrixElement(canny_cache,x-1,y,&alpha_pixel); |
483 | 0 | (void) GetMatrixElement(canny_cache,x+1,y,&beta_pixel); |
484 | 0 | break; |
485 | 0 | } |
486 | 0 | case 3: |
487 | 0 | { |
488 | | /* |
489 | | 135 degrees, northeast and southwest. |
490 | | */ |
491 | 0 | (void) GetMatrixElement(canny_cache,x+1,y-1,&beta_pixel); |
492 | 0 | (void) GetMatrixElement(canny_cache,x-1,y+1,&alpha_pixel); |
493 | 0 | break; |
494 | 0 | } |
495 | 0 | } |
496 | 0 | pixel.intensity=pixel.magnitude; |
497 | 0 | if ((pixel.magnitude < alpha_pixel.magnitude) || |
498 | 0 | (pixel.magnitude < beta_pixel.magnitude)) |
499 | 0 | pixel.intensity=0; |
500 | 0 | (void) SetMatrixElement(canny_cache,x,y,&pixel); |
501 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
502 | | #pragma omp critical (MagickCore_CannyEdgeImage) |
503 | | #endif |
504 | 0 | { |
505 | 0 | if (pixel.intensity < min) |
506 | 0 | min=pixel.intensity; |
507 | 0 | if (pixel.intensity > max) |
508 | 0 | max=pixel.intensity; |
509 | 0 | } |
510 | 0 | *q=(Quantum) 0; |
511 | 0 | q+=(ptrdiff_t) GetPixelChannels(edge_image); |
512 | 0 | } |
513 | 0 | if (SyncCacheViewAuthenticPixels(edge_view,exception) == MagickFalse) |
514 | 0 | status=MagickFalse; |
515 | 0 | } |
516 | 0 | edge_view=DestroyCacheView(edge_view); |
517 | | /* |
518 | | Estimate hysteresis threshold. |
519 | | */ |
520 | 0 | lower_threshold=lower_percent*(max-min)+min; |
521 | 0 | upper_threshold=upper_percent*(max-min)+min; |
522 | | /* |
523 | | Hysteresis threshold. |
524 | | */ |
525 | 0 | edge_view=AcquireAuthenticCacheView(edge_image,exception); |
526 | 0 | for (y=0; y < (ssize_t) edge_image->rows; y++) |
527 | 0 | { |
528 | 0 | ssize_t |
529 | 0 | x; |
530 | |
|
531 | 0 | if (status == MagickFalse) |
532 | 0 | continue; |
533 | 0 | for (x=0; x < (ssize_t) edge_image->columns; x++) |
534 | 0 | { |
535 | 0 | CannyInfo |
536 | 0 | pixel; |
537 | |
|
538 | 0 | const Quantum |
539 | 0 | *magick_restrict p; |
540 | | |
541 | | /* |
542 | | Edge if pixel gradient higher than upper threshold. |
543 | | */ |
544 | 0 | p=GetCacheViewVirtualPixels(edge_view,x,y,1,1,exception); |
545 | 0 | if (p == (const Quantum *) NULL) |
546 | 0 | continue; |
547 | 0 | status=GetMatrixElement(canny_cache,x,y,&pixel); |
548 | 0 | if (status == MagickFalse) |
549 | 0 | continue; |
550 | 0 | if ((GetPixelIntensity(edge_image,p) == 0.0) && |
551 | 0 | (pixel.intensity >= upper_threshold)) |
552 | 0 | status=TraceEdges(edge_image,edge_view,canny_cache,x,y,lower_threshold, |
553 | 0 | exception); |
554 | 0 | } |
555 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
556 | 0 | { |
557 | 0 | MagickBooleanType |
558 | 0 | proceed; |
559 | |
|
560 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
561 | | #pragma omp atomic |
562 | | #endif |
563 | 0 | progress++; |
564 | 0 | proceed=SetImageProgress(image,CannyEdgeImageTag,progress,image->rows); |
565 | 0 | if (proceed == MagickFalse) |
566 | 0 | status=MagickFalse; |
567 | 0 | } |
568 | 0 | } |
569 | 0 | edge_view=DestroyCacheView(edge_view); |
570 | | /* |
571 | | Free resources. |
572 | | */ |
573 | 0 | canny_cache=DestroyMatrixInfo(canny_cache); |
574 | 0 | return(edge_image); |
575 | 0 | } |
576 | | |
577 | | /* |
578 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
579 | | % % |
580 | | % % |
581 | | % % |
582 | | % G e t I m a g e F e a t u r e s % |
583 | | % % |
584 | | % % |
585 | | % % |
586 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
587 | | % |
588 | | % GetImageFeatures() returns features for each channel in the image in |
589 | | % each of four directions (horizontal, vertical, left and right diagonals) |
590 | | % for the specified distance. The features include the angular second |
591 | | % moment, contrast, correlation, sum of squares: variance, inverse difference |
592 | | % moment, sum average, sum variance, sum entropy, entropy, difference variance, |
593 | | % difference entropy, information measures of correlation 1, information |
594 | | % measures of correlation 2, and maximum correlation coefficient. You can |
595 | | % access the red channel contrast, for example, like this: |
596 | | % |
597 | | % channel_features=GetImageFeatures(image,1,exception); |
598 | | % contrast=channel_features[RedPixelChannel].contrast[0]; |
599 | | % |
600 | | % Use MagickRelinquishMemory() to free the features buffer. |
601 | | % |
602 | | % The format of the GetImageFeatures method is: |
603 | | % |
604 | | % ChannelFeatures *GetImageFeatures(const Image *image, |
605 | | % const size_t distance,ExceptionInfo *exception) |
606 | | % |
607 | | % A description of each parameter follows: |
608 | | % |
609 | | % o image: the image. |
610 | | % |
611 | | % o distance: the distance. |
612 | | % |
613 | | % o exception: return any errors or warnings in this structure. |
614 | | % |
615 | | */ |
616 | | MagickExport ChannelFeatures *GetImageFeatures(const Image *image, |
617 | | const size_t distance,ExceptionInfo *exception) |
618 | 0 | { |
619 | 0 | typedef struct _ChannelStatistics |
620 | 0 | { |
621 | 0 | PixelInfo |
622 | 0 | direction[4]; /* horizontal, vertical, left and right diagonals */ |
623 | 0 | } ChannelStatistics; |
624 | |
|
625 | 0 | CacheView |
626 | 0 | *image_view; |
627 | |
|
628 | 0 | ChannelFeatures |
629 | 0 | *channel_features; |
630 | |
|
631 | 0 | ChannelStatistics |
632 | 0 | **cooccurrence, |
633 | 0 | correlation, |
634 | 0 | *density_x, |
635 | 0 | *density_xy, |
636 | 0 | *density_y, |
637 | 0 | entropy_x, |
638 | 0 | entropy_xy, |
639 | 0 | entropy_xy1, |
640 | 0 | entropy_xy2, |
641 | 0 | entropy_y, |
642 | 0 | mean, |
643 | 0 | **Q, |
644 | 0 | *sum, |
645 | 0 | sum_squares, |
646 | 0 | variance; |
647 | |
|
648 | 0 | PixelPacket |
649 | 0 | gray, |
650 | 0 | *grays; |
651 | |
|
652 | 0 | MagickBooleanType |
653 | 0 | status; |
654 | |
|
655 | 0 | ssize_t |
656 | 0 | i, |
657 | 0 | r; |
658 | |
|
659 | 0 | size_t |
660 | 0 | length; |
661 | |
|
662 | 0 | unsigned int |
663 | 0 | number_grays; |
664 | |
|
665 | 0 | assert(image != (Image *) NULL); |
666 | 0 | assert(image->signature == MagickCoreSignature); |
667 | 0 | if (IsEventLogging() != MagickFalse) |
668 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
669 | 0 | if ((image->columns < (distance+1)) || (image->rows < (distance+1))) |
670 | 0 | return((ChannelFeatures *) NULL); |
671 | 0 | length=MaxPixelChannels+1UL; |
672 | 0 | channel_features=(ChannelFeatures *) AcquireQuantumMemory(length, |
673 | 0 | sizeof(*channel_features)); |
674 | 0 | if (channel_features == (ChannelFeatures *) NULL) |
675 | 0 | ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed"); |
676 | 0 | (void) memset(channel_features,0,length* |
677 | 0 | sizeof(*channel_features)); |
678 | | /* |
679 | | Form grays. |
680 | | */ |
681 | 0 | grays=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*grays)); |
682 | 0 | if (grays == (PixelPacket *) NULL) |
683 | 0 | { |
684 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
685 | 0 | channel_features); |
686 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
687 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
688 | 0 | return(channel_features); |
689 | 0 | } |
690 | 0 | for (i=0; i <= (ssize_t) MaxMap; i++) |
691 | 0 | { |
692 | 0 | grays[i].red=(~0U); |
693 | 0 | grays[i].green=(~0U); |
694 | 0 | grays[i].blue=(~0U); |
695 | 0 | grays[i].alpha=(~0U); |
696 | 0 | grays[i].black=(~0U); |
697 | 0 | } |
698 | 0 | status=MagickTrue; |
699 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
700 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
701 | | #pragma omp parallel for schedule(static) shared(status) \ |
702 | | magick_number_threads(image,image,image->rows,1) |
703 | | #endif |
704 | 0 | for (r=0; r < (ssize_t) image->rows; r++) |
705 | 0 | { |
706 | 0 | const Quantum |
707 | 0 | *magick_restrict p; |
708 | |
|
709 | 0 | ssize_t |
710 | 0 | x; |
711 | |
|
712 | 0 | if (status == MagickFalse) |
713 | 0 | continue; |
714 | 0 | p=GetCacheViewVirtualPixels(image_view,0,r,image->columns,1,exception); |
715 | 0 | if (p == (const Quantum *) NULL) |
716 | 0 | { |
717 | 0 | status=MagickFalse; |
718 | 0 | continue; |
719 | 0 | } |
720 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
721 | 0 | { |
722 | 0 | grays[ScaleQuantumToMap(GetPixelRed(image,p))].red= |
723 | 0 | ScaleQuantumToMap(GetPixelRed(image,p)); |
724 | 0 | grays[ScaleQuantumToMap(GetPixelGreen(image,p))].green= |
725 | 0 | ScaleQuantumToMap(GetPixelGreen(image,p)); |
726 | 0 | grays[ScaleQuantumToMap(GetPixelBlue(image,p))].blue= |
727 | 0 | ScaleQuantumToMap(GetPixelBlue(image,p)); |
728 | 0 | if (image->colorspace == CMYKColorspace) |
729 | 0 | grays[ScaleQuantumToMap(GetPixelBlack(image,p))].black= |
730 | 0 | ScaleQuantumToMap(GetPixelBlack(image,p)); |
731 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
732 | 0 | grays[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha= |
733 | 0 | ScaleQuantumToMap(GetPixelAlpha(image,p)); |
734 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
735 | 0 | } |
736 | 0 | } |
737 | 0 | image_view=DestroyCacheView(image_view); |
738 | 0 | if (status == MagickFalse) |
739 | 0 | { |
740 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
741 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
742 | 0 | channel_features); |
743 | 0 | return(channel_features); |
744 | 0 | } |
745 | 0 | (void) memset(&gray,0,sizeof(gray)); |
746 | 0 | for (i=0; i <= (ssize_t) MaxMap; i++) |
747 | 0 | { |
748 | 0 | if (grays[i].red != ~0U) |
749 | 0 | grays[gray.red++].red=grays[i].red; |
750 | 0 | if (grays[i].green != ~0U) |
751 | 0 | grays[gray.green++].green=grays[i].green; |
752 | 0 | if (grays[i].blue != ~0U) |
753 | 0 | grays[gray.blue++].blue=grays[i].blue; |
754 | 0 | if (image->colorspace == CMYKColorspace) |
755 | 0 | if (grays[i].black != ~0U) |
756 | 0 | grays[gray.black++].black=grays[i].black; |
757 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
758 | 0 | if (grays[i].alpha != ~0U) |
759 | 0 | grays[gray.alpha++].alpha=grays[i].alpha; |
760 | 0 | } |
761 | | /* |
762 | | Allocate spatial dependence matrix. |
763 | | */ |
764 | 0 | number_grays=gray.red; |
765 | 0 | if (gray.green > number_grays) |
766 | 0 | number_grays=gray.green; |
767 | 0 | if (gray.blue > number_grays) |
768 | 0 | number_grays=gray.blue; |
769 | 0 | if (image->colorspace == CMYKColorspace) |
770 | 0 | if (gray.black > number_grays) |
771 | 0 | number_grays=gray.black; |
772 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
773 | 0 | if (gray.alpha > number_grays) |
774 | 0 | number_grays=gray.alpha; |
775 | 0 | cooccurrence=(ChannelStatistics **) AcquireQuantumMemory(number_grays, |
776 | 0 | sizeof(*cooccurrence)); |
777 | 0 | density_x=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1, |
778 | 0 | 2*sizeof(*density_x)); |
779 | 0 | density_xy=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1, |
780 | 0 | 2*sizeof(*density_xy)); |
781 | 0 | density_y=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1, |
782 | 0 | 2*sizeof(*density_y)); |
783 | 0 | Q=(ChannelStatistics **) AcquireQuantumMemory(number_grays,sizeof(*Q)); |
784 | 0 | sum=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(*sum)); |
785 | 0 | if ((cooccurrence == (ChannelStatistics **) NULL) || |
786 | 0 | (density_x == (ChannelStatistics *) NULL) || |
787 | 0 | (density_xy == (ChannelStatistics *) NULL) || |
788 | 0 | (density_y == (ChannelStatistics *) NULL) || |
789 | 0 | (Q == (ChannelStatistics **) NULL) || |
790 | 0 | (sum == (ChannelStatistics *) NULL)) |
791 | 0 | { |
792 | 0 | if (Q != (ChannelStatistics **) NULL) |
793 | 0 | { |
794 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
795 | 0 | Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]); |
796 | 0 | Q=(ChannelStatistics **) RelinquishMagickMemory(Q); |
797 | 0 | } |
798 | 0 | if (sum != (ChannelStatistics *) NULL) |
799 | 0 | sum=(ChannelStatistics *) RelinquishMagickMemory(sum); |
800 | 0 | if (density_y != (ChannelStatistics *) NULL) |
801 | 0 | density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y); |
802 | 0 | if (density_xy != (ChannelStatistics *) NULL) |
803 | 0 | density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy); |
804 | 0 | if (density_x != (ChannelStatistics *) NULL) |
805 | 0 | density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x); |
806 | 0 | if (cooccurrence != (ChannelStatistics **) NULL) |
807 | 0 | { |
808 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
809 | 0 | cooccurrence[i]=(ChannelStatistics *) |
810 | 0 | RelinquishMagickMemory(cooccurrence[i]); |
811 | 0 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory( |
812 | 0 | cooccurrence); |
813 | 0 | } |
814 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
815 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
816 | 0 | channel_features); |
817 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
818 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
819 | 0 | return(channel_features); |
820 | 0 | } |
821 | 0 | (void) memset(&correlation,0,sizeof(correlation)); |
822 | 0 | (void) memset(density_x,0,2*(number_grays+1)*sizeof(*density_x)); |
823 | 0 | (void) memset(density_xy,0,2*(number_grays+1)*sizeof(*density_xy)); |
824 | 0 | (void) memset(density_y,0,2*(number_grays+1)*sizeof(*density_y)); |
825 | 0 | (void) memset(&mean,0,sizeof(mean)); |
826 | 0 | (void) memset(sum,0,number_grays*sizeof(*sum)); |
827 | 0 | (void) memset(&sum_squares,0,sizeof(sum_squares)); |
828 | 0 | (void) memset(density_xy,0,2*number_grays*sizeof(*density_xy)); |
829 | 0 | (void) memset(&entropy_x,0,sizeof(entropy_x)); |
830 | 0 | (void) memset(&entropy_xy,0,sizeof(entropy_xy)); |
831 | 0 | (void) memset(&entropy_xy1,0,sizeof(entropy_xy1)); |
832 | 0 | (void) memset(&entropy_xy2,0,sizeof(entropy_xy2)); |
833 | 0 | (void) memset(&entropy_y,0,sizeof(entropy_y)); |
834 | 0 | (void) memset(&variance,0,sizeof(variance)); |
835 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
836 | 0 | { |
837 | 0 | cooccurrence[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays, |
838 | 0 | sizeof(**cooccurrence)); |
839 | 0 | Q[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(**Q)); |
840 | 0 | if ((cooccurrence[i] == (ChannelStatistics *) NULL) || |
841 | 0 | (Q[i] == (ChannelStatistics *) NULL)) |
842 | 0 | break; |
843 | 0 | (void) memset(cooccurrence[i],0,number_grays* |
844 | 0 | sizeof(**cooccurrence)); |
845 | 0 | (void) memset(Q[i],0,number_grays*sizeof(**Q)); |
846 | 0 | } |
847 | 0 | if (i < (ssize_t) number_grays) |
848 | 0 | { |
849 | 0 | for (i--; i >= 0; i--) |
850 | 0 | { |
851 | 0 | if (Q[i] != (ChannelStatistics *) NULL) |
852 | 0 | Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]); |
853 | 0 | if (cooccurrence[i] != (ChannelStatistics *) NULL) |
854 | 0 | cooccurrence[i]=(ChannelStatistics *) |
855 | 0 | RelinquishMagickMemory(cooccurrence[i]); |
856 | 0 | } |
857 | 0 | Q=(ChannelStatistics **) RelinquishMagickMemory(Q); |
858 | 0 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence); |
859 | 0 | sum=(ChannelStatistics *) RelinquishMagickMemory(sum); |
860 | 0 | density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y); |
861 | 0 | density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy); |
862 | 0 | density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x); |
863 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
864 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
865 | 0 | channel_features); |
866 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
867 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
868 | 0 | return(channel_features); |
869 | 0 | } |
870 | | /* |
871 | | Initialize spatial dependence matrix. |
872 | | */ |
873 | 0 | status=MagickTrue; |
874 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
875 | 0 | for (r=0; r < (ssize_t) image->rows; r++) |
876 | 0 | { |
877 | 0 | const Quantum |
878 | 0 | *magick_restrict p; |
879 | |
|
880 | 0 | ssize_t |
881 | 0 | x; |
882 | |
|
883 | 0 | ssize_t |
884 | 0 | offset, |
885 | 0 | u, |
886 | 0 | v; |
887 | |
|
888 | 0 | if (status == MagickFalse) |
889 | 0 | continue; |
890 | 0 | p=GetCacheViewVirtualPixels(image_view,-(ssize_t) distance,r,image->columns+ |
891 | 0 | 2*distance,distance+2,exception); |
892 | 0 | if (p == (const Quantum *) NULL) |
893 | 0 | { |
894 | 0 | status=MagickFalse; |
895 | 0 | continue; |
896 | 0 | } |
897 | 0 | p+=(ptrdiff_t) distance*GetPixelChannels(image);; |
898 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
899 | 0 | { |
900 | 0 | for (i=0; i < 4; i++) |
901 | 0 | { |
902 | 0 | switch (i) |
903 | 0 | { |
904 | 0 | case 0: |
905 | 0 | default: |
906 | 0 | { |
907 | | /* |
908 | | Horizontal adjacency. |
909 | | */ |
910 | 0 | offset=(ssize_t) distance; |
911 | 0 | break; |
912 | 0 | } |
913 | 0 | case 1: |
914 | 0 | { |
915 | | /* |
916 | | Vertical adjacency. |
917 | | */ |
918 | 0 | offset=(ssize_t) (image->columns+2*distance); |
919 | 0 | break; |
920 | 0 | } |
921 | 0 | case 2: |
922 | 0 | { |
923 | | /* |
924 | | Right diagonal adjacency. |
925 | | */ |
926 | 0 | offset=(ssize_t) ((image->columns+2*distance)-distance); |
927 | 0 | break; |
928 | 0 | } |
929 | 0 | case 3: |
930 | 0 | { |
931 | | /* |
932 | | Left diagonal adjacency. |
933 | | */ |
934 | 0 | offset=(ssize_t) ((image->columns+2*distance)+distance); |
935 | 0 | break; |
936 | 0 | } |
937 | 0 | } |
938 | 0 | u=0; |
939 | 0 | v=0; |
940 | 0 | while (grays[u].red != ScaleQuantumToMap(GetPixelRed(image,p))) |
941 | 0 | u++; |
942 | 0 | while (grays[v].red != ScaleQuantumToMap(GetPixelRed(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
943 | 0 | v++; |
944 | 0 | cooccurrence[u][v].direction[i].red++; |
945 | 0 | cooccurrence[v][u].direction[i].red++; |
946 | 0 | u=0; |
947 | 0 | v=0; |
948 | 0 | while (grays[u].green != ScaleQuantumToMap(GetPixelGreen(image,p))) |
949 | 0 | u++; |
950 | 0 | while (grays[v].green != ScaleQuantumToMap(GetPixelGreen(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
951 | 0 | v++; |
952 | 0 | cooccurrence[u][v].direction[i].green++; |
953 | 0 | cooccurrence[v][u].direction[i].green++; |
954 | 0 | u=0; |
955 | 0 | v=0; |
956 | 0 | while (grays[u].blue != ScaleQuantumToMap(GetPixelBlue(image,p))) |
957 | 0 | u++; |
958 | 0 | while (grays[v].blue != ScaleQuantumToMap(GetPixelBlue(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
959 | 0 | v++; |
960 | 0 | cooccurrence[u][v].direction[i].blue++; |
961 | 0 | cooccurrence[v][u].direction[i].blue++; |
962 | 0 | if (image->colorspace == CMYKColorspace) |
963 | 0 | { |
964 | 0 | u=0; |
965 | 0 | v=0; |
966 | 0 | while (grays[u].black != ScaleQuantumToMap(GetPixelBlack(image,p))) |
967 | 0 | u++; |
968 | 0 | while (grays[v].black != ScaleQuantumToMap(GetPixelBlack(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
969 | 0 | v++; |
970 | 0 | cooccurrence[u][v].direction[i].black++; |
971 | 0 | cooccurrence[v][u].direction[i].black++; |
972 | 0 | } |
973 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
974 | 0 | { |
975 | 0 | u=0; |
976 | 0 | v=0; |
977 | 0 | while (grays[u].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p))) |
978 | 0 | u++; |
979 | 0 | while (grays[v].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
980 | 0 | v++; |
981 | 0 | cooccurrence[u][v].direction[i].alpha++; |
982 | 0 | cooccurrence[v][u].direction[i].alpha++; |
983 | 0 | } |
984 | 0 | } |
985 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
986 | 0 | } |
987 | 0 | } |
988 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
989 | 0 | image_view=DestroyCacheView(image_view); |
990 | 0 | if (status == MagickFalse) |
991 | 0 | { |
992 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
993 | 0 | cooccurrence[i]=(ChannelStatistics *) |
994 | 0 | RelinquishMagickMemory(cooccurrence[i]); |
995 | 0 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence); |
996 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
997 | 0 | channel_features); |
998 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
999 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
1000 | 0 | return(channel_features); |
1001 | 0 | } |
1002 | | /* |
1003 | | Normalize spatial dependence matrix. |
1004 | | */ |
1005 | 0 | for (i=0; i < 4; i++) |
1006 | 0 | { |
1007 | 0 | double |
1008 | 0 | normalize; |
1009 | |
|
1010 | 0 | ssize_t |
1011 | 0 | y; |
1012 | |
|
1013 | 0 | switch (i) |
1014 | 0 | { |
1015 | 0 | case 0: |
1016 | 0 | default: |
1017 | 0 | { |
1018 | | /* |
1019 | | Horizontal adjacency. |
1020 | | */ |
1021 | 0 | normalize=2.0*image->rows*(image->columns-distance); |
1022 | 0 | break; |
1023 | 0 | } |
1024 | 0 | case 1: |
1025 | 0 | { |
1026 | | /* |
1027 | | Vertical adjacency. |
1028 | | */ |
1029 | 0 | normalize=2.0*(image->rows-distance)*image->columns; |
1030 | 0 | break; |
1031 | 0 | } |
1032 | 0 | case 2: |
1033 | 0 | { |
1034 | | /* |
1035 | | Right diagonal adjacency. |
1036 | | */ |
1037 | 0 | normalize=2.0*(image->rows-distance)*(image->columns-distance); |
1038 | 0 | break; |
1039 | 0 | } |
1040 | 0 | case 3: |
1041 | 0 | { |
1042 | | /* |
1043 | | Left diagonal adjacency. |
1044 | | */ |
1045 | 0 | normalize=2.0*(image->rows-distance)*(image->columns-distance); |
1046 | 0 | break; |
1047 | 0 | } |
1048 | 0 | } |
1049 | 0 | normalize=MagickSafeReciprocal(normalize); |
1050 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1051 | 0 | { |
1052 | 0 | ssize_t |
1053 | 0 | x; |
1054 | |
|
1055 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1056 | 0 | { |
1057 | 0 | cooccurrence[x][y].direction[i].red*=normalize; |
1058 | 0 | cooccurrence[x][y].direction[i].green*=normalize; |
1059 | 0 | cooccurrence[x][y].direction[i].blue*=normalize; |
1060 | 0 | if (image->colorspace == CMYKColorspace) |
1061 | 0 | cooccurrence[x][y].direction[i].black*=normalize; |
1062 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1063 | 0 | cooccurrence[x][y].direction[i].alpha*=normalize; |
1064 | 0 | } |
1065 | 0 | } |
1066 | 0 | } |
1067 | | /* |
1068 | | Compute texture features. |
1069 | | */ |
1070 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1071 | | #pragma omp parallel for schedule(static) shared(status) \ |
1072 | | magick_number_threads(image,image,number_grays,1) |
1073 | | #endif |
1074 | 0 | for (i=0; i < 4; i++) |
1075 | 0 | { |
1076 | 0 | ssize_t |
1077 | 0 | y; |
1078 | |
|
1079 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1080 | 0 | { |
1081 | 0 | ssize_t |
1082 | 0 | x; |
1083 | |
|
1084 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1085 | 0 | { |
1086 | | /* |
1087 | | Angular second moment: measure of homogeneity of the image. |
1088 | | */ |
1089 | 0 | channel_features[RedPixelChannel].angular_second_moment[i]+= |
1090 | 0 | cooccurrence[x][y].direction[i].red* |
1091 | 0 | cooccurrence[x][y].direction[i].red; |
1092 | 0 | channel_features[GreenPixelChannel].angular_second_moment[i]+= |
1093 | 0 | cooccurrence[x][y].direction[i].green* |
1094 | 0 | cooccurrence[x][y].direction[i].green; |
1095 | 0 | channel_features[BluePixelChannel].angular_second_moment[i]+= |
1096 | 0 | cooccurrence[x][y].direction[i].blue* |
1097 | 0 | cooccurrence[x][y].direction[i].blue; |
1098 | 0 | if (image->colorspace == CMYKColorspace) |
1099 | 0 | channel_features[BlackPixelChannel].angular_second_moment[i]+= |
1100 | 0 | cooccurrence[x][y].direction[i].black* |
1101 | 0 | cooccurrence[x][y].direction[i].black; |
1102 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1103 | 0 | channel_features[AlphaPixelChannel].angular_second_moment[i]+= |
1104 | 0 | cooccurrence[x][y].direction[i].alpha* |
1105 | 0 | cooccurrence[x][y].direction[i].alpha; |
1106 | | /* |
1107 | | Correlation: measure of linear-dependencies in the image. |
1108 | | */ |
1109 | 0 | sum[y].direction[i].red+=cooccurrence[x][y].direction[i].red; |
1110 | 0 | sum[y].direction[i].green+=cooccurrence[x][y].direction[i].green; |
1111 | 0 | sum[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1112 | 0 | if (image->colorspace == CMYKColorspace) |
1113 | 0 | sum[y].direction[i].black+=cooccurrence[x][y].direction[i].black; |
1114 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1115 | 0 | sum[y].direction[i].alpha+=cooccurrence[x][y].direction[i].alpha; |
1116 | 0 | correlation.direction[i].red+=x*y*cooccurrence[x][y].direction[i].red; |
1117 | 0 | correlation.direction[i].green+=x*y* |
1118 | 0 | cooccurrence[x][y].direction[i].green; |
1119 | 0 | correlation.direction[i].blue+=x*y* |
1120 | 0 | cooccurrence[x][y].direction[i].blue; |
1121 | 0 | if (image->colorspace == CMYKColorspace) |
1122 | 0 | correlation.direction[i].black+=x*y* |
1123 | 0 | cooccurrence[x][y].direction[i].black; |
1124 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1125 | 0 | correlation.direction[i].alpha+=x*y* |
1126 | 0 | cooccurrence[x][y].direction[i].alpha; |
1127 | | /* |
1128 | | Inverse Difference Moment. |
1129 | | */ |
1130 | 0 | channel_features[RedPixelChannel].inverse_difference_moment[i]+= |
1131 | 0 | cooccurrence[x][y].direction[i].red/((y-x)*(y-x)+1); |
1132 | 0 | channel_features[GreenPixelChannel].inverse_difference_moment[i]+= |
1133 | 0 | cooccurrence[x][y].direction[i].green/((y-x)*(y-x)+1); |
1134 | 0 | channel_features[BluePixelChannel].inverse_difference_moment[i]+= |
1135 | 0 | cooccurrence[x][y].direction[i].blue/((y-x)*(y-x)+1); |
1136 | 0 | if (image->colorspace == CMYKColorspace) |
1137 | 0 | channel_features[BlackPixelChannel].inverse_difference_moment[i]+= |
1138 | 0 | cooccurrence[x][y].direction[i].black/((y-x)*(y-x)+1); |
1139 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1140 | 0 | channel_features[AlphaPixelChannel].inverse_difference_moment[i]+= |
1141 | 0 | cooccurrence[x][y].direction[i].alpha/((y-x)*(y-x)+1); |
1142 | | /* |
1143 | | Sum average. |
1144 | | */ |
1145 | 0 | density_xy[y+x+2].direction[i].red+= |
1146 | 0 | cooccurrence[x][y].direction[i].red; |
1147 | 0 | density_xy[y+x+2].direction[i].green+= |
1148 | 0 | cooccurrence[x][y].direction[i].green; |
1149 | 0 | density_xy[y+x+2].direction[i].blue+= |
1150 | 0 | cooccurrence[x][y].direction[i].blue; |
1151 | 0 | if (image->colorspace == CMYKColorspace) |
1152 | 0 | density_xy[y+x+2].direction[i].black+= |
1153 | 0 | cooccurrence[x][y].direction[i].black; |
1154 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1155 | 0 | density_xy[y+x+2].direction[i].alpha+= |
1156 | 0 | cooccurrence[x][y].direction[i].alpha; |
1157 | | /* |
1158 | | Entropy. |
1159 | | */ |
1160 | 0 | channel_features[RedPixelChannel].entropy[i]-= |
1161 | 0 | cooccurrence[x][y].direction[i].red* |
1162 | 0 | log2(cooccurrence[x][y].direction[i].red); |
1163 | 0 | channel_features[GreenPixelChannel].entropy[i]-= |
1164 | 0 | cooccurrence[x][y].direction[i].green* |
1165 | 0 | log2(cooccurrence[x][y].direction[i].green); |
1166 | 0 | channel_features[BluePixelChannel].entropy[i]-= |
1167 | 0 | cooccurrence[x][y].direction[i].blue* |
1168 | 0 | log2(cooccurrence[x][y].direction[i].blue); |
1169 | 0 | if (image->colorspace == CMYKColorspace) |
1170 | 0 | channel_features[BlackPixelChannel].entropy[i]-= |
1171 | 0 | cooccurrence[x][y].direction[i].black* |
1172 | 0 | log2(cooccurrence[x][y].direction[i].black); |
1173 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1174 | 0 | channel_features[AlphaPixelChannel].entropy[i]-= |
1175 | 0 | cooccurrence[x][y].direction[i].alpha* |
1176 | 0 | log2(cooccurrence[x][y].direction[i].alpha); |
1177 | | /* |
1178 | | Information Measures of Correlation. |
1179 | | */ |
1180 | 0 | density_x[x].direction[i].red+=cooccurrence[x][y].direction[i].red; |
1181 | 0 | density_x[x].direction[i].green+=cooccurrence[x][y].direction[i].green; |
1182 | 0 | density_x[x].direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1183 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1184 | 0 | density_x[x].direction[i].alpha+= |
1185 | 0 | cooccurrence[x][y].direction[i].alpha; |
1186 | 0 | if (image->colorspace == CMYKColorspace) |
1187 | 0 | density_x[x].direction[i].black+= |
1188 | 0 | cooccurrence[x][y].direction[i].black; |
1189 | 0 | density_y[y].direction[i].red+=cooccurrence[x][y].direction[i].red; |
1190 | 0 | density_y[y].direction[i].green+=cooccurrence[x][y].direction[i].green; |
1191 | 0 | density_y[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1192 | 0 | if (image->colorspace == CMYKColorspace) |
1193 | 0 | density_y[y].direction[i].black+= |
1194 | 0 | cooccurrence[x][y].direction[i].black; |
1195 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1196 | 0 | density_y[y].direction[i].alpha+= |
1197 | 0 | cooccurrence[x][y].direction[i].alpha; |
1198 | 0 | } |
1199 | 0 | mean.direction[i].red+=y*sum[y].direction[i].red; |
1200 | 0 | sum_squares.direction[i].red+=y*y*sum[y].direction[i].red; |
1201 | 0 | mean.direction[i].green+=y*sum[y].direction[i].green; |
1202 | 0 | sum_squares.direction[i].green+=y*y*sum[y].direction[i].green; |
1203 | 0 | mean.direction[i].blue+=y*sum[y].direction[i].blue; |
1204 | 0 | sum_squares.direction[i].blue+=y*y*sum[y].direction[i].blue; |
1205 | 0 | if (image->colorspace == CMYKColorspace) |
1206 | 0 | { |
1207 | 0 | mean.direction[i].black+=y*sum[y].direction[i].black; |
1208 | 0 | sum_squares.direction[i].black+=y*y*sum[y].direction[i].black; |
1209 | 0 | } |
1210 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1211 | 0 | { |
1212 | 0 | mean.direction[i].alpha+=y*sum[y].direction[i].alpha; |
1213 | 0 | sum_squares.direction[i].alpha+=y*y*sum[y].direction[i].alpha; |
1214 | 0 | } |
1215 | 0 | } |
1216 | | /* |
1217 | | Correlation: measure of linear-dependencies in the image. |
1218 | | */ |
1219 | 0 | channel_features[RedPixelChannel].correlation[i]= |
1220 | 0 | (correlation.direction[i].red-mean.direction[i].red* |
1221 | 0 | mean.direction[i].red)/(sqrt(sum_squares.direction[i].red- |
1222 | 0 | (mean.direction[i].red*mean.direction[i].red))*sqrt( |
1223 | 0 | sum_squares.direction[i].red-(mean.direction[i].red* |
1224 | 0 | mean.direction[i].red))); |
1225 | 0 | channel_features[GreenPixelChannel].correlation[i]= |
1226 | 0 | (correlation.direction[i].green-mean.direction[i].green* |
1227 | 0 | mean.direction[i].green)/(sqrt(sum_squares.direction[i].green- |
1228 | 0 | (mean.direction[i].green*mean.direction[i].green))*sqrt( |
1229 | 0 | sum_squares.direction[i].green-(mean.direction[i].green* |
1230 | 0 | mean.direction[i].green))); |
1231 | 0 | channel_features[BluePixelChannel].correlation[i]= |
1232 | 0 | (correlation.direction[i].blue-mean.direction[i].blue* |
1233 | 0 | mean.direction[i].blue)/(sqrt(sum_squares.direction[i].blue- |
1234 | 0 | (mean.direction[i].blue*mean.direction[i].blue))*sqrt( |
1235 | 0 | sum_squares.direction[i].blue-(mean.direction[i].blue* |
1236 | 0 | mean.direction[i].blue))); |
1237 | 0 | if (image->colorspace == CMYKColorspace) |
1238 | 0 | channel_features[BlackPixelChannel].correlation[i]= |
1239 | 0 | (correlation.direction[i].black-mean.direction[i].black* |
1240 | 0 | mean.direction[i].black)/(sqrt(sum_squares.direction[i].black- |
1241 | 0 | (mean.direction[i].black*mean.direction[i].black))*sqrt( |
1242 | 0 | sum_squares.direction[i].black-(mean.direction[i].black* |
1243 | 0 | mean.direction[i].black))); |
1244 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1245 | 0 | channel_features[AlphaPixelChannel].correlation[i]= |
1246 | 0 | (correlation.direction[i].alpha-mean.direction[i].alpha* |
1247 | 0 | mean.direction[i].alpha)/(sqrt(sum_squares.direction[i].alpha- |
1248 | 0 | (mean.direction[i].alpha*mean.direction[i].alpha))*sqrt( |
1249 | 0 | sum_squares.direction[i].alpha-(mean.direction[i].alpha* |
1250 | 0 | mean.direction[i].alpha))); |
1251 | 0 | } |
1252 | | /* |
1253 | | Compute more texture features. |
1254 | | */ |
1255 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1256 | | #pragma omp parallel for schedule(static) shared(status) \ |
1257 | | magick_number_threads(image,image,number_grays,1) |
1258 | | #endif |
1259 | 0 | for (i=0; i < 4; i++) |
1260 | 0 | { |
1261 | 0 | ssize_t |
1262 | 0 | x; |
1263 | |
|
1264 | 0 | for (x=2; x < (ssize_t) (2*number_grays); x++) |
1265 | 0 | { |
1266 | | /* |
1267 | | Sum average. |
1268 | | */ |
1269 | 0 | channel_features[RedPixelChannel].sum_average[i]+= |
1270 | 0 | x*density_xy[x].direction[i].red; |
1271 | 0 | channel_features[GreenPixelChannel].sum_average[i]+= |
1272 | 0 | x*density_xy[x].direction[i].green; |
1273 | 0 | channel_features[BluePixelChannel].sum_average[i]+= |
1274 | 0 | x*density_xy[x].direction[i].blue; |
1275 | 0 | if (image->colorspace == CMYKColorspace) |
1276 | 0 | channel_features[BlackPixelChannel].sum_average[i]+= |
1277 | 0 | x*density_xy[x].direction[i].black; |
1278 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1279 | 0 | channel_features[AlphaPixelChannel].sum_average[i]+= |
1280 | 0 | x*density_xy[x].direction[i].alpha; |
1281 | | /* |
1282 | | Sum entropy. |
1283 | | */ |
1284 | 0 | channel_features[RedPixelChannel].sum_entropy[i]-= |
1285 | 0 | density_xy[x].direction[i].red* |
1286 | 0 | log2(density_xy[x].direction[i].red); |
1287 | 0 | channel_features[GreenPixelChannel].sum_entropy[i]-= |
1288 | 0 | density_xy[x].direction[i].green* |
1289 | 0 | log2(density_xy[x].direction[i].green); |
1290 | 0 | channel_features[BluePixelChannel].sum_entropy[i]-= |
1291 | 0 | density_xy[x].direction[i].blue* |
1292 | 0 | log2(density_xy[x].direction[i].blue); |
1293 | 0 | if (image->colorspace == CMYKColorspace) |
1294 | 0 | channel_features[BlackPixelChannel].sum_entropy[i]-= |
1295 | 0 | density_xy[x].direction[i].black* |
1296 | 0 | log2(density_xy[x].direction[i].black); |
1297 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1298 | 0 | channel_features[AlphaPixelChannel].sum_entropy[i]-= |
1299 | 0 | density_xy[x].direction[i].alpha* |
1300 | 0 | log2(density_xy[x].direction[i].alpha); |
1301 | | /* |
1302 | | Sum variance. |
1303 | | */ |
1304 | 0 | channel_features[RedPixelChannel].sum_variance[i]+= |
1305 | 0 | (x-channel_features[RedPixelChannel].sum_entropy[i])* |
1306 | 0 | (x-channel_features[RedPixelChannel].sum_entropy[i])* |
1307 | 0 | density_xy[x].direction[i].red; |
1308 | 0 | channel_features[GreenPixelChannel].sum_variance[i]+= |
1309 | 0 | (x-channel_features[GreenPixelChannel].sum_entropy[i])* |
1310 | 0 | (x-channel_features[GreenPixelChannel].sum_entropy[i])* |
1311 | 0 | density_xy[x].direction[i].green; |
1312 | 0 | channel_features[BluePixelChannel].sum_variance[i]+= |
1313 | 0 | (x-channel_features[BluePixelChannel].sum_entropy[i])* |
1314 | 0 | (x-channel_features[BluePixelChannel].sum_entropy[i])* |
1315 | 0 | density_xy[x].direction[i].blue; |
1316 | 0 | if (image->colorspace == CMYKColorspace) |
1317 | 0 | channel_features[BlackPixelChannel].sum_variance[i]+= |
1318 | 0 | (x-channel_features[BlackPixelChannel].sum_entropy[i])* |
1319 | 0 | (x-channel_features[BlackPixelChannel].sum_entropy[i])* |
1320 | 0 | density_xy[x].direction[i].black; |
1321 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1322 | 0 | channel_features[AlphaPixelChannel].sum_variance[i]+= |
1323 | 0 | (x-channel_features[AlphaPixelChannel].sum_entropy[i])* |
1324 | 0 | (x-channel_features[AlphaPixelChannel].sum_entropy[i])* |
1325 | 0 | density_xy[x].direction[i].alpha; |
1326 | 0 | } |
1327 | 0 | } |
1328 | | /* |
1329 | | Compute more texture features. |
1330 | | */ |
1331 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1332 | | #pragma omp parallel for schedule(static) shared(status) \ |
1333 | | magick_number_threads(image,image,number_grays,1) |
1334 | | #endif |
1335 | 0 | for (i=0; i < 4; i++) |
1336 | 0 | { |
1337 | 0 | ssize_t |
1338 | 0 | y; |
1339 | |
|
1340 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1341 | 0 | { |
1342 | 0 | ssize_t |
1343 | 0 | x; |
1344 | |
|
1345 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1346 | 0 | { |
1347 | | /* |
1348 | | Sum of Squares: Variance |
1349 | | */ |
1350 | 0 | variance.direction[i].red+=(y-mean.direction[i].red+1)* |
1351 | 0 | (y-mean.direction[i].red+1)*cooccurrence[x][y].direction[i].red; |
1352 | 0 | variance.direction[i].green+=(y-mean.direction[i].green+1)* |
1353 | 0 | (y-mean.direction[i].green+1)*cooccurrence[x][y].direction[i].green; |
1354 | 0 | variance.direction[i].blue+=(y-mean.direction[i].blue+1)* |
1355 | 0 | (y-mean.direction[i].blue+1)*cooccurrence[x][y].direction[i].blue; |
1356 | 0 | if (image->colorspace == CMYKColorspace) |
1357 | 0 | variance.direction[i].black+=(y-mean.direction[i].black+1)* |
1358 | 0 | (y-mean.direction[i].black+1)*cooccurrence[x][y].direction[i].black; |
1359 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1360 | 0 | variance.direction[i].alpha+=(y-mean.direction[i].alpha+1)* |
1361 | 0 | (y-mean.direction[i].alpha+1)* |
1362 | 0 | cooccurrence[x][y].direction[i].alpha; |
1363 | | /* |
1364 | | Sum average / Difference Variance. |
1365 | | */ |
1366 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].red+= |
1367 | 0 | cooccurrence[x][y].direction[i].red; |
1368 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].green+= |
1369 | 0 | cooccurrence[x][y].direction[i].green; |
1370 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].blue+= |
1371 | 0 | cooccurrence[x][y].direction[i].blue; |
1372 | 0 | if (image->colorspace == CMYKColorspace) |
1373 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].black+= |
1374 | 0 | cooccurrence[x][y].direction[i].black; |
1375 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1376 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].alpha+= |
1377 | 0 | cooccurrence[x][y].direction[i].alpha; |
1378 | | /* |
1379 | | Information Measures of Correlation. |
1380 | | */ |
1381 | 0 | entropy_xy.direction[i].red-=cooccurrence[x][y].direction[i].red* |
1382 | 0 | log2(cooccurrence[x][y].direction[i].red); |
1383 | 0 | entropy_xy.direction[i].green-=cooccurrence[x][y].direction[i].green* |
1384 | 0 | log2(cooccurrence[x][y].direction[i].green); |
1385 | 0 | entropy_xy.direction[i].blue-=cooccurrence[x][y].direction[i].blue* |
1386 | 0 | log2(cooccurrence[x][y].direction[i].blue); |
1387 | 0 | if (image->colorspace == CMYKColorspace) |
1388 | 0 | entropy_xy.direction[i].black-=cooccurrence[x][y].direction[i].black* |
1389 | 0 | log2(cooccurrence[x][y].direction[i].black); |
1390 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1391 | 0 | entropy_xy.direction[i].alpha-= |
1392 | 0 | cooccurrence[x][y].direction[i].alpha*log2( |
1393 | 0 | cooccurrence[x][y].direction[i].alpha); |
1394 | 0 | entropy_xy1.direction[i].red-=(cooccurrence[x][y].direction[i].red* |
1395 | 0 | log2(density_x[x].direction[i].red*density_y[y].direction[i].red)); |
1396 | 0 | entropy_xy1.direction[i].green-=(cooccurrence[x][y].direction[i].green* |
1397 | 0 | log2(density_x[x].direction[i].green* |
1398 | 0 | density_y[y].direction[i].green)); |
1399 | 0 | entropy_xy1.direction[i].blue-=(cooccurrence[x][y].direction[i].blue* |
1400 | 0 | log2(density_x[x].direction[i].blue*density_y[y].direction[i].blue)); |
1401 | 0 | if (image->colorspace == CMYKColorspace) |
1402 | 0 | entropy_xy1.direction[i].black-=( |
1403 | 0 | cooccurrence[x][y].direction[i].black*log2( |
1404 | 0 | density_x[x].direction[i].black*density_y[y].direction[i].black)); |
1405 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1406 | 0 | entropy_xy1.direction[i].alpha-=( |
1407 | 0 | cooccurrence[x][y].direction[i].alpha*log2( |
1408 | 0 | density_x[x].direction[i].alpha*density_y[y].direction[i].alpha)); |
1409 | 0 | entropy_xy2.direction[i].red-=(density_x[x].direction[i].red* |
1410 | 0 | density_y[y].direction[i].red*log2(density_x[x].direction[i].red* |
1411 | 0 | density_y[y].direction[i].red)); |
1412 | 0 | entropy_xy2.direction[i].green-=(density_x[x].direction[i].green* |
1413 | 0 | density_y[y].direction[i].green*log2(density_x[x].direction[i].green* |
1414 | 0 | density_y[y].direction[i].green)); |
1415 | 0 | entropy_xy2.direction[i].blue-=(density_x[x].direction[i].blue* |
1416 | 0 | density_y[y].direction[i].blue*log2(density_x[x].direction[i].blue* |
1417 | 0 | density_y[y].direction[i].blue)); |
1418 | 0 | if (image->colorspace == CMYKColorspace) |
1419 | 0 | entropy_xy2.direction[i].black-=(density_x[x].direction[i].black* |
1420 | 0 | density_y[y].direction[i].black*log2( |
1421 | 0 | density_x[x].direction[i].black*density_y[y].direction[i].black)); |
1422 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1423 | 0 | entropy_xy2.direction[i].alpha-=(density_x[x].direction[i].alpha* |
1424 | 0 | density_y[y].direction[i].alpha*log2( |
1425 | 0 | density_x[x].direction[i].alpha*density_y[y].direction[i].alpha)); |
1426 | 0 | } |
1427 | 0 | } |
1428 | 0 | channel_features[RedPixelChannel].variance_sum_of_squares[i]= |
1429 | 0 | variance.direction[i].red; |
1430 | 0 | channel_features[GreenPixelChannel].variance_sum_of_squares[i]= |
1431 | 0 | variance.direction[i].green; |
1432 | 0 | channel_features[BluePixelChannel].variance_sum_of_squares[i]= |
1433 | 0 | variance.direction[i].blue; |
1434 | 0 | if (image->colorspace == CMYKColorspace) |
1435 | 0 | channel_features[BlackPixelChannel].variance_sum_of_squares[i]= |
1436 | 0 | variance.direction[i].black; |
1437 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1438 | 0 | channel_features[AlphaPixelChannel].variance_sum_of_squares[i]= |
1439 | 0 | variance.direction[i].alpha; |
1440 | 0 | } |
1441 | | /* |
1442 | | Compute more texture features. |
1443 | | */ |
1444 | 0 | (void) memset(&variance,0,sizeof(variance)); |
1445 | 0 | (void) memset(&sum_squares,0,sizeof(sum_squares)); |
1446 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1447 | | #pragma omp parallel for schedule(static) shared(status) \ |
1448 | | magick_number_threads(image,image,number_grays,1) |
1449 | | #endif |
1450 | 0 | for (i=0; i < 4; i++) |
1451 | 0 | { |
1452 | 0 | ssize_t |
1453 | 0 | x; |
1454 | |
|
1455 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1456 | 0 | { |
1457 | | /* |
1458 | | Difference variance. |
1459 | | */ |
1460 | 0 | variance.direction[i].red+=density_xy[x].direction[i].red; |
1461 | 0 | variance.direction[i].green+=density_xy[x].direction[i].green; |
1462 | 0 | variance.direction[i].blue+=density_xy[x].direction[i].blue; |
1463 | 0 | if (image->colorspace == CMYKColorspace) |
1464 | 0 | variance.direction[i].black+=density_xy[x].direction[i].black; |
1465 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1466 | 0 | variance.direction[i].alpha+=density_xy[x].direction[i].alpha; |
1467 | 0 | sum_squares.direction[i].red+=density_xy[x].direction[i].red* |
1468 | 0 | density_xy[x].direction[i].red; |
1469 | 0 | sum_squares.direction[i].green+=density_xy[x].direction[i].green* |
1470 | 0 | density_xy[x].direction[i].green; |
1471 | 0 | sum_squares.direction[i].blue+=density_xy[x].direction[i].blue* |
1472 | 0 | density_xy[x].direction[i].blue; |
1473 | 0 | if (image->colorspace == CMYKColorspace) |
1474 | 0 | sum_squares.direction[i].black+=density_xy[x].direction[i].black* |
1475 | 0 | density_xy[x].direction[i].black; |
1476 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1477 | 0 | sum_squares.direction[i].alpha+=density_xy[x].direction[i].alpha* |
1478 | 0 | density_xy[x].direction[i].alpha; |
1479 | | /* |
1480 | | Difference entropy. |
1481 | | */ |
1482 | 0 | channel_features[RedPixelChannel].difference_entropy[i]-= |
1483 | 0 | density_xy[x].direction[i].red* |
1484 | 0 | log2(density_xy[x].direction[i].red); |
1485 | 0 | channel_features[GreenPixelChannel].difference_entropy[i]-= |
1486 | 0 | density_xy[x].direction[i].green* |
1487 | 0 | log2(density_xy[x].direction[i].green); |
1488 | 0 | channel_features[BluePixelChannel].difference_entropy[i]-= |
1489 | 0 | density_xy[x].direction[i].blue* |
1490 | 0 | log2(density_xy[x].direction[i].blue); |
1491 | 0 | if (image->colorspace == CMYKColorspace) |
1492 | 0 | channel_features[BlackPixelChannel].difference_entropy[i]-= |
1493 | 0 | density_xy[x].direction[i].black* |
1494 | 0 | log2(density_xy[x].direction[i].black); |
1495 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1496 | 0 | channel_features[AlphaPixelChannel].difference_entropy[i]-= |
1497 | 0 | density_xy[x].direction[i].alpha* |
1498 | 0 | log2(density_xy[x].direction[i].alpha); |
1499 | | /* |
1500 | | Information Measures of Correlation. |
1501 | | */ |
1502 | 0 | entropy_x.direction[i].red-=(density_x[x].direction[i].red* |
1503 | 0 | log2(density_x[x].direction[i].red)); |
1504 | 0 | entropy_x.direction[i].green-=(density_x[x].direction[i].green* |
1505 | 0 | log2(density_x[x].direction[i].green)); |
1506 | 0 | entropy_x.direction[i].blue-=(density_x[x].direction[i].blue* |
1507 | 0 | log2(density_x[x].direction[i].blue)); |
1508 | 0 | if (image->colorspace == CMYKColorspace) |
1509 | 0 | entropy_x.direction[i].black-=(density_x[x].direction[i].black* |
1510 | 0 | log2(density_x[x].direction[i].black)); |
1511 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1512 | 0 | entropy_x.direction[i].alpha-=(density_x[x].direction[i].alpha* |
1513 | 0 | log2(density_x[x].direction[i].alpha)); |
1514 | 0 | entropy_y.direction[i].red-=(density_y[x].direction[i].red* |
1515 | 0 | log2(density_y[x].direction[i].red)); |
1516 | 0 | entropy_y.direction[i].green-=(density_y[x].direction[i].green* |
1517 | 0 | log2(density_y[x].direction[i].green)); |
1518 | 0 | entropy_y.direction[i].blue-=(density_y[x].direction[i].blue* |
1519 | 0 | log2(density_y[x].direction[i].blue)); |
1520 | 0 | if (image->colorspace == CMYKColorspace) |
1521 | 0 | entropy_y.direction[i].black-=(density_y[x].direction[i].black* |
1522 | 0 | log2(density_y[x].direction[i].black)); |
1523 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1524 | 0 | entropy_y.direction[i].alpha-=(density_y[x].direction[i].alpha* |
1525 | 0 | log2(density_y[x].direction[i].alpha)); |
1526 | 0 | } |
1527 | | /* |
1528 | | Difference variance. |
1529 | | */ |
1530 | 0 | channel_features[RedPixelChannel].difference_variance[i]= |
1531 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].red)- |
1532 | 0 | (variance.direction[i].red*variance.direction[i].red))/ |
1533 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1534 | 0 | channel_features[GreenPixelChannel].difference_variance[i]= |
1535 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].green)- |
1536 | 0 | (variance.direction[i].green*variance.direction[i].green))/ |
1537 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1538 | 0 | channel_features[BluePixelChannel].difference_variance[i]= |
1539 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].blue)- |
1540 | 0 | (variance.direction[i].blue*variance.direction[i].blue))/ |
1541 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1542 | 0 | if (image->colorspace == CMYKColorspace) |
1543 | 0 | channel_features[BlackPixelChannel].difference_variance[i]= |
1544 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].black)- |
1545 | 0 | (variance.direction[i].black*variance.direction[i].black))/ |
1546 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1547 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1548 | 0 | channel_features[AlphaPixelChannel].difference_variance[i]= |
1549 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].alpha)- |
1550 | 0 | (variance.direction[i].alpha*variance.direction[i].alpha))/ |
1551 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1552 | | /* |
1553 | | Information Measures of Correlation. |
1554 | | */ |
1555 | 0 | channel_features[RedPixelChannel].measure_of_correlation_1[i]= |
1556 | 0 | (entropy_xy.direction[i].red-entropy_xy1.direction[i].red)/ |
1557 | 0 | (entropy_x.direction[i].red > entropy_y.direction[i].red ? |
1558 | 0 | entropy_x.direction[i].red : entropy_y.direction[i].red); |
1559 | 0 | channel_features[GreenPixelChannel].measure_of_correlation_1[i]= |
1560 | 0 | (entropy_xy.direction[i].green-entropy_xy1.direction[i].green)/ |
1561 | 0 | (entropy_x.direction[i].green > entropy_y.direction[i].green ? |
1562 | 0 | entropy_x.direction[i].green : entropy_y.direction[i].green); |
1563 | 0 | channel_features[BluePixelChannel].measure_of_correlation_1[i]= |
1564 | 0 | (entropy_xy.direction[i].blue-entropy_xy1.direction[i].blue)/ |
1565 | 0 | (entropy_x.direction[i].blue > entropy_y.direction[i].blue ? |
1566 | 0 | entropy_x.direction[i].blue : entropy_y.direction[i].blue); |
1567 | 0 | if (image->colorspace == CMYKColorspace) |
1568 | 0 | channel_features[BlackPixelChannel].measure_of_correlation_1[i]= |
1569 | 0 | (entropy_xy.direction[i].black-entropy_xy1.direction[i].black)/ |
1570 | 0 | (entropy_x.direction[i].black > entropy_y.direction[i].black ? |
1571 | 0 | entropy_x.direction[i].black : entropy_y.direction[i].black); |
1572 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1573 | 0 | channel_features[AlphaPixelChannel].measure_of_correlation_1[i]= |
1574 | 0 | (entropy_xy.direction[i].alpha-entropy_xy1.direction[i].alpha)/ |
1575 | 0 | (entropy_x.direction[i].alpha > entropy_y.direction[i].alpha ? |
1576 | 0 | entropy_x.direction[i].alpha : entropy_y.direction[i].alpha); |
1577 | 0 | channel_features[RedPixelChannel].measure_of_correlation_2[i]= |
1578 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].red- |
1579 | 0 | entropy_xy.direction[i].red))))); |
1580 | 0 | channel_features[GreenPixelChannel].measure_of_correlation_2[i]= |
1581 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].green- |
1582 | 0 | entropy_xy.direction[i].green))))); |
1583 | 0 | channel_features[BluePixelChannel].measure_of_correlation_2[i]= |
1584 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].blue- |
1585 | 0 | entropy_xy.direction[i].blue))))); |
1586 | 0 | if (image->colorspace == CMYKColorspace) |
1587 | 0 | channel_features[BlackPixelChannel].measure_of_correlation_2[i]= |
1588 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].black- |
1589 | 0 | entropy_xy.direction[i].black))))); |
1590 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1591 | 0 | channel_features[AlphaPixelChannel].measure_of_correlation_2[i]= |
1592 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].alpha- |
1593 | 0 | entropy_xy.direction[i].alpha))))); |
1594 | 0 | } |
1595 | | /* |
1596 | | Compute more texture features. |
1597 | | */ |
1598 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1599 | | #pragma omp parallel for schedule(static) shared(status) \ |
1600 | | magick_number_threads(image,image,number_grays,1) |
1601 | | #endif |
1602 | 0 | for (i=0; i < 4; i++) |
1603 | 0 | { |
1604 | 0 | ssize_t |
1605 | 0 | z; |
1606 | |
|
1607 | 0 | for (z=0; z < (ssize_t) number_grays; z++) |
1608 | 0 | { |
1609 | 0 | ssize_t |
1610 | 0 | y; |
1611 | |
|
1612 | 0 | ChannelStatistics |
1613 | 0 | pixel; |
1614 | |
|
1615 | 0 | (void) memset(&pixel,0,sizeof(pixel)); |
1616 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1617 | 0 | { |
1618 | 0 | ssize_t |
1619 | 0 | x; |
1620 | |
|
1621 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1622 | 0 | { |
1623 | | /* |
1624 | | Contrast: amount of local variations present in an image. |
1625 | | */ |
1626 | 0 | if (((y-x) == z) || ((x-y) == z)) |
1627 | 0 | { |
1628 | 0 | pixel.direction[i].red+=cooccurrence[x][y].direction[i].red; |
1629 | 0 | pixel.direction[i].green+=cooccurrence[x][y].direction[i].green; |
1630 | 0 | pixel.direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1631 | 0 | if (image->colorspace == CMYKColorspace) |
1632 | 0 | pixel.direction[i].black+=cooccurrence[x][y].direction[i].black; |
1633 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1634 | 0 | pixel.direction[i].alpha+= |
1635 | 0 | cooccurrence[x][y].direction[i].alpha; |
1636 | 0 | } |
1637 | | /* |
1638 | | Maximum Correlation Coefficient. |
1639 | | */ |
1640 | 0 | if ((fabs(density_x[z].direction[i].red) > MagickEpsilon) && |
1641 | 0 | (fabs(density_y[x].direction[i].red) > MagickEpsilon)) |
1642 | 0 | Q[z][y].direction[i].red+=cooccurrence[z][x].direction[i].red* |
1643 | 0 | cooccurrence[y][x].direction[i].red/density_x[z].direction[i].red/ |
1644 | 0 | density_y[x].direction[i].red; |
1645 | 0 | if ((fabs(density_x[z].direction[i].green) > MagickEpsilon) && |
1646 | 0 | (fabs(density_y[x].direction[i].red) > MagickEpsilon)) |
1647 | 0 | Q[z][y].direction[i].green+=cooccurrence[z][x].direction[i].green* |
1648 | 0 | cooccurrence[y][x].direction[i].green/ |
1649 | 0 | density_x[z].direction[i].green/density_y[x].direction[i].red; |
1650 | 0 | if ((fabs(density_x[z].direction[i].blue) > MagickEpsilon) && |
1651 | 0 | (fabs(density_y[x].direction[i].blue) > MagickEpsilon)) |
1652 | 0 | Q[z][y].direction[i].blue+=cooccurrence[z][x].direction[i].blue* |
1653 | 0 | cooccurrence[y][x].direction[i].blue/ |
1654 | 0 | density_x[z].direction[i].blue/density_y[x].direction[i].blue; |
1655 | 0 | if (image->colorspace == CMYKColorspace) |
1656 | 0 | if ((fabs(density_x[z].direction[i].black) > MagickEpsilon) && |
1657 | 0 | (fabs(density_y[x].direction[i].black) > MagickEpsilon)) |
1658 | 0 | Q[z][y].direction[i].black+=cooccurrence[z][x].direction[i].black* |
1659 | 0 | cooccurrence[y][x].direction[i].black/ |
1660 | 0 | density_x[z].direction[i].black/density_y[x].direction[i].black; |
1661 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1662 | 0 | if ((fabs(density_x[z].direction[i].alpha) > MagickEpsilon) && |
1663 | 0 | (fabs(density_y[x].direction[i].alpha) > MagickEpsilon)) |
1664 | 0 | Q[z][y].direction[i].alpha+= |
1665 | 0 | cooccurrence[z][x].direction[i].alpha* |
1666 | 0 | cooccurrence[y][x].direction[i].alpha/ |
1667 | 0 | density_x[z].direction[i].alpha/ |
1668 | 0 | density_y[x].direction[i].alpha; |
1669 | 0 | } |
1670 | 0 | } |
1671 | 0 | channel_features[RedPixelChannel].contrast[i]+=z*z* |
1672 | 0 | pixel.direction[i].red; |
1673 | 0 | channel_features[GreenPixelChannel].contrast[i]+=z*z* |
1674 | 0 | pixel.direction[i].green; |
1675 | 0 | channel_features[BluePixelChannel].contrast[i]+=z*z* |
1676 | 0 | pixel.direction[i].blue; |
1677 | 0 | if (image->colorspace == CMYKColorspace) |
1678 | 0 | channel_features[BlackPixelChannel].contrast[i]+=z*z* |
1679 | 0 | pixel.direction[i].black; |
1680 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1681 | 0 | channel_features[AlphaPixelChannel].contrast[i]+=z*z* |
1682 | 0 | pixel.direction[i].alpha; |
1683 | 0 | } |
1684 | | /* |
1685 | | Maximum Correlation Coefficient. |
1686 | | Future: return second largest eigenvalue of Q. |
1687 | | */ |
1688 | 0 | channel_features[RedPixelChannel].maximum_correlation_coefficient[i]= |
1689 | 0 | sqrt(-1.0); |
1690 | 0 | channel_features[GreenPixelChannel].maximum_correlation_coefficient[i]= |
1691 | 0 | sqrt(-1.0); |
1692 | 0 | channel_features[BluePixelChannel].maximum_correlation_coefficient[i]= |
1693 | 0 | sqrt(-1.0); |
1694 | 0 | if (image->colorspace == CMYKColorspace) |
1695 | 0 | channel_features[BlackPixelChannel].maximum_correlation_coefficient[i]= |
1696 | 0 | sqrt(-1.0); |
1697 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1698 | 0 | channel_features[AlphaPixelChannel].maximum_correlation_coefficient[i]= |
1699 | 0 | sqrt(-1.0); |
1700 | 0 | } |
1701 | | /* |
1702 | | Relinquish resources. |
1703 | | */ |
1704 | 0 | sum=(ChannelStatistics *) RelinquishMagickMemory(sum); |
1705 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
1706 | 0 | Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]); |
1707 | 0 | Q=(ChannelStatistics **) RelinquishMagickMemory(Q); |
1708 | 0 | density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y); |
1709 | 0 | density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy); |
1710 | 0 | density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x); |
1711 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
1712 | 0 | cooccurrence[i]=(ChannelStatistics *) |
1713 | 0 | RelinquishMagickMemory(cooccurrence[i]); |
1714 | 0 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence); |
1715 | 0 | return(channel_features); |
1716 | 0 | } |
1717 | | |
1718 | | /* |
1719 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1720 | | % % |
1721 | | % % |
1722 | | % % |
1723 | | % H o u g h L i n e I m a g e % |
1724 | | % % |
1725 | | % % |
1726 | | % % |
1727 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1728 | | % |
1729 | | % HoughLineImage() can be used in conjunction with any binary edge extracted |
1730 | | % image (we recommend Canny) to identify lines in the image. The algorithm |
1731 | | % accumulates counts for every white pixel for every possible orientation (for |
1732 | | % angles from 0 to 179 in 1 degree increments) and distance from the center of |
1733 | | % the image to the corner (in 1 px increments) and stores the counts in an |
1734 | | % accumulator matrix of angle vs distance. The size of the accumulator is |
1735 | | % 180x(diagonal/2). Next it searches this space for peaks in counts and |
1736 | | % converts the locations of the peaks to slope and intercept in the normal |
1737 | | % x,y input image space. Use the slope/intercepts to find the endpoints |
1738 | | % clipped to the bounds of the image. The lines are then drawn. The counts |
1739 | | % are a measure of the length of the lines. |
1740 | | % |
1741 | | % The format of the HoughLineImage method is: |
1742 | | % |
1743 | | % Image *HoughLineImage(const Image *image,const size_t width, |
1744 | | % const size_t height,const size_t threshold,ExceptionInfo *exception) |
1745 | | % |
1746 | | % A description of each parameter follows: |
1747 | | % |
1748 | | % o image: the image. |
1749 | | % |
1750 | | % o width, height: find line pairs as local maxima in this neighborhood. |
1751 | | % |
1752 | | % o threshold: the line count threshold. |
1753 | | % |
1754 | | % o exception: return any errors or warnings in this structure. |
1755 | | % |
1756 | | */ |
1757 | | |
1758 | | static inline double MagickRound(double x) |
1759 | 0 | { |
1760 | | /* |
1761 | | Round the fraction to nearest integer. |
1762 | | */ |
1763 | 0 | if ((x-floor(x)) < (ceil(x)-x)) |
1764 | 0 | return(floor(x)); |
1765 | 0 | return(ceil(x)); |
1766 | 0 | } |
1767 | | |
1768 | | static Image *RenderHoughLines(const ImageInfo *image_info,const size_t columns, |
1769 | | const size_t rows,ExceptionInfo *exception) |
1770 | 0 | { |
1771 | 0 | #define BoundingBox "viewbox" |
1772 | |
|
1773 | 0 | DrawInfo |
1774 | 0 | *draw_info; |
1775 | |
|
1776 | 0 | Image |
1777 | 0 | *image; |
1778 | |
|
1779 | 0 | MagickBooleanType |
1780 | 0 | status; |
1781 | | |
1782 | | /* |
1783 | | Open image. |
1784 | | */ |
1785 | 0 | image=AcquireImage(image_info,exception); |
1786 | 0 | status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
1787 | 0 | if (status == MagickFalse) |
1788 | 0 | { |
1789 | 0 | image=DestroyImageList(image); |
1790 | 0 | return((Image *) NULL); |
1791 | 0 | } |
1792 | 0 | image->columns=columns; |
1793 | 0 | image->rows=rows; |
1794 | 0 | draw_info=CloneDrawInfo(image_info,(DrawInfo *) NULL); |
1795 | 0 | draw_info->affine.sx=image->resolution.x == 0.0 ? 1.0 : image->resolution.x/ |
1796 | 0 | DefaultResolution; |
1797 | 0 | draw_info->affine.sy=image->resolution.y == 0.0 ? 1.0 : image->resolution.y/ |
1798 | 0 | DefaultResolution; |
1799 | 0 | image->columns=CastDoubleToSizeT(draw_info->affine.sx*image->columns); |
1800 | 0 | image->rows=CastDoubleToSizeT(draw_info->affine.sy*image->rows); |
1801 | 0 | status=SetImageExtent(image,image->columns,image->rows,exception); |
1802 | 0 | if (status == MagickFalse) |
1803 | 0 | return(DestroyImageList(image)); |
1804 | 0 | if (SetImageBackgroundColor(image,exception) == MagickFalse) |
1805 | 0 | { |
1806 | 0 | image=DestroyImageList(image); |
1807 | 0 | return((Image *) NULL); |
1808 | 0 | } |
1809 | | /* |
1810 | | Render drawing. |
1811 | | */ |
1812 | 0 | if (GetBlobStreamData(image) == (unsigned char *) NULL) |
1813 | 0 | draw_info->primitive=FileToString(image->filename,~0UL,exception); |
1814 | 0 | else |
1815 | 0 | { |
1816 | 0 | draw_info->primitive=(char *) AcquireQuantumMemory(1,(size_t) |
1817 | 0 | GetBlobSize(image)+1); |
1818 | 0 | if (draw_info->primitive != (char *) NULL) |
1819 | 0 | { |
1820 | 0 | (void) memcpy(draw_info->primitive,GetBlobStreamData(image), |
1821 | 0 | (size_t) GetBlobSize(image)); |
1822 | 0 | draw_info->primitive[GetBlobSize(image)]='\0'; |
1823 | 0 | } |
1824 | 0 | } |
1825 | 0 | (void) DrawImage(image,draw_info,exception); |
1826 | 0 | draw_info=DestroyDrawInfo(draw_info); |
1827 | 0 | if (CloseBlob(image) == MagickFalse) |
1828 | 0 | image=DestroyImageList(image); |
1829 | 0 | return(GetFirstImageInList(image)); |
1830 | 0 | } |
1831 | | |
1832 | | MagickExport Image *HoughLineImage(const Image *image,const size_t width, |
1833 | | const size_t height,const size_t threshold,ExceptionInfo *exception) |
1834 | 0 | { |
1835 | 0 | #define HoughLineImageTag "HoughLine/Image" |
1836 | |
|
1837 | 0 | CacheView |
1838 | 0 | *image_view; |
1839 | |
|
1840 | 0 | char |
1841 | 0 | message[MagickPathExtent], |
1842 | 0 | path[MagickPathExtent]; |
1843 | |
|
1844 | 0 | const char |
1845 | 0 | *artifact; |
1846 | |
|
1847 | 0 | double |
1848 | 0 | hough_height; |
1849 | |
|
1850 | 0 | Image |
1851 | 0 | *lines_image = NULL; |
1852 | |
|
1853 | 0 | ImageInfo |
1854 | 0 | *image_info; |
1855 | |
|
1856 | 0 | int |
1857 | 0 | file; |
1858 | |
|
1859 | 0 | MagickBooleanType |
1860 | 0 | status; |
1861 | |
|
1862 | 0 | MagickOffsetType |
1863 | 0 | progress; |
1864 | |
|
1865 | 0 | MatrixInfo |
1866 | 0 | *accumulator; |
1867 | |
|
1868 | 0 | PointInfo |
1869 | 0 | center; |
1870 | |
|
1871 | 0 | ssize_t |
1872 | 0 | y; |
1873 | |
|
1874 | 0 | size_t |
1875 | 0 | accumulator_height, |
1876 | 0 | accumulator_width, |
1877 | 0 | line_count; |
1878 | | |
1879 | | /* |
1880 | | Create the accumulator. |
1881 | | */ |
1882 | 0 | assert(image != (const Image *) NULL); |
1883 | 0 | assert(image->signature == MagickCoreSignature); |
1884 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1885 | 0 | assert(exception->signature == MagickCoreSignature); |
1886 | 0 | if (IsEventLogging() != MagickFalse) |
1887 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1888 | 0 | accumulator_width=180; |
1889 | 0 | hough_height=((sqrt(2.0)*(double) (image->rows > image->columns ? |
1890 | 0 | image->rows : image->columns))/2.0); |
1891 | 0 | accumulator_height=(size_t) (2.0*hough_height); |
1892 | 0 | accumulator=AcquireMatrixInfo(accumulator_width,accumulator_height, |
1893 | 0 | sizeof(double),exception); |
1894 | 0 | if (accumulator == (MatrixInfo *) NULL) |
1895 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1896 | 0 | if (NullMatrix(accumulator) == MagickFalse) |
1897 | 0 | { |
1898 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
1899 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1900 | 0 | } |
1901 | | /* |
1902 | | Populate the accumulator. |
1903 | | */ |
1904 | 0 | status=MagickTrue; |
1905 | 0 | progress=0; |
1906 | 0 | center.x=(double) image->columns/2.0; |
1907 | 0 | center.y=(double) image->rows/2.0; |
1908 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
1909 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
1910 | 0 | { |
1911 | 0 | const Quantum |
1912 | 0 | *magick_restrict p; |
1913 | |
|
1914 | 0 | ssize_t |
1915 | 0 | x; |
1916 | |
|
1917 | 0 | if (status == MagickFalse) |
1918 | 0 | continue; |
1919 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
1920 | 0 | if (p == (Quantum *) NULL) |
1921 | 0 | { |
1922 | 0 | status=MagickFalse; |
1923 | 0 | continue; |
1924 | 0 | } |
1925 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
1926 | 0 | { |
1927 | 0 | if (GetPixelIntensity(image,p) > ((double) QuantumRange/2.0)) |
1928 | 0 | { |
1929 | 0 | ssize_t |
1930 | 0 | i; |
1931 | |
|
1932 | 0 | for (i=0; i < 180; i++) |
1933 | 0 | { |
1934 | 0 | double |
1935 | 0 | count, |
1936 | 0 | radius; |
1937 | |
|
1938 | 0 | radius=(((double) x-center.x)*cos(DegreesToRadians((double) i)))+ |
1939 | 0 | (((double) y-center.y)*sin(DegreesToRadians((double) i))); |
1940 | 0 | (void) GetMatrixElement(accumulator,i,(ssize_t) |
1941 | 0 | MagickRound(radius+hough_height),&count); |
1942 | 0 | count++; |
1943 | 0 | (void) SetMatrixElement(accumulator,i,(ssize_t) |
1944 | 0 | MagickRound(radius+hough_height),&count); |
1945 | 0 | } |
1946 | 0 | } |
1947 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
1948 | 0 | } |
1949 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1950 | 0 | { |
1951 | 0 | MagickBooleanType |
1952 | 0 | proceed; |
1953 | |
|
1954 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1955 | | #pragma omp atomic |
1956 | | #endif |
1957 | 0 | progress++; |
1958 | 0 | proceed=SetImageProgress(image,CannyEdgeImageTag,progress,image->rows); |
1959 | 0 | if (proceed == MagickFalse) |
1960 | 0 | status=MagickFalse; |
1961 | 0 | } |
1962 | 0 | } |
1963 | 0 | image_view=DestroyCacheView(image_view); |
1964 | 0 | if (status == MagickFalse) |
1965 | 0 | { |
1966 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
1967 | 0 | return((Image *) NULL); |
1968 | 0 | } |
1969 | | /* |
1970 | | Generate line segments from accumulator. |
1971 | | */ |
1972 | 0 | file=AcquireUniqueFileResource(path); |
1973 | 0 | if (file == -1) |
1974 | 0 | { |
1975 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
1976 | 0 | return((Image *) NULL); |
1977 | 0 | } |
1978 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
1979 | 0 | "# Hough line transform: %.20gx%.20g%+.20g\n",(double) width, |
1980 | 0 | (double) height,(double) threshold); |
1981 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
1982 | 0 | status=MagickFalse; |
1983 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
1984 | 0 | "viewbox 0 0 %.20g %.20g\n",(double) image->columns,(double) image->rows); |
1985 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
1986 | 0 | status=MagickFalse; |
1987 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
1988 | 0 | "# x1,y1 x2,y2 # count angle distance\n"); |
1989 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
1990 | 0 | status=MagickFalse; |
1991 | 0 | line_count=image->columns > image->rows ? image->columns/4 : image->rows/4; |
1992 | 0 | if (threshold != 0) |
1993 | 0 | line_count=threshold; |
1994 | 0 | for (y=0; y < (ssize_t) accumulator_height; y++) |
1995 | 0 | { |
1996 | 0 | ssize_t |
1997 | 0 | x; |
1998 | |
|
1999 | 0 | for (x=0; x < (ssize_t) accumulator_width; x++) |
2000 | 0 | { |
2001 | 0 | double |
2002 | 0 | count; |
2003 | |
|
2004 | 0 | (void) GetMatrixElement(accumulator,x,y,&count); |
2005 | 0 | if (count >= (double) line_count) |
2006 | 0 | { |
2007 | 0 | double |
2008 | 0 | maxima; |
2009 | |
|
2010 | 0 | SegmentInfo |
2011 | 0 | line; |
2012 | |
|
2013 | 0 | ssize_t |
2014 | 0 | v; |
2015 | | |
2016 | | /* |
2017 | | Is point a local maxima? |
2018 | | */ |
2019 | 0 | maxima=count; |
2020 | 0 | for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++) |
2021 | 0 | { |
2022 | 0 | ssize_t |
2023 | 0 | u; |
2024 | |
|
2025 | 0 | for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++) |
2026 | 0 | { |
2027 | 0 | if ((u != 0) || (v !=0)) |
2028 | 0 | { |
2029 | 0 | (void) GetMatrixElement(accumulator,x+u,y+v,&count); |
2030 | 0 | if (count > maxima) |
2031 | 0 | { |
2032 | 0 | maxima=count; |
2033 | 0 | break; |
2034 | 0 | } |
2035 | 0 | } |
2036 | 0 | } |
2037 | 0 | if (u < (ssize_t) (width/2)) |
2038 | 0 | break; |
2039 | 0 | } |
2040 | 0 | (void) GetMatrixElement(accumulator,x,y,&count); |
2041 | 0 | if (maxima > count) |
2042 | 0 | continue; |
2043 | 0 | if ((x >= 45) && (x <= 135)) |
2044 | 0 | { |
2045 | | /* |
2046 | | y = (r-x cos(t))/sin(t) |
2047 | | */ |
2048 | 0 | line.x1=0.0; |
2049 | 0 | line.y1=((double) (y-(accumulator_height/2.0))-((line.x1- |
2050 | 0 | (image->columns/2.0))*cos(DegreesToRadians((double) x))))/ |
2051 | 0 | sin(DegreesToRadians((double) x))+(image->rows/2.0); |
2052 | 0 | line.x2=(double) image->columns; |
2053 | 0 | line.y2=((double) (y-(accumulator_height/2.0))-((line.x2- |
2054 | 0 | (image->columns/2.0))*cos(DegreesToRadians((double) x))))/ |
2055 | 0 | sin(DegreesToRadians((double) x))+(image->rows/2.0); |
2056 | 0 | } |
2057 | 0 | else |
2058 | 0 | { |
2059 | | /* |
2060 | | x = (r-y cos(t))/sin(t) |
2061 | | */ |
2062 | 0 | line.y1=0.0; |
2063 | 0 | line.x1=((double) (y-(accumulator_height/2.0))-((line.y1- |
2064 | 0 | (image->rows/2.0))*sin(DegreesToRadians((double) x))))/ |
2065 | 0 | cos(DegreesToRadians((double) x))+(image->columns/2.0); |
2066 | 0 | line.y2=(double) image->rows; |
2067 | 0 | line.x2=((double) (y-(accumulator_height/2.0))-((line.y2- |
2068 | 0 | (image->rows/2.0))*sin(DegreesToRadians((double) x))))/ |
2069 | 0 | cos(DegreesToRadians((double) x))+(image->columns/2.0); |
2070 | 0 | } |
2071 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
2072 | 0 | "line %g,%g %g,%g # %g %g %g\n",line.x1,line.y1,line.x2,line.y2, |
2073 | 0 | maxima,(double) x,(double) y); |
2074 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
2075 | 0 | status=MagickFalse; |
2076 | 0 | } |
2077 | 0 | } |
2078 | 0 | } |
2079 | 0 | (void) close_utf8(file); |
2080 | | /* |
2081 | | Render lines to image canvas. |
2082 | | */ |
2083 | 0 | image_info=AcquireImageInfo(); |
2084 | 0 | image_info->background_color=image->background_color; |
2085 | 0 | (void) FormatLocaleString(image_info->filename,MagickPathExtent,"%s",path); |
2086 | 0 | artifact=GetImageArtifact(image,"background"); |
2087 | 0 | if (artifact != (const char *) NULL) |
2088 | 0 | (void) SetImageOption(image_info,"background",artifact); |
2089 | 0 | artifact=GetImageArtifact(image,"fill"); |
2090 | 0 | if (artifact != (const char *) NULL) |
2091 | 0 | (void) SetImageOption(image_info,"fill",artifact); |
2092 | 0 | artifact=GetImageArtifact(image,"stroke"); |
2093 | 0 | if (artifact != (const char *) NULL) |
2094 | 0 | (void) SetImageOption(image_info,"stroke",artifact); |
2095 | 0 | artifact=GetImageArtifact(image,"strokewidth"); |
2096 | 0 | if (artifact != (const char *) NULL) |
2097 | 0 | (void) SetImageOption(image_info,"strokewidth",artifact); |
2098 | 0 | lines_image=RenderHoughLines(image_info,image->columns,image->rows,exception); |
2099 | 0 | artifact=GetImageArtifact(image,"hough-lines:accumulator"); |
2100 | 0 | if ((lines_image != (Image *) NULL) && |
2101 | 0 | (IsStringTrue(artifact) != MagickFalse)) |
2102 | 0 | { |
2103 | 0 | Image |
2104 | 0 | *accumulator_image; |
2105 | |
|
2106 | 0 | accumulator_image=MatrixToImage(accumulator,exception); |
2107 | 0 | if (accumulator_image != (Image *) NULL) |
2108 | 0 | AppendImageToList(&lines_image,accumulator_image); |
2109 | 0 | } |
2110 | | /* |
2111 | | Free resources. |
2112 | | */ |
2113 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
2114 | 0 | image_info=DestroyImageInfo(image_info); |
2115 | 0 | (void) RelinquishUniqueFileResource(path); |
2116 | 0 | return(GetFirstImageInList(lines_image)); |
2117 | 0 | } |
2118 | | |
2119 | | /* |
2120 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2121 | | % % |
2122 | | % % |
2123 | | % % |
2124 | | % M e a n S h i f t I m a g e % |
2125 | | % % |
2126 | | % % |
2127 | | % % |
2128 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2129 | | % |
2130 | | % MeanShiftImage() delineate arbitrarily shaped clusters in the image. For |
2131 | | % each pixel, it visits all the pixels in the neighborhood specified by |
2132 | | % the window centered at the pixel and excludes those that are outside the |
2133 | | % radius=(window-1)/2 surrounding the pixel. From those pixels, it finds those |
2134 | | % that are within the specified color distance from the current mean, and |
2135 | | % computes a new x,y centroid from those coordinates and a new mean. This new |
2136 | | % x,y centroid is used as the center for a new window. This process iterates |
2137 | | % until it converges and the final mean is replaces the (original window |
2138 | | % center) pixel value. It repeats this process for the next pixel, etc., |
2139 | | % until it processes all pixels in the image. Results are typically better with |
2140 | | % colorspaces other than sRGB. We recommend YIQ, YUV or YCbCr. |
2141 | | % |
2142 | | % The format of the MeanShiftImage method is: |
2143 | | % |
2144 | | % Image *MeanShiftImage(const Image *image,const size_t width, |
2145 | | % const size_t height,const double color_distance, |
2146 | | % ExceptionInfo *exception) |
2147 | | % |
2148 | | % A description of each parameter follows: |
2149 | | % |
2150 | | % o image: the image. |
2151 | | % |
2152 | | % o width, height: find pixels in this neighborhood. |
2153 | | % |
2154 | | % o color_distance: the color distance. |
2155 | | % |
2156 | | % o exception: return any errors or warnings in this structure. |
2157 | | % |
2158 | | */ |
2159 | | MagickExport Image *MeanShiftImage(const Image *image,const size_t width, |
2160 | | const size_t height,const double color_distance,ExceptionInfo *exception) |
2161 | 0 | { |
2162 | 0 | #define MaxMeanShiftIterations 100 |
2163 | 0 | #define MeanShiftImageTag "MeanShift/Image" |
2164 | |
|
2165 | 0 | CacheView |
2166 | 0 | *image_view, |
2167 | 0 | *mean_view, |
2168 | 0 | *pixel_view; |
2169 | |
|
2170 | 0 | Image |
2171 | 0 | *mean_image; |
2172 | |
|
2173 | 0 | MagickBooleanType |
2174 | 0 | status; |
2175 | |
|
2176 | 0 | MagickOffsetType |
2177 | 0 | progress; |
2178 | |
|
2179 | 0 | ssize_t |
2180 | 0 | y; |
2181 | |
|
2182 | 0 | assert(image != (const Image *) NULL); |
2183 | 0 | assert(image->signature == MagickCoreSignature); |
2184 | 0 | assert(exception != (ExceptionInfo *) NULL); |
2185 | 0 | assert(exception->signature == MagickCoreSignature); |
2186 | 0 | if (IsEventLogging() != MagickFalse) |
2187 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
2188 | 0 | mean_image=CloneImage(image,0,0,MagickTrue,exception); |
2189 | 0 | if (mean_image == (Image *) NULL) |
2190 | 0 | return((Image *) NULL); |
2191 | 0 | if (SetImageStorageClass(mean_image,DirectClass,exception) == MagickFalse) |
2192 | 0 | { |
2193 | 0 | mean_image=DestroyImage(mean_image); |
2194 | 0 | return((Image *) NULL); |
2195 | 0 | } |
2196 | 0 | status=MagickTrue; |
2197 | 0 | progress=0; |
2198 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
2199 | 0 | pixel_view=AcquireVirtualCacheView(image,exception); |
2200 | 0 | mean_view=AcquireAuthenticCacheView(mean_image,exception); |
2201 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2202 | | #pragma omp parallel for schedule(static) shared(status,progress) \ |
2203 | | magick_number_threads(mean_image,mean_image,mean_image->rows,1) |
2204 | | #endif |
2205 | 0 | for (y=0; y < (ssize_t) mean_image->rows; y++) |
2206 | 0 | { |
2207 | 0 | const Quantum |
2208 | 0 | *magick_restrict p; |
2209 | |
|
2210 | 0 | Quantum |
2211 | 0 | *magick_restrict q; |
2212 | |
|
2213 | 0 | ssize_t |
2214 | 0 | x; |
2215 | |
|
2216 | 0 | if (status == MagickFalse) |
2217 | 0 | continue; |
2218 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
2219 | 0 | q=GetCacheViewAuthenticPixels(mean_view,0,y,mean_image->columns,1, |
2220 | 0 | exception); |
2221 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
2222 | 0 | { |
2223 | 0 | status=MagickFalse; |
2224 | 0 | continue; |
2225 | 0 | } |
2226 | 0 | for (x=0; x < (ssize_t) mean_image->columns; x++) |
2227 | 0 | { |
2228 | 0 | PixelInfo |
2229 | 0 | mean_pixel, |
2230 | 0 | previous_pixel; |
2231 | |
|
2232 | 0 | PointInfo |
2233 | 0 | mean_location, |
2234 | 0 | previous_location; |
2235 | |
|
2236 | 0 | ssize_t |
2237 | 0 | i; |
2238 | |
|
2239 | 0 | GetPixelInfo(image,&mean_pixel); |
2240 | 0 | GetPixelInfoPixel(image,p,&mean_pixel); |
2241 | 0 | mean_location.x=(double) x; |
2242 | 0 | mean_location.y=(double) y; |
2243 | 0 | for (i=0; i < MaxMeanShiftIterations; i++) |
2244 | 0 | { |
2245 | 0 | double |
2246 | 0 | distance, |
2247 | 0 | gamma = 1.0; |
2248 | |
|
2249 | 0 | PixelInfo |
2250 | 0 | sum_pixel; |
2251 | |
|
2252 | 0 | PointInfo |
2253 | 0 | sum_location; |
2254 | |
|
2255 | 0 | ssize_t |
2256 | 0 | count, |
2257 | 0 | v; |
2258 | |
|
2259 | 0 | sum_location.x=0.0; |
2260 | 0 | sum_location.y=0.0; |
2261 | 0 | GetPixelInfo(image,&sum_pixel); |
2262 | 0 | previous_location=mean_location; |
2263 | 0 | previous_pixel=mean_pixel; |
2264 | 0 | count=0; |
2265 | 0 | for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++) |
2266 | 0 | { |
2267 | 0 | ssize_t |
2268 | 0 | u; |
2269 | |
|
2270 | 0 | for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++) |
2271 | 0 | { |
2272 | 0 | if ((v*v+u*u) <= (ssize_t) ((width/2)*(height/2))) |
2273 | 0 | { |
2274 | 0 | PixelInfo |
2275 | 0 | pixel; |
2276 | |
|
2277 | 0 | status=GetOneCacheViewVirtualPixelInfo(pixel_view,(ssize_t) |
2278 | 0 | MagickRound(mean_location.x+u),(ssize_t) MagickRound( |
2279 | 0 | mean_location.y+v),&pixel,exception); |
2280 | 0 | distance=(mean_pixel.red-pixel.red)*(mean_pixel.red-pixel.red)+ |
2281 | 0 | (mean_pixel.green-pixel.green)*(mean_pixel.green-pixel.green)+ |
2282 | 0 | (mean_pixel.blue-pixel.blue)*(mean_pixel.blue-pixel.blue); |
2283 | 0 | if (distance <= (color_distance*color_distance)) |
2284 | 0 | { |
2285 | 0 | sum_location.x+=mean_location.x+u; |
2286 | 0 | sum_location.y+=mean_location.y+v; |
2287 | 0 | sum_pixel.red+=pixel.red; |
2288 | 0 | sum_pixel.green+=pixel.green; |
2289 | 0 | sum_pixel.blue+=pixel.blue; |
2290 | 0 | sum_pixel.alpha+=pixel.alpha; |
2291 | 0 | count++; |
2292 | 0 | } |
2293 | 0 | } |
2294 | 0 | } |
2295 | 0 | } |
2296 | 0 | if (count != 0) |
2297 | 0 | gamma=MagickSafeReciprocal((double) count); |
2298 | 0 | mean_location.x=gamma*sum_location.x; |
2299 | 0 | mean_location.y=gamma*sum_location.y; |
2300 | 0 | mean_pixel.red=gamma*sum_pixel.red; |
2301 | 0 | mean_pixel.green=gamma*sum_pixel.green; |
2302 | 0 | mean_pixel.blue=gamma*sum_pixel.blue; |
2303 | 0 | mean_pixel.alpha=gamma*sum_pixel.alpha; |
2304 | 0 | distance=(mean_location.x-previous_location.x)* |
2305 | 0 | (mean_location.x-previous_location.x)+ |
2306 | 0 | (mean_location.y-previous_location.y)* |
2307 | 0 | (mean_location.y-previous_location.y)+ |
2308 | 0 | 255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)* |
2309 | 0 | 255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)+ |
2310 | 0 | 255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)* |
2311 | 0 | 255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)+ |
2312 | 0 | 255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue)* |
2313 | 0 | 255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue); |
2314 | 0 | if (distance <= 3.0) |
2315 | 0 | break; |
2316 | 0 | } |
2317 | 0 | SetPixelRed(mean_image,ClampToQuantum(mean_pixel.red),q); |
2318 | 0 | SetPixelGreen(mean_image,ClampToQuantum(mean_pixel.green),q); |
2319 | 0 | SetPixelBlue(mean_image,ClampToQuantum(mean_pixel.blue),q); |
2320 | 0 | SetPixelAlpha(mean_image,ClampToQuantum(mean_pixel.alpha),q); |
2321 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
2322 | 0 | q+=(ptrdiff_t) GetPixelChannels(mean_image); |
2323 | 0 | } |
2324 | 0 | if (SyncCacheViewAuthenticPixels(mean_view,exception) == MagickFalse) |
2325 | 0 | status=MagickFalse; |
2326 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
2327 | 0 | { |
2328 | 0 | MagickBooleanType |
2329 | 0 | proceed; |
2330 | |
|
2331 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2332 | | #pragma omp atomic |
2333 | | #endif |
2334 | 0 | progress++; |
2335 | 0 | proceed=SetImageProgress(image,MeanShiftImageTag,progress,image->rows); |
2336 | 0 | if (proceed == MagickFalse) |
2337 | 0 | status=MagickFalse; |
2338 | 0 | } |
2339 | 0 | } |
2340 | 0 | mean_view=DestroyCacheView(mean_view); |
2341 | 0 | pixel_view=DestroyCacheView(pixel_view); |
2342 | 0 | image_view=DestroyCacheView(image_view); |
2343 | 0 | return(mean_image); |
2344 | 0 | } |