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) QueryMagickColor("#f1001ecc",&highlight,exception);
165 artifact=GetImageArtifact(image,"highlight-color");
166 if (artifact != (const char *) NULL)
167 (void) QueryMagickColor(artifact,&highlight,exception);
168 (void) QueryMagickColor("#ffffffcc",&lowlight,exception);
169 artifact=GetImageArtifact(image,"lowlight-color");
170 if (artifact != (const char *) NULL)
171 (void) QueryMagickColor(artifact,&lowlight,exception);
172 if (highlight_image->colorspace == CMYKColorspace)
174 ConvertRGBToCMYK(&highlight);
175 ConvertRGBToCMYK(&lowlight);
178 Generate difference image.
181 image_view=AcquireCacheView(image);
182 reconstruct_view=AcquireCacheView(reconstruct_image);
183 highlight_view=AcquireCacheView(highlight_image);
184 #if defined(MAGICKCORE_OPENMP_SUPPORT)
185 #pragma omp parallel for schedule(dynamic,4) shared(status)
187 for (y=0; y < (ssize_t) image->rows; y++)
192 register const Quantum
202 if (status == MagickFalse)
204 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
205 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
207 r=QueueCacheViewAuthenticPixels(highlight_view,0,y,highlight_image->columns,
209 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL) ||
210 (r == (Quantum *) NULL))
215 for (x=0; x < (ssize_t) image->columns; x++)
223 difference=MagickFalse;
224 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
233 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
234 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
235 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
236 if ((traits == UndefinedPixelTrait) ||
237 (reconstruct_traits == UndefinedPixelTrait))
239 if (fabs(p[i]-(double) q[channel]) >= MagickEpsilon)
240 difference=MagickTrue;
242 if (difference == MagickFalse)
243 SetPixelPixelInfo(highlight_image,&lowlight,r);
245 SetPixelPixelInfo(highlight_image,&highlight,r);
246 p+=GetPixelChannels(image);
247 q+=GetPixelChannels(reconstruct_image);
248 r+=GetPixelChannels(highlight_image);
250 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
251 if (sync == MagickFalse)
254 highlight_view=DestroyCacheView(highlight_view);
255 reconstruct_view=DestroyCacheView(reconstruct_view);
256 image_view=DestroyCacheView(image_view);
257 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
258 highlight_image=DestroyImage(highlight_image);
259 if (status == MagickFalse)
260 difference_image=DestroyImage(difference_image);
261 return(difference_image);
265 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
269 % G e t I m a g e D i s t o r t i o n %
273 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
275 % GetImageDistortion() compares one or more pixel channels of an image to a
276 % reconstructed image and returns the specified distortion metric.
278 % The format of the CompareImages method is:
280 % MagickBooleanType GetImageDistortion(const Image *image,
281 % const Image *reconstruct_image,const MetricType metric,
282 % double *distortion,ExceptionInfo *exception)
284 % A description of each parameter follows:
286 % o image: the image.
288 % o reconstruct_image: the reconstruct image.
290 % o metric: the metric.
292 % o distortion: the computed distortion between the images.
294 % o exception: return any errors or warnings in this structure.
298 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
299 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
312 Compute the absolute difference in pixels between two images.
315 image_view=AcquireCacheView(image);
316 reconstruct_view=AcquireCacheView(reconstruct_image);
317 #if defined(MAGICKCORE_OPENMP_SUPPORT)
318 #pragma omp parallel for schedule(dynamic,4) shared(status)
320 for (y=0; y < (ssize_t) image->rows; y++)
323 channel_distortion[MaxPixelChannels+1];
325 register const Quantum
333 if (status == MagickFalse)
335 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
336 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
338 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
343 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
344 for (x=0; x < (ssize_t) image->columns; x++)
352 difference=MagickFalse;
353 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
362 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
363 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
364 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
365 if ((traits == UndefinedPixelTrait) ||
366 (reconstruct_traits == UndefinedPixelTrait))
368 if (p[i] != q[channel])
369 difference=MagickTrue;
371 if (difference != MagickFalse)
373 channel_distortion[i]++;
374 channel_distortion[MaxPixelChannels]++;
376 p+=GetPixelChannels(image);
377 q+=GetPixelChannels(reconstruct_image);
379 #if defined(MAGICKCORE_OPENMP_SUPPORT)
380 #pragma omp critical (MagickCore_GetAbsoluteError)
382 for (i=0; i <= MaxPixelChannels; i++)
383 distortion[i]+=channel_distortion[i];
385 reconstruct_view=DestroyCacheView(reconstruct_view);
386 image_view=DestroyCacheView(image_view);
390 static size_t GetNumberChannels(const Image *image)
399 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
404 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
405 if (traits != UndefinedPixelTrait)
411 static MagickBooleanType GetFuzzDistortion(const Image *image,
412 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
428 image_view=AcquireCacheView(image);
429 reconstruct_view=AcquireCacheView(reconstruct_image);
430 #if defined(MAGICKCORE_OPENMP_SUPPORT)
431 #pragma omp parallel for schedule(dynamic,4) shared(status)
433 for (y=0; y < (ssize_t) image->rows; y++)
436 channel_distortion[MaxPixelChannels+1];
438 register const Quantum
446 if (status == MagickFalse)
448 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
449 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
451 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
456 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
457 for (x=0; x < (ssize_t) image->columns; x++)
462 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
474 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
475 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
476 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
477 if ((traits == UndefinedPixelTrait) ||
478 (reconstruct_traits == UndefinedPixelTrait))
480 distance=QuantumScale*(p[i]-(MagickRealType) q[channel]);
482 channel_distortion[i]+=distance;
483 channel_distortion[MaxPixelChannels]+=distance;
485 p+=GetPixelChannels(image);
486 q+=GetPixelChannels(reconstruct_image);
488 #if defined(MAGICKCORE_OPENMP_SUPPORT)
489 #pragma omp critical (MagickCore_GetMeanSquaredError)
491 for (i=0; i <= MaxPixelChannels; i++)
492 distortion[i]+=channel_distortion[i];
494 reconstruct_view=DestroyCacheView(reconstruct_view);
495 image_view=DestroyCacheView(image_view);
496 for (i=0; i <= MaxPixelChannels; i++)
497 distortion[i]/=((double) image->columns*image->rows);
498 distortion[MaxPixelChannels]/=(double) GetNumberChannels(image);
499 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]);
503 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
504 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
520 image_view=AcquireCacheView(image);
521 reconstruct_view=AcquireCacheView(reconstruct_image);
522 #if defined(MAGICKCORE_OPENMP_SUPPORT)
523 #pragma omp parallel for schedule(dynamic,4) shared(status)
525 for (y=0; y < (ssize_t) image->rows; y++)
528 channel_distortion[MaxPixelChannels+1];
530 register const Quantum
538 if (status == MagickFalse)
540 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
541 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
543 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
548 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
549 for (x=0; x < (ssize_t) image->columns; x++)
554 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
566 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
567 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
568 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
569 if ((traits == UndefinedPixelTrait) ||
570 (reconstruct_traits == UndefinedPixelTrait))
572 distance=QuantumScale*fabs(p[i]-(double) q[channel]);
573 channel_distortion[i]+=distance;
574 channel_distortion[MaxPixelChannels]+=distance;
576 p+=GetPixelChannels(image);
577 q+=GetPixelChannels(reconstruct_image);
579 #if defined(MAGICKCORE_OPENMP_SUPPORT)
580 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
582 for (i=0; i <= MaxPixelChannels; i++)
583 distortion[i]+=channel_distortion[i];
585 reconstruct_view=DestroyCacheView(reconstruct_view);
586 image_view=DestroyCacheView(image_view);
587 for (i=0; i <= MaxPixelChannels; i++)
588 distortion[i]/=((double) image->columns*image->rows);
589 distortion[MaxPixelChannels]/=(double) GetNumberChannels(image);
593 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
594 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
619 image_view=AcquireCacheView(image);
620 reconstruct_view=AcquireCacheView(reconstruct_image);
621 for (y=0; y < (ssize_t) image->rows; y++)
623 register const Quantum
630 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
631 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
633 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
638 for (x=0; x < (ssize_t) image->columns; x++)
643 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
655 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
656 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
657 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
658 if ((traits == UndefinedPixelTrait) ||
659 (reconstruct_traits == UndefinedPixelTrait))
661 distance=fabs(alpha*p[i]-beta*q[channel]);
662 distortion[i]+=distance;
663 distortion[MaxPixelChannels]+=distance;
664 mean_error+=distance*distance;
665 if (distance > maximum_error)
666 maximum_error=distance;
669 p+=GetPixelChannels(image);
670 q+=GetPixelChannels(reconstruct_image);
673 reconstruct_view=DestroyCacheView(reconstruct_view);
674 image_view=DestroyCacheView(image_view);
675 image->error.mean_error_per_pixel=distortion[MaxPixelChannels]/area;
676 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
677 image->error.normalized_maximum_error=QuantumScale*maximum_error;
681 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
682 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
698 image_view=AcquireCacheView(image);
699 reconstruct_view=AcquireCacheView(reconstruct_image);
700 #if defined(MAGICKCORE_OPENMP_SUPPORT)
701 #pragma omp parallel for schedule(dynamic,4) shared(status)
703 for (y=0; y < (ssize_t) image->rows; y++)
706 channel_distortion[MaxPixelChannels+1];
708 register const Quantum
716 if (status == MagickFalse)
718 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
719 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
721 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
726 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
727 for (x=0; x < (ssize_t) image->columns; x++)
732 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
744 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
745 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
746 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
747 if ((traits == UndefinedPixelTrait) ||
748 (reconstruct_traits == UndefinedPixelTrait))
750 distance=QuantumScale*(p[i]-(MagickRealType) q[channel]);
752 channel_distortion[i]+=distance;
753 channel_distortion[MaxPixelChannels]+=distance;
755 p+=GetPixelChannels(image);
756 q+=GetPixelChannels(reconstruct_image);
758 #if defined(MAGICKCORE_OPENMP_SUPPORT)
759 #pragma omp critical (MagickCore_GetMeanSquaredError)
761 for (i=0; i <= MaxPixelChannels; i++)
762 distortion[i]+=channel_distortion[i];
764 reconstruct_view=DestroyCacheView(reconstruct_view);
765 image_view=DestroyCacheView(image_view);
766 for (i=0; i <= MaxPixelChannels; i++)
767 distortion[i]/=((double) image->columns*image->rows);
768 distortion[MaxPixelChannels]/=GetNumberChannels(image);
772 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
773 const Image *image,const Image *reconstruct_image,double *distortion,
774 ExceptionInfo *exception)
776 #define SimilarityImageTag "Similarity/Image"
784 *reconstruct_statistics;
802 Normalize to account for variation due to lighting and exposure condition.
804 image_statistics=GetImageStatistics(image,exception);
805 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
808 for (i=0; i <= MaxPixelChannels; i++)
810 area=1.0/((MagickRealType) image->columns*image->rows);
811 image_view=AcquireCacheView(image);
812 reconstruct_view=AcquireCacheView(reconstruct_image);
813 for (y=0; y < (ssize_t) image->rows; y++)
815 register const Quantum
822 if (status == MagickFalse)
824 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
825 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
827 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
832 for (x=0; x < (ssize_t) image->columns; x++)
837 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
846 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
847 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
848 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
849 if ((traits == UndefinedPixelTrait) ||
850 (reconstruct_traits == UndefinedPixelTrait))
852 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
853 (q[channel]-reconstruct_statistics[channel].mean);
855 p+=GetPixelChannels(image);
856 q+=GetPixelChannels(image);
858 if (image->progress_monitor != (MagickProgressMonitor) NULL)
863 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
865 if (proceed == MagickFalse)
869 reconstruct_view=DestroyCacheView(reconstruct_view);
870 image_view=DestroyCacheView(image_view);
872 Divide by the standard deviation.
874 distortion[MaxPixelChannels]=0.0;
875 for (i=0; i < MaxPixelChannels; i++)
883 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
884 gamma=image_statistics[i].standard_deviation*
885 reconstruct_statistics[channel].standard_deviation;
886 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
887 distortion[i]=QuantumRange*gamma*distortion[i];
888 distortion[MaxPixelChannels]+=distortion[i]*distortion[i];
890 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]/
891 GetNumberChannels(image));
895 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
896 reconstruct_statistics);
897 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
902 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
903 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
916 image_view=AcquireCacheView(image);
917 reconstruct_view=AcquireCacheView(reconstruct_image);
918 #if defined(MAGICKCORE_OPENMP_SUPPORT)
919 #pragma omp parallel for schedule(dynamic,4) shared(status)
921 for (y=0; y < (ssize_t) image->rows; y++)
924 channel_distortion[MaxPixelChannels+1];
926 register const Quantum
934 if (status == MagickFalse)
936 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
937 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
938 reconstruct_image->columns,1,exception);
939 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
944 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
945 for (x=0; x < (ssize_t) image->columns; x++)
950 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
962 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
963 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
964 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
965 if ((traits == UndefinedPixelTrait) ||
966 (reconstruct_traits == UndefinedPixelTrait))
968 distance=QuantumScale*fabs(p[i]-(double) q[channel]);
969 if (distance > channel_distortion[RedChannel])
970 channel_distortion[i]=distance;
971 if (distance > channel_distortion[MaxPixelChannels])
972 channel_distortion[MaxPixelChannels]=distance;
974 p+=GetPixelChannels(image);
975 q+=GetPixelChannels(image);
977 #if defined(MAGICKCORE_OPENMP_SUPPORT)
978 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
980 for (i=0; i <= MaxPixelChannels; i++)
981 if (channel_distortion[i] > distortion[i])
982 distortion[i]=channel_distortion[i];
984 reconstruct_view=DestroyCacheView(reconstruct_view);
985 image_view=DestroyCacheView(image_view);
989 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
990 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
998 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
999 for (i=0; i <= MaxPixelChannels; i++)
1000 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1004 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1005 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1013 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1014 for (i=0; i <= MaxPixelChannels; i++)
1015 distortion[i]=sqrt(distortion[i]);
1019 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1020 const Image *reconstruct_image,const MetricType metric,double *distortion,
1021 ExceptionInfo *exception)
1024 *channel_distortion;
1032 assert(image != (Image *) NULL);
1033 assert(image->signature == MagickSignature);
1034 if (image->debug != MagickFalse)
1035 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1036 assert(reconstruct_image != (const Image *) NULL);
1037 assert(reconstruct_image->signature == MagickSignature);
1038 assert(distortion != (double *) NULL);
1040 if (image->debug != MagickFalse)
1041 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1042 if ((reconstruct_image->columns != image->columns) ||
1043 (reconstruct_image->rows != image->rows))
1044 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1046 Get image distortion.
1048 length=MaxPixelChannels+1;
1049 channel_distortion=(double *) AcquireQuantumMemory(length,
1050 sizeof(*channel_distortion));
1051 if (channel_distortion == (double *) NULL)
1052 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1053 (void) ResetMagickMemory(channel_distortion,0,length*
1054 sizeof(*channel_distortion));
1057 case AbsoluteErrorMetric:
1059 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1063 case FuzzErrorMetric:
1065 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1069 case MeanAbsoluteErrorMetric:
1071 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1072 channel_distortion,exception);
1075 case MeanErrorPerPixelMetric:
1077 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1081 case MeanSquaredErrorMetric:
1083 status=GetMeanSquaredDistortion(image,reconstruct_image,
1084 channel_distortion,exception);
1087 case NormalizedCrossCorrelationErrorMetric:
1090 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1091 channel_distortion,exception);
1094 case PeakAbsoluteErrorMetric:
1096 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1097 channel_distortion,exception);
1100 case PeakSignalToNoiseRatioMetric:
1102 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1103 channel_distortion,exception);
1106 case RootMeanSquaredErrorMetric:
1108 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1109 channel_distortion,exception);
1113 *distortion=channel_distortion[MaxPixelChannels];
1114 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1119 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1123 % G e t I m a g e D i s t o r t i o n s %
1127 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1129 % GetImageDistrortion() compares the pixel channels of an image to a
1130 % reconstructed image and returns the specified distortion metric for each
1133 % The format of the CompareImages method is:
1135 % double *GetImageDistortions(const Image *image,
1136 % const Image *reconstruct_image,const MetricType metric,
1137 % ExceptionInfo *exception)
1139 % A description of each parameter follows:
1141 % o image: the image.
1143 % o reconstruct_image: the reconstruct image.
1145 % o metric: the metric.
1147 % o exception: return any errors or warnings in this structure.
1150 MagickExport double *GetImageDistortions(Image *image,
1151 const Image *reconstruct_image,const MetricType metric,
1152 ExceptionInfo *exception)
1155 *channel_distortion;
1163 assert(image != (Image *) NULL);
1164 assert(image->signature == MagickSignature);
1165 if (image->debug != MagickFalse)
1166 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1167 assert(reconstruct_image != (const Image *) NULL);
1168 assert(reconstruct_image->signature == MagickSignature);
1169 if (image->debug != MagickFalse)
1170 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1171 if ((reconstruct_image->columns != image->columns) ||
1172 (reconstruct_image->rows != image->rows))
1174 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1175 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1176 return((double *) NULL);
1179 Get image distortion.
1181 length=MaxPixelChannels+1UL;
1182 channel_distortion=(double *) AcquireQuantumMemory(length,
1183 sizeof(*channel_distortion));
1184 if (channel_distortion == (double *) NULL)
1185 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1186 (void) ResetMagickMemory(channel_distortion,0,length*
1187 sizeof(*channel_distortion));
1191 case AbsoluteErrorMetric:
1193 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1197 case FuzzErrorMetric:
1199 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1203 case MeanAbsoluteErrorMetric:
1205 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1206 channel_distortion,exception);
1209 case MeanErrorPerPixelMetric:
1211 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1215 case MeanSquaredErrorMetric:
1217 status=GetMeanSquaredDistortion(image,reconstruct_image,
1218 channel_distortion,exception);
1221 case NormalizedCrossCorrelationErrorMetric:
1224 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1225 channel_distortion,exception);
1228 case PeakAbsoluteErrorMetric:
1230 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1231 channel_distortion,exception);
1234 case PeakSignalToNoiseRatioMetric:
1236 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1237 channel_distortion,exception);
1240 case RootMeanSquaredErrorMetric:
1242 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1243 channel_distortion,exception);
1247 if (status == MagickFalse)
1249 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1250 return((double *) NULL);
1252 return(channel_distortion);
1256 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1260 % I s I m a g e s E q u a l %
1264 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1266 % IsImagesEqual() measures the difference between colors at each pixel
1267 % location of two images. A value other than 0 means the colors match
1268 % exactly. Otherwise an error measure is computed by summing over all
1269 % pixels in an image the distance squared in RGB space between each image
1270 % pixel and its corresponding pixel in the reconstruct image. The error
1271 % measure is assigned to these image members:
1273 % o mean_error_per_pixel: The mean error for any single pixel in
1276 % o normalized_mean_error: The normalized mean quantization error for
1277 % any single pixel in the image. This distance measure is normalized to
1278 % a range between 0 and 1. It is independent of the range of red, green,
1279 % and blue values in the image.
1281 % o normalized_maximum_error: The normalized maximum quantization
1282 % error for any single pixel in the image. This distance measure is
1283 % normalized to a range between 0 and 1. It is independent of the range
1284 % of red, green, and blue values in your image.
1286 % A small normalized mean square error, accessed as
1287 % image->normalized_mean_error, suggests the images are very similar in
1288 % spatial layout and color.
1290 % The format of the IsImagesEqual method is:
1292 % MagickBooleanType IsImagesEqual(Image *image,
1293 % const Image *reconstruct_image,ExceptionInfo *exception)
1295 % A description of each parameter follows.
1297 % o image: the image.
1299 % o reconstruct_image: the reconstruct image.
1301 % o exception: return any errors or warnings in this structure.
1304 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1305 const Image *reconstruct_image,ExceptionInfo *exception)
1318 mean_error_per_pixel;
1323 assert(image != (Image *) NULL);
1324 assert(image->signature == MagickSignature);
1325 assert(reconstruct_image != (const Image *) NULL);
1326 assert(reconstruct_image->signature == MagickSignature);
1327 if ((reconstruct_image->columns != image->columns) ||
1328 (reconstruct_image->rows != image->rows))
1329 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1332 mean_error_per_pixel=0.0;
1334 image_view=AcquireCacheView(image);
1335 reconstruct_view=AcquireCacheView(reconstruct_image);
1336 for (y=0; y < (ssize_t) image->rows; y++)
1338 register const Quantum
1345 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1346 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1348 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1350 for (x=0; x < (ssize_t) image->columns; x++)
1355 distance=fabs(GetPixelRed(image,p)-(double)
1356 GetPixelRed(reconstruct_image,q));
1357 mean_error_per_pixel+=distance;
1358 mean_error+=distance*distance;
1359 if (distance > maximum_error)
1360 maximum_error=distance;
1362 distance=fabs(GetPixelGreen(image,p)-(double)
1363 GetPixelGreen(reconstruct_image,q));
1364 mean_error_per_pixel+=distance;
1365 mean_error+=distance*distance;
1366 if (distance > maximum_error)
1367 maximum_error=distance;
1369 distance=fabs(GetPixelBlue(image,p)-(double)
1370 GetPixelBlue(reconstruct_image,q));
1371 mean_error_per_pixel+=distance;
1372 mean_error+=distance*distance;
1373 if (distance > maximum_error)
1374 maximum_error=distance;
1376 if (image->matte != MagickFalse)
1378 distance=fabs(GetPixelAlpha(image,p)-(double)
1379 GetPixelAlpha(reconstruct_image,q));
1380 mean_error_per_pixel+=distance;
1381 mean_error+=distance*distance;
1382 if (distance > maximum_error)
1383 maximum_error=distance;
1386 if ((image->colorspace == CMYKColorspace) &&
1387 (reconstruct_image->colorspace == CMYKColorspace))
1389 distance=fabs(GetPixelBlack(image,p)-(double)
1390 GetPixelBlack(reconstruct_image,q));
1391 mean_error_per_pixel+=distance;
1392 mean_error+=distance*distance;
1393 if (distance > maximum_error)
1394 maximum_error=distance;
1397 p+=GetPixelChannels(image);
1398 q+=GetPixelChannels(reconstruct_image);
1401 reconstruct_view=DestroyCacheView(reconstruct_view);
1402 image_view=DestroyCacheView(image_view);
1403 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1404 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1406 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1407 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1412 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1416 % S i m i l a r i t y I m a g e %
1420 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1422 % SimilarityImage() compares the reference image of the image and returns the
1423 % best match offset. In addition, it returns a similarity image such that an
1424 % exact match location is completely white and if none of the pixels match,
1425 % black, otherwise some gray level in-between.
1427 % The format of the SimilarityImageImage method is:
1429 % Image *SimilarityImage(const Image *image,const Image *reference,
1430 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1432 % A description of each parameter follows:
1434 % o image: the image.
1436 % o reference: find an area of the image that closely resembles this image.
1438 % o the best match offset of the reference image within the image.
1440 % o similarity: the computed similarity between the images.
1442 % o exception: return any errors or warnings in this structure.
1446 static double GetNCCDistortion(const Image *image,
1447 const Image *reconstruct_image,
1448 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1450 #define SimilarityImageTag "Similarity/Image"
1476 Normalize to account for variation due to lighting and exposure condition.
1478 image_statistics=GetImageStatistics(image,exception);
1481 area=1.0/((MagickRealType) image->columns*image->rows);
1482 image_view=AcquireCacheView(image);
1483 reconstruct_view=AcquireCacheView(reconstruct_image);
1484 for (y=0; y < (ssize_t) image->rows; y++)
1486 register const Quantum
1493 if (status == MagickFalse)
1495 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1496 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1498 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1503 for (x=0; x < (ssize_t) image->columns; x++)
1505 distortion+=area*QuantumScale*(GetPixelRed(image,p)-
1506 image_statistics[RedChannel].mean)*(
1507 GetPixelRed(reconstruct_image,q)-
1508 reconstruct_statistics[RedChannel].mean);
1509 distortion+=area*QuantumScale*(GetPixelGreen(image,p)-
1510 image_statistics[GreenChannel].mean)*(
1511 GetPixelGreen(reconstruct_image,q)-
1512 reconstruct_statistics[GreenChannel].mean);
1513 distortion+=area*QuantumScale*(GetPixelBlue(image,p)-
1514 GetPixelBlue(reconstruct_image,q)-
1515 image_statistics[BlueChannel].mean)*(
1516 reconstruct_statistics[BlueChannel].mean);
1517 if ((image->colorspace == CMYKColorspace) &&
1518 (reconstruct_image->colorspace == CMYKColorspace))
1519 distortion+=area*QuantumScale*(GetPixelBlack(image,p)-
1520 image_statistics[BlackChannel].mean)*(
1521 GetPixelBlack(reconstruct_image,q)-
1522 reconstruct_statistics[BlackChannel].mean);
1523 if (image->matte != MagickFalse)
1524 distortion+=area*QuantumScale*(GetPixelAlpha(image,p)-
1525 image_statistics[OpacityChannel].mean)*(
1526 GetPixelAlpha(reconstruct_image,q)-
1527 reconstruct_statistics[OpacityChannel].mean);
1528 p+=GetPixelChannels(image);
1529 q+=GetPixelChannels(reconstruct_image);
1532 reconstruct_view=DestroyCacheView(reconstruct_view);
1533 image_view=DestroyCacheView(image_view);
1535 Divide by the standard deviation.
1537 gamma=image_statistics[MaxPixelChannels].standard_deviation*
1538 reconstruct_statistics[MaxPixelChannels].standard_deviation;
1539 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1540 distortion=QuantumRange*gamma*distortion;
1542 if (image->matte != MagickFalse)
1544 if (image->colorspace == CMYKColorspace)
1546 distortion=sqrt(distortion/number_channels);
1550 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1552 return(1.0-distortion);
1555 static double GetSimilarityMetric(const Image *image,const Image *reference,
1556 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1557 const ssize_t y_offset,ExceptionInfo *exception)
1568 SetGeometry(reference,&geometry);
1569 geometry.x=x_offset;
1570 geometry.y=y_offset;
1571 similarity_image=CropImage(image,&geometry,exception);
1572 if (similarity_image == (Image *) NULL)
1574 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1576 similarity_image=DestroyImage(similarity_image);
1580 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1581 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1583 #define SimilarityImageTag "Similarity/Image"
1589 *reference_statistics;
1603 assert(image != (const Image *) NULL);
1604 assert(image->signature == MagickSignature);
1605 if (image->debug != MagickFalse)
1606 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1607 assert(exception != (ExceptionInfo *) NULL);
1608 assert(exception->signature == MagickSignature);
1609 assert(offset != (RectangleInfo *) NULL);
1610 SetGeometry(reference,offset);
1611 *similarity_metric=1.0;
1612 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1613 ThrowImageException(ImageError,"ImageSizeDiffers");
1614 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1615 image->rows-reference->rows+1,MagickTrue,exception);
1616 if (similarity_image == (Image *) NULL)
1617 return((Image *) NULL);
1618 if (SetImageStorageClass(similarity_image,DirectClass,exception) == MagickFalse)
1620 similarity_image=DestroyImage(similarity_image);
1621 return((Image *) NULL);
1624 Measure similarity of reference image against image.
1628 reference_statistics=GetImageStatistics(reference,exception);
1629 similarity_view=AcquireCacheView(similarity_image);
1630 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1631 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1633 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1644 if (status == MagickFalse)
1646 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1648 if (q == (Quantum *) NULL)
1653 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1655 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1657 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1658 #pragma omp critical (MagickCore_SimilarityImage)
1660 if (similarity < *similarity_metric)
1662 *similarity_metric=similarity;
1666 SetPixelRed(similarity_image,ClampToQuantum(QuantumRange-
1667 QuantumRange*similarity),q);
1668 SetPixelGreen(similarity_image,GetPixelRed(image,q),q);
1669 SetPixelBlue(similarity_image,GetPixelRed(image,q),q);
1670 q+=GetPixelChannels(similarity_image);
1672 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1674 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1679 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1680 #pragma omp critical (MagickCore_SimilarityImage)
1682 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1684 if (proceed == MagickFalse)
1688 similarity_view=DestroyCacheView(similarity_view);
1689 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1690 reference_statistics);
1691 return(similarity_image);