/src/imagemagick/MagickCore/feature.c
Line | Count | Source |
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/license/ % |
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:%.17gx%.17g;blur:%.17gx%.17g+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 | { |
676 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
677 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
678 | 0 | return(channel_features); |
679 | 0 | } |
680 | 0 | (void) memset(channel_features,0,length* |
681 | 0 | sizeof(*channel_features)); |
682 | | /* |
683 | | Form grays. |
684 | | */ |
685 | 0 | grays=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*grays)); |
686 | 0 | if (grays == (PixelPacket *) NULL) |
687 | 0 | { |
688 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
689 | 0 | channel_features); |
690 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
691 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
692 | 0 | return(channel_features); |
693 | 0 | } |
694 | 0 | for (i=0; i <= (ssize_t) MaxMap; i++) |
695 | 0 | { |
696 | 0 | grays[i].red=(~0U); |
697 | 0 | grays[i].green=(~0U); |
698 | 0 | grays[i].blue=(~0U); |
699 | 0 | grays[i].alpha=(~0U); |
700 | 0 | grays[i].black=(~0U); |
701 | 0 | } |
702 | 0 | status=MagickTrue; |
703 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
704 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
705 | | #pragma omp parallel for schedule(static) shared(status) \ |
706 | | magick_number_threads(image,image,image->rows,1) |
707 | | #endif |
708 | 0 | for (r=0; r < (ssize_t) image->rows; r++) |
709 | 0 | { |
710 | 0 | const Quantum |
711 | 0 | *magick_restrict p; |
712 | |
|
713 | 0 | ssize_t |
714 | 0 | x; |
715 | |
|
716 | 0 | if (status == MagickFalse) |
717 | 0 | continue; |
718 | 0 | p=GetCacheViewVirtualPixels(image_view,0,r,image->columns,1,exception); |
719 | 0 | if (p == (const Quantum *) NULL) |
720 | 0 | { |
721 | 0 | status=MagickFalse; |
722 | 0 | continue; |
723 | 0 | } |
724 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
725 | 0 | { |
726 | 0 | grays[ScaleQuantumToMap(GetPixelRed(image,p))].red= |
727 | 0 | ScaleQuantumToMap(GetPixelRed(image,p)); |
728 | 0 | grays[ScaleQuantumToMap(GetPixelGreen(image,p))].green= |
729 | 0 | ScaleQuantumToMap(GetPixelGreen(image,p)); |
730 | 0 | grays[ScaleQuantumToMap(GetPixelBlue(image,p))].blue= |
731 | 0 | ScaleQuantumToMap(GetPixelBlue(image,p)); |
732 | 0 | if (image->colorspace == CMYKColorspace) |
733 | 0 | grays[ScaleQuantumToMap(GetPixelBlack(image,p))].black= |
734 | 0 | ScaleQuantumToMap(GetPixelBlack(image,p)); |
735 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
736 | 0 | grays[ScaleQuantumToMap(GetPixelAlpha(image,p))].alpha= |
737 | 0 | ScaleQuantumToMap(GetPixelAlpha(image,p)); |
738 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
739 | 0 | } |
740 | 0 | } |
741 | 0 | image_view=DestroyCacheView(image_view); |
742 | 0 | if (status == MagickFalse) |
743 | 0 | { |
744 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
745 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
746 | 0 | channel_features); |
747 | 0 | return(channel_features); |
748 | 0 | } |
749 | 0 | (void) memset(&gray,0,sizeof(gray)); |
750 | 0 | for (i=0; i <= (ssize_t) MaxMap; i++) |
751 | 0 | { |
752 | 0 | if (grays[i].red != ~0U) |
753 | 0 | grays[gray.red++].red=grays[i].red; |
754 | 0 | if (grays[i].green != ~0U) |
755 | 0 | grays[gray.green++].green=grays[i].green; |
756 | 0 | if (grays[i].blue != ~0U) |
757 | 0 | grays[gray.blue++].blue=grays[i].blue; |
758 | 0 | if (image->colorspace == CMYKColorspace) |
759 | 0 | if (grays[i].black != ~0U) |
760 | 0 | grays[gray.black++].black=grays[i].black; |
761 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
762 | 0 | if (grays[i].alpha != ~0U) |
763 | 0 | grays[gray.alpha++].alpha=grays[i].alpha; |
764 | 0 | } |
765 | | /* |
766 | | Allocate spatial dependence matrix. |
767 | | */ |
768 | 0 | number_grays=gray.red; |
769 | 0 | if (gray.green > number_grays) |
770 | 0 | number_grays=gray.green; |
771 | 0 | if (gray.blue > number_grays) |
772 | 0 | number_grays=gray.blue; |
773 | 0 | if (image->colorspace == CMYKColorspace) |
774 | 0 | if (gray.black > number_grays) |
775 | 0 | number_grays=gray.black; |
776 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
777 | 0 | if (gray.alpha > number_grays) |
778 | 0 | number_grays=gray.alpha; |
779 | 0 | cooccurrence=(ChannelStatistics **) AcquireQuantumMemory(number_grays, |
780 | 0 | sizeof(*cooccurrence)); |
781 | 0 | density_x=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1, |
782 | 0 | 2*sizeof(*density_x)); |
783 | 0 | density_xy=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1, |
784 | 0 | 2*sizeof(*density_xy)); |
785 | 0 | density_y=(ChannelStatistics *) AcquireQuantumMemory(number_grays+1, |
786 | 0 | 2*sizeof(*density_y)); |
787 | 0 | Q=(ChannelStatistics **) AcquireQuantumMemory(number_grays,sizeof(*Q)); |
788 | 0 | sum=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(*sum)); |
789 | 0 | if ((cooccurrence == (ChannelStatistics **) NULL) || |
790 | 0 | (density_x == (ChannelStatistics *) NULL) || |
791 | 0 | (density_xy == (ChannelStatistics *) NULL) || |
792 | 0 | (density_y == (ChannelStatistics *) NULL) || |
793 | 0 | (Q == (ChannelStatistics **) NULL) || |
794 | 0 | (sum == (ChannelStatistics *) NULL)) |
795 | 0 | { |
796 | 0 | if (Q != (ChannelStatistics **) NULL) |
797 | 0 | Q=(ChannelStatistics **) RelinquishMagickMemory(Q); |
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 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory( |
808 | 0 | cooccurrence); |
809 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
810 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
811 | 0 | channel_features); |
812 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
813 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
814 | 0 | return(channel_features); |
815 | 0 | } |
816 | 0 | (void) memset(&correlation,0,sizeof(correlation)); |
817 | 0 | (void) memset(density_x,0,2*(number_grays+1)*sizeof(*density_x)); |
818 | 0 | (void) memset(density_xy,0,2*(number_grays+1)*sizeof(*density_xy)); |
819 | 0 | (void) memset(density_y,0,2*(number_grays+1)*sizeof(*density_y)); |
820 | 0 | (void) memset(&mean,0,sizeof(mean)); |
821 | 0 | (void) memset(sum,0,number_grays*sizeof(*sum)); |
822 | 0 | (void) memset(&sum_squares,0,sizeof(sum_squares)); |
823 | 0 | (void) memset(density_xy,0,2*number_grays*sizeof(*density_xy)); |
824 | 0 | (void) memset(&entropy_x,0,sizeof(entropy_x)); |
825 | 0 | (void) memset(&entropy_xy,0,sizeof(entropy_xy)); |
826 | 0 | (void) memset(&entropy_xy1,0,sizeof(entropy_xy1)); |
827 | 0 | (void) memset(&entropy_xy2,0,sizeof(entropy_xy2)); |
828 | 0 | (void) memset(&entropy_y,0,sizeof(entropy_y)); |
829 | 0 | (void) memset(&variance,0,sizeof(variance)); |
830 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
831 | 0 | { |
832 | 0 | cooccurrence[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays, |
833 | 0 | sizeof(**cooccurrence)); |
834 | 0 | Q[i]=(ChannelStatistics *) AcquireQuantumMemory(number_grays,sizeof(**Q)); |
835 | 0 | if ((cooccurrence[i] == (ChannelStatistics *) NULL) || |
836 | 0 | (Q[i] == (ChannelStatistics *) NULL)) |
837 | 0 | break; |
838 | 0 | (void) memset(cooccurrence[i],0,number_grays* |
839 | 0 | sizeof(**cooccurrence)); |
840 | 0 | (void) memset(Q[i],0,number_grays*sizeof(**Q)); |
841 | 0 | } |
842 | 0 | if (i < (ssize_t) number_grays) |
843 | 0 | { |
844 | 0 | for (i--; i >= 0; i--) |
845 | 0 | { |
846 | 0 | if (Q[i] != (ChannelStatistics *) NULL) |
847 | 0 | Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]); |
848 | 0 | if (cooccurrence[i] != (ChannelStatistics *) NULL) |
849 | 0 | cooccurrence[i]=(ChannelStatistics *) |
850 | 0 | RelinquishMagickMemory(cooccurrence[i]); |
851 | 0 | } |
852 | 0 | Q=(ChannelStatistics **) RelinquishMagickMemory(Q); |
853 | 0 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence); |
854 | 0 | sum=(ChannelStatistics *) RelinquishMagickMemory(sum); |
855 | 0 | density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y); |
856 | 0 | density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy); |
857 | 0 | density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x); |
858 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
859 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
860 | 0 | channel_features); |
861 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
862 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
863 | 0 | return(channel_features); |
864 | 0 | } |
865 | | /* |
866 | | Initialize spatial dependence matrix. |
867 | | */ |
868 | 0 | status=MagickTrue; |
869 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
870 | 0 | for (r=0; r < (ssize_t) image->rows; r++) |
871 | 0 | { |
872 | 0 | const Quantum |
873 | 0 | *magick_restrict p; |
874 | |
|
875 | 0 | ssize_t |
876 | 0 | x; |
877 | |
|
878 | 0 | ssize_t |
879 | 0 | offset, |
880 | 0 | u, |
881 | 0 | v; |
882 | |
|
883 | 0 | if (status == MagickFalse) |
884 | 0 | continue; |
885 | 0 | p=GetCacheViewVirtualPixels(image_view,-(ssize_t) distance,r,image->columns+ |
886 | 0 | 2*distance,distance+2,exception); |
887 | 0 | if (p == (const Quantum *) NULL) |
888 | 0 | { |
889 | 0 | status=MagickFalse; |
890 | 0 | continue; |
891 | 0 | } |
892 | 0 | p+=(ptrdiff_t) distance*GetPixelChannels(image);; |
893 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
894 | 0 | { |
895 | 0 | for (i=0; i < 4; i++) |
896 | 0 | { |
897 | 0 | switch (i) |
898 | 0 | { |
899 | 0 | case 0: |
900 | 0 | default: |
901 | 0 | { |
902 | | /* |
903 | | Horizontal adjacency. |
904 | | */ |
905 | 0 | offset=(ssize_t) distance; |
906 | 0 | break; |
907 | 0 | } |
908 | 0 | case 1: |
909 | 0 | { |
910 | | /* |
911 | | Vertical adjacency. |
912 | | */ |
913 | 0 | offset=(ssize_t) (image->columns+2*distance); |
914 | 0 | break; |
915 | 0 | } |
916 | 0 | case 2: |
917 | 0 | { |
918 | | /* |
919 | | Right diagonal adjacency. |
920 | | */ |
921 | 0 | offset=(ssize_t) ((image->columns+2*distance)-distance); |
922 | 0 | break; |
923 | 0 | } |
924 | 0 | case 3: |
925 | 0 | { |
926 | | /* |
927 | | Left diagonal adjacency. |
928 | | */ |
929 | 0 | offset=(ssize_t) ((image->columns+2*distance)+distance); |
930 | 0 | break; |
931 | 0 | } |
932 | 0 | } |
933 | 0 | u=0; |
934 | 0 | v=0; |
935 | 0 | while (grays[u].red != ScaleQuantumToMap(GetPixelRed(image,p))) |
936 | 0 | u++; |
937 | 0 | while (grays[v].red != ScaleQuantumToMap(GetPixelRed(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
938 | 0 | v++; |
939 | 0 | cooccurrence[u][v].direction[i].red++; |
940 | 0 | cooccurrence[v][u].direction[i].red++; |
941 | 0 | u=0; |
942 | 0 | v=0; |
943 | 0 | while (grays[u].green != ScaleQuantumToMap(GetPixelGreen(image,p))) |
944 | 0 | u++; |
945 | 0 | while (grays[v].green != ScaleQuantumToMap(GetPixelGreen(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
946 | 0 | v++; |
947 | 0 | cooccurrence[u][v].direction[i].green++; |
948 | 0 | cooccurrence[v][u].direction[i].green++; |
949 | 0 | u=0; |
950 | 0 | v=0; |
951 | 0 | while (grays[u].blue != ScaleQuantumToMap(GetPixelBlue(image,p))) |
952 | 0 | u++; |
953 | 0 | while (grays[v].blue != ScaleQuantumToMap(GetPixelBlue(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
954 | 0 | v++; |
955 | 0 | cooccurrence[u][v].direction[i].blue++; |
956 | 0 | cooccurrence[v][u].direction[i].blue++; |
957 | 0 | if (image->colorspace == CMYKColorspace) |
958 | 0 | { |
959 | 0 | u=0; |
960 | 0 | v=0; |
961 | 0 | while (grays[u].black != ScaleQuantumToMap(GetPixelBlack(image,p))) |
962 | 0 | u++; |
963 | 0 | while (grays[v].black != ScaleQuantumToMap(GetPixelBlack(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
964 | 0 | v++; |
965 | 0 | cooccurrence[u][v].direction[i].black++; |
966 | 0 | cooccurrence[v][u].direction[i].black++; |
967 | 0 | } |
968 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
969 | 0 | { |
970 | 0 | u=0; |
971 | 0 | v=0; |
972 | 0 | while (grays[u].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p))) |
973 | 0 | u++; |
974 | 0 | while (grays[v].alpha != ScaleQuantumToMap(GetPixelAlpha(image,p+offset*(ssize_t) GetPixelChannels(image)))) |
975 | 0 | v++; |
976 | 0 | cooccurrence[u][v].direction[i].alpha++; |
977 | 0 | cooccurrence[v][u].direction[i].alpha++; |
978 | 0 | } |
979 | 0 | } |
980 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
981 | 0 | } |
982 | 0 | } |
983 | 0 | grays=(PixelPacket *) RelinquishMagickMemory(grays); |
984 | 0 | image_view=DestroyCacheView(image_view); |
985 | 0 | if (status == MagickFalse) |
986 | 0 | { |
987 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
988 | 0 | cooccurrence[i]=(ChannelStatistics *) |
989 | 0 | RelinquishMagickMemory(cooccurrence[i]); |
990 | 0 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence); |
991 | 0 | channel_features=(ChannelFeatures *) RelinquishMagickMemory( |
992 | 0 | channel_features); |
993 | 0 | (void) ThrowMagickException(exception,GetMagickModule(), |
994 | 0 | ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
995 | 0 | return(channel_features); |
996 | 0 | } |
997 | | /* |
998 | | Normalize spatial dependence matrix. |
999 | | */ |
1000 | 0 | for (i=0; i < 4; i++) |
1001 | 0 | { |
1002 | 0 | double |
1003 | 0 | normalize; |
1004 | |
|
1005 | 0 | ssize_t |
1006 | 0 | y; |
1007 | |
|
1008 | 0 | switch (i) |
1009 | 0 | { |
1010 | 0 | case 0: |
1011 | 0 | default: |
1012 | 0 | { |
1013 | | /* |
1014 | | Horizontal adjacency. |
1015 | | */ |
1016 | 0 | normalize=2.0*image->rows*(image->columns-distance); |
1017 | 0 | break; |
1018 | 0 | } |
1019 | 0 | case 1: |
1020 | 0 | { |
1021 | | /* |
1022 | | Vertical adjacency. |
1023 | | */ |
1024 | 0 | normalize=2.0*(image->rows-distance)*image->columns; |
1025 | 0 | break; |
1026 | 0 | } |
1027 | 0 | case 2: |
1028 | 0 | { |
1029 | | /* |
1030 | | Right diagonal adjacency. |
1031 | | */ |
1032 | 0 | normalize=2.0*(image->rows-distance)*(image->columns-distance); |
1033 | 0 | break; |
1034 | 0 | } |
1035 | 0 | case 3: |
1036 | 0 | { |
1037 | | /* |
1038 | | Left diagonal adjacency. |
1039 | | */ |
1040 | 0 | normalize=2.0*(image->rows-distance)*(image->columns-distance); |
1041 | 0 | break; |
1042 | 0 | } |
1043 | 0 | } |
1044 | 0 | normalize=MagickSafeReciprocal(normalize); |
1045 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1046 | 0 | { |
1047 | 0 | ssize_t |
1048 | 0 | x; |
1049 | |
|
1050 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1051 | 0 | { |
1052 | 0 | cooccurrence[x][y].direction[i].red*=normalize; |
1053 | 0 | cooccurrence[x][y].direction[i].green*=normalize; |
1054 | 0 | cooccurrence[x][y].direction[i].blue*=normalize; |
1055 | 0 | if (image->colorspace == CMYKColorspace) |
1056 | 0 | cooccurrence[x][y].direction[i].black*=normalize; |
1057 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1058 | 0 | cooccurrence[x][y].direction[i].alpha*=normalize; |
1059 | 0 | } |
1060 | 0 | } |
1061 | 0 | } |
1062 | | /* |
1063 | | Compute texture features. |
1064 | | */ |
1065 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1066 | | #pragma omp parallel for schedule(static) shared(status) \ |
1067 | | magick_number_threads(image,image,number_grays,1) |
1068 | | #endif |
1069 | 0 | for (i=0; i < 4; i++) |
1070 | 0 | { |
1071 | 0 | ssize_t |
1072 | 0 | y; |
1073 | |
|
1074 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1075 | 0 | { |
1076 | 0 | ssize_t |
1077 | 0 | x; |
1078 | |
|
1079 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1080 | 0 | { |
1081 | | /* |
1082 | | Angular second moment: measure of homogeneity of the image. |
1083 | | */ |
1084 | 0 | channel_features[RedPixelChannel].angular_second_moment[i]+= |
1085 | 0 | cooccurrence[x][y].direction[i].red* |
1086 | 0 | cooccurrence[x][y].direction[i].red; |
1087 | 0 | channel_features[GreenPixelChannel].angular_second_moment[i]+= |
1088 | 0 | cooccurrence[x][y].direction[i].green* |
1089 | 0 | cooccurrence[x][y].direction[i].green; |
1090 | 0 | channel_features[BluePixelChannel].angular_second_moment[i]+= |
1091 | 0 | cooccurrence[x][y].direction[i].blue* |
1092 | 0 | cooccurrence[x][y].direction[i].blue; |
1093 | 0 | if (image->colorspace == CMYKColorspace) |
1094 | 0 | channel_features[BlackPixelChannel].angular_second_moment[i]+= |
1095 | 0 | cooccurrence[x][y].direction[i].black* |
1096 | 0 | cooccurrence[x][y].direction[i].black; |
1097 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1098 | 0 | channel_features[AlphaPixelChannel].angular_second_moment[i]+= |
1099 | 0 | cooccurrence[x][y].direction[i].alpha* |
1100 | 0 | cooccurrence[x][y].direction[i].alpha; |
1101 | | /* |
1102 | | Correlation: measure of linear-dependencies in the image. |
1103 | | */ |
1104 | 0 | sum[y].direction[i].red+=cooccurrence[x][y].direction[i].red; |
1105 | 0 | sum[y].direction[i].green+=cooccurrence[x][y].direction[i].green; |
1106 | 0 | sum[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1107 | 0 | if (image->colorspace == CMYKColorspace) |
1108 | 0 | sum[y].direction[i].black+=cooccurrence[x][y].direction[i].black; |
1109 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1110 | 0 | sum[y].direction[i].alpha+=cooccurrence[x][y].direction[i].alpha; |
1111 | 0 | correlation.direction[i].red+=x*y*cooccurrence[x][y].direction[i].red; |
1112 | 0 | correlation.direction[i].green+=x*y* |
1113 | 0 | cooccurrence[x][y].direction[i].green; |
1114 | 0 | correlation.direction[i].blue+=x*y* |
1115 | 0 | cooccurrence[x][y].direction[i].blue; |
1116 | 0 | if (image->colorspace == CMYKColorspace) |
1117 | 0 | correlation.direction[i].black+=x*y* |
1118 | 0 | cooccurrence[x][y].direction[i].black; |
1119 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1120 | 0 | correlation.direction[i].alpha+=x*y* |
1121 | 0 | cooccurrence[x][y].direction[i].alpha; |
1122 | | /* |
1123 | | Inverse Difference Moment. |
1124 | | */ |
1125 | 0 | channel_features[RedPixelChannel].inverse_difference_moment[i]+= |
1126 | 0 | cooccurrence[x][y].direction[i].red/((y-x)*(y-x)+1); |
1127 | 0 | channel_features[GreenPixelChannel].inverse_difference_moment[i]+= |
1128 | 0 | cooccurrence[x][y].direction[i].green/((y-x)*(y-x)+1); |
1129 | 0 | channel_features[BluePixelChannel].inverse_difference_moment[i]+= |
1130 | 0 | cooccurrence[x][y].direction[i].blue/((y-x)*(y-x)+1); |
1131 | 0 | if (image->colorspace == CMYKColorspace) |
1132 | 0 | channel_features[BlackPixelChannel].inverse_difference_moment[i]+= |
1133 | 0 | cooccurrence[x][y].direction[i].black/((y-x)*(y-x)+1); |
1134 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1135 | 0 | channel_features[AlphaPixelChannel].inverse_difference_moment[i]+= |
1136 | 0 | cooccurrence[x][y].direction[i].alpha/((y-x)*(y-x)+1); |
1137 | | /* |
1138 | | Sum average. |
1139 | | */ |
1140 | 0 | density_xy[y+x+2].direction[i].red+= |
1141 | 0 | cooccurrence[x][y].direction[i].red; |
1142 | 0 | density_xy[y+x+2].direction[i].green+= |
1143 | 0 | cooccurrence[x][y].direction[i].green; |
1144 | 0 | density_xy[y+x+2].direction[i].blue+= |
1145 | 0 | cooccurrence[x][y].direction[i].blue; |
1146 | 0 | if (image->colorspace == CMYKColorspace) |
1147 | 0 | density_xy[y+x+2].direction[i].black+= |
1148 | 0 | cooccurrence[x][y].direction[i].black; |
1149 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1150 | 0 | density_xy[y+x+2].direction[i].alpha+= |
1151 | 0 | cooccurrence[x][y].direction[i].alpha; |
1152 | | /* |
1153 | | Entropy. |
1154 | | */ |
1155 | 0 | channel_features[RedPixelChannel].entropy[i]-= |
1156 | 0 | cooccurrence[x][y].direction[i].red* |
1157 | 0 | log2(cooccurrence[x][y].direction[i].red); |
1158 | 0 | channel_features[GreenPixelChannel].entropy[i]-= |
1159 | 0 | cooccurrence[x][y].direction[i].green* |
1160 | 0 | log2(cooccurrence[x][y].direction[i].green); |
1161 | 0 | channel_features[BluePixelChannel].entropy[i]-= |
1162 | 0 | cooccurrence[x][y].direction[i].blue* |
1163 | 0 | log2(cooccurrence[x][y].direction[i].blue); |
1164 | 0 | if (image->colorspace == CMYKColorspace) |
1165 | 0 | channel_features[BlackPixelChannel].entropy[i]-= |
1166 | 0 | cooccurrence[x][y].direction[i].black* |
1167 | 0 | log2(cooccurrence[x][y].direction[i].black); |
1168 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1169 | 0 | channel_features[AlphaPixelChannel].entropy[i]-= |
1170 | 0 | cooccurrence[x][y].direction[i].alpha* |
1171 | 0 | log2(cooccurrence[x][y].direction[i].alpha); |
1172 | | /* |
1173 | | Information Measures of Correlation. |
1174 | | */ |
1175 | 0 | density_x[x].direction[i].red+=cooccurrence[x][y].direction[i].red; |
1176 | 0 | density_x[x].direction[i].green+=cooccurrence[x][y].direction[i].green; |
1177 | 0 | density_x[x].direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1178 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1179 | 0 | density_x[x].direction[i].alpha+= |
1180 | 0 | cooccurrence[x][y].direction[i].alpha; |
1181 | 0 | if (image->colorspace == CMYKColorspace) |
1182 | 0 | density_x[x].direction[i].black+= |
1183 | 0 | cooccurrence[x][y].direction[i].black; |
1184 | 0 | density_y[y].direction[i].red+=cooccurrence[x][y].direction[i].red; |
1185 | 0 | density_y[y].direction[i].green+=cooccurrence[x][y].direction[i].green; |
1186 | 0 | density_y[y].direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1187 | 0 | if (image->colorspace == CMYKColorspace) |
1188 | 0 | density_y[y].direction[i].black+= |
1189 | 0 | cooccurrence[x][y].direction[i].black; |
1190 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1191 | 0 | density_y[y].direction[i].alpha+= |
1192 | 0 | cooccurrence[x][y].direction[i].alpha; |
1193 | 0 | } |
1194 | 0 | mean.direction[i].red+=y*sum[y].direction[i].red; |
1195 | 0 | sum_squares.direction[i].red+=y*y*sum[y].direction[i].red; |
1196 | 0 | mean.direction[i].green+=y*sum[y].direction[i].green; |
1197 | 0 | sum_squares.direction[i].green+=y*y*sum[y].direction[i].green; |
1198 | 0 | mean.direction[i].blue+=y*sum[y].direction[i].blue; |
1199 | 0 | sum_squares.direction[i].blue+=y*y*sum[y].direction[i].blue; |
1200 | 0 | if (image->colorspace == CMYKColorspace) |
1201 | 0 | { |
1202 | 0 | mean.direction[i].black+=y*sum[y].direction[i].black; |
1203 | 0 | sum_squares.direction[i].black+=y*y*sum[y].direction[i].black; |
1204 | 0 | } |
1205 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1206 | 0 | { |
1207 | 0 | mean.direction[i].alpha+=y*sum[y].direction[i].alpha; |
1208 | 0 | sum_squares.direction[i].alpha+=y*y*sum[y].direction[i].alpha; |
1209 | 0 | } |
1210 | 0 | } |
1211 | | /* |
1212 | | Correlation: measure of linear-dependencies in the image. |
1213 | | */ |
1214 | 0 | channel_features[RedPixelChannel].correlation[i]= |
1215 | 0 | (correlation.direction[i].red-mean.direction[i].red* |
1216 | 0 | mean.direction[i].red)/(sqrt(sum_squares.direction[i].red- |
1217 | 0 | (mean.direction[i].red*mean.direction[i].red))*sqrt( |
1218 | 0 | sum_squares.direction[i].red-(mean.direction[i].red* |
1219 | 0 | mean.direction[i].red))); |
1220 | 0 | channel_features[GreenPixelChannel].correlation[i]= |
1221 | 0 | (correlation.direction[i].green-mean.direction[i].green* |
1222 | 0 | mean.direction[i].green)/(sqrt(sum_squares.direction[i].green- |
1223 | 0 | (mean.direction[i].green*mean.direction[i].green))*sqrt( |
1224 | 0 | sum_squares.direction[i].green-(mean.direction[i].green* |
1225 | 0 | mean.direction[i].green))); |
1226 | 0 | channel_features[BluePixelChannel].correlation[i]= |
1227 | 0 | (correlation.direction[i].blue-mean.direction[i].blue* |
1228 | 0 | mean.direction[i].blue)/(sqrt(sum_squares.direction[i].blue- |
1229 | 0 | (mean.direction[i].blue*mean.direction[i].blue))*sqrt( |
1230 | 0 | sum_squares.direction[i].blue-(mean.direction[i].blue* |
1231 | 0 | mean.direction[i].blue))); |
1232 | 0 | if (image->colorspace == CMYKColorspace) |
1233 | 0 | channel_features[BlackPixelChannel].correlation[i]= |
1234 | 0 | (correlation.direction[i].black-mean.direction[i].black* |
1235 | 0 | mean.direction[i].black)/(sqrt(sum_squares.direction[i].black- |
1236 | 0 | (mean.direction[i].black*mean.direction[i].black))*sqrt( |
1237 | 0 | sum_squares.direction[i].black-(mean.direction[i].black* |
1238 | 0 | mean.direction[i].black))); |
1239 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1240 | 0 | channel_features[AlphaPixelChannel].correlation[i]= |
1241 | 0 | (correlation.direction[i].alpha-mean.direction[i].alpha* |
1242 | 0 | mean.direction[i].alpha)/(sqrt(sum_squares.direction[i].alpha- |
1243 | 0 | (mean.direction[i].alpha*mean.direction[i].alpha))*sqrt( |
1244 | 0 | sum_squares.direction[i].alpha-(mean.direction[i].alpha* |
1245 | 0 | mean.direction[i].alpha))); |
1246 | 0 | } |
1247 | | /* |
1248 | | Compute more texture features. |
1249 | | */ |
1250 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1251 | | #pragma omp parallel for schedule(static) shared(status) \ |
1252 | | magick_number_threads(image,image,number_grays,1) |
1253 | | #endif |
1254 | 0 | for (i=0; i < 4; i++) |
1255 | 0 | { |
1256 | 0 | ssize_t |
1257 | 0 | x; |
1258 | |
|
1259 | 0 | for (x=2; x < (ssize_t) (2*number_grays); x++) |
1260 | 0 | { |
1261 | | /* |
1262 | | Sum average. |
1263 | | */ |
1264 | 0 | channel_features[RedPixelChannel].sum_average[i]+= |
1265 | 0 | x*density_xy[x].direction[i].red; |
1266 | 0 | channel_features[GreenPixelChannel].sum_average[i]+= |
1267 | 0 | x*density_xy[x].direction[i].green; |
1268 | 0 | channel_features[BluePixelChannel].sum_average[i]+= |
1269 | 0 | x*density_xy[x].direction[i].blue; |
1270 | 0 | if (image->colorspace == CMYKColorspace) |
1271 | 0 | channel_features[BlackPixelChannel].sum_average[i]+= |
1272 | 0 | x*density_xy[x].direction[i].black; |
1273 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1274 | 0 | channel_features[AlphaPixelChannel].sum_average[i]+= |
1275 | 0 | x*density_xy[x].direction[i].alpha; |
1276 | | /* |
1277 | | Sum entropy. |
1278 | | */ |
1279 | 0 | channel_features[RedPixelChannel].sum_entropy[i]-= |
1280 | 0 | density_xy[x].direction[i].red* |
1281 | 0 | log2(density_xy[x].direction[i].red); |
1282 | 0 | channel_features[GreenPixelChannel].sum_entropy[i]-= |
1283 | 0 | density_xy[x].direction[i].green* |
1284 | 0 | log2(density_xy[x].direction[i].green); |
1285 | 0 | channel_features[BluePixelChannel].sum_entropy[i]-= |
1286 | 0 | density_xy[x].direction[i].blue* |
1287 | 0 | log2(density_xy[x].direction[i].blue); |
1288 | 0 | if (image->colorspace == CMYKColorspace) |
1289 | 0 | channel_features[BlackPixelChannel].sum_entropy[i]-= |
1290 | 0 | density_xy[x].direction[i].black* |
1291 | 0 | log2(density_xy[x].direction[i].black); |
1292 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1293 | 0 | channel_features[AlphaPixelChannel].sum_entropy[i]-= |
1294 | 0 | density_xy[x].direction[i].alpha* |
1295 | 0 | log2(density_xy[x].direction[i].alpha); |
1296 | | /* |
1297 | | Sum variance. |
1298 | | */ |
1299 | 0 | channel_features[RedPixelChannel].sum_variance[i]+= |
1300 | 0 | (x-channel_features[RedPixelChannel].sum_entropy[i])* |
1301 | 0 | (x-channel_features[RedPixelChannel].sum_entropy[i])* |
1302 | 0 | density_xy[x].direction[i].red; |
1303 | 0 | channel_features[GreenPixelChannel].sum_variance[i]+= |
1304 | 0 | (x-channel_features[GreenPixelChannel].sum_entropy[i])* |
1305 | 0 | (x-channel_features[GreenPixelChannel].sum_entropy[i])* |
1306 | 0 | density_xy[x].direction[i].green; |
1307 | 0 | channel_features[BluePixelChannel].sum_variance[i]+= |
1308 | 0 | (x-channel_features[BluePixelChannel].sum_entropy[i])* |
1309 | 0 | (x-channel_features[BluePixelChannel].sum_entropy[i])* |
1310 | 0 | density_xy[x].direction[i].blue; |
1311 | 0 | if (image->colorspace == CMYKColorspace) |
1312 | 0 | channel_features[BlackPixelChannel].sum_variance[i]+= |
1313 | 0 | (x-channel_features[BlackPixelChannel].sum_entropy[i])* |
1314 | 0 | (x-channel_features[BlackPixelChannel].sum_entropy[i])* |
1315 | 0 | density_xy[x].direction[i].black; |
1316 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1317 | 0 | channel_features[AlphaPixelChannel].sum_variance[i]+= |
1318 | 0 | (x-channel_features[AlphaPixelChannel].sum_entropy[i])* |
1319 | 0 | (x-channel_features[AlphaPixelChannel].sum_entropy[i])* |
1320 | 0 | density_xy[x].direction[i].alpha; |
1321 | 0 | } |
1322 | 0 | } |
1323 | | /* |
1324 | | Compute more texture features. |
1325 | | */ |
1326 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1327 | | #pragma omp parallel for schedule(static) shared(status) \ |
1328 | | magick_number_threads(image,image,number_grays,1) |
1329 | | #endif |
1330 | 0 | for (i=0; i < 4; i++) |
1331 | 0 | { |
1332 | 0 | ssize_t |
1333 | 0 | y; |
1334 | |
|
1335 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1336 | 0 | { |
1337 | 0 | ssize_t |
1338 | 0 | x; |
1339 | |
|
1340 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1341 | 0 | { |
1342 | | /* |
1343 | | Sum of Squares: Variance |
1344 | | */ |
1345 | 0 | variance.direction[i].red+=(y-mean.direction[i].red+1)* |
1346 | 0 | (y-mean.direction[i].red+1)*cooccurrence[x][y].direction[i].red; |
1347 | 0 | variance.direction[i].green+=(y-mean.direction[i].green+1)* |
1348 | 0 | (y-mean.direction[i].green+1)*cooccurrence[x][y].direction[i].green; |
1349 | 0 | variance.direction[i].blue+=(y-mean.direction[i].blue+1)* |
1350 | 0 | (y-mean.direction[i].blue+1)*cooccurrence[x][y].direction[i].blue; |
1351 | 0 | if (image->colorspace == CMYKColorspace) |
1352 | 0 | variance.direction[i].black+=(y-mean.direction[i].black+1)* |
1353 | 0 | (y-mean.direction[i].black+1)*cooccurrence[x][y].direction[i].black; |
1354 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1355 | 0 | variance.direction[i].alpha+=(y-mean.direction[i].alpha+1)* |
1356 | 0 | (y-mean.direction[i].alpha+1)* |
1357 | 0 | cooccurrence[x][y].direction[i].alpha; |
1358 | | /* |
1359 | | Sum average / Difference Variance. |
1360 | | */ |
1361 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].red+= |
1362 | 0 | cooccurrence[x][y].direction[i].red; |
1363 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].green+= |
1364 | 0 | cooccurrence[x][y].direction[i].green; |
1365 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].blue+= |
1366 | 0 | cooccurrence[x][y].direction[i].blue; |
1367 | 0 | if (image->colorspace == CMYKColorspace) |
1368 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].black+= |
1369 | 0 | cooccurrence[x][y].direction[i].black; |
1370 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1371 | 0 | density_xy[MagickAbsoluteValue(y-x)].direction[i].alpha+= |
1372 | 0 | cooccurrence[x][y].direction[i].alpha; |
1373 | | /* |
1374 | | Information Measures of Correlation. |
1375 | | */ |
1376 | 0 | entropy_xy.direction[i].red-=cooccurrence[x][y].direction[i].red* |
1377 | 0 | log2(cooccurrence[x][y].direction[i].red); |
1378 | 0 | entropy_xy.direction[i].green-=cooccurrence[x][y].direction[i].green* |
1379 | 0 | log2(cooccurrence[x][y].direction[i].green); |
1380 | 0 | entropy_xy.direction[i].blue-=cooccurrence[x][y].direction[i].blue* |
1381 | 0 | log2(cooccurrence[x][y].direction[i].blue); |
1382 | 0 | if (image->colorspace == CMYKColorspace) |
1383 | 0 | entropy_xy.direction[i].black-=cooccurrence[x][y].direction[i].black* |
1384 | 0 | log2(cooccurrence[x][y].direction[i].black); |
1385 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1386 | 0 | entropy_xy.direction[i].alpha-= |
1387 | 0 | cooccurrence[x][y].direction[i].alpha*log2( |
1388 | 0 | cooccurrence[x][y].direction[i].alpha); |
1389 | 0 | entropy_xy1.direction[i].red-=(cooccurrence[x][y].direction[i].red* |
1390 | 0 | log2(density_x[x].direction[i].red*density_y[y].direction[i].red)); |
1391 | 0 | entropy_xy1.direction[i].green-=(cooccurrence[x][y].direction[i].green* |
1392 | 0 | log2(density_x[x].direction[i].green* |
1393 | 0 | density_y[y].direction[i].green)); |
1394 | 0 | entropy_xy1.direction[i].blue-=(cooccurrence[x][y].direction[i].blue* |
1395 | 0 | log2(density_x[x].direction[i].blue*density_y[y].direction[i].blue)); |
1396 | 0 | if (image->colorspace == CMYKColorspace) |
1397 | 0 | entropy_xy1.direction[i].black-=( |
1398 | 0 | cooccurrence[x][y].direction[i].black*log2( |
1399 | 0 | density_x[x].direction[i].black*density_y[y].direction[i].black)); |
1400 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1401 | 0 | entropy_xy1.direction[i].alpha-=( |
1402 | 0 | cooccurrence[x][y].direction[i].alpha*log2( |
1403 | 0 | density_x[x].direction[i].alpha*density_y[y].direction[i].alpha)); |
1404 | 0 | entropy_xy2.direction[i].red-=(density_x[x].direction[i].red* |
1405 | 0 | density_y[y].direction[i].red*log2(density_x[x].direction[i].red* |
1406 | 0 | density_y[y].direction[i].red)); |
1407 | 0 | entropy_xy2.direction[i].green-=(density_x[x].direction[i].green* |
1408 | 0 | density_y[y].direction[i].green*log2(density_x[x].direction[i].green* |
1409 | 0 | density_y[y].direction[i].green)); |
1410 | 0 | entropy_xy2.direction[i].blue-=(density_x[x].direction[i].blue* |
1411 | 0 | density_y[y].direction[i].blue*log2(density_x[x].direction[i].blue* |
1412 | 0 | density_y[y].direction[i].blue)); |
1413 | 0 | if (image->colorspace == CMYKColorspace) |
1414 | 0 | entropy_xy2.direction[i].black-=(density_x[x].direction[i].black* |
1415 | 0 | density_y[y].direction[i].black*log2( |
1416 | 0 | density_x[x].direction[i].black*density_y[y].direction[i].black)); |
1417 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1418 | 0 | entropy_xy2.direction[i].alpha-=(density_x[x].direction[i].alpha* |
1419 | 0 | density_y[y].direction[i].alpha*log2( |
1420 | 0 | density_x[x].direction[i].alpha*density_y[y].direction[i].alpha)); |
1421 | 0 | } |
1422 | 0 | } |
1423 | 0 | channel_features[RedPixelChannel].variance_sum_of_squares[i]= |
1424 | 0 | variance.direction[i].red; |
1425 | 0 | channel_features[GreenPixelChannel].variance_sum_of_squares[i]= |
1426 | 0 | variance.direction[i].green; |
1427 | 0 | channel_features[BluePixelChannel].variance_sum_of_squares[i]= |
1428 | 0 | variance.direction[i].blue; |
1429 | 0 | if (image->colorspace == CMYKColorspace) |
1430 | 0 | channel_features[BlackPixelChannel].variance_sum_of_squares[i]= |
1431 | 0 | variance.direction[i].black; |
1432 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1433 | 0 | channel_features[AlphaPixelChannel].variance_sum_of_squares[i]= |
1434 | 0 | variance.direction[i].alpha; |
1435 | 0 | } |
1436 | | /* |
1437 | | Compute more texture features. |
1438 | | */ |
1439 | 0 | (void) memset(&variance,0,sizeof(variance)); |
1440 | 0 | (void) memset(&sum_squares,0,sizeof(sum_squares)); |
1441 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1442 | | #pragma omp parallel for schedule(static) shared(status) \ |
1443 | | magick_number_threads(image,image,number_grays,1) |
1444 | | #endif |
1445 | 0 | for (i=0; i < 4; i++) |
1446 | 0 | { |
1447 | 0 | ssize_t |
1448 | 0 | x; |
1449 | |
|
1450 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1451 | 0 | { |
1452 | | /* |
1453 | | Difference variance. |
1454 | | */ |
1455 | 0 | variance.direction[i].red+=density_xy[x].direction[i].red; |
1456 | 0 | variance.direction[i].green+=density_xy[x].direction[i].green; |
1457 | 0 | variance.direction[i].blue+=density_xy[x].direction[i].blue; |
1458 | 0 | if (image->colorspace == CMYKColorspace) |
1459 | 0 | variance.direction[i].black+=density_xy[x].direction[i].black; |
1460 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1461 | 0 | variance.direction[i].alpha+=density_xy[x].direction[i].alpha; |
1462 | 0 | sum_squares.direction[i].red+=density_xy[x].direction[i].red* |
1463 | 0 | density_xy[x].direction[i].red; |
1464 | 0 | sum_squares.direction[i].green+=density_xy[x].direction[i].green* |
1465 | 0 | density_xy[x].direction[i].green; |
1466 | 0 | sum_squares.direction[i].blue+=density_xy[x].direction[i].blue* |
1467 | 0 | density_xy[x].direction[i].blue; |
1468 | 0 | if (image->colorspace == CMYKColorspace) |
1469 | 0 | sum_squares.direction[i].black+=density_xy[x].direction[i].black* |
1470 | 0 | density_xy[x].direction[i].black; |
1471 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1472 | 0 | sum_squares.direction[i].alpha+=density_xy[x].direction[i].alpha* |
1473 | 0 | density_xy[x].direction[i].alpha; |
1474 | | /* |
1475 | | Difference entropy. |
1476 | | */ |
1477 | 0 | channel_features[RedPixelChannel].difference_entropy[i]-= |
1478 | 0 | density_xy[x].direction[i].red* |
1479 | 0 | log2(density_xy[x].direction[i].red); |
1480 | 0 | channel_features[GreenPixelChannel].difference_entropy[i]-= |
1481 | 0 | density_xy[x].direction[i].green* |
1482 | 0 | log2(density_xy[x].direction[i].green); |
1483 | 0 | channel_features[BluePixelChannel].difference_entropy[i]-= |
1484 | 0 | density_xy[x].direction[i].blue* |
1485 | 0 | log2(density_xy[x].direction[i].blue); |
1486 | 0 | if (image->colorspace == CMYKColorspace) |
1487 | 0 | channel_features[BlackPixelChannel].difference_entropy[i]-= |
1488 | 0 | density_xy[x].direction[i].black* |
1489 | 0 | log2(density_xy[x].direction[i].black); |
1490 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1491 | 0 | channel_features[AlphaPixelChannel].difference_entropy[i]-= |
1492 | 0 | density_xy[x].direction[i].alpha* |
1493 | 0 | log2(density_xy[x].direction[i].alpha); |
1494 | | /* |
1495 | | Information Measures of Correlation. |
1496 | | */ |
1497 | 0 | entropy_x.direction[i].red-=(density_x[x].direction[i].red* |
1498 | 0 | log2(density_x[x].direction[i].red)); |
1499 | 0 | entropy_x.direction[i].green-=(density_x[x].direction[i].green* |
1500 | 0 | log2(density_x[x].direction[i].green)); |
1501 | 0 | entropy_x.direction[i].blue-=(density_x[x].direction[i].blue* |
1502 | 0 | log2(density_x[x].direction[i].blue)); |
1503 | 0 | if (image->colorspace == CMYKColorspace) |
1504 | 0 | entropy_x.direction[i].black-=(density_x[x].direction[i].black* |
1505 | 0 | log2(density_x[x].direction[i].black)); |
1506 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1507 | 0 | entropy_x.direction[i].alpha-=(density_x[x].direction[i].alpha* |
1508 | 0 | log2(density_x[x].direction[i].alpha)); |
1509 | 0 | entropy_y.direction[i].red-=(density_y[x].direction[i].red* |
1510 | 0 | log2(density_y[x].direction[i].red)); |
1511 | 0 | entropy_y.direction[i].green-=(density_y[x].direction[i].green* |
1512 | 0 | log2(density_y[x].direction[i].green)); |
1513 | 0 | entropy_y.direction[i].blue-=(density_y[x].direction[i].blue* |
1514 | 0 | log2(density_y[x].direction[i].blue)); |
1515 | 0 | if (image->colorspace == CMYKColorspace) |
1516 | 0 | entropy_y.direction[i].black-=(density_y[x].direction[i].black* |
1517 | 0 | log2(density_y[x].direction[i].black)); |
1518 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1519 | 0 | entropy_y.direction[i].alpha-=(density_y[x].direction[i].alpha* |
1520 | 0 | log2(density_y[x].direction[i].alpha)); |
1521 | 0 | } |
1522 | | /* |
1523 | | Difference variance. |
1524 | | */ |
1525 | 0 | channel_features[RedPixelChannel].difference_variance[i]= |
1526 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].red)- |
1527 | 0 | (variance.direction[i].red*variance.direction[i].red))/ |
1528 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1529 | 0 | channel_features[GreenPixelChannel].difference_variance[i]= |
1530 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].green)- |
1531 | 0 | (variance.direction[i].green*variance.direction[i].green))/ |
1532 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1533 | 0 | channel_features[BluePixelChannel].difference_variance[i]= |
1534 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].blue)- |
1535 | 0 | (variance.direction[i].blue*variance.direction[i].blue))/ |
1536 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1537 | 0 | if (image->colorspace == CMYKColorspace) |
1538 | 0 | channel_features[BlackPixelChannel].difference_variance[i]= |
1539 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].black)- |
1540 | 0 | (variance.direction[i].black*variance.direction[i].black))/ |
1541 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1542 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1543 | 0 | channel_features[AlphaPixelChannel].difference_variance[i]= |
1544 | 0 | (((double) number_grays*number_grays*sum_squares.direction[i].alpha)- |
1545 | 0 | (variance.direction[i].alpha*variance.direction[i].alpha))/ |
1546 | 0 | ((double) number_grays*number_grays*number_grays*number_grays); |
1547 | | /* |
1548 | | Information Measures of Correlation. |
1549 | | */ |
1550 | 0 | channel_features[RedPixelChannel].measure_of_correlation_1[i]= |
1551 | 0 | (entropy_xy.direction[i].red-entropy_xy1.direction[i].red)/ |
1552 | 0 | (entropy_x.direction[i].red > entropy_y.direction[i].red ? |
1553 | 0 | entropy_x.direction[i].red : entropy_y.direction[i].red); |
1554 | 0 | channel_features[GreenPixelChannel].measure_of_correlation_1[i]= |
1555 | 0 | (entropy_xy.direction[i].green-entropy_xy1.direction[i].green)/ |
1556 | 0 | (entropy_x.direction[i].green > entropy_y.direction[i].green ? |
1557 | 0 | entropy_x.direction[i].green : entropy_y.direction[i].green); |
1558 | 0 | channel_features[BluePixelChannel].measure_of_correlation_1[i]= |
1559 | 0 | (entropy_xy.direction[i].blue-entropy_xy1.direction[i].blue)/ |
1560 | 0 | (entropy_x.direction[i].blue > entropy_y.direction[i].blue ? |
1561 | 0 | entropy_x.direction[i].blue : entropy_y.direction[i].blue); |
1562 | 0 | if (image->colorspace == CMYKColorspace) |
1563 | 0 | channel_features[BlackPixelChannel].measure_of_correlation_1[i]= |
1564 | 0 | (entropy_xy.direction[i].black-entropy_xy1.direction[i].black)/ |
1565 | 0 | (entropy_x.direction[i].black > entropy_y.direction[i].black ? |
1566 | 0 | entropy_x.direction[i].black : entropy_y.direction[i].black); |
1567 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1568 | 0 | channel_features[AlphaPixelChannel].measure_of_correlation_1[i]= |
1569 | 0 | (entropy_xy.direction[i].alpha-entropy_xy1.direction[i].alpha)/ |
1570 | 0 | (entropy_x.direction[i].alpha > entropy_y.direction[i].alpha ? |
1571 | 0 | entropy_x.direction[i].alpha : entropy_y.direction[i].alpha); |
1572 | 0 | channel_features[RedPixelChannel].measure_of_correlation_2[i]= |
1573 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].red- |
1574 | 0 | entropy_xy.direction[i].red))))); |
1575 | 0 | channel_features[GreenPixelChannel].measure_of_correlation_2[i]= |
1576 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].green- |
1577 | 0 | entropy_xy.direction[i].green))))); |
1578 | 0 | channel_features[BluePixelChannel].measure_of_correlation_2[i]= |
1579 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].blue- |
1580 | 0 | entropy_xy.direction[i].blue))))); |
1581 | 0 | if (image->colorspace == CMYKColorspace) |
1582 | 0 | channel_features[BlackPixelChannel].measure_of_correlation_2[i]= |
1583 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].black- |
1584 | 0 | entropy_xy.direction[i].black))))); |
1585 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1586 | 0 | channel_features[AlphaPixelChannel].measure_of_correlation_2[i]= |
1587 | 0 | (sqrt(fabs(1.0-exp(-2.0*(double) (entropy_xy2.direction[i].alpha- |
1588 | 0 | entropy_xy.direction[i].alpha))))); |
1589 | 0 | } |
1590 | | /* |
1591 | | Compute more texture features. |
1592 | | */ |
1593 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1594 | | #pragma omp parallel for schedule(static) shared(status) \ |
1595 | | magick_number_threads(image,image,number_grays,1) |
1596 | | #endif |
1597 | 0 | for (i=0; i < 4; i++) |
1598 | 0 | { |
1599 | 0 | ssize_t |
1600 | 0 | z; |
1601 | |
|
1602 | 0 | for (z=0; z < (ssize_t) number_grays; z++) |
1603 | 0 | { |
1604 | 0 | ssize_t |
1605 | 0 | y; |
1606 | |
|
1607 | 0 | ChannelStatistics |
1608 | 0 | pixel; |
1609 | |
|
1610 | 0 | (void) memset(&pixel,0,sizeof(pixel)); |
1611 | 0 | for (y=0; y < (ssize_t) number_grays; y++) |
1612 | 0 | { |
1613 | 0 | ssize_t |
1614 | 0 | x; |
1615 | |
|
1616 | 0 | for (x=0; x < (ssize_t) number_grays; x++) |
1617 | 0 | { |
1618 | | /* |
1619 | | Contrast: amount of local variations present in an image. |
1620 | | */ |
1621 | 0 | if (((y-x) == z) || ((x-y) == z)) |
1622 | 0 | { |
1623 | 0 | pixel.direction[i].red+=cooccurrence[x][y].direction[i].red; |
1624 | 0 | pixel.direction[i].green+=cooccurrence[x][y].direction[i].green; |
1625 | 0 | pixel.direction[i].blue+=cooccurrence[x][y].direction[i].blue; |
1626 | 0 | if (image->colorspace == CMYKColorspace) |
1627 | 0 | pixel.direction[i].black+=cooccurrence[x][y].direction[i].black; |
1628 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1629 | 0 | pixel.direction[i].alpha+= |
1630 | 0 | cooccurrence[x][y].direction[i].alpha; |
1631 | 0 | } |
1632 | | /* |
1633 | | Maximum Correlation Coefficient. |
1634 | | */ |
1635 | 0 | if ((fabs(density_x[z].direction[i].red) > MagickEpsilon) && |
1636 | 0 | (fabs(density_y[x].direction[i].red) > MagickEpsilon)) |
1637 | 0 | Q[z][y].direction[i].red+=cooccurrence[z][x].direction[i].red* |
1638 | 0 | cooccurrence[y][x].direction[i].red/density_x[z].direction[i].red/ |
1639 | 0 | density_y[x].direction[i].red; |
1640 | 0 | if ((fabs(density_x[z].direction[i].green) > MagickEpsilon) && |
1641 | 0 | (fabs(density_y[x].direction[i].red) > MagickEpsilon)) |
1642 | 0 | Q[z][y].direction[i].green+=cooccurrence[z][x].direction[i].green* |
1643 | 0 | cooccurrence[y][x].direction[i].green/ |
1644 | 0 | density_x[z].direction[i].green/density_y[x].direction[i].red; |
1645 | 0 | if ((fabs(density_x[z].direction[i].blue) > MagickEpsilon) && |
1646 | 0 | (fabs(density_y[x].direction[i].blue) > MagickEpsilon)) |
1647 | 0 | Q[z][y].direction[i].blue+=cooccurrence[z][x].direction[i].blue* |
1648 | 0 | cooccurrence[y][x].direction[i].blue/ |
1649 | 0 | density_x[z].direction[i].blue/density_y[x].direction[i].blue; |
1650 | 0 | if (image->colorspace == CMYKColorspace) |
1651 | 0 | if ((fabs(density_x[z].direction[i].black) > MagickEpsilon) && |
1652 | 0 | (fabs(density_y[x].direction[i].black) > MagickEpsilon)) |
1653 | 0 | Q[z][y].direction[i].black+=cooccurrence[z][x].direction[i].black* |
1654 | 0 | cooccurrence[y][x].direction[i].black/ |
1655 | 0 | density_x[z].direction[i].black/density_y[x].direction[i].black; |
1656 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1657 | 0 | if ((fabs(density_x[z].direction[i].alpha) > MagickEpsilon) && |
1658 | 0 | (fabs(density_y[x].direction[i].alpha) > MagickEpsilon)) |
1659 | 0 | Q[z][y].direction[i].alpha+= |
1660 | 0 | cooccurrence[z][x].direction[i].alpha* |
1661 | 0 | cooccurrence[y][x].direction[i].alpha/ |
1662 | 0 | density_x[z].direction[i].alpha/ |
1663 | 0 | density_y[x].direction[i].alpha; |
1664 | 0 | } |
1665 | 0 | } |
1666 | 0 | channel_features[RedPixelChannel].contrast[i]+=z*z* |
1667 | 0 | pixel.direction[i].red; |
1668 | 0 | channel_features[GreenPixelChannel].contrast[i]+=z*z* |
1669 | 0 | pixel.direction[i].green; |
1670 | 0 | channel_features[BluePixelChannel].contrast[i]+=z*z* |
1671 | 0 | pixel.direction[i].blue; |
1672 | 0 | if (image->colorspace == CMYKColorspace) |
1673 | 0 | channel_features[BlackPixelChannel].contrast[i]+=z*z* |
1674 | 0 | pixel.direction[i].black; |
1675 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1676 | 0 | channel_features[AlphaPixelChannel].contrast[i]+=z*z* |
1677 | 0 | pixel.direction[i].alpha; |
1678 | 0 | } |
1679 | | /* |
1680 | | Maximum Correlation Coefficient. |
1681 | | Future: return second largest eigenvalue of Q. |
1682 | | */ |
1683 | 0 | channel_features[RedPixelChannel].maximum_correlation_coefficient[i]= |
1684 | 0 | sqrt(-1.0); |
1685 | 0 | channel_features[GreenPixelChannel].maximum_correlation_coefficient[i]= |
1686 | 0 | sqrt(-1.0); |
1687 | 0 | channel_features[BluePixelChannel].maximum_correlation_coefficient[i]= |
1688 | 0 | sqrt(-1.0); |
1689 | 0 | if (image->colorspace == CMYKColorspace) |
1690 | 0 | channel_features[BlackPixelChannel].maximum_correlation_coefficient[i]= |
1691 | 0 | sqrt(-1.0); |
1692 | 0 | if (image->alpha_trait != UndefinedPixelTrait) |
1693 | 0 | channel_features[AlphaPixelChannel].maximum_correlation_coefficient[i]= |
1694 | 0 | sqrt(-1.0); |
1695 | 0 | } |
1696 | | /* |
1697 | | Relinquish resources. |
1698 | | */ |
1699 | 0 | sum=(ChannelStatistics *) RelinquishMagickMemory(sum); |
1700 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
1701 | 0 | Q[i]=(ChannelStatistics *) RelinquishMagickMemory(Q[i]); |
1702 | 0 | Q=(ChannelStatistics **) RelinquishMagickMemory(Q); |
1703 | 0 | density_y=(ChannelStatistics *) RelinquishMagickMemory(density_y); |
1704 | 0 | density_xy=(ChannelStatistics *) RelinquishMagickMemory(density_xy); |
1705 | 0 | density_x=(ChannelStatistics *) RelinquishMagickMemory(density_x); |
1706 | 0 | for (i=0; i < (ssize_t) number_grays; i++) |
1707 | 0 | cooccurrence[i]=(ChannelStatistics *) |
1708 | 0 | RelinquishMagickMemory(cooccurrence[i]); |
1709 | 0 | cooccurrence=(ChannelStatistics **) RelinquishMagickMemory(cooccurrence); |
1710 | 0 | return(channel_features); |
1711 | 0 | } |
1712 | | |
1713 | | /* |
1714 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1715 | | % % |
1716 | | % % |
1717 | | % % |
1718 | | % H o u g h L i n e I m a g e % |
1719 | | % % |
1720 | | % % |
1721 | | % % |
1722 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
1723 | | % |
1724 | | % HoughLineImage() can be used in conjunction with any binary edge extracted |
1725 | | % image (we recommend Canny) to identify lines in the image. The algorithm |
1726 | | % accumulates counts for every white pixel for every possible orientation (for |
1727 | | % angles from 0 to 179 in 1 degree increments) and distance from the center of |
1728 | | % the image to the corner (in 1 px increments) and stores the counts in an |
1729 | | % accumulator matrix of angle vs distance. The size of the accumulator is |
1730 | | % 180x(diagonal/2). Next it searches this space for peaks in counts and |
1731 | | % converts the locations of the peaks to slope and intercept in the normal |
1732 | | % x,y input image space. Use the slope/intercepts to find the endpoints |
1733 | | % clipped to the bounds of the image. The lines are then drawn. The counts |
1734 | | % are a measure of the length of the lines. |
1735 | | % |
1736 | | % The format of the HoughLineImage method is: |
1737 | | % |
1738 | | % Image *HoughLineImage(const Image *image,const size_t width, |
1739 | | % const size_t height,const size_t threshold,ExceptionInfo *exception) |
1740 | | % |
1741 | | % A description of each parameter follows: |
1742 | | % |
1743 | | % o image: the image. |
1744 | | % |
1745 | | % o width, height: find line pairs as local maxima in this neighborhood. |
1746 | | % |
1747 | | % o threshold: the line count threshold. |
1748 | | % |
1749 | | % o exception: return any errors or warnings in this structure. |
1750 | | % |
1751 | | */ |
1752 | | |
1753 | | static inline double MagickRound(double x) |
1754 | 0 | { |
1755 | | /* |
1756 | | Round the fraction to nearest integer. |
1757 | | */ |
1758 | 0 | if ((x-floor(x)) < (ceil(x)-x)) |
1759 | 0 | return(floor(x)); |
1760 | 0 | return(ceil(x)); |
1761 | 0 | } |
1762 | | |
1763 | | static Image *RenderHoughLines(const ImageInfo *image_info,const size_t columns, |
1764 | | const size_t rows,ExceptionInfo *exception) |
1765 | 0 | { |
1766 | 0 | #define BoundingBox "viewbox" |
1767 | |
|
1768 | 0 | DrawInfo |
1769 | 0 | *draw_info; |
1770 | |
|
1771 | 0 | Image |
1772 | 0 | *image; |
1773 | |
|
1774 | 0 | MagickBooleanType |
1775 | 0 | status; |
1776 | | |
1777 | | /* |
1778 | | Open image. |
1779 | | */ |
1780 | 0 | image=AcquireImage(image_info,exception); |
1781 | 0 | status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); |
1782 | 0 | if (status == MagickFalse) |
1783 | 0 | { |
1784 | 0 | image=DestroyImageList(image); |
1785 | 0 | return((Image *) NULL); |
1786 | 0 | } |
1787 | 0 | image->columns=columns; |
1788 | 0 | image->rows=rows; |
1789 | 0 | draw_info=CloneDrawInfo(image_info,(DrawInfo *) NULL); |
1790 | 0 | draw_info->affine.sx=image->resolution.x == 0.0 ? 1.0 : image->resolution.x/ |
1791 | 0 | DefaultResolution; |
1792 | 0 | draw_info->affine.sy=image->resolution.y == 0.0 ? 1.0 : image->resolution.y/ |
1793 | 0 | DefaultResolution; |
1794 | 0 | image->columns=CastDoubleToSizeT(draw_info->affine.sx*image->columns); |
1795 | 0 | image->rows=CastDoubleToSizeT(draw_info->affine.sy*image->rows); |
1796 | 0 | status=SetImageExtent(image,image->columns,image->rows,exception); |
1797 | 0 | if (status == MagickFalse) |
1798 | 0 | return(DestroyImageList(image)); |
1799 | 0 | if (SetImageBackgroundColor(image,exception) == MagickFalse) |
1800 | 0 | { |
1801 | 0 | draw_info=DestroyDrawInfo(draw_info); |
1802 | 0 | image=DestroyImageList(image); |
1803 | 0 | return((Image *) NULL); |
1804 | 0 | } |
1805 | | /* |
1806 | | Render drawing. |
1807 | | */ |
1808 | 0 | if (GetBlobStreamData(image) == (unsigned char *) NULL) |
1809 | 0 | draw_info->primitive=FileToString(image->filename,~0UL,exception); |
1810 | 0 | else |
1811 | 0 | { |
1812 | 0 | draw_info->primitive=(char *) AcquireQuantumMemory(1,(size_t) |
1813 | 0 | GetBlobSize(image)+1); |
1814 | 0 | if (draw_info->primitive != (char *) NULL) |
1815 | 0 | { |
1816 | 0 | (void) memcpy(draw_info->primitive,GetBlobStreamData(image), |
1817 | 0 | (size_t) GetBlobSize(image)); |
1818 | 0 | draw_info->primitive[GetBlobSize(image)]='\0'; |
1819 | 0 | } |
1820 | 0 | } |
1821 | 0 | (void) DrawImage(image,draw_info,exception); |
1822 | 0 | draw_info=DestroyDrawInfo(draw_info); |
1823 | 0 | if (CloseBlob(image) == MagickFalse) |
1824 | 0 | image=DestroyImageList(image); |
1825 | 0 | return(GetFirstImageInList(image)); |
1826 | 0 | } |
1827 | | |
1828 | | MagickExport Image *HoughLineImage(const Image *image,const size_t width, |
1829 | | const size_t height,const size_t threshold,ExceptionInfo *exception) |
1830 | 0 | { |
1831 | 0 | #define HoughLineImageTag "HoughLine/Image" |
1832 | |
|
1833 | 0 | CacheView |
1834 | 0 | *image_view; |
1835 | |
|
1836 | 0 | char |
1837 | 0 | message[MagickPathExtent], |
1838 | 0 | path[MagickPathExtent]; |
1839 | |
|
1840 | 0 | const char |
1841 | 0 | *artifact; |
1842 | |
|
1843 | 0 | double |
1844 | 0 | hough_height; |
1845 | |
|
1846 | 0 | Image |
1847 | 0 | *lines_image = NULL; |
1848 | |
|
1849 | 0 | ImageInfo |
1850 | 0 | *image_info; |
1851 | |
|
1852 | 0 | int |
1853 | 0 | file; |
1854 | |
|
1855 | 0 | MagickBooleanType |
1856 | 0 | status; |
1857 | |
|
1858 | 0 | MagickOffsetType |
1859 | 0 | progress; |
1860 | |
|
1861 | 0 | MatrixInfo |
1862 | 0 | *accumulator; |
1863 | |
|
1864 | 0 | PointInfo |
1865 | 0 | center; |
1866 | |
|
1867 | 0 | ssize_t |
1868 | 0 | y; |
1869 | |
|
1870 | 0 | size_t |
1871 | 0 | accumulator_height, |
1872 | 0 | accumulator_width, |
1873 | 0 | line_count; |
1874 | | |
1875 | | /* |
1876 | | Create the accumulator. |
1877 | | */ |
1878 | 0 | assert(image != (const Image *) NULL); |
1879 | 0 | assert(image->signature == MagickCoreSignature); |
1880 | 0 | assert(exception != (ExceptionInfo *) NULL); |
1881 | 0 | assert(exception->signature == MagickCoreSignature); |
1882 | 0 | if (IsEventLogging() != MagickFalse) |
1883 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
1884 | 0 | accumulator_width=180; |
1885 | 0 | hough_height=((sqrt(2.0)*(double) (image->rows > image->columns ? |
1886 | 0 | image->rows : image->columns))/2.0); |
1887 | 0 | accumulator_height=(size_t) (2.0*hough_height); |
1888 | 0 | accumulator=AcquireMatrixInfo(accumulator_width,accumulator_height, |
1889 | 0 | sizeof(double),exception); |
1890 | 0 | if (accumulator == (MatrixInfo *) NULL) |
1891 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1892 | 0 | if (NullMatrix(accumulator) == MagickFalse) |
1893 | 0 | { |
1894 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
1895 | 0 | ThrowImageException(ResourceLimitError,"MemoryAllocationFailed"); |
1896 | 0 | } |
1897 | | /* |
1898 | | Populate the accumulator. |
1899 | | */ |
1900 | 0 | status=MagickTrue; |
1901 | 0 | progress=0; |
1902 | 0 | center.x=(double) image->columns/2.0; |
1903 | 0 | center.y=(double) image->rows/2.0; |
1904 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
1905 | 0 | for (y=0; y < (ssize_t) image->rows; y++) |
1906 | 0 | { |
1907 | 0 | const Quantum |
1908 | 0 | *magick_restrict p; |
1909 | |
|
1910 | 0 | ssize_t |
1911 | 0 | x; |
1912 | |
|
1913 | 0 | if (status == MagickFalse) |
1914 | 0 | continue; |
1915 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
1916 | 0 | if (p == (Quantum *) NULL) |
1917 | 0 | { |
1918 | 0 | status=MagickFalse; |
1919 | 0 | continue; |
1920 | 0 | } |
1921 | 0 | for (x=0; x < (ssize_t) image->columns; x++) |
1922 | 0 | { |
1923 | 0 | if (GetPixelIntensity(image,p) > ((double) QuantumRange/2.0)) |
1924 | 0 | { |
1925 | 0 | ssize_t |
1926 | 0 | i; |
1927 | |
|
1928 | 0 | for (i=0; i < 180; i++) |
1929 | 0 | { |
1930 | 0 | double |
1931 | 0 | count, |
1932 | 0 | radius; |
1933 | |
|
1934 | 0 | radius=(((double) x-center.x)*cos(DegreesToRadians((double) i)))+ |
1935 | 0 | (((double) y-center.y)*sin(DegreesToRadians((double) i))); |
1936 | 0 | (void) GetMatrixElement(accumulator,i,(ssize_t) |
1937 | 0 | MagickRound(radius+hough_height),&count); |
1938 | 0 | count++; |
1939 | 0 | (void) SetMatrixElement(accumulator,i,(ssize_t) |
1940 | 0 | MagickRound(radius+hough_height),&count); |
1941 | 0 | } |
1942 | 0 | } |
1943 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
1944 | 0 | } |
1945 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
1946 | 0 | { |
1947 | 0 | MagickBooleanType |
1948 | 0 | proceed; |
1949 | |
|
1950 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
1951 | | #pragma omp atomic |
1952 | | #endif |
1953 | 0 | progress++; |
1954 | 0 | proceed=SetImageProgress(image,CannyEdgeImageTag,progress,image->rows); |
1955 | 0 | if (proceed == MagickFalse) |
1956 | 0 | status=MagickFalse; |
1957 | 0 | } |
1958 | 0 | } |
1959 | 0 | image_view=DestroyCacheView(image_view); |
1960 | 0 | if (status == MagickFalse) |
1961 | 0 | { |
1962 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
1963 | 0 | return((Image *) NULL); |
1964 | 0 | } |
1965 | | /* |
1966 | | Generate line segments from accumulator. |
1967 | | */ |
1968 | 0 | file=AcquireUniqueFileResource(path); |
1969 | 0 | if (file == -1) |
1970 | 0 | { |
1971 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
1972 | 0 | return((Image *) NULL); |
1973 | 0 | } |
1974 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
1975 | 0 | "# Hough line transform: %.17gx%.17g%+.20g\n",(double) width, |
1976 | 0 | (double) height,(double) threshold); |
1977 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
1978 | 0 | status=MagickFalse; |
1979 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
1980 | 0 | "viewbox 0 0 %.17g %.17g\n",(double) image->columns,(double) image->rows); |
1981 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
1982 | 0 | status=MagickFalse; |
1983 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
1984 | 0 | "# x1,y1 x2,y2 # count angle distance\n"); |
1985 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
1986 | 0 | status=MagickFalse; |
1987 | 0 | line_count=image->columns > image->rows ? image->columns/4 : image->rows/4; |
1988 | 0 | if (threshold != 0) |
1989 | 0 | line_count=threshold; |
1990 | 0 | for (y=0; y < (ssize_t) accumulator_height; y++) |
1991 | 0 | { |
1992 | 0 | ssize_t |
1993 | 0 | x; |
1994 | |
|
1995 | 0 | for (x=0; x < (ssize_t) accumulator_width; x++) |
1996 | 0 | { |
1997 | 0 | double |
1998 | 0 | count; |
1999 | |
|
2000 | 0 | (void) GetMatrixElement(accumulator,x,y,&count); |
2001 | 0 | if (count >= (double) line_count) |
2002 | 0 | { |
2003 | 0 | double |
2004 | 0 | maxima; |
2005 | |
|
2006 | 0 | SegmentInfo |
2007 | 0 | line; |
2008 | |
|
2009 | 0 | ssize_t |
2010 | 0 | v; |
2011 | | |
2012 | | /* |
2013 | | Is point a local maxima? |
2014 | | */ |
2015 | 0 | maxima=count; |
2016 | 0 | for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++) |
2017 | 0 | { |
2018 | 0 | ssize_t |
2019 | 0 | u; |
2020 | |
|
2021 | 0 | for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++) |
2022 | 0 | { |
2023 | 0 | if ((u != 0) || (v !=0)) |
2024 | 0 | { |
2025 | 0 | (void) GetMatrixElement(accumulator,x+u,y+v,&count); |
2026 | 0 | if (count > maxima) |
2027 | 0 | { |
2028 | 0 | maxima=count; |
2029 | 0 | break; |
2030 | 0 | } |
2031 | 0 | } |
2032 | 0 | } |
2033 | 0 | if (u < (ssize_t) (width/2)) |
2034 | 0 | break; |
2035 | 0 | } |
2036 | 0 | (void) GetMatrixElement(accumulator,x,y,&count); |
2037 | 0 | if (maxima > count) |
2038 | 0 | continue; |
2039 | 0 | if ((x >= 45) && (x <= 135)) |
2040 | 0 | { |
2041 | | /* |
2042 | | y = (r-x cos(t))/sin(t) |
2043 | | */ |
2044 | 0 | line.x1=0.0; |
2045 | 0 | line.y1=((double) (y-(accumulator_height/2.0))-((line.x1- |
2046 | 0 | (image->columns/2.0))*cos(DegreesToRadians((double) x))))/ |
2047 | 0 | sin(DegreesToRadians((double) x))+(image->rows/2.0); |
2048 | 0 | line.x2=(double) image->columns; |
2049 | 0 | line.y2=((double) (y-(accumulator_height/2.0))-((line.x2- |
2050 | 0 | (image->columns/2.0))*cos(DegreesToRadians((double) x))))/ |
2051 | 0 | sin(DegreesToRadians((double) x))+(image->rows/2.0); |
2052 | 0 | } |
2053 | 0 | else |
2054 | 0 | { |
2055 | | /* |
2056 | | x = (r-y cos(t))/sin(t) |
2057 | | */ |
2058 | 0 | line.y1=0.0; |
2059 | 0 | line.x1=((double) (y-(accumulator_height/2.0))-((line.y1- |
2060 | 0 | (image->rows/2.0))*sin(DegreesToRadians((double) x))))/ |
2061 | 0 | cos(DegreesToRadians((double) x))+(image->columns/2.0); |
2062 | 0 | line.y2=(double) image->rows; |
2063 | 0 | line.x2=((double) (y-(accumulator_height/2.0))-((line.y2- |
2064 | 0 | (image->rows/2.0))*sin(DegreesToRadians((double) x))))/ |
2065 | 0 | cos(DegreesToRadians((double) x))+(image->columns/2.0); |
2066 | 0 | } |
2067 | 0 | (void) FormatLocaleString(message,MagickPathExtent, |
2068 | 0 | "line %g,%g %g,%g # %g %g %g\n",line.x1,line.y1,line.x2,line.y2, |
2069 | 0 | maxima,(double) x,(double) y); |
2070 | 0 | if (write(file,message,strlen(message)) != (ssize_t) strlen(message)) |
2071 | 0 | status=MagickFalse; |
2072 | 0 | } |
2073 | 0 | } |
2074 | 0 | } |
2075 | 0 | (void) close_utf8(file); |
2076 | | /* |
2077 | | Render lines to image canvas. |
2078 | | */ |
2079 | 0 | image_info=AcquireImageInfo(); |
2080 | 0 | image_info->background_color=image->background_color; |
2081 | 0 | (void) FormatLocaleString(image_info->filename,MagickPathExtent,"%s",path); |
2082 | 0 | artifact=GetImageArtifact(image,"background"); |
2083 | 0 | if (artifact != (const char *) NULL) |
2084 | 0 | (void) SetImageOption(image_info,"background",artifact); |
2085 | 0 | artifact=GetImageArtifact(image,"fill"); |
2086 | 0 | if (artifact != (const char *) NULL) |
2087 | 0 | (void) SetImageOption(image_info,"fill",artifact); |
2088 | 0 | artifact=GetImageArtifact(image,"stroke"); |
2089 | 0 | if (artifact != (const char *) NULL) |
2090 | 0 | (void) SetImageOption(image_info,"stroke",artifact); |
2091 | 0 | artifact=GetImageArtifact(image,"strokewidth"); |
2092 | 0 | if (artifact != (const char *) NULL) |
2093 | 0 | (void) SetImageOption(image_info,"strokewidth",artifact); |
2094 | 0 | lines_image=RenderHoughLines(image_info,image->columns,image->rows,exception); |
2095 | 0 | artifact=GetImageArtifact(image,"hough-lines:accumulator"); |
2096 | 0 | if ((lines_image != (Image *) NULL) && |
2097 | 0 | (IsStringTrue(artifact) != MagickFalse)) |
2098 | 0 | { |
2099 | 0 | Image |
2100 | 0 | *accumulator_image; |
2101 | |
|
2102 | 0 | accumulator_image=MatrixToImage(accumulator,exception); |
2103 | 0 | if (accumulator_image != (Image *) NULL) |
2104 | 0 | AppendImageToList(&lines_image,accumulator_image); |
2105 | 0 | } |
2106 | | /* |
2107 | | Free resources. |
2108 | | */ |
2109 | 0 | accumulator=DestroyMatrixInfo(accumulator); |
2110 | 0 | image_info=DestroyImageInfo(image_info); |
2111 | 0 | (void) RelinquishUniqueFileResource(path); |
2112 | 0 | return(GetFirstImageInList(lines_image)); |
2113 | 0 | } |
2114 | | |
2115 | | /* |
2116 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2117 | | % % |
2118 | | % % |
2119 | | % % |
2120 | | % M e a n S h i f t I m a g e % |
2121 | | % % |
2122 | | % % |
2123 | | % % |
2124 | | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
2125 | | % |
2126 | | % MeanShiftImage() delineate arbitrarily shaped clusters in the image. For |
2127 | | % each pixel, it visits all the pixels in the neighborhood specified by |
2128 | | % the window centered at the pixel and excludes those that are outside the |
2129 | | % radius=(window-1)/2 surrounding the pixel. From those pixels, it finds those |
2130 | | % that are within the specified color distance from the current mean, and |
2131 | | % computes a new x,y centroid from those coordinates and a new mean. This new |
2132 | | % x,y centroid is used as the center for a new window. This process iterates |
2133 | | % until it converges and the final mean is replaces the (original window |
2134 | | % center) pixel value. It repeats this process for the next pixel, etc., |
2135 | | % until it processes all pixels in the image. Results are typically better with |
2136 | | % colorspaces other than sRGB. We recommend YIQ, YUV or YCbCr. |
2137 | | % |
2138 | | % The format of the MeanShiftImage method is: |
2139 | | % |
2140 | | % Image *MeanShiftImage(const Image *image,const size_t width, |
2141 | | % const size_t height,const double color_distance, |
2142 | | % ExceptionInfo *exception) |
2143 | | % |
2144 | | % A description of each parameter follows: |
2145 | | % |
2146 | | % o image: the image. |
2147 | | % |
2148 | | % o width, height: find pixels in this neighborhood. |
2149 | | % |
2150 | | % o color_distance: the color distance. |
2151 | | % |
2152 | | % o exception: return any errors or warnings in this structure. |
2153 | | % |
2154 | | */ |
2155 | | MagickExport Image *MeanShiftImage(const Image *image,const size_t width, |
2156 | | const size_t height,const double color_distance,ExceptionInfo *exception) |
2157 | 0 | { |
2158 | 0 | #define MaxMeanShiftIterations 100 |
2159 | 0 | #define MeanShiftImageTag "MeanShift/Image" |
2160 | |
|
2161 | 0 | CacheView |
2162 | 0 | *image_view, |
2163 | 0 | *mean_view, |
2164 | 0 | *pixel_view; |
2165 | |
|
2166 | 0 | Image |
2167 | 0 | *mean_image; |
2168 | |
|
2169 | 0 | MagickBooleanType |
2170 | 0 | status; |
2171 | |
|
2172 | 0 | MagickOffsetType |
2173 | 0 | progress; |
2174 | |
|
2175 | 0 | ssize_t |
2176 | 0 | y; |
2177 | |
|
2178 | 0 | assert(image != (const Image *) NULL); |
2179 | 0 | assert(image->signature == MagickCoreSignature); |
2180 | 0 | assert(exception != (ExceptionInfo *) NULL); |
2181 | 0 | assert(exception->signature == MagickCoreSignature); |
2182 | 0 | if (IsEventLogging() != MagickFalse) |
2183 | 0 | (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
2184 | 0 | mean_image=CloneImage(image,0,0,MagickTrue,exception); |
2185 | 0 | if (mean_image == (Image *) NULL) |
2186 | 0 | return((Image *) NULL); |
2187 | 0 | if (SetImageStorageClass(mean_image,DirectClass,exception) == MagickFalse) |
2188 | 0 | { |
2189 | 0 | mean_image=DestroyImage(mean_image); |
2190 | 0 | return((Image *) NULL); |
2191 | 0 | } |
2192 | 0 | status=MagickTrue; |
2193 | 0 | progress=0; |
2194 | 0 | image_view=AcquireVirtualCacheView(image,exception); |
2195 | 0 | pixel_view=AcquireVirtualCacheView(image,exception); |
2196 | 0 | mean_view=AcquireAuthenticCacheView(mean_image,exception); |
2197 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2198 | | #pragma omp parallel for schedule(static) shared(status,progress) \ |
2199 | | magick_number_threads(mean_image,mean_image,mean_image->rows,1) |
2200 | | #endif |
2201 | 0 | for (y=0; y < (ssize_t) mean_image->rows; y++) |
2202 | 0 | { |
2203 | 0 | const Quantum |
2204 | 0 | *magick_restrict p; |
2205 | |
|
2206 | 0 | Quantum |
2207 | 0 | *magick_restrict q; |
2208 | |
|
2209 | 0 | ssize_t |
2210 | 0 | x; |
2211 | |
|
2212 | 0 | if (status == MagickFalse) |
2213 | 0 | continue; |
2214 | 0 | p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
2215 | 0 | q=GetCacheViewAuthenticPixels(mean_view,0,y,mean_image->columns,1, |
2216 | 0 | exception); |
2217 | 0 | if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
2218 | 0 | { |
2219 | 0 | status=MagickFalse; |
2220 | 0 | continue; |
2221 | 0 | } |
2222 | 0 | for (x=0; x < (ssize_t) mean_image->columns; x++) |
2223 | 0 | { |
2224 | 0 | PixelInfo |
2225 | 0 | mean_pixel, |
2226 | 0 | previous_pixel; |
2227 | |
|
2228 | 0 | PointInfo |
2229 | 0 | mean_location, |
2230 | 0 | previous_location; |
2231 | |
|
2232 | 0 | ssize_t |
2233 | 0 | i; |
2234 | |
|
2235 | 0 | GetPixelInfo(image,&mean_pixel); |
2236 | 0 | GetPixelInfoPixel(image,p,&mean_pixel); |
2237 | 0 | mean_location.x=(double) x; |
2238 | 0 | mean_location.y=(double) y; |
2239 | 0 | for (i=0; i < MaxMeanShiftIterations; i++) |
2240 | 0 | { |
2241 | 0 | double |
2242 | 0 | distance, |
2243 | 0 | gamma = 1.0; |
2244 | |
|
2245 | 0 | PixelInfo |
2246 | 0 | sum_pixel; |
2247 | |
|
2248 | 0 | PointInfo |
2249 | 0 | sum_location; |
2250 | |
|
2251 | 0 | ssize_t |
2252 | 0 | count, |
2253 | 0 | v; |
2254 | |
|
2255 | 0 | sum_location.x=0.0; |
2256 | 0 | sum_location.y=0.0; |
2257 | 0 | GetPixelInfo(image,&sum_pixel); |
2258 | 0 | previous_location=mean_location; |
2259 | 0 | previous_pixel=mean_pixel; |
2260 | 0 | count=0; |
2261 | 0 | for (v=(-((ssize_t) height/2)); v <= (((ssize_t) height/2)); v++) |
2262 | 0 | { |
2263 | 0 | ssize_t |
2264 | 0 | u; |
2265 | |
|
2266 | 0 | for (u=(-((ssize_t) width/2)); u <= (((ssize_t) width/2)); u++) |
2267 | 0 | { |
2268 | 0 | if ((v*v+u*u) <= (ssize_t) ((width/2)*(height/2))) |
2269 | 0 | { |
2270 | 0 | PixelInfo |
2271 | 0 | pixel; |
2272 | |
|
2273 | 0 | status=GetOneCacheViewVirtualPixelInfo(pixel_view,(ssize_t) |
2274 | 0 | MagickRound(mean_location.x+u),(ssize_t) MagickRound( |
2275 | 0 | mean_location.y+v),&pixel,exception); |
2276 | 0 | distance=(mean_pixel.red-pixel.red)*(mean_pixel.red-pixel.red)+ |
2277 | 0 | (mean_pixel.green-pixel.green)*(mean_pixel.green-pixel.green)+ |
2278 | 0 | (mean_pixel.blue-pixel.blue)*(mean_pixel.blue-pixel.blue); |
2279 | 0 | if (distance <= (color_distance*color_distance)) |
2280 | 0 | { |
2281 | 0 | sum_location.x+=mean_location.x+u; |
2282 | 0 | sum_location.y+=mean_location.y+v; |
2283 | 0 | sum_pixel.red+=pixel.red; |
2284 | 0 | sum_pixel.green+=pixel.green; |
2285 | 0 | sum_pixel.blue+=pixel.blue; |
2286 | 0 | sum_pixel.alpha+=pixel.alpha; |
2287 | 0 | count++; |
2288 | 0 | } |
2289 | 0 | } |
2290 | 0 | } |
2291 | 0 | } |
2292 | 0 | if (count != 0) |
2293 | 0 | gamma=MagickSafeReciprocal((double) count); |
2294 | 0 | mean_location.x=gamma*sum_location.x; |
2295 | 0 | mean_location.y=gamma*sum_location.y; |
2296 | 0 | mean_pixel.red=gamma*sum_pixel.red; |
2297 | 0 | mean_pixel.green=gamma*sum_pixel.green; |
2298 | 0 | mean_pixel.blue=gamma*sum_pixel.blue; |
2299 | 0 | mean_pixel.alpha=gamma*sum_pixel.alpha; |
2300 | 0 | distance=(mean_location.x-previous_location.x)* |
2301 | 0 | (mean_location.x-previous_location.x)+ |
2302 | 0 | (mean_location.y-previous_location.y)* |
2303 | 0 | (mean_location.y-previous_location.y)+ |
2304 | 0 | 255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)* |
2305 | 0 | 255.0*QuantumScale*(mean_pixel.red-previous_pixel.red)+ |
2306 | 0 | 255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)* |
2307 | 0 | 255.0*QuantumScale*(mean_pixel.green-previous_pixel.green)+ |
2308 | 0 | 255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue)* |
2309 | 0 | 255.0*QuantumScale*(mean_pixel.blue-previous_pixel.blue); |
2310 | 0 | if (distance <= 3.0) |
2311 | 0 | break; |
2312 | 0 | } |
2313 | 0 | SetPixelRed(mean_image,ClampToQuantum(mean_pixel.red),q); |
2314 | 0 | SetPixelGreen(mean_image,ClampToQuantum(mean_pixel.green),q); |
2315 | 0 | SetPixelBlue(mean_image,ClampToQuantum(mean_pixel.blue),q); |
2316 | 0 | SetPixelAlpha(mean_image,ClampToQuantum(mean_pixel.alpha),q); |
2317 | 0 | p+=(ptrdiff_t) GetPixelChannels(image); |
2318 | 0 | q+=(ptrdiff_t) GetPixelChannels(mean_image); |
2319 | 0 | } |
2320 | 0 | if (SyncCacheViewAuthenticPixels(mean_view,exception) == MagickFalse) |
2321 | 0 | status=MagickFalse; |
2322 | 0 | if (image->progress_monitor != (MagickProgressMonitor) NULL) |
2323 | 0 | { |
2324 | 0 | MagickBooleanType |
2325 | 0 | proceed; |
2326 | |
|
2327 | | #if defined(MAGICKCORE_OPENMP_SUPPORT) |
2328 | | #pragma omp atomic |
2329 | | #endif |
2330 | 0 | progress++; |
2331 | 0 | proceed=SetImageProgress(image,MeanShiftImageTag,progress,image->rows); |
2332 | 0 | if (proceed == MagickFalse) |
2333 | 0 | status=MagickFalse; |
2334 | 0 | } |
2335 | 0 | } |
2336 | 0 | mean_view=DestroyCacheView(mean_view); |
2337 | 0 | pixel_view=DestroyCacheView(pixel_view); |
2338 | 0 | image_view=DestroyCacheView(image_view); |
2339 | 0 | return(mean_image); |
2340 | 0 | } |