]> granicus.if.org Git - imagemagick/commitdiff
OTSU thresholding framework
authorCristy <mikayla-grace@urban-warrior.org>
Sun, 17 Mar 2019 21:15:35 +0000 (17:15 -0400)
committerCristy <mikayla-grace@urban-warrior.org>
Sun, 17 Mar 2019 21:15:35 +0000 (17:15 -0400)
MagickCore/option.c
MagickCore/threshold.c
MagickCore/threshold.h
MagickWand/magick-image.c
MagickWand/magick-image.h
MagickWand/mogrify.c
MagickWand/operation.c
PerlMagick/Magick.xs
PerlMagick/quantum/quantum.xs.in

index daac3b5d8e6b39f9024837fbd049f31a29d6cf0f..5dd2fa3659c0cfe52aa7198c92b40f408b75d4fa 100644 (file)
@@ -341,6 +341,7 @@ static const OptionInfo
     { "  normalize", 0, UndefinedOptionFlag, MagickFalse },
     { "  opaque", 0, UndefinedOptionFlag, MagickFalse },
     { "  ordered-dither", 0, UndefinedOptionFlag, MagickFalse },
+    { "  otsu-threshold", 0, UndefinedOptionFlag, MagickFalse },
     { "  paint", 0, UndefinedOptionFlag, MagickFalse },
     { "  posterize", 0, UndefinedOptionFlag, MagickFalse },
     { "  raise", 0, UndefinedOptionFlag, MagickFalse },
@@ -894,6 +895,8 @@ static const OptionInfo
     { "-ordered-dither", 1L, SimpleOperatorFlag, MagickFalse },
     { "+orient", 0L, ImageInfoOptionFlag, MagickFalse },
     { "-orient", 1L, ImageInfoOptionFlag, MagickFalse },
+    { "+otsu-threshold", 0L, DeprecateOptionFlag | FireOptionFlag, MagickTrue },
+    { "-otsu-threshold", 0L, ListOperatorFlag | FireOptionFlag, MagickFalse },
     { "+page", 0L, ImageInfoOptionFlag, MagickFalse },
     { "-page", 1L, ImageInfoOptionFlag, MagickFalse },
     { "+paint", 0L, DeprecateOptionFlag, MagickTrue },
index 00b875ad151523ec3b33b4b2d05e110d691490b4..447019fe04af0ef40d717b4dbf0b262c81697adf 100644 (file)
@@ -1836,6 +1836,164 @@ MagickExport MagickBooleanType OrderedDitherImage(Image *image,
 %                                                                             %
 %                                                                             %
 %                                                                             %
+%     O T S U T h r e s h o l d I m a g e                                     %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  OTSUThresholdImage() automatically performs clustering-based image
+%  thresholding. The algorithm calculates the optimum threshold separates into
+%  two classes so that their combined spread (intra-class variance) is minimal,
+%  and their inter-class variance is maximal.
+%
+%  The format of the OTSUThresholdImage method is:
+%
+%      MagickBooleanType OTSUThresholdImage(Image *image,
+%        ExceptionInfo *exception)
+%
+%  A description of each parameter follows:
+%
+%    o image: the image.
+%
+%    o exception: return any errors or warnings in this structure.
+%
+*/
+MagickExport MagickBooleanType OTSUThresholdImage(Image *image,
+  ExceptionInfo *exception)
+{
+#define ThresholdImageTag  "Threshold/Image"
+
+  CacheView
+    *image_view;
+
+  GeometryInfo
+    geometry_info;
+
+  MagickBooleanType
+    status;
+
+  MagickOffsetType
+    progress;
+
+  PixelInfo
+    threshold;
+
+  MagickStatusType
+    flags;
+
+  ssize_t
+    y;
+
+  assert(image != (Image *) NULL);
+  assert(image->signature == MagickCoreSignature);
+  if (image->debug != MagickFalse)
+    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
+  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
+    return(MagickFalse);
+  if (IsGrayColorspace(image->colorspace) != MagickFalse)
+    (void) TransformImageColorspace(image,sRGBColorspace,exception);
+  GetPixelInfo(image,&threshold);
+  flags=ParseGeometry("100",&geometry_info);
+  threshold.red=geometry_info.rho;
+  threshold.green=geometry_info.rho;
+  threshold.blue=geometry_info.rho;
+  threshold.black=geometry_info.rho;
+  threshold.alpha=100.0;
+  if ((flags & SigmaValue) != 0)
+    threshold.green=geometry_info.sigma;
+  if ((flags & XiValue) != 0)
+    threshold.blue=geometry_info.xi;
+  if ((flags & PsiValue) != 0)
+    threshold.alpha=geometry_info.psi;
+  if (threshold.colorspace == CMYKColorspace)
+    {
+      if ((flags & PsiValue) != 0)
+        threshold.black=geometry_info.psi;
+      if ((flags & ChiValue) != 0)
+        threshold.alpha=geometry_info.chi;
+    }
+  if ((flags & PercentValue) != 0)
+    {
+      threshold.red*=(MagickRealType) (QuantumRange/100.0);
+      threshold.green*=(MagickRealType) (QuantumRange/100.0);
+      threshold.blue*=(MagickRealType) (QuantumRange/100.0);
+      threshold.black*=(MagickRealType) (QuantumRange/100.0);
+      threshold.alpha*=(MagickRealType) (QuantumRange/100.0);
+    }
+  /*
+    OTSU threshold image.
+  */
+  status=MagickTrue;
+  progress=0;
+  image_view=AcquireAuthenticCacheView(image,exception);
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+  #pragma omp parallel for schedule(static) shared(progress,status) \
+    magick_number_threads(image,image,image->rows,1)
+#endif
+  for (y=0; y < (ssize_t) image->rows; y++)
+  {
+    register ssize_t
+      x;
+
+    register Quantum
+      *magick_restrict q;
+
+    if (status == MagickFalse)
+      continue;
+    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
+    if (q == (Quantum *) NULL)
+      {
+        status=MagickFalse;
+        continue;
+      }
+    for (x=0; x < (ssize_t) image->columns; x++)
+    {
+      double
+        pixel;
+
+      register ssize_t
+        i;
+
+      pixel=GetPixelIntensity(image,q);
+      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
+      {
+        PixelChannel channel = GetPixelChannelChannel(image,i);
+        PixelTrait traits = GetPixelChannelTraits(image,channel);
+        if ((traits & UpdatePixelTrait) == 0)
+          continue;
+        if (image->channel_mask != DefaultChannels)
+          pixel=(double) q[i];
+        if (pixel > GetPixelInfoChannel(&threshold,channel))
+          q[i]=QuantumRange;
+      }
+      q+=GetPixelChannels(image);
+    }
+    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
+      status=MagickFalse;
+    if (image->progress_monitor != (MagickProgressMonitor) NULL)
+      {
+        MagickBooleanType
+          proceed;
+
+#if defined(MAGICKCORE_OPENMP_SUPPORT)
+        #pragma omp atomic
+#endif
+        progress++;
+        proceed=SetImageProgress(image,ThresholdImageTag,progress,image->rows);
+        if (proceed == MagickFalse)
+          status=MagickFalse;
+      }
+  }
+  image_view=DestroyCacheView(image_view);
+  return(status);
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
 %     P e r c e p t i b l e I m a g e                                         %
 %                                                                             %
 %                                                                             %
index 91bb1da239e5b394d88af7cca3f8c80c54854662..1008fa47a68a0da9b57f4592ffc678d29da338b8 100644 (file)
@@ -48,6 +48,7 @@ extern MagickExport MagickBooleanType
   ClampImage(Image *,ExceptionInfo *),
   ListThresholdMaps(FILE *,ExceptionInfo *),
   OrderedDitherImage(Image *,const char *,ExceptionInfo *),
+  OTSUThresholdImage(Image *,ExceptionInfo *),
   PerceptibleImage(Image *,const double,ExceptionInfo *),
   RandomThresholdImage(Image *,const double,const double,ExceptionInfo *),
   RangeThresholdImage(Image *,const double,const double,const double,
index f6cf1e27297b74a519c768f18449948189748f8c..c5f193ebb34c25bf13ae8078811a01da3bb81c89 100644 (file)
@@ -7559,6 +7559,42 @@ WandExport MagickBooleanType MagickOrderedDitherImage(MagickWand *wand,
 %                                                                             %
 %                                                                             %
 %                                                                             %
+%   M a g i c k O T S U T h r e s h o l d I m a g e                           %
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%  MagickOTSUThresholdImage() automatically performs clustering-based image
+%  thresholding. The algorithm calculates the optimum threshold separates into
+%  two classes so that their combined spread (intra-class variance) is minimal,
+%  and their inter-class variance is maximal.
+%
+%  The format of the MagickOTSUThresholdImage method is:
+%
+%      MagickBooleanType MagickOTSUThresholdImage(MagickWand *wand)
+%
+%  A description of each parameter follows:
+%
+%    o wand: the magick wand.
+%
+*/
+WandExport MagickBooleanType MagickOTSUThresholdImage(MagickWand *wand)
+{
+  assert(wand != (MagickWand *) NULL);
+  assert(wand->signature == MagickWandSignature);
+  if (wand->debug != MagickFalse)
+    (void) LogMagickEvent(WandEvent,GetMagickModule(),"%s",wand->name);
+  if (wand->images == (Image *) NULL)
+    ThrowWandException(WandError,"ContainsNoImages",wand->name);
+  return(OTSUThresholdImage(wand->images,wand->exception));
+}
+\f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
 %   M a g i c k P i n g I m a g e                                             %
 %                                                                             %
 %                                                                             %
index d6db943e362bfcf848f455db6e174cf31624af63..61643e0484d05c2c760f780b50d9f1d847c16ca1 100644 (file)
@@ -208,6 +208,7 @@ extern WandExport MagickBooleanType
     const double,const MagickBooleanType),
   MagickOptimizeImageTransparency(MagickWand *),
   MagickOrderedDitherImage(MagickWand *,const char *),
+  MagickOTSUThresholdImage(MagickWand *),
   MagickTransparentPaintImage(MagickWand *,const PixelWand *,
     const double,const double,const MagickBooleanType invert),
   MagickPingImage(MagickWand *,const char *),
index 9eba2f096bb0332fb541b08313b75e6b1d14e50d..ecbae9d1d546bce3650e430e8f876abb4abfb997 100644 (file)
@@ -2336,6 +2336,12 @@ WandExport MagickBooleanType MogrifyImage(ImageInfo *image_info,const int argc,
             (void) OrderedDitherImage(*image,argv[i+1],exception);
             break;
           }
+        if (LocaleCompare("otsu-threshold",option+1) == 0)
+          {
+            (void) SyncImageSettings(mogrify_info,*image,exception);
+            (void) OTSUThresholdImage(*image,exception);
+            break;
+          }
         break;
       }
       case 'p':
@@ -5623,6 +5629,8 @@ WandExport MagickBooleanType MogrifyImageCommand(ImageInfo *image_info,
                 argv[i]);
             break;
           }
+        if (LocaleCompare("otsu-threshold",option+1) == 0)
+          break;
         ThrowMogrifyException(OptionError,"UnrecognizedOption",option)
       }
       case 'p':
index c93c123e110b9180951ae3205da48d4c11c45bcf..0f0dbeccabd37fd15399a99f8e55a09424ba3f47 100644 (file)
@@ -2895,6 +2895,11 @@ static MagickBooleanType CLISimpleOperatorImage(MagickCLI *cli_wand,
           (void) OrderedDitherImage(_image,arg1,_exception);
           break;
         }
+      if (LocaleCompare("otsu-threshold",option+1) == 0)
+        {
+          (void) OTSUThresholdImage(_image,_exception);
+          break;
+        }
       CLIWandExceptionBreak(OptionError,"UnrecognizedOption",option);
     }
     case 'p':
index f63c7ecb586334d466693ad5c595181fc73ca26d..24788ec5d7f5d499ec18ba536e15abddd52fb936 100644 (file)
@@ -575,6 +575,7 @@ static struct
     { "CLAHE", { {"geometry", StringReference}, {"width", IntegerReference},
       {"height", IntegerReference}, {"number-bins", IntegerReference},
       {"clip-limit", RealReference} } },
+    { "OTSUThreshold", { {"channel", MagickChannelOptions} } },
   };
 
 static SplayTreeInfo
@@ -7647,6 +7648,8 @@ Mogrify(ref,...)
     RangeThresholdImage= 296
     CLAHE              = 297
     CLAHEImage         = 298
+    OTSUThreshold      = 299
+    OTSUThresholdImage = 300
     MogrifyRegion      = 666
   PPCODE:
   {
@@ -11501,6 +11504,16 @@ Mogrify(ref,...)
             geometry_info.psi,exception);
           break;
         }
+        case 150:  /* OTSUThreshold */
+        {
+          if (attribute_flag[0] != 0)
+            channel=(ChannelType) argument_list[0].integer_reference;
+          channel_mask=SetImageChannelMask(image,channel);
+          (void) OTSUThresholdImage(image,exception);
+          if (image != (Image *) NULL)
+            (void) SetImageChannelMask(image,channel_mask);
+          break;
+        }
       }
       if (next != (Image *) NULL)
         (void) CatchImageException(next);
index c70f208fb216a48d530aa771b3fa1fbe6baf19b0..664c9e8262bb68e0cdd3ba4599f5fad0f3684d8c 100644 (file)
@@ -575,6 +575,7 @@ static struct
     { "CLAHE", { {"geometry", StringReference}, {"width", IntegerReference},
       {"height", IntegerReference}, {"number-bins", IntegerReference},
       {"clip-limit", RealReference} } },
+    { "OTSUThreshold", { {"channel", MagickChannelOptions} } },
   };
 
 static SplayTreeInfo
@@ -7646,6 +7647,8 @@ Mogrify(ref,...)
     RangeThresholdImage= 296
     CLAHE              = 297
     CLAHEImage         = 298
+    OTSUThreshold      = 299
+    OTSUThresholdImage = 300
     MogrifyRegion      = 666
   PPCODE:
   {
@@ -11507,6 +11510,16 @@ Mogrify(ref,...)
             geometry_info.psi,exception);
           break;
         }
+        case 150:  /* OTSUThreshold */
+        {
+          if (attribute_flag[0] != 0)
+            channel=(ChannelType) argument_list[0].integer_reference;
+          channel_mask=SetImageChannelMask(image,channel);
+          (void) OTSUThresholdImage(image,exception);
+          if (image != (Image *) NULL)
+            (void) SetImageChannelMask(image,channel_mask);
+          break;
+        }
       }
       if (next != (Image *) NULL)
         (void) CatchImageException(next);