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.

Canny Edge Detection Custom Effect (Nokia Imaging SDK)

From Wiki
Jump to: navigation, search

This article explains how to create a Canny Edge Detection custom effect. This is one of edge detection algorithms used for image processing.

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: influencer (02 Mar 2014)

This article explains how to create a Canny Edge Detection .

Contents

Introduction

The Canny Edge Detection algorithm is known to many as the optimal edge detector. It uses a multi-stage algorithm to detect a wide range of edges in images. It was developed by John F. Canny in 1986.

The Canny Edge Detection first smoothes the image to eliminate noise. It then finds the image gradient to highlight regions with high spatial derivatives. The algorithm then tracks along these regions and suppresses any pixel that is not at the maximum (nonmaximum suppression). The gradient array is now further reduced by hysteresis. Hysteresis is used to track along the remaining pixels that have not been suppressed. Hysteresis uses two thresholds and if the magnitude is below the first threshold, it is set to zero (made a nonedge). If the magnitude is above the high threshold, it is made an edge. And if the magnitude is between the 2 thresholds, then it is set to zero unless there is a path from this pixel to a pixel with a gradient above T2.

Source code

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

CannyEdgeDetection.cs (17 Feb 2014)

  1. // ============================================================================
  2. // DATE        AUTHOR                   DESCRIPTION
  3. // ----------  -----------------------  ---------------------------------------
  4. // 2014.02.13  Engin.Kırmacı            Initial creation
  5. // 2014.02.17  Engin.Kırmacı            Reducing Math calc for performance
  6. // ============================================================================
  7.  
  8. using Nokia.Graphics.Imaging;
  9. using System;
  10. using System.Diagnostics;
  11. using Windows.UI;
  12.  
  13. namespace NISDKExtendedEffects.ImageEffects
  14. {
  15.     public class CannyEdgeDetection : CustomEffectBase
  16.     {
  17.         public int Width, Height;
  18.         public int[,] GreyImage;
  19.         public float[,] DerivativeX;
  20.         public float[,] DerivativeY;
  21.         public int[,] FilteredImage;
  22.         public float[,] Gradient;
  23.         public float[,] NonMax;
  24.         public int[,] PostHysteresis;
  25.         public int[,] EdgeMap;
  26.         public int[,] VisitedMap;
  27.  
  28.         //public float[,] GNH;
  29.         //public float[,] GNL;
  30.  
  31.         private int[,] GaussianKernel;
  32.         private int KernelWeight;
  33.         private int KernelSize = 5;
  34.         private float Sigma = 1;   // for N=2 Sigma =0.85  N=5 Sigma =1, N=9 Sigma = 2    2*Sigma = (int)N/2
  35.         private float MaxHysteresisThresh, MinHysteresisThresh;
  36.         private int[,] EdgePoints;
  37.  
  38.         public CannyEdgeDetection(IImageProvider source)
  39.             : base(source)
  40.         {
  41.         }
  42.  
  43.         protected override void OnProcess(PixelRegion sourcePixelRegion, PixelRegion targetPixelRegion)
  44.         {
  45.             Width = (int)sourcePixelRegion.Bounds.Width;
  46.             Height = (int)sourcePixelRegion.Bounds.Height;
  47.  
  48.             MaxHysteresisThresh = 20F;
  49.             MinHysteresisThresh = 10F;
  50.  
  51.             EdgeMap = new int[Width, Height];
  52.             VisitedMap = new int[Width, Height];
  53.  
  54.             GreyImage = new int[Width, Height];
  55.  
  56.             int i, j;
  57.  
  58.             for (i = 0; i < Height; i++)
  59.             {
  60.                 for (j = 0; j < Width; j++)
  61.                 {
  62.                     var currentPixel = sourcePixelRegion.ImagePixels[i * Width + j];
  63.                     GreyImage[j, i] = (int)((((currentPixel & 0x00ff0000) >> 16) + ((currentPixel & 0x0000ff00) >> 8) + (currentPixel & 0x000000ff)) / 3.0);
  64.                 }
  65.             }
  66.  
  67.             Gradient = new float[Width, Height];
  68.             //NonMax = new float[Width, Height];
  69.             PostHysteresis = new int[Width, Height];
  70.  
  71.             DerivativeX = new float[Width, Height];
  72.             DerivativeY = new float[Width, Height];
  73.  
  74.             //Gaussian Filter Input Image
  75.             FilteredImage = GaussianFilter(GreyImage);
  76.  
  77.             //Sobel Masks
  78.             int[,] Dx = {{1,0,-1},
  79.                          {1,0,-1},
  80.                          {1,0,-1}};
  81.  
  82.             int[,] Dy = {{1,1,1},
  83.                          {0,0,0},
  84.                          {-1,-1,-1}};
  85.  
  86.             DerivativeX = Differentiate(FilteredImage, Dx);
  87.  
  88.             DerivativeY = Differentiate(FilteredImage, Dy);
  89.  
  90.             //Compute the gradient magnitude based on derivatives in x and y:
  91.             for (i = 0; i <= (Width - 1); i++)
  92.             {
  93.                 for (j = 0; j <= (Height - 1); j++)
  94.                 {
  95.                     Gradient[i, j] = (float)Math.Sqrt((DerivativeX[i, j] * DerivativeX[i, j]) + (DerivativeY[i, j] * DerivativeY[i, j]));
  96.                 }
  97.             }
  98.             // Perform Non maximum suppression:
  99.             NonMax = Gradient;
  100.  
  101.             //for (i = 0; i <= (Width - 1); i++)
  102.             //{
  103.             //    for (j = 0; j <= (Height - 1); j++)
  104.             //    {
  105.             //        NonMax[i, j] = Gradient[i, j];
  106.             //    }
  107.             //}
  108.  
  109.             int Limit = KernelSize / 2;
  110.             float Tangent;
  111.  
  112.             for (i = Limit; i <= (Width - Limit) - 1; i++)
  113.             {
  114.                 for (j = Limit; j <= (Height - Limit) - 1; j++)
  115.                 {
  116.                     if (DerivativeX[i, j] == 0)
  117.                         Tangent = 90F;
  118.                     else
  119.                         Tangent = (float)(Math.Atan(DerivativeY[i, j] / DerivativeX[i, j]) * 180 / Math.PI); //rad to degree
  120.  
  121.                     //Horizontal Edge
  122.                     if (((-22.5 < Tangent) && (Tangent <= 22.5)) || ((157.5 < Tangent) && (Tangent <= -157.5)))
  123.                     {
  124.                         if ((Gradient[i, j] < Gradient[i, j + 1]) || (Gradient[i, j] < Gradient[i, j - 1]))
  125.                             NonMax[i, j] = 0;
  126.                     }
  127.  
  128.                     //Vertical Edge
  129.                     if (((-112.5 < Tangent) && (Tangent <= -67.5)) || ((67.5 < Tangent) && (Tangent <= 112.5)))
  130.                     {
  131.                         if ((Gradient[i, j] < Gradient[i + 1, j]) || (Gradient[i, j] < Gradient[i - 1, j]))
  132.                             NonMax[i, j] = 0;
  133.                     }
  134.  
  135.                     //+45 Degree Edge
  136.                     if (((-67.5 < Tangent) && (Tangent <= -22.5)) || ((112.5 < Tangent) && (Tangent <= 157.5)))
  137.                     {
  138.                         if ((Gradient[i, j] < Gradient[i + 1, j - 1]) || (Gradient[i, j] < Gradient[i - 1, j + 1]))
  139.                             NonMax[i, j] = 0;
  140.                     }
  141.  
  142.                     //-45 Degree Edge
  143.                     if (((-157.5 < Tangent) && (Tangent <= -112.5)) || ((67.5 < Tangent) && (Tangent <= 22.5)))
  144.                     {
  145.                         if ((Gradient[i, j] < Gradient[i + 1, j + 1]) || (Gradient[i, j] < Gradient[i - 1, j - 1]))
  146.                             NonMax[i, j] = 0;
  147.                     }
  148.                 }
  149.             }
  150.  
  151.             //PostHysteresis = NonMax;
  152.             for (i = Limit; i <= (Width - Limit) - 1; i++)
  153.             {
  154.                 for (j = Limit; j <= (Height - Limit) - 1; j++)
  155.                 {
  156.                     PostHysteresis[i, j] = (int)NonMax[i, j];
  157.                 }
  158.             }
  159.  
  160.             //Find Max and Min in Post Hysterisis
  161.             float min, max;
  162.             min = 100;
  163.             max = 0;
  164.             for (i = Limit; i <= (Width - Limit) - 1; i++)
  165.                 for (j = Limit; j <= (Height - Limit) - 1; j++)
  166.                 {
  167.                     if (PostHysteresis[i, j] > max)
  168.                     {
  169.                         max = PostHysteresis[i, j];
  170.                     }
  171.  
  172.                     if ((PostHysteresis[i, j] < min) && (PostHysteresis[i, j] > 0))
  173.                     {
  174.                         min = PostHysteresis[i, j];
  175.                     }
  176.                 }
  177.             //GNH = new float[Width, Height];
  178.             //GNL = new float[Width, Height];
  179.  
  180.             EdgePoints = new int[Width, Height];
  181.  
  182.             for (i = Limit; i <= (Width - Limit) - 1; i++)
  183.             {
  184.                 for (j = Limit; j <= (Height - Limit) - 1; j++)
  185.                 {
  186.                     if (PostHysteresis[i, j] >= MaxHysteresisThresh)
  187.                     {
  188.                         EdgePoints[i, j] = 1;
  189.                         //GNH[i, j] = 255;
  190.                     }
  191.                     if ((PostHysteresis[i, j] < MaxHysteresisThresh) && (PostHysteresis[i, j] >= MinHysteresisThresh))
  192.                     {
  193.                         EdgePoints[i, j] = 2;
  194.                         //GNL[i, j] = 255;
  195.                     }
  196.                 }
  197.             }
  198.  
  199.             HysterisisThresholding(EdgePoints);
  200.  
  201.             uint white = 0xff000000 | (255 << 16) | (255 << 8) | 255;
  202.             uint black = 0xff000000 | (0 << 16) | (0 << 8) | 0;
  203.  
  204.             for (i = 0; i <= (Width - 1); i++)
  205.                 for (j = 0; j <= (Height - 1); j++)
  206.                 {
  207.                     targetPixelRegion.ImagePixels[j * Width + i] = EdgeMap[i, j] == 1 ? white : black;
  208.                 }
  209.  
  210.             return;
  211.         }
  212.  
  213.         private void GenerateGaussianKernel(int N, float S, out int Weight)
  214.         {
  215.             float Sigma = S;
  216.             float pi;
  217.             pi = (float)Math.PI;
  218.             int i, j;
  219.             int SizeofKernel = N;
  220.  
  221.             float[,] Kernel = new float[N, N];
  222.             GaussianKernel = new int[N, N];
  223.             float[,] OP = new float[N, N];
  224.             float D1, D2;
  225.  
  226.             D1 = 1 / (2 * pi * Sigma * Sigma);
  227.             D2 = 2 * Sigma * Sigma;
  228.  
  229.             float min = 1000;
  230.  
  231.             for (i = -SizeofKernel / 2; i <= SizeofKernel / 2; i++)
  232.             {
  233.                 for (j = -SizeofKernel / 2; j <= SizeofKernel / 2; j++)
  234.                 {
  235.                     Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = ((1 / D1) * (float)Math.Exp(-(i * i + j * j) / D2));
  236.                     if (Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] < min)
  237.                         min = Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
  238.                 }
  239.             }
  240.             int mult = (int)(1 / min);
  241.             int sum = 0;
  242.             if ((min > 0) && (min < 1))
  243.             {
  244.                 for (i = -SizeofKernel / 2; i <= SizeofKernel / 2; i++)
  245.                 {
  246.                     for (j = -SizeofKernel / 2; j <= SizeofKernel / 2; j++)
  247.                     {
  248.                         Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (float)Math.Round(Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] * mult, 0);
  249.                         GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (int)Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
  250.                         sum = sum + GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
  251.                     }
  252.                 }
  253.             }
  254.             else
  255.             {
  256.                 sum = 0;
  257.                 for (i = -SizeofKernel / 2; i <= SizeofKernel / 2; i++)
  258.                 {
  259.                     for (j = -SizeofKernel / 2; j <= SizeofKernel / 2; j++)
  260.                     {
  261.                         Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (float)Math.Round(Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j], 0);
  262.                         GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j] = (int)Kernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
  263.                         sum = sum + GaussianKernel[SizeofKernel / 2 + i, SizeofKernel / 2 + j];
  264.                     }
  265.                 }
  266.             }
  267.             //Normalizing kernel Weight
  268.             Weight = sum;
  269.  
  270.             return;
  271.         }
  272.  
  273.         private int[,] GaussianFilter(int[,] Data)
  274.         {
  275.             GenerateGaussianKernel(KernelSize, Sigma, out KernelWeight);
  276.  
  277.             int[,] Output = new int[Width, Height];
  278.             int i, j, k, l;
  279.             int Limit = KernelSize / 2;
  280.  
  281.             float Sum = 0;
  282.  
  283.             Output = Data; // Removes Unwanted Data Omission due to kernel bias while convolution
  284.  
  285.             for (i = Limit; i <= ((Width - 1) - Limit); i++)
  286.             {
  287.                 for (j = Limit; j <= ((Height - 1) - Limit); j++)
  288.                 {
  289.                     Sum = 0;
  290.                     for (k = -Limit; k <= Limit; k++)
  291.                     {
  292.                         for (l = -Limit; l <= Limit; l++)
  293.                         {
  294.                             Sum = Sum + ((float)Data[i + k, j + l] * GaussianKernel[Limit + k, Limit + l]);
  295.                         }
  296.                     }
  297.                     Output[i, j] = (int)(Math.Round(Sum / (float)KernelWeight));
  298.                 }
  299.             }
  300.  
  301.             return Output;
  302.         }
  303.  
  304.         private float[,] Differentiate(int[,] Data, int[,] Filter)
  305.         {
  306.             int i, j, k, l, Fh, Fw;
  307.  
  308.             Fw = Filter.GetLength(0);
  309.             Fh = Filter.GetLength(1);
  310.             float sum = 0;
  311.             float[,] Output = new float[Width, Height];
  312.  
  313.             for (i = Fw / 2; i <= (Width - Fw / 2) - 1; i++)
  314.             {
  315.                 for (j = Fh / 2; j <= (Height - Fh / 2) - 1; j++)
  316.                 {
  317.                     sum = 0;
  318.                     for (k = -Fw / 2; k <= Fw / 2; k++)
  319.                     {
  320.                         for (l = -Fh / 2; l <= Fh / 2; l++)
  321.                         {
  322.                             sum = sum + Data[i + k, j + l] * Filter[Fw / 2 + k, Fh / 2 + l];
  323.                         }
  324.                     }
  325.                     Output[i, j] = sum;
  326.                 }
  327.             }
  328.             return Output;
  329.         }
  330.  
  331.         private void HysterisisThresholding(int[,] Edges)
  332.         {
  333.             int i, j;
  334.             int Limit = KernelSize / 2;
  335.  
  336.             for (i = Limit; i <= (Width - 1) - Limit; i++)
  337.                 for (j = Limit; j <= (Height - 1) - Limit; j++)
  338.                 {
  339.                     if (Edges[i, j] == 1)
  340.                     {
  341.                         EdgeMap[i, j] = 1;
  342.                     }
  343.                 }
  344.  
  345.             for (i = Limit; i <= (Width - 1) - Limit; i++)
  346.             {
  347.                 for (j = Limit; j <= (Height - 1) - Limit; j++)
  348.                 {
  349.                     if (Edges[i, j] == 1)
  350.                     {
  351.                         EdgeMap[i, j] = 1;
  352.                         Travers(i, j);
  353.                         VisitedMap[i, j] = 1;
  354.                     }
  355.                 }
  356.             }
  357.  
  358.             return;
  359.         }
  360.  
  361.         private void Travers(int X, int Y)
  362.         {
  363.             if (VisitedMap[X, Y] == 1)
  364.             {
  365.                 return;
  366.             }
  367.  
  368.             //1
  369.             if (EdgePoints[X + 1, Y] == 2)
  370.             {
  371.                 EdgeMap[X + 1, Y] = 1;
  372.                 VisitedMap[X + 1, Y] = 1;
  373.                 Travers(X + 1, Y);
  374.                 return;
  375.             }
  376.             //2
  377.             if (EdgePoints[X + 1, Y - 1] == 2)
  378.             {
  379.                 EdgeMap[X + 1, Y - 1] = 1;
  380.                 VisitedMap[X + 1, Y - 1] = 1;
  381.                 Travers(X + 1, Y - 1);
  382.                 return;
  383.             }
  384.  
  385.             //3
  386.  
  387.             if (EdgePoints[X, Y - 1] == 2)
  388.             {
  389.                 EdgeMap[X, Y - 1] = 1;
  390.                 VisitedMap[X, Y - 1] = 1;
  391.                 Travers(X, Y - 1);
  392.                 return;
  393.             }
  394.  
  395.             //4
  396.  
  397.             if (EdgePoints[X - 1, Y - 1] == 2)
  398.             {
  399.                 EdgeMap[X - 1, Y - 1] = 1;
  400.                 VisitedMap[X - 1, Y - 1] = 1;
  401.                 Travers(X - 1, Y - 1);
  402.                 return;
  403.             }
  404.             //5
  405.             if (EdgePoints[X - 1, Y] == 2)
  406.             {
  407.                 EdgeMap[X - 1, Y] = 1;
  408.                 VisitedMap[X - 1, Y] = 1;
  409.                 Travers(X - 1, Y);
  410.                 return;
  411.             }
  412.             //6
  413.             if (EdgePoints[X - 1, Y + 1] == 2)
  414.             {
  415.                 EdgeMap[X - 1, Y + 1] = 1;
  416.                 VisitedMap[X - 1, Y + 1] = 1;
  417.                 Travers(X - 1, Y + 1);
  418.                 return;
  419.             }
  420.             //7
  421.             if (EdgePoints[X, Y + 1] == 2)
  422.             {
  423.                 EdgeMap[X, Y + 1] = 1;
  424.                 VisitedMap[X, Y + 1] = 1;
  425.                 Travers(X, Y + 1);
  426.                 return;
  427.             }
  428.             //8
  429.  
  430.             if (EdgePoints[X + 1, Y + 1] == 2)
  431.             {
  432.                 EdgeMap[X + 1, Y + 1] = 1;
  433.                 VisitedMap[X + 1, Y + 1] = 1;
  434.                 Travers(X + 1, Y + 1);
  435.                 return;
  436.             }
  437.  
  438.             //VisitedMap[X, Y] = 1;
  439.             return;
  440.         }
  441.     }
  442. }

Testing

The Test Apps for Viewing Custom Filters allow you to cycle through a number of different custom effects (including CannyEdgeDetection) 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 CannyEdgeDetection
Lumia 920 0-1 FPS

The CannyEdgeDetection class runs 0-1 FPS (Frames Per Second).

Code walkthrough

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 CannyEdgeDetection(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.     }
  22. }

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 detect edges in an image using Canny Edge Detection. 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 2 March 2014, at 17:15.
289 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.

×