2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 % CCCC OOO M M PPPP AAA RRRR EEEEE %
7 % C O O MM MM P P A A R R E %
8 % C O O M M M PPPP AAAAA RRRR EEE %
9 % C O O M M P A A R R E %
10 % CCCC OOO M M P A A R R EEEEE %
13 % MagickCore Image Comparison Methods %
20 % Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
26 % http://www.imagemagick.org/script/license.php %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
43 #include "MagickCore/studio.h"
44 #include "MagickCore/artifact.h"
45 #include "MagickCore/cache-view.h"
46 #include "MagickCore/client.h"
47 #include "MagickCore/color.h"
48 #include "MagickCore/color-private.h"
49 #include "MagickCore/colorspace.h"
50 #include "MagickCore/colorspace-private.h"
51 #include "MagickCore/compare.h"
52 #include "MagickCore/composite-private.h"
53 #include "MagickCore/constitute.h"
54 #include "MagickCore/exception-private.h"
55 #include "MagickCore/geometry.h"
56 #include "MagickCore/image-private.h"
57 #include "MagickCore/list.h"
58 #include "MagickCore/log.h"
59 #include "MagickCore/memory_.h"
60 #include "MagickCore/monitor.h"
61 #include "MagickCore/monitor-private.h"
62 #include "MagickCore/option.h"
63 #include "MagickCore/pixel-accessor.h"
64 #include "MagickCore/resource_.h"
65 #include "MagickCore/string_.h"
66 #include "MagickCore/statistic.h"
67 #include "MagickCore/transform.h"
68 #include "MagickCore/utility.h"
69 #include "MagickCore/version.h"
72 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
76 % C o m p a r e I m a g e %
80 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
82 % CompareImages() compares one or more pixel channels of an image to a
83 % reconstructed image and returns the difference image.
85 % The format of the CompareImages method is:
87 % Image *CompareImages(const Image *image,const Image *reconstruct_image,
88 % const MetricType metric,double *distortion,ExceptionInfo *exception)
90 % A description of each parameter follows:
94 % o reconstruct_image: the reconstruct image.
96 % o metric: the metric.
98 % o distortion: the computed distortion between the images.
100 % o exception: return any errors or warnings in this structure.
103 MagickExport Image *CompareImages(Image *image,const Image *reconstruct_image,
104 const MetricType metric,double *distortion,ExceptionInfo *exception)
128 assert(image != (Image *) NULL);
129 assert(image->signature == MagickSignature);
130 if (image->debug != MagickFalse)
131 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
132 assert(reconstruct_image != (const Image *) NULL);
133 assert(reconstruct_image->signature == MagickSignature);
134 assert(distortion != (double *) NULL);
136 if (image->debug != MagickFalse)
137 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
138 if ((reconstruct_image->columns != image->columns) ||
139 (reconstruct_image->rows != image->rows))
140 ThrowImageException(ImageError,"ImageSizeDiffers");
141 status=GetImageDistortion(image,reconstruct_image,metric,distortion,
143 if (status == MagickFalse)
144 return((Image *) NULL);
145 difference_image=CloneImage(image,0,0,MagickTrue,exception);
146 if (difference_image == (Image *) NULL)
147 return((Image *) NULL);
148 (void) SetImageAlphaChannel(difference_image,OpaqueAlphaChannel,exception);
149 highlight_image=CloneImage(image,image->columns,image->rows,MagickTrue,
151 if (highlight_image == (Image *) NULL)
153 difference_image=DestroyImage(difference_image);
154 return((Image *) NULL);
156 status=SetImageStorageClass(highlight_image,DirectClass,exception);
157 if (status == MagickFalse)
159 difference_image=DestroyImage(difference_image);
160 highlight_image=DestroyImage(highlight_image);
161 return((Image *) NULL);
163 (void) SetImageAlphaChannel(highlight_image,OpaqueAlphaChannel,exception);
164 (void) QueryMagickColorCompliance("#f1001ecc",AllCompliance,&highlight,
166 artifact=GetImageArtifact(image,"highlight-color");
167 if (artifact != (const char *) NULL)
168 (void) QueryMagickColorCompliance(artifact,AllCompliance,&highlight,
170 (void) QueryMagickColorCompliance("#ffffffcc",AllCompliance,&lowlight,
172 artifact=GetImageArtifact(image,"lowlight-color");
173 if (artifact != (const char *) NULL)
174 (void) QueryMagickColorCompliance(artifact,AllCompliance,&lowlight,
176 if (highlight_image->colorspace == CMYKColorspace)
178 ConvertRGBToCMYK(&highlight);
179 ConvertRGBToCMYK(&lowlight);
182 Generate difference image.
185 image_view=AcquireCacheView(image);
186 reconstruct_view=AcquireCacheView(reconstruct_image);
187 highlight_view=AcquireCacheView(highlight_image);
188 #if defined(MAGICKCORE_OPENMP_SUPPORT)
189 #pragma omp parallel for schedule(dynamic,4) shared(status)
191 for (y=0; y < (ssize_t) image->rows; y++)
196 register const Quantum
206 if (status == MagickFalse)
208 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
209 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
211 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
213 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
214 (r == (Quantum *) NULL))
219 for (x=0; x < (ssize_t) image->columns; x++)
227 difference=MagickFalse;
228 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
240 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
241 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
242 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
243 if ((traits == UndefinedPixelTrait) ||
244 (reconstruct_traits == UndefinedPixelTrait))
246 if ((reconstruct_traits & UpdatePixelTrait) == 0)
248 distance=p[i]-(MagickRealType)
249 GetPixelChannel(reconstruct_image,channel,q);
250 if (fabs((double) distance) >= MagickEpsilon)
251 difference=MagickTrue;
253 if (difference == MagickFalse)
254 SetPixelPixelInfo(highlight_image,&lowlight,r);
256 SetPixelPixelInfo(highlight_image,&highlight,r);
257 p+=GetPixelChannels(image);
258 q+=GetPixelChannels(reconstruct_image);
259 r+=GetPixelChannels(highlight_image);
261 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
262 if (sync == MagickFalse)
265 highlight_view=DestroyCacheView(highlight_view);
266 reconstruct_view=DestroyCacheView(reconstruct_view);
267 image_view=DestroyCacheView(image_view);
268 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
269 highlight_image=DestroyImage(highlight_image);
270 if (status == MagickFalse)
271 difference_image=DestroyImage(difference_image);
272 return(difference_image);
276 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
280 % G e t I m a g e D i s t o r t i o n %
284 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
286 % GetImageDistortion() compares one or more pixel channels of an image to a
287 % reconstructed image and returns the specified distortion metric.
289 % The format of the CompareImages method is:
291 % MagickBooleanType GetImageDistortion(const Image *image,
292 % const Image *reconstruct_image,const MetricType metric,
293 % double *distortion,ExceptionInfo *exception)
295 % A description of each parameter follows:
297 % o image: the image.
299 % o reconstruct_image: the reconstruct image.
301 % o metric: the metric.
303 % o distortion: the computed distortion between the images.
305 % o exception: return any errors or warnings in this structure.
309 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
310 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
323 Compute the absolute difference in pixels between two images.
326 image_view=AcquireCacheView(image);
327 reconstruct_view=AcquireCacheView(reconstruct_image);
328 #if defined(MAGICKCORE_OPENMP_SUPPORT)
329 #pragma omp parallel for schedule(dynamic,4) shared(status)
331 for (y=0; y < (ssize_t) image->rows; y++)
334 channel_distortion[MaxPixelChannels+1];
336 register const Quantum
344 if (status == MagickFalse)
346 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
347 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
349 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
354 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
355 for (x=0; x < (ssize_t) image->columns; x++)
363 difference=MagickFalse;
364 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
373 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
374 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
375 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
376 if ((traits == UndefinedPixelTrait) ||
377 (reconstruct_traits == UndefinedPixelTrait))
379 if ((reconstruct_traits & UpdatePixelTrait) == 0)
381 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
382 difference=MagickTrue;
384 if (difference != MagickFalse)
386 channel_distortion[i]++;
387 channel_distortion[MaxPixelChannels]++;
389 p+=GetPixelChannels(image);
390 q+=GetPixelChannels(reconstruct_image);
392 #if defined(MAGICKCORE_OPENMP_SUPPORT)
393 #pragma omp critical (MagickCore_GetAbsoluteError)
395 for (i=0; i <= MaxPixelChannels; i++)
396 distortion[i]+=channel_distortion[i];
398 reconstruct_view=DestroyCacheView(reconstruct_view);
399 image_view=DestroyCacheView(image_view);
403 static size_t GetImageChannels(const Image *image)
412 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
417 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
418 if ((traits & UpdatePixelTrait) != 0)
424 static MagickBooleanType GetFuzzDistortion(const Image *image,
425 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
441 image_view=AcquireCacheView(image);
442 reconstruct_view=AcquireCacheView(reconstruct_image);
443 #if defined(MAGICKCORE_OPENMP_SUPPORT)
444 #pragma omp parallel for schedule(dynamic,4) shared(status)
446 for (y=0; y < (ssize_t) image->rows; y++)
449 channel_distortion[MaxPixelChannels+1];
451 register const Quantum
459 if (status == MagickFalse)
461 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
462 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
464 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
469 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
470 for (x=0; x < (ssize_t) image->columns; x++)
475 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
487 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
488 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
489 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
490 if ((traits == UndefinedPixelTrait) ||
491 (reconstruct_traits == UndefinedPixelTrait))
493 if ((reconstruct_traits & UpdatePixelTrait) == 0)
495 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
496 reconstruct_image,channel,q));
498 channel_distortion[i]+=distance;
499 channel_distortion[MaxPixelChannels]+=distance;
501 p+=GetPixelChannels(image);
502 q+=GetPixelChannels(reconstruct_image);
504 #if defined(MAGICKCORE_OPENMP_SUPPORT)
505 #pragma omp critical (MagickCore_GetMeanSquaredError)
507 for (i=0; i <= MaxPixelChannels; i++)
508 distortion[i]+=channel_distortion[i];
510 reconstruct_view=DestroyCacheView(reconstruct_view);
511 image_view=DestroyCacheView(image_view);
512 for (i=0; i <= MaxPixelChannels; i++)
513 distortion[i]/=((double) image->columns*image->rows);
514 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
515 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]);
519 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
520 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
536 image_view=AcquireCacheView(image);
537 reconstruct_view=AcquireCacheView(reconstruct_image);
538 #if defined(MAGICKCORE_OPENMP_SUPPORT)
539 #pragma omp parallel for schedule(dynamic,4) shared(status)
541 for (y=0; y < (ssize_t) image->rows; y++)
544 channel_distortion[MaxPixelChannels+1];
546 register const Quantum
554 if (status == MagickFalse)
556 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
557 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
559 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
564 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
565 for (x=0; x < (ssize_t) image->columns; x++)
570 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
582 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
583 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
584 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
585 if ((traits == UndefinedPixelTrait) ||
586 (reconstruct_traits == UndefinedPixelTrait))
588 if ((reconstruct_traits & UpdatePixelTrait) == 0)
590 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
591 reconstruct_image,channel,q));
592 channel_distortion[i]+=distance;
593 channel_distortion[MaxPixelChannels]+=distance;
595 p+=GetPixelChannels(image);
596 q+=GetPixelChannels(reconstruct_image);
598 #if defined(MAGICKCORE_OPENMP_SUPPORT)
599 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
601 for (i=0; i <= MaxPixelChannels; i++)
602 distortion[i]+=channel_distortion[i];
604 reconstruct_view=DestroyCacheView(reconstruct_view);
605 image_view=DestroyCacheView(image_view);
606 for (i=0; i <= MaxPixelChannels; i++)
607 distortion[i]/=((double) image->columns*image->rows);
608 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
612 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
613 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
638 image_view=AcquireCacheView(image);
639 reconstruct_view=AcquireCacheView(reconstruct_image);
640 for (y=0; y < (ssize_t) image->rows; y++)
642 register const Quantum
649 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
650 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
652 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
657 for (x=0; x < (ssize_t) image->columns; x++)
662 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
674 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
675 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
676 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
677 if ((traits == UndefinedPixelTrait) ||
678 (reconstruct_traits == UndefinedPixelTrait))
680 if ((reconstruct_traits & UpdatePixelTrait) == 0)
682 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
683 reconstruct_image,channel,q)));
684 distortion[i]+=distance;
685 distortion[MaxPixelChannels]+=distance;
686 mean_error+=distance*distance;
687 if (distance > maximum_error)
688 maximum_error=distance;
691 p+=GetPixelChannels(image);
692 q+=GetPixelChannels(reconstruct_image);
695 reconstruct_view=DestroyCacheView(reconstruct_view);
696 image_view=DestroyCacheView(image_view);
697 image->error.mean_error_per_pixel=distortion[MaxPixelChannels]/area;
698 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
699 image->error.normalized_maximum_error=QuantumScale*maximum_error;
703 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
704 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
720 image_view=AcquireCacheView(image);
721 reconstruct_view=AcquireCacheView(reconstruct_image);
722 #if defined(MAGICKCORE_OPENMP_SUPPORT)
723 #pragma omp parallel for schedule(dynamic,4) shared(status)
725 for (y=0; y < (ssize_t) image->rows; y++)
728 channel_distortion[MaxPixelChannels+1];
730 register const Quantum
738 if (status == MagickFalse)
740 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
741 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
743 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
748 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
749 for (x=0; x < (ssize_t) image->columns; x++)
754 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
766 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
767 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
768 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
769 if ((traits == UndefinedPixelTrait) ||
770 (reconstruct_traits == UndefinedPixelTrait))
772 if ((reconstruct_traits & UpdatePixelTrait) == 0)
774 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
775 reconstruct_image,channel,q));
777 channel_distortion[i]+=distance;
778 channel_distortion[MaxPixelChannels]+=distance;
780 p+=GetPixelChannels(image);
781 q+=GetPixelChannels(reconstruct_image);
783 #if defined(MAGICKCORE_OPENMP_SUPPORT)
784 #pragma omp critical (MagickCore_GetMeanSquaredError)
786 for (i=0; i <= MaxPixelChannels; i++)
787 distortion[i]+=channel_distortion[i];
789 reconstruct_view=DestroyCacheView(reconstruct_view);
790 image_view=DestroyCacheView(image_view);
791 for (i=0; i <= MaxPixelChannels; i++)
792 distortion[i]/=((double) image->columns*image->rows);
793 distortion[MaxPixelChannels]/=GetImageChannels(image);
797 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
798 const Image *image,const Image *reconstruct_image,double *distortion,
799 ExceptionInfo *exception)
801 #define SimilarityImageTag "Similarity/Image"
809 *reconstruct_statistics;
827 Normalize to account for variation due to lighting and exposure condition.
829 image_statistics=GetImageStatistics(image,exception);
830 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
833 for (i=0; i <= MaxPixelChannels; i++)
835 area=1.0/((MagickRealType) image->columns*image->rows);
836 image_view=AcquireCacheView(image);
837 reconstruct_view=AcquireCacheView(reconstruct_image);
838 for (y=0; y < (ssize_t) image->rows; y++)
840 register const Quantum
847 if (status == MagickFalse)
849 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
850 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
852 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
857 for (x=0; x < (ssize_t) image->columns; x++)
862 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
871 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
872 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
873 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
874 if ((traits == UndefinedPixelTrait) ||
875 (reconstruct_traits == UndefinedPixelTrait))
877 if ((reconstruct_traits & UpdatePixelTrait) == 0)
879 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
880 (GetPixelChannel(reconstruct_image,channel,q)-
881 reconstruct_statistics[channel].mean);
883 p+=GetPixelChannels(image);
884 q+=GetPixelChannels(image);
886 if (image->progress_monitor != (MagickProgressMonitor) NULL)
891 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
893 if (proceed == MagickFalse)
897 reconstruct_view=DestroyCacheView(reconstruct_view);
898 image_view=DestroyCacheView(image_view);
900 Divide by the standard deviation.
902 distortion[MaxPixelChannels]=0.0;
903 for (i=0; i < MaxPixelChannels; i++)
911 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
912 gamma=image_statistics[i].standard_deviation*
913 reconstruct_statistics[channel].standard_deviation;
914 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
915 distortion[i]=QuantumRange*gamma*distortion[i];
916 distortion[MaxPixelChannels]+=distortion[i]*distortion[i];
918 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]/
919 GetImageChannels(image));
923 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
924 reconstruct_statistics);
925 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
930 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
931 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
944 image_view=AcquireCacheView(image);
945 reconstruct_view=AcquireCacheView(reconstruct_image);
946 #if defined(MAGICKCORE_OPENMP_SUPPORT)
947 #pragma omp parallel for schedule(dynamic,4) shared(status)
949 for (y=0; y < (ssize_t) image->rows; y++)
952 channel_distortion[MaxPixelChannels+1];
954 register const Quantum
962 if (status == MagickFalse)
964 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
965 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
966 reconstruct_image->columns,1,exception);
967 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
972 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
973 for (x=0; x < (ssize_t) image->columns; x++)
978 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
990 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
991 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
992 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
993 if ((traits == UndefinedPixelTrait) ||
994 (reconstruct_traits == UndefinedPixelTrait))
996 if ((reconstruct_traits & UpdatePixelTrait) == 0)
998 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
999 reconstruct_image,channel,q));
1000 if (distance > channel_distortion[i])
1001 channel_distortion[i]=distance;
1002 if (distance > channel_distortion[MaxPixelChannels])
1003 channel_distortion[MaxPixelChannels]=distance;
1005 p+=GetPixelChannels(image);
1006 q+=GetPixelChannels(image);
1008 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1009 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1011 for (i=0; i <= MaxPixelChannels; i++)
1012 if (channel_distortion[i] > distortion[i])
1013 distortion[i]=channel_distortion[i];
1015 reconstruct_view=DestroyCacheView(reconstruct_view);
1016 image_view=DestroyCacheView(image_view);
1020 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1021 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1029 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1030 for (i=0; i <= MaxPixelChannels; i++)
1031 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1035 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1036 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1044 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1045 for (i=0; i <= MaxPixelChannels; i++)
1046 distortion[i]=sqrt(distortion[i]);
1050 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1051 const Image *reconstruct_image,const MetricType metric,double *distortion,
1052 ExceptionInfo *exception)
1055 *channel_distortion;
1063 assert(image != (Image *) NULL);
1064 assert(image->signature == MagickSignature);
1065 if (image->debug != MagickFalse)
1066 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1067 assert(reconstruct_image != (const Image *) NULL);
1068 assert(reconstruct_image->signature == MagickSignature);
1069 assert(distortion != (double *) NULL);
1071 if (image->debug != MagickFalse)
1072 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1073 if ((reconstruct_image->columns != image->columns) ||
1074 (reconstruct_image->rows != image->rows))
1075 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1077 Get image distortion.
1079 length=MaxPixelChannels+1;
1080 channel_distortion=(double *) AcquireQuantumMemory(length,
1081 sizeof(*channel_distortion));
1082 if (channel_distortion == (double *) NULL)
1083 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1084 (void) ResetMagickMemory(channel_distortion,0,length*
1085 sizeof(*channel_distortion));
1088 case AbsoluteErrorMetric:
1090 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1094 case FuzzErrorMetric:
1096 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1100 case MeanAbsoluteErrorMetric:
1102 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1103 channel_distortion,exception);
1106 case MeanErrorPerPixelMetric:
1108 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1112 case MeanSquaredErrorMetric:
1114 status=GetMeanSquaredDistortion(image,reconstruct_image,
1115 channel_distortion,exception);
1118 case NormalizedCrossCorrelationErrorMetric:
1121 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1122 channel_distortion,exception);
1125 case PeakAbsoluteErrorMetric:
1127 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1128 channel_distortion,exception);
1131 case PeakSignalToNoiseRatioMetric:
1133 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1134 channel_distortion,exception);
1137 case RootMeanSquaredErrorMetric:
1139 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1140 channel_distortion,exception);
1144 *distortion=channel_distortion[MaxPixelChannels];
1145 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1150 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1154 % G e t I m a g e D i s t o r t i o n s %
1158 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1160 % GetImageDistrortion() compares the pixel channels of an image to a
1161 % reconstructed image and returns the specified distortion metric for each
1164 % The format of the CompareImages method is:
1166 % double *GetImageDistortions(const Image *image,
1167 % const Image *reconstruct_image,const MetricType metric,
1168 % ExceptionInfo *exception)
1170 % A description of each parameter follows:
1172 % o image: the image.
1174 % o reconstruct_image: the reconstruct image.
1176 % o metric: the metric.
1178 % o exception: return any errors or warnings in this structure.
1181 MagickExport double *GetImageDistortions(Image *image,
1182 const Image *reconstruct_image,const MetricType metric,
1183 ExceptionInfo *exception)
1186 *channel_distortion;
1194 assert(image != (Image *) NULL);
1195 assert(image->signature == MagickSignature);
1196 if (image->debug != MagickFalse)
1197 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1198 assert(reconstruct_image != (const Image *) NULL);
1199 assert(reconstruct_image->signature == MagickSignature);
1200 if (image->debug != MagickFalse)
1201 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1202 if ((reconstruct_image->columns != image->columns) ||
1203 (reconstruct_image->rows != image->rows))
1205 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1206 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1207 return((double *) NULL);
1210 Get image distortion.
1212 length=MaxPixelChannels+1UL;
1213 channel_distortion=(double *) AcquireQuantumMemory(length,
1214 sizeof(*channel_distortion));
1215 if (channel_distortion == (double *) NULL)
1216 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1217 (void) ResetMagickMemory(channel_distortion,0,length*
1218 sizeof(*channel_distortion));
1222 case AbsoluteErrorMetric:
1224 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1228 case FuzzErrorMetric:
1230 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1234 case MeanAbsoluteErrorMetric:
1236 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1237 channel_distortion,exception);
1240 case MeanErrorPerPixelMetric:
1242 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1246 case MeanSquaredErrorMetric:
1248 status=GetMeanSquaredDistortion(image,reconstruct_image,
1249 channel_distortion,exception);
1252 case NormalizedCrossCorrelationErrorMetric:
1255 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1256 channel_distortion,exception);
1259 case PeakAbsoluteErrorMetric:
1261 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1262 channel_distortion,exception);
1265 case PeakSignalToNoiseRatioMetric:
1267 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1268 channel_distortion,exception);
1271 case RootMeanSquaredErrorMetric:
1273 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1274 channel_distortion,exception);
1278 if (status == MagickFalse)
1280 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1281 return((double *) NULL);
1283 return(channel_distortion);
1287 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1291 % I s I m a g e s E q u a l %
1295 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1297 % IsImagesEqual() measures the difference between colors at each pixel
1298 % location of two images. A value other than 0 means the colors match
1299 % exactly. Otherwise an error measure is computed by summing over all
1300 % pixels in an image the distance squared in RGB space between each image
1301 % pixel and its corresponding pixel in the reconstruct image. The error
1302 % measure is assigned to these image members:
1304 % o mean_error_per_pixel: The mean error for any single pixel in
1307 % o normalized_mean_error: The normalized mean quantization error for
1308 % any single pixel in the image. This distance measure is normalized to
1309 % a range between 0 and 1. It is independent of the range of red, green,
1310 % and blue values in the image.
1312 % o normalized_maximum_error: The normalized maximum quantization
1313 % error for any single pixel in the image. This distance measure is
1314 % normalized to a range between 0 and 1. It is independent of the range
1315 % of red, green, and blue values in your image.
1317 % A small normalized mean square error, accessed as
1318 % image->normalized_mean_error, suggests the images are very similar in
1319 % spatial layout and color.
1321 % The format of the IsImagesEqual method is:
1323 % MagickBooleanType IsImagesEqual(Image *image,
1324 % const Image *reconstruct_image,ExceptionInfo *exception)
1326 % A description of each parameter follows.
1328 % o image: the image.
1330 % o reconstruct_image: the reconstruct image.
1332 % o exception: return any errors or warnings in this structure.
1335 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1336 const Image *reconstruct_image,ExceptionInfo *exception)
1349 mean_error_per_pixel;
1354 assert(image != (Image *) NULL);
1355 assert(image->signature == MagickSignature);
1356 assert(reconstruct_image != (const Image *) NULL);
1357 assert(reconstruct_image->signature == MagickSignature);
1358 if ((reconstruct_image->columns != image->columns) ||
1359 (reconstruct_image->rows != image->rows))
1360 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1363 mean_error_per_pixel=0.0;
1365 image_view=AcquireCacheView(image);
1366 reconstruct_view=AcquireCacheView(reconstruct_image);
1367 for (y=0; y < (ssize_t) image->rows; y++)
1369 register const Quantum
1376 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1377 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1379 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1381 for (x=0; x < (ssize_t) image->columns; x++)
1386 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1398 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1399 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1400 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1401 if ((traits == UndefinedPixelTrait) ||
1402 (reconstruct_traits == UndefinedPixelTrait))
1404 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1406 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1408 mean_error_per_pixel+=distance;
1409 mean_error+=distance*distance;
1410 if (distance > maximum_error)
1411 maximum_error=distance;
1414 p+=GetPixelChannels(image);
1415 q+=GetPixelChannels(reconstruct_image);
1418 reconstruct_view=DestroyCacheView(reconstruct_view);
1419 image_view=DestroyCacheView(image_view);
1420 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1421 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1423 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1424 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1429 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1433 % S i m i l a r i t y I m a g e %
1437 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1439 % SimilarityImage() compares the reference image of the image and returns the
1440 % best match offset. In addition, it returns a similarity image such that an
1441 % exact match location is completely white and if none of the pixels match,
1442 % black, otherwise some gray level in-between.
1444 % The format of the SimilarityImageImage method is:
1446 % Image *SimilarityImage(const Image *image,const Image *reference,
1447 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1449 % A description of each parameter follows:
1451 % o image: the image.
1453 % o reference: find an area of the image that closely resembles this image.
1455 % o the best match offset of the reference image within the image.
1457 % o similarity: the computed similarity between the images.
1459 % o exception: return any errors or warnings in this structure.
1463 static double GetNCCDistortion(const Image *image,
1464 const Image *reconstruct_image,
1465 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1467 #define SimilarityImageTag "Similarity/Image"
1490 Normalize to account for variation due to lighting and exposure condition.
1492 image_statistics=GetImageStatistics(image,exception);
1495 area=1.0/((MagickRealType) image->columns*image->rows);
1496 image_view=AcquireCacheView(image);
1497 reconstruct_view=AcquireCacheView(reconstruct_image);
1498 for (y=0; y < (ssize_t) image->rows; y++)
1500 register const Quantum
1507 if (status == MagickFalse)
1509 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1510 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1512 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1517 for (x=0; x < (ssize_t) image->columns; x++)
1522 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1531 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1532 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1533 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1534 if ((traits == UndefinedPixelTrait) ||
1535 (reconstruct_traits == UndefinedPixelTrait))
1537 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1539 distortion+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
1540 (GetPixelChannel(reconstruct_image,channel,q)-
1541 reconstruct_statistics[channel].mean);
1543 p+=GetPixelChannels(image);
1544 q+=GetPixelChannels(reconstruct_image);
1547 reconstruct_view=DestroyCacheView(reconstruct_view);
1548 image_view=DestroyCacheView(image_view);
1550 Divide by the standard deviation.
1552 gamma=image_statistics[MaxPixelChannels].standard_deviation*
1553 reconstruct_statistics[MaxPixelChannels].standard_deviation;
1554 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1555 distortion=QuantumRange*gamma*distortion;
1556 distortion=sqrt(distortion/GetImageChannels(image));
1560 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1562 return(1.0-distortion);
1565 static double GetSimilarityMetric(const Image *image,const Image *reference,
1566 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1567 const ssize_t y_offset,ExceptionInfo *exception)
1578 SetGeometry(reference,&geometry);
1579 geometry.x=x_offset;
1580 geometry.y=y_offset;
1581 similarity_image=CropImage(image,&geometry,exception);
1582 if (similarity_image == (Image *) NULL)
1584 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1586 similarity_image=DestroyImage(similarity_image);
1590 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1591 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1593 #define SimilarityImageTag "Similarity/Image"
1599 *reference_statistics;
1613 assert(image != (const Image *) NULL);
1614 assert(image->signature == MagickSignature);
1615 if (image->debug != MagickFalse)
1616 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1617 assert(exception != (ExceptionInfo *) NULL);
1618 assert(exception->signature == MagickSignature);
1619 assert(offset != (RectangleInfo *) NULL);
1620 SetGeometry(reference,offset);
1621 *similarity_metric=1.0;
1622 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1623 ThrowImageException(ImageError,"ImageSizeDiffers");
1624 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1625 image->rows-reference->rows+1,MagickTrue,exception);
1626 if (similarity_image == (Image *) NULL)
1627 return((Image *) NULL);
1628 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1629 if (status == MagickFalse)
1631 similarity_image=DestroyImage(similarity_image);
1632 return((Image *) NULL);
1635 Measure similarity of reference image against image.
1639 reference_statistics=GetImageStatistics(reference,exception);
1640 similarity_view=AcquireCacheView(similarity_image);
1641 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1642 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1644 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1655 if (status == MagickFalse)
1657 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1659 if (q == (Quantum *) NULL)
1664 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1669 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1671 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1672 #pragma omp critical (MagickCore_SimilarityImage)
1674 if (similarity < *similarity_metric)
1676 *similarity_metric=similarity;
1680 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1689 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1690 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1691 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1692 if ((traits == UndefinedPixelTrait) ||
1693 (similarity_traits == UndefinedPixelTrait))
1695 if ((similarity_traits & UpdatePixelTrait) == 0)
1697 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1698 QuantumRange*similarity),q);
1700 q+=GetPixelChannels(similarity_image);
1702 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1704 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1709 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1710 #pragma omp critical (MagickCore_SimilarityImage)
1712 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1714 if (proceed == MagickFalse)
1718 similarity_view=DestroyCacheView(similarity_view);
1719 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1720 reference_statistics);
1721 return(similarity_image);