Namespaces

Variants
Actions

Please note that as of October 24, 2014, the Nokia Developer Wiki will no longer be accepting user contributions, including new entries, edits and comments, as we begin transitioning to our new home, in the Windows Phone Development Wiki. We plan to move over the majority of the existing entries over the next few weeks. Thanks for all your past and future contributions.

Otsu Threshold Custom Effect (Nokia Imaging SDK)

From Wiki
Jump to: navigation, search

This article explains how to create an Otsu Threshold custom effect. This is one of many binarization algorithms.

SignpostIcon XAML 40.png
Article Metadata
Tested with
Devices(s): Nokia Lumia 920
Dependencies: Nokia Imaging SDK 1.0
Article
Created: Engin Kırmacı (19 Feb 2014)
Last edited: Engin Kırmacı (19 Feb 2014)

Contents

Introduction

The Otsu Threshold is one of [binarization algorithms], named after its inventor Nobuyuki Otsu. This method involves iterating through all the possible threshold values and calculating a measure of spread for the pixel levels each side of the threshold, i.e. the pixels that either fall in foreground or background. The aim is to find the threshold value where the sum of foreground and background spreads is at its minimum.

Source code

Full source code for the OtsuThresholdEffect custom effect is provided below (toggle "Expand") and also in a ready-to-use file here: OtsuThresholdEffect.cs.

OtsuThresholdEffect.cs (19 Feb 2014)

  1. // =====================================================================================
  2. // DATE        AUTHOR                   DESCRIPTION
  3. // ----------  -----------------------  ------------------------------------------------
  4. // 2014.02.13  Engin.Kırmacı            Initial creation
  5. // 2014.02.19  Engin.Kırmacı            Change behavior 'Threshold' property, removed
  6. //                                      unused namespaces
  7. //
  8. // CREDITS
  9. // =====================================================================================
  10. // AUTHOR        ARTICLE
  11. // ------------  -----------------------------------------------------------------------
  12. // Tolga Birdal  http://www.codeproject.com/Articles/38319/Famous-Otsu-Thresholding-in-C
  13. //
  14. // =====================================================================================
  15.  
  16. using Nokia.Graphics.Imaging;
  17.  
  18. namespace NISDKExtendedEffects.ImageEffects
  19. {
  20.     public class OtsuThresholdEffect : CustomEffectBase
  21.     {
  22.         private byte _threshold { get; set; }
  23.  
  24.         //After filter applied, returns calculated threshold value
  25.         public byte Threshold { get { return _threshold; } }
  26.  
  27.         public OtsuThresholdEffect(IImageProvider source)
  28.             : base(source, true)
  29.         {
  30.         }
  31.  
  32.         protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  33.         {
  34.             float[] vet = new float[256];
  35.             int[] hist = new int[256];
  36.  
  37.             // simply computes the grayscale image histogram
  38.             sourcePixelRegion.ForEachRow((index, width, pos) =>
  39.             {
  40.                 for (int x = 0; x < width; x += 3, index += 3)
  41.                 {
  42.                     uint currentPixel = sourcePixelRegion.ImagePixels[index];
  43.  
  44.                     uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  45.                     uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  46.                     uint blue = currentPixel & 0x000000ff; // blue color component
  47.  
  48.                     //luminance formula
  49.                     var p = (byte)(0.21 * red + 0.71 * green + 0.07 * blue);
  50.                     hist[p]++;
  51.                 }
  52.             });
  53.  
  54.             float p1, p2, p12;
  55.             int k;
  56.  
  57.             // loop through all possible t values and maximize between class variance
  58.             for (k = 1; k != 255; k++)
  59.             {
  60.                 p1 = Px(0, k, hist);
  61.                 p2 = Px(k + 1, 255, hist);
  62.                 p12 = p1 * p2;
  63.                 if (p12 == 0)
  64.                     p12 = 1;
  65.                 float diff = (Mx(0, k, hist) * p2) - (Mx(k + 1, 255, hist) * p1);
  66.                 vet[k] = (float)diff * diff / p12;
  67.             }
  68.  
  69.             _threshold = (byte)findMax(vet, 256);
  70.  
  71.             uint white = 0xff000000 | (255 << 16) | (255 << 8) | 255;
  72.             uint black = 0xff000000 | (0 << 16) | (0 << 8) | 0;
  73.  
  74.             // simple routine for thresholding
  75.             sourcePixelRegion.ForEachRow((index, width, pos) =>
  76.             {
  77.                 for (int x = 0; x < width; ++x, ++index)
  78.                 {
  79.                     uint currentPixel = sourcePixelRegion.ImagePixels[index];
  80.  
  81.                     uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  82.                     uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  83.                     uint blue = currentPixel & 0x000000ff; // blue color component
  84.  
  85.                     if ((byte)(0.21 * red + 0.71 * green + 0.07 * blue) < _threshold)
  86.                         sourcePixelRegion.ImagePixels[index] = black;
  87.                     else
  88.                         sourcePixelRegion.ImagePixels[index] = white;
  89.                 }
  90.             });
  91.         }
  92.  
  93.         // function is used to compute the q values in the equation
  94.         private float Px(int init, int end, int[] hist)
  95.         {
  96.             int sum = 0;
  97.             int i;
  98.             for (i = init; i <= end; i++)
  99.                 sum += hist[i];
  100.  
  101.             return (float)sum;
  102.         }
  103.  
  104.         // function is used to compute the mean values in the equation (mu)
  105.         private float Mx(int init, int end, int[] hist)
  106.         {
  107.             int sum = 0;
  108.             int i;
  109.             for (i = init; i <= end; i++)
  110.                 sum += i * hist[i];
  111.  
  112.             return (float)sum;
  113.         }
  114.  
  115.         // finds the maximum element in a vector
  116.         private int findMax(float[] vec, int n)
  117.         {
  118.             float maxVec = 0;
  119.             int idx = 0;
  120.             int i;
  121.  
  122.             for (i = 1; i < n - 1; i++)
  123.             {
  124.                 if (vec[i] > maxVec)
  125.                 {
  126.                     maxVec = vec[i];
  127.                     idx = i;
  128.                 }
  129.             }
  130.             return idx;
  131.         }
  132.     }
  133. }

Testing

The Test Apps for Viewing Custom Filters allow you to cycle through a number of different custom effects (including OtsuThresholdEffect) and apply them to the real-time camera preview or a static image.

In addition, the custom effect file can be dropped into your own project, and used as described in the section Using the filter.

Pre-requisites

Performance

Device OtsuThresholdEffect
Lumia 920 9-10 FPS

The OtsuThresholdEffect class runs 9-10 FPS (Frames Per Second).

Code walkthrough

First, Otsu Threshold computes histogram data of a grayscale image. When computing histogram data, we don't need to iterate every pixel. Iterating a pixel by three gives enough data and higher performance. Also following code uses luminance formula to convert iterated pixels to grayscale.

  1. // simply computes the grayscale image histogram
  2. sourcePixelRegion.ForEachRow((index, width, pos) =>
  3. {
  4.     for (int x = 0; x < width; x += 3, index += 3)
  5.     {
  6.         uint currentPixel = sourcePixelRegion.ImagePixels[index];
  7.  
  8.         uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  9.         uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  10.         uint blue = currentPixel & 0x000000ff; // blue color component
  11.  
  12.         //luminance formula
  13.         var p = (byte)(0.21 * red + 0.71 * green + 0.07 * blue);
  14.         hist[p]++;
  15.     }
  16. });

Then, Otsu's method computes all possible threshold values and calculating a measure of spread for the pixel levels each side of the threshold. For more information about [algorithm]

  1. // loop through all possible t values and maximize between class variance
  2. for (k = 1; k != 255; k++)
  3. {
  4.     p1 = Px(0, k, hist);
  5.     p2 = Px(k + 1, 255, hist);
  6.     p12 = p1 * p2;
  7.     if (p12 == 0)
  8.         p12 = 1;
  9.     float diff = (Mx(0, k, hist) * p2) - (Mx(k + 1, 255, hist) * p1);
  10.     vet[k] = (float)diff * diff / p12;
  11. }
  12.  
  13. _threshold = (byte)findMax(vet, 256);

Finally, simple binarization method binarize image by using calculated threshold value.

  1. // simple routine for thresholding
  2. sourcePixelRegion.ForEachRow((index, width, pos) =>
  3. {
  4.     for (int x = 0; x < width; ++x, ++index)
  5.     {
  6.         uint currentPixel = sourcePixelRegion.ImagePixels[index];
  7.  
  8.         uint red = (currentPixel & 0x00ff0000) >> 16; // red color component
  9.         uint green = (currentPixel & 0x0000ff00) >> 8; // green color component
  10.         uint blue = currentPixel & 0x000000ff; // blue color component
  11.  
  12.         if ((byte)(0.21 * red + 0.71 * green + 0.07 * blue) < _threshold)
  13.             sourcePixelRegion.ImagePixels[index] = black;
  14.         else
  15.             sourcePixelRegion.ImagePixels[index] = white;
  16.     }
  17. });

Using the filter

Drop an image control into your XAML.

  1. <Image x:Name="FilterEffectImage" Width="800" Height="480" Stretch="Fill" Grid.RowSpan="2" />

Use this code to apply the filter effect to your chosen image and assign it to the XAML image control.

  1. // Initialize a WriteableBitmap with the dimensions of the XAML image control
  2. WriteableBitmap writeableBitmap = new WriteableBitmap((int)FilterEffectImage.Width, (int)FilterEffectImage.Height);
  3.  
  4. // Example: Accessing an image stream within a standard photo chooser task callback
  5. // http://msdn.microsoft.com/en-us/library/windowsphone/develop/hh394019(v=vs.105).aspx
  6. //using (var imageStream = new StreamImageSource(e.ChosenPhoto))
  7.  
  8. // Example: Accessing an image stream from a sample picture loaded with the project in a folder called "Pictures"
  9. var resource = App.GetResourceStream(new Uri(string.Concat("Pictures/", "sample_photo_08.jpg"), UriKind.Relative));
  10. using (var imageStream = new StreamImageSource(resource.Stream))
  11. {
  12.     // Applying the custom filter effect to the image stream
  13.     using (var customEffect = new OtsuThresholdEffect(imageStream))
  14.     {
  15.         // Rendering the resulting image to a WriteableBitmap
  16.         using (var renderer = new WriteableBitmapRenderer(customEffect, writeableBitmap))
  17.         {
  18.             // Applying the WriteableBitmap to our xaml image control
  19.             FilterEffectImage.Source = await renderer.RenderAsync();
  20.  
  21.             // while debugging, it writes calculated threshold value to Output Window 
  22.             System.Diagnostics.Debug.WriteLine(customEffect.Threshold);
  23.         }
  24.     }
  25. }

License

The code has been released with the standard MIT License, and can be viewed in the Github project here.

Summary

Hopefully you've enjoyed seeing how to binarize an image using Otsu's method. This is just one of many things you can do with the amazing Nokia Imaging SDK.

As with all articles on the wiki, you are welcome to contribute any changes to this code that would improve the quality, whether it be additional features or improving its efficiency.

This page was last modified on 19 February 2014, at 08:46.
237 page views in the last 30 days.

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×