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