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++)
236 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
237 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
238 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
239 if ((traits == UndefinedPixelTrait) ||
240 (reconstruct_traits == UndefinedPixelTrait))
242 if ((reconstruct_traits & UpdatePixelTrait) == 0)
244 distance=p[i]-(MagickRealType)
245 GetPixelChannel(reconstruct_image,channel,q);
246 if (fabs((double) distance) >= MagickEpsilon)
247 difference=MagickTrue;
249 if (difference == MagickFalse)
250 SetPixelPixelInfo(highlight_image,&lowlight,r);
252 SetPixelPixelInfo(highlight_image,&highlight,r);
253 p+=GetPixelChannels(image);
254 q+=GetPixelChannels(reconstruct_image);
255 r+=GetPixelChannels(highlight_image);
257 sync=SyncCacheViewAuthenticPixels(highlight_view,exception);
258 if (sync == MagickFalse)
261 highlight_view=DestroyCacheView(highlight_view);
262 reconstruct_view=DestroyCacheView(reconstruct_view);
263 image_view=DestroyCacheView(image_view);
264 (void) CompositeImage(difference_image,image->compose,highlight_image,0,0);
265 highlight_image=DestroyImage(highlight_image);
266 if (status == MagickFalse)
267 difference_image=DestroyImage(difference_image);
268 return(difference_image);
272 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
276 % G e t I m a g e D i s t o r t i o n %
280 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
282 % GetImageDistortion() compares one or more pixel channels of an image to a
283 % reconstructed image and returns the specified distortion metric.
285 % The format of the CompareImages method is:
287 % MagickBooleanType GetImageDistortion(const Image *image,
288 % const Image *reconstruct_image,const MetricType metric,
289 % double *distortion,ExceptionInfo *exception)
291 % A description of each parameter follows:
293 % o image: the image.
295 % o reconstruct_image: the reconstruct image.
297 % o metric: the metric.
299 % o distortion: the computed distortion between the images.
301 % o exception: return any errors or warnings in this structure.
305 static MagickBooleanType GetAbsoluteDistortion(const Image *image,
306 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
319 Compute the absolute difference in pixels between two images.
322 image_view=AcquireCacheView(image);
323 reconstruct_view=AcquireCacheView(reconstruct_image);
324 #if defined(MAGICKCORE_OPENMP_SUPPORT)
325 #pragma omp parallel for schedule(dynamic,4) shared(status)
327 for (y=0; y < (ssize_t) image->rows; y++)
330 channel_distortion[MaxPixelChannels+1];
332 register const Quantum
340 if (status == MagickFalse)
342 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
343 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
345 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
350 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
351 for (x=0; x < (ssize_t) image->columns; x++)
359 difference=MagickFalse;
360 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
369 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
370 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
371 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
372 if ((traits == UndefinedPixelTrait) ||
373 (reconstruct_traits == UndefinedPixelTrait))
375 if ((reconstruct_traits & UpdatePixelTrait) == 0)
377 if (p[i] != GetPixelChannel(reconstruct_image,channel,q))
378 difference=MagickTrue;
380 if (difference != MagickFalse)
382 channel_distortion[i]++;
383 channel_distortion[MaxPixelChannels]++;
385 p+=GetPixelChannels(image);
386 q+=GetPixelChannels(reconstruct_image);
388 #if defined(MAGICKCORE_OPENMP_SUPPORT)
389 #pragma omp critical (MagickCore_GetAbsoluteError)
391 for (i=0; i <= MaxPixelChannels; i++)
392 distortion[i]+=channel_distortion[i];
394 reconstruct_view=DestroyCacheView(reconstruct_view);
395 image_view=DestroyCacheView(image_view);
399 static size_t GetImageChannels(const Image *image)
408 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
413 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
414 if ((traits & UpdatePixelTrait) != 0)
420 static MagickBooleanType GetFuzzDistortion(const Image *image,
421 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
437 image_view=AcquireCacheView(image);
438 reconstruct_view=AcquireCacheView(reconstruct_image);
439 #if defined(MAGICKCORE_OPENMP_SUPPORT)
440 #pragma omp parallel for schedule(dynamic,4) shared(status)
442 for (y=0; y < (ssize_t) image->rows; y++)
445 channel_distortion[MaxPixelChannels+1];
447 register const Quantum
455 if (status == MagickFalse)
457 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
458 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
460 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
465 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
466 for (x=0; x < (ssize_t) image->columns; x++)
471 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
483 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
484 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
485 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
486 if ((traits == UndefinedPixelTrait) ||
487 (reconstruct_traits == UndefinedPixelTrait))
489 if ((reconstruct_traits & UpdatePixelTrait) == 0)
491 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
492 reconstruct_image,channel,q));
494 channel_distortion[i]+=distance;
495 channel_distortion[MaxPixelChannels]+=distance;
497 p+=GetPixelChannels(image);
498 q+=GetPixelChannels(reconstruct_image);
500 #if defined(MAGICKCORE_OPENMP_SUPPORT)
501 #pragma omp critical (MagickCore_GetMeanSquaredError)
503 for (i=0; i <= MaxPixelChannels; i++)
504 distortion[i]+=channel_distortion[i];
506 reconstruct_view=DestroyCacheView(reconstruct_view);
507 image_view=DestroyCacheView(image_view);
508 for (i=0; i <= MaxPixelChannels; i++)
509 distortion[i]/=((double) image->columns*image->rows);
510 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
511 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]);
515 static MagickBooleanType GetMeanAbsoluteDistortion(const Image *image,
516 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
532 image_view=AcquireCacheView(image);
533 reconstruct_view=AcquireCacheView(reconstruct_image);
534 #if defined(MAGICKCORE_OPENMP_SUPPORT)
535 #pragma omp parallel for schedule(dynamic,4) shared(status)
537 for (y=0; y < (ssize_t) image->rows; y++)
540 channel_distortion[MaxPixelChannels+1];
542 register const Quantum
550 if (status == MagickFalse)
552 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
553 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
555 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
560 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
561 for (x=0; x < (ssize_t) image->columns; x++)
566 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
578 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
579 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
580 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
581 if ((traits == UndefinedPixelTrait) ||
582 (reconstruct_traits == UndefinedPixelTrait))
584 if ((reconstruct_traits & UpdatePixelTrait) == 0)
586 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
587 reconstruct_image,channel,q));
588 channel_distortion[i]+=distance;
589 channel_distortion[MaxPixelChannels]+=distance;
591 p+=GetPixelChannels(image);
592 q+=GetPixelChannels(reconstruct_image);
594 #if defined(MAGICKCORE_OPENMP_SUPPORT)
595 #pragma omp critical (MagickCore_GetMeanAbsoluteError)
597 for (i=0; i <= MaxPixelChannels; i++)
598 distortion[i]+=channel_distortion[i];
600 reconstruct_view=DestroyCacheView(reconstruct_view);
601 image_view=DestroyCacheView(image_view);
602 for (i=0; i <= MaxPixelChannels; i++)
603 distortion[i]/=((double) image->columns*image->rows);
604 distortion[MaxPixelChannels]/=(double) GetImageChannels(image);
608 static MagickBooleanType GetMeanErrorPerPixel(Image *image,
609 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
634 image_view=AcquireCacheView(image);
635 reconstruct_view=AcquireCacheView(reconstruct_image);
636 for (y=0; y < (ssize_t) image->rows; y++)
638 register const Quantum
645 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
646 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
648 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
653 for (x=0; x < (ssize_t) image->columns; x++)
658 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
670 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
671 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
672 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
673 if ((traits == UndefinedPixelTrait) ||
674 (reconstruct_traits == UndefinedPixelTrait))
676 if ((reconstruct_traits & UpdatePixelTrait) == 0)
678 distance=fabs((double) (alpha*p[i]-beta*GetPixelChannel(
679 reconstruct_image,channel,q)));
680 distortion[i]+=distance;
681 distortion[MaxPixelChannels]+=distance;
682 mean_error+=distance*distance;
683 if (distance > maximum_error)
684 maximum_error=distance;
687 p+=GetPixelChannels(image);
688 q+=GetPixelChannels(reconstruct_image);
691 reconstruct_view=DestroyCacheView(reconstruct_view);
692 image_view=DestroyCacheView(image_view);
693 image->error.mean_error_per_pixel=distortion[MaxPixelChannels]/area;
694 image->error.normalized_mean_error=QuantumScale*QuantumScale*mean_error/area;
695 image->error.normalized_maximum_error=QuantumScale*maximum_error;
699 static MagickBooleanType GetMeanSquaredDistortion(const Image *image,
700 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
716 image_view=AcquireCacheView(image);
717 reconstruct_view=AcquireCacheView(reconstruct_image);
718 #if defined(MAGICKCORE_OPENMP_SUPPORT)
719 #pragma omp parallel for schedule(dynamic,4) shared(status)
721 for (y=0; y < (ssize_t) image->rows; y++)
724 channel_distortion[MaxPixelChannels+1];
726 register const Quantum
734 if (status == MagickFalse)
736 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
737 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
739 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
744 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
745 for (x=0; x < (ssize_t) image->columns; x++)
750 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
762 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
763 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
764 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
765 if ((traits == UndefinedPixelTrait) ||
766 (reconstruct_traits == UndefinedPixelTrait))
768 if ((reconstruct_traits & UpdatePixelTrait) == 0)
770 distance=QuantumScale*(p[i]-(MagickRealType) GetPixelChannel(
771 reconstruct_image,channel,q));
773 channel_distortion[i]+=distance;
774 channel_distortion[MaxPixelChannels]+=distance;
776 p+=GetPixelChannels(image);
777 q+=GetPixelChannels(reconstruct_image);
779 #if defined(MAGICKCORE_OPENMP_SUPPORT)
780 #pragma omp critical (MagickCore_GetMeanSquaredError)
782 for (i=0; i <= MaxPixelChannels; i++)
783 distortion[i]+=channel_distortion[i];
785 reconstruct_view=DestroyCacheView(reconstruct_view);
786 image_view=DestroyCacheView(image_view);
787 for (i=0; i <= MaxPixelChannels; i++)
788 distortion[i]/=((double) image->columns*image->rows);
789 distortion[MaxPixelChannels]/=GetImageChannels(image);
793 static MagickBooleanType GetNormalizedCrossCorrelationDistortion(
794 const Image *image,const Image *reconstruct_image,double *distortion,
795 ExceptionInfo *exception)
797 #define SimilarityImageTag "Similarity/Image"
805 *reconstruct_statistics;
823 Normalize to account for variation due to lighting and exposure condition.
825 image_statistics=GetImageStatistics(image,exception);
826 reconstruct_statistics=GetImageStatistics(reconstruct_image,exception);
829 for (i=0; i <= MaxPixelChannels; i++)
831 area=1.0/((MagickRealType) image->columns*image->rows);
832 image_view=AcquireCacheView(image);
833 reconstruct_view=AcquireCacheView(reconstruct_image);
834 for (y=0; y < (ssize_t) image->rows; y++)
836 register const Quantum
843 if (status == MagickFalse)
845 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
846 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
848 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
853 for (x=0; x < (ssize_t) image->columns; x++)
858 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
867 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
868 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
869 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
870 if ((traits == UndefinedPixelTrait) ||
871 (reconstruct_traits == UndefinedPixelTrait))
873 if ((reconstruct_traits & UpdatePixelTrait) == 0)
875 distortion[i]+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
876 (GetPixelChannel(reconstruct_image,channel,q)-
877 reconstruct_statistics[channel].mean);
879 p+=GetPixelChannels(image);
880 q+=GetPixelChannels(image);
882 if (image->progress_monitor != (MagickProgressMonitor) NULL)
887 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
889 if (proceed == MagickFalse)
893 reconstruct_view=DestroyCacheView(reconstruct_view);
894 image_view=DestroyCacheView(image_view);
896 Divide by the standard deviation.
898 distortion[MaxPixelChannels]=0.0;
899 for (i=0; i < MaxPixelChannels; i++)
907 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
908 gamma=image_statistics[i].standard_deviation*
909 reconstruct_statistics[channel].standard_deviation;
910 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
911 distortion[i]=QuantumRange*gamma*distortion[i];
912 distortion[MaxPixelChannels]+=distortion[i]*distortion[i];
914 distortion[MaxPixelChannels]=sqrt(distortion[MaxPixelChannels]/
915 GetImageChannels(image));
919 reconstruct_statistics=(ChannelStatistics *) RelinquishMagickMemory(
920 reconstruct_statistics);
921 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
926 static MagickBooleanType GetPeakAbsoluteDistortion(const Image *image,
927 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
940 image_view=AcquireCacheView(image);
941 reconstruct_view=AcquireCacheView(reconstruct_image);
942 #if defined(MAGICKCORE_OPENMP_SUPPORT)
943 #pragma omp parallel for schedule(dynamic,4) shared(status)
945 for (y=0; y < (ssize_t) image->rows; y++)
948 channel_distortion[MaxPixelChannels+1];
950 register const Quantum
958 if (status == MagickFalse)
960 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
961 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,
962 reconstruct_image->columns,1,exception);
963 if ((p == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
968 (void) ResetMagickMemory(channel_distortion,0,sizeof(channel_distortion));
969 for (x=0; x < (ssize_t) image->columns; x++)
974 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
986 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
987 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
988 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
989 if ((traits == UndefinedPixelTrait) ||
990 (reconstruct_traits == UndefinedPixelTrait))
992 if ((reconstruct_traits & UpdatePixelTrait) == 0)
994 distance=QuantumScale*fabs(p[i]-(MagickRealType) GetPixelChannel(
995 reconstruct_image,channel,q));
996 if (distance > channel_distortion[i])
997 channel_distortion[i]=distance;
998 if (distance > channel_distortion[MaxPixelChannels])
999 channel_distortion[MaxPixelChannels]=distance;
1001 p+=GetPixelChannels(image);
1002 q+=GetPixelChannels(image);
1004 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1005 #pragma omp critical (MagickCore_GetPeakAbsoluteError)
1007 for (i=0; i <= MaxPixelChannels; i++)
1008 if (channel_distortion[i] > distortion[i])
1009 distortion[i]=channel_distortion[i];
1011 reconstruct_view=DestroyCacheView(reconstruct_view);
1012 image_view=DestroyCacheView(image_view);
1016 static MagickBooleanType GetPeakSignalToNoiseRatio(const Image *image,
1017 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1025 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1026 for (i=0; i <= MaxPixelChannels; i++)
1027 distortion[i]=20.0*log10((double) 1.0/sqrt(distortion[i]));
1031 static MagickBooleanType GetRootMeanSquaredDistortion(const Image *image,
1032 const Image *reconstruct_image,double *distortion,ExceptionInfo *exception)
1040 status=GetMeanSquaredDistortion(image,reconstruct_image,distortion,exception);
1041 for (i=0; i <= MaxPixelChannels; i++)
1042 distortion[i]=sqrt(distortion[i]);
1046 MagickExport MagickBooleanType GetImageDistortion(Image *image,
1047 const Image *reconstruct_image,const MetricType metric,double *distortion,
1048 ExceptionInfo *exception)
1051 *channel_distortion;
1059 assert(image != (Image *) NULL);
1060 assert(image->signature == MagickSignature);
1061 if (image->debug != MagickFalse)
1062 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1063 assert(reconstruct_image != (const Image *) NULL);
1064 assert(reconstruct_image->signature == MagickSignature);
1065 assert(distortion != (double *) NULL);
1067 if (image->debug != MagickFalse)
1068 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1069 if ((reconstruct_image->columns != image->columns) ||
1070 (reconstruct_image->rows != image->rows))
1071 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1073 Get image distortion.
1075 length=MaxPixelChannels+1;
1076 channel_distortion=(double *) AcquireQuantumMemory(length,
1077 sizeof(*channel_distortion));
1078 if (channel_distortion == (double *) NULL)
1079 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1080 (void) ResetMagickMemory(channel_distortion,0,length*
1081 sizeof(*channel_distortion));
1084 case AbsoluteErrorMetric:
1086 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1090 case FuzzErrorMetric:
1092 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1096 case MeanAbsoluteErrorMetric:
1098 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1099 channel_distortion,exception);
1102 case MeanErrorPerPixelMetric:
1104 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1108 case MeanSquaredErrorMetric:
1110 status=GetMeanSquaredDistortion(image,reconstruct_image,
1111 channel_distortion,exception);
1114 case NormalizedCrossCorrelationErrorMetric:
1117 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1118 channel_distortion,exception);
1121 case PeakAbsoluteErrorMetric:
1123 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1124 channel_distortion,exception);
1127 case PeakSignalToNoiseRatioMetric:
1129 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1130 channel_distortion,exception);
1133 case RootMeanSquaredErrorMetric:
1135 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1136 channel_distortion,exception);
1140 *distortion=channel_distortion[MaxPixelChannels];
1141 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1146 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1150 % G e t I m a g e D i s t o r t i o n s %
1154 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1156 % GetImageDistrortion() compares the pixel channels of an image to a
1157 % reconstructed image and returns the specified distortion metric for each
1160 % The format of the CompareImages method is:
1162 % double *GetImageDistortions(const Image *image,
1163 % const Image *reconstruct_image,const MetricType metric,
1164 % ExceptionInfo *exception)
1166 % A description of each parameter follows:
1168 % o image: the image.
1170 % o reconstruct_image: the reconstruct image.
1172 % o metric: the metric.
1174 % o exception: return any errors or warnings in this structure.
1177 MagickExport double *GetImageDistortions(Image *image,
1178 const Image *reconstruct_image,const MetricType metric,
1179 ExceptionInfo *exception)
1182 *channel_distortion;
1190 assert(image != (Image *) NULL);
1191 assert(image->signature == MagickSignature);
1192 if (image->debug != MagickFalse)
1193 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1194 assert(reconstruct_image != (const Image *) NULL);
1195 assert(reconstruct_image->signature == MagickSignature);
1196 if (image->debug != MagickFalse)
1197 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1198 if ((reconstruct_image->columns != image->columns) ||
1199 (reconstruct_image->rows != image->rows))
1201 (void) ThrowMagickException(&image->exception,GetMagickModule(),
1202 ImageError,"ImageSizeDiffers","`%s'",image->filename);
1203 return((double *) NULL);
1206 Get image distortion.
1208 length=MaxPixelChannels+1UL;
1209 channel_distortion=(double *) AcquireQuantumMemory(length,
1210 sizeof(*channel_distortion));
1211 if (channel_distortion == (double *) NULL)
1212 ThrowFatalException(ResourceLimitFatalError,"MemoryAllocationFailed");
1213 (void) ResetMagickMemory(channel_distortion,0,length*
1214 sizeof(*channel_distortion));
1218 case AbsoluteErrorMetric:
1220 status=GetAbsoluteDistortion(image,reconstruct_image,channel_distortion,
1224 case FuzzErrorMetric:
1226 status=GetFuzzDistortion(image,reconstruct_image,channel_distortion,
1230 case MeanAbsoluteErrorMetric:
1232 status=GetMeanAbsoluteDistortion(image,reconstruct_image,
1233 channel_distortion,exception);
1236 case MeanErrorPerPixelMetric:
1238 status=GetMeanErrorPerPixel(image,reconstruct_image,channel_distortion,
1242 case MeanSquaredErrorMetric:
1244 status=GetMeanSquaredDistortion(image,reconstruct_image,
1245 channel_distortion,exception);
1248 case NormalizedCrossCorrelationErrorMetric:
1251 status=GetNormalizedCrossCorrelationDistortion(image,reconstruct_image,
1252 channel_distortion,exception);
1255 case PeakAbsoluteErrorMetric:
1257 status=GetPeakAbsoluteDistortion(image,reconstruct_image,
1258 channel_distortion,exception);
1261 case PeakSignalToNoiseRatioMetric:
1263 status=GetPeakSignalToNoiseRatio(image,reconstruct_image,
1264 channel_distortion,exception);
1267 case RootMeanSquaredErrorMetric:
1269 status=GetRootMeanSquaredDistortion(image,reconstruct_image,
1270 channel_distortion,exception);
1274 if (status == MagickFalse)
1276 channel_distortion=(double *) RelinquishMagickMemory(channel_distortion);
1277 return((double *) NULL);
1279 return(channel_distortion);
1283 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1287 % I s I m a g e s E q u a l %
1291 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1293 % IsImagesEqual() measures the difference between colors at each pixel
1294 % location of two images. A value other than 0 means the colors match
1295 % exactly. Otherwise an error measure is computed by summing over all
1296 % pixels in an image the distance squared in RGB space between each image
1297 % pixel and its corresponding pixel in the reconstruct image. The error
1298 % measure is assigned to these image members:
1300 % o mean_error_per_pixel: The mean error for any single pixel in
1303 % o normalized_mean_error: The normalized mean quantization error for
1304 % any single pixel in the image. This distance measure is normalized to
1305 % a range between 0 and 1. It is independent of the range of red, green,
1306 % and blue values in the image.
1308 % o normalized_maximum_error: The normalized maximum quantization
1309 % error for any single pixel in the image. This distance measure is
1310 % normalized to a range between 0 and 1. It is independent of the range
1311 % of red, green, and blue values in your image.
1313 % A small normalized mean square error, accessed as
1314 % image->normalized_mean_error, suggests the images are very similar in
1315 % spatial layout and color.
1317 % The format of the IsImagesEqual method is:
1319 % MagickBooleanType IsImagesEqual(Image *image,
1320 % const Image *reconstruct_image,ExceptionInfo *exception)
1322 % A description of each parameter follows.
1324 % o image: the image.
1326 % o reconstruct_image: the reconstruct image.
1328 % o exception: return any errors or warnings in this structure.
1331 MagickExport MagickBooleanType IsImagesEqual(Image *image,
1332 const Image *reconstruct_image,ExceptionInfo *exception)
1345 mean_error_per_pixel;
1350 assert(image != (Image *) NULL);
1351 assert(image->signature == MagickSignature);
1352 assert(reconstruct_image != (const Image *) NULL);
1353 assert(reconstruct_image->signature == MagickSignature);
1354 if ((reconstruct_image->columns != image->columns) ||
1355 (reconstruct_image->rows != image->rows))
1356 ThrowBinaryException(ImageError,"ImageSizeDiffers",image->filename);
1359 mean_error_per_pixel=0.0;
1361 image_view=AcquireCacheView(image);
1362 reconstruct_view=AcquireCacheView(reconstruct_image);
1363 for (y=0; y < (ssize_t) image->rows; y++)
1365 register const Quantum
1372 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1373 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1375 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1377 for (x=0; x < (ssize_t) image->columns; x++)
1382 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1394 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1395 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1396 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1397 if ((traits == UndefinedPixelTrait) ||
1398 (reconstruct_traits == UndefinedPixelTrait))
1400 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1402 distance=fabs(p[i]-(MagickRealType) GetPixelChannel(reconstruct_image,
1404 mean_error_per_pixel+=distance;
1405 mean_error+=distance*distance;
1406 if (distance > maximum_error)
1407 maximum_error=distance;
1410 p+=GetPixelChannels(image);
1411 q+=GetPixelChannels(reconstruct_image);
1414 reconstruct_view=DestroyCacheView(reconstruct_view);
1415 image_view=DestroyCacheView(image_view);
1416 image->error.mean_error_per_pixel=(double) (mean_error_per_pixel/area);
1417 image->error.normalized_mean_error=(double) (QuantumScale*QuantumScale*
1419 image->error.normalized_maximum_error=(double) (QuantumScale*maximum_error);
1420 status=image->error.mean_error_per_pixel == 0.0 ? MagickTrue : MagickFalse;
1425 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1429 % S i m i l a r i t y I m a g e %
1433 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1435 % SimilarityImage() compares the reference image of the image and returns the
1436 % best match offset. In addition, it returns a similarity image such that an
1437 % exact match location is completely white and if none of the pixels match,
1438 % black, otherwise some gray level in-between.
1440 % The format of the SimilarityImageImage method is:
1442 % Image *SimilarityImage(const Image *image,const Image *reference,
1443 % RectangleInfo *offset,double *similarity,ExceptionInfo *exception)
1445 % A description of each parameter follows:
1447 % o image: the image.
1449 % o reference: find an area of the image that closely resembles this image.
1451 % o the best match offset of the reference image within the image.
1453 % o similarity: the computed similarity between the images.
1455 % o exception: return any errors or warnings in this structure.
1459 static double GetNCCDistortion(const Image *image,
1460 const Image *reconstruct_image,
1461 const ChannelStatistics *reconstruct_statistics,ExceptionInfo *exception)
1463 #define SimilarityImageTag "Similarity/Image"
1486 Normalize to account for variation due to lighting and exposure condition.
1488 image_statistics=GetImageStatistics(image,exception);
1491 area=1.0/((MagickRealType) image->columns*image->rows);
1492 image_view=AcquireCacheView(image);
1493 reconstruct_view=AcquireCacheView(reconstruct_image);
1494 for (y=0; y < (ssize_t) image->rows; y++)
1496 register const Quantum
1503 if (status == MagickFalse)
1505 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1506 q=GetCacheViewVirtualPixels(reconstruct_view,0,y,reconstruct_image->columns,
1508 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1513 for (x=0; x < (ssize_t) image->columns; x++)
1518 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1527 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1528 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1529 reconstruct_traits=GetPixelChannelMapTraits(reconstruct_image,channel);
1530 if ((traits == UndefinedPixelTrait) ||
1531 (reconstruct_traits == UndefinedPixelTrait))
1533 if ((reconstruct_traits & UpdatePixelTrait) == 0)
1535 distortion+=area*QuantumScale*(p[i]-image_statistics[i].mean)*
1536 (GetPixelChannel(reconstruct_image,channel,q)-
1537 reconstruct_statistics[channel].mean);
1539 p+=GetPixelChannels(image);
1540 q+=GetPixelChannels(reconstruct_image);
1543 reconstruct_view=DestroyCacheView(reconstruct_view);
1544 image_view=DestroyCacheView(image_view);
1546 Divide by the standard deviation.
1548 gamma=image_statistics[MaxPixelChannels].standard_deviation*
1549 reconstruct_statistics[MaxPixelChannels].standard_deviation;
1550 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1551 distortion=QuantumRange*gamma*distortion;
1552 distortion=sqrt(distortion/GetImageChannels(image));
1556 image_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1558 return(1.0-distortion);
1561 static double GetSimilarityMetric(const Image *image,const Image *reference,
1562 const ChannelStatistics *reference_statistics,const ssize_t x_offset,
1563 const ssize_t y_offset,ExceptionInfo *exception)
1574 SetGeometry(reference,&geometry);
1575 geometry.x=x_offset;
1576 geometry.y=y_offset;
1577 similarity_image=CropImage(image,&geometry,exception);
1578 if (similarity_image == (Image *) NULL)
1580 distortion=GetNCCDistortion(reference,similarity_image,reference_statistics,
1582 similarity_image=DestroyImage(similarity_image);
1586 MagickExport Image *SimilarityImage(Image *image,const Image *reference,
1587 RectangleInfo *offset,double *similarity_metric,ExceptionInfo *exception)
1589 #define SimilarityImageTag "Similarity/Image"
1595 *reference_statistics;
1609 assert(image != (const Image *) NULL);
1610 assert(image->signature == MagickSignature);
1611 if (image->debug != MagickFalse)
1612 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1613 assert(exception != (ExceptionInfo *) NULL);
1614 assert(exception->signature == MagickSignature);
1615 assert(offset != (RectangleInfo *) NULL);
1616 SetGeometry(reference,offset);
1617 *similarity_metric=1.0;
1618 if ((reference->columns > image->columns) || (reference->rows > image->rows))
1619 ThrowImageException(ImageError,"ImageSizeDiffers");
1620 similarity_image=CloneImage(image,image->columns-reference->columns+1,
1621 image->rows-reference->rows+1,MagickTrue,exception);
1622 if (similarity_image == (Image *) NULL)
1623 return((Image *) NULL);
1624 status=SetImageStorageClass(similarity_image,DirectClass,exception);
1625 if (status == MagickFalse)
1627 similarity_image=DestroyImage(similarity_image);
1628 return((Image *) NULL);
1631 Measure similarity of reference image against image.
1635 reference_statistics=GetImageStatistics(reference,exception);
1636 similarity_view=AcquireCacheView(similarity_image);
1637 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1638 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1640 for (y=0; y < (ssize_t) (image->rows-reference->rows+1); y++)
1651 if (status == MagickFalse)
1653 q=GetCacheViewAuthenticPixels(similarity_view,0,y,similarity_image->columns,
1655 if (q == (Quantum *) NULL)
1660 for (x=0; x < (ssize_t) (image->columns-reference->columns+1); x++)
1665 similarity=GetSimilarityMetric(image,reference,reference_statistics,x,y,
1667 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1668 #pragma omp critical (MagickCore_SimilarityImage)
1670 if (similarity < *similarity_metric)
1672 *similarity_metric=similarity;
1676 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1685 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
1686 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
1687 similarity_traits=GetPixelChannelMapTraits(similarity_image,channel);
1688 if ((traits == UndefinedPixelTrait) ||
1689 (similarity_traits == UndefinedPixelTrait))
1691 if ((similarity_traits & UpdatePixelTrait) == 0)
1693 SetPixelChannel(similarity_image,channel,ClampToQuantum(QuantumRange-
1694 QuantumRange*similarity),q);
1696 q+=GetPixelChannels(similarity_image);
1698 if (SyncCacheViewAuthenticPixels(similarity_view,exception) == MagickFalse)
1700 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1705 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1706 #pragma omp critical (MagickCore_SimilarityImage)
1708 proceed=SetImageProgress(image,SimilarityImageTag,progress++,
1710 if (proceed == MagickFalse)
1714 similarity_view=DestroyCacheView(similarity_view);
1715 reference_statistics=(ChannelStatistics *) RelinquishMagickMemory(
1716 reference_statistics);
1717 return(similarity_image);