namespace VisualMath.Accord.Imaging { using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using AForge.Imaging; using AForge.Imaging.Filters; using AForge; /// /// Harris Corners Detector. /// /// /// This class implements the Harris corners detector. /// Sample usage: /// /// // create corners detector's instance /// HarrisCornersDetector hcd = new HarrisCornersDetector( ); /// // process image searching for corners /// Point[] corners = hcd.ProcessImage( image ); /// // process points /// foreach ( Point corner in corners ) /// { /// // ... /// } /// /// /// /// References: /// /// /// P. D. Kovesi. MATLAB and Octave Functions for Computer Vision and Image Processing. /// School of Computer Science and Software Engineering, The University of Western Australia. /// Available in: http://www.csse.uwa.edu.au/~pk/Research/MatlabFns/Spatial/harris.m /// /// C.G. Harris and M.J. Stephens. "A combined corner and edge detector", /// Proceedings Fourth Alvey Vision Conference, Manchester. /// pp 147-151, 1988. /// /// Alison Noble, "Descriptions of Image Surfaces", PhD thesis, Department /// of Engineering Science, Oxford University 1989, p45. /// /// /// /// /// /// /// public class HarrisCornersDetector : ICornersDetector { private float k = 0.04f; private float threshold = 1000f; private double sigma = 1.4; private int r = 3; /// /// Harris parameter k. Default value is 0.04. /// public float K { get { return k; } set { k = value; } } /// /// Harris threshold. Default value is 1000. /// public float Threshold { get { return threshold; } set { threshold = value; } } /// /// Gaussian smoothing sigma. Default value is 1.4. /// public double Sigma { get { return sigma; } set { sigma = value; } } /// /// Non-maximum suppression window radius. Default value is 3. /// public int Suppression { get { return r; } set { r = value; } } /// /// Initializes a new instance of the class. /// public HarrisCornersDetector() { } /// /// Initializes a new instance of the class. /// public HarrisCornersDetector(float k) : this() { this.k = k; } /// /// Initializes a new instance of the class. /// public HarrisCornersDetector(float k, float threshold) : this() { this.k = k; this.threshold = threshold; } /// /// Initializes a new instance of the class. /// public HarrisCornersDetector(float k, float threshold, double sigma) : this() { this.k = k; this.threshold = threshold; this.sigma = sigma; } /// /// Process image looking for corners. /// /// /// Source image data to process. /// /// Returns list of found corners (X-Y coordinates). /// /// /// The source image has incorrect pixel format. /// /// public List ProcessImage(UnmanagedImage image) { // check image format if ( (image.PixelFormat != PixelFormat.Format8bppIndexed) && (image.PixelFormat != PixelFormat.Format24bppRgb) && (image.PixelFormat != PixelFormat.Format32bppRgb) && (image.PixelFormat != PixelFormat.Format32bppArgb) ) { throw new UnsupportedImageFormatException("Unsupported pixel format of the source image."); } // make sure we have grayscale image UnmanagedImage grayImage = null; if (image.PixelFormat == PixelFormat.Format8bppIndexed) { grayImage = image; } else { // create temporary grayscale image grayImage = Grayscale.CommonAlgorithms.BT709.Apply(image); } // get source image size int width = grayImage.Width; int height = grayImage.Height; int stride = grayImage.Stride; int offset = stride - width; // 1. Calculate partial differences UnmanagedImage diffx = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed); UnmanagedImage diffy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed); UnmanagedImage diffxy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed); unsafe { // Compute dx and dy byte* src = (byte*)grayImage.ImageData.ToPointer(); byte* dx = (byte*)diffx.ImageData.ToPointer(); byte* dy = (byte*)diffy.ImageData.ToPointer(); byte* dxy = (byte*)diffxy.ImageData.ToPointer(); // for each line for (int y = 0; y < height; y++) { // for each pixel for (int x = 0; x < width; x++, src++, dx++, dy++) { // TODO: Place those verifications // outside the innermost loop if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { *dx = *dy = 0; continue; } int h = -(src[-stride - 1] + src[-1] + src[stride - 1]) + (src[-stride + 1] + src[+1] + src[stride + 1]); *dx = (byte)(h > 255 ? 255 : h < 0 ? 0 : h); int v = -(src[-stride - 1] + src[-stride] + src[-stride + 1]) + (src[+stride - 1] + src[+stride] + src[+stride + 1]); *dy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v); } src += offset; dx += offset; dy += offset; } // Compute dxy dx = (byte*)diffx.ImageData.ToPointer(); dxy = (byte*)diffxy.ImageData.ToPointer(); // for each line for (int y = 0; y < height; y++) { // for each pixel for (int x = 0; x < width; x++, dx++, dxy++) { if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { *dxy = 0; continue; } int v = -(dx[-stride - 1] + dx[-stride] + dx[-stride + 1]) + (dx[+stride - 1] + dx[+stride] + dx[+stride + 1]); *dxy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v); } dx += offset; dxy += offset; } } // 2. Smooth the diff images if (sigma > 0.0) { GaussianBlur blur = new GaussianBlur(sigma); blur.ApplyInPlace(diffx); blur.ApplyInPlace(diffy); blur.ApplyInPlace(diffxy); } // 3. Compute Harris Corner Response float[,] H = new float[height, width]; unsafe { byte* ptrA = (byte*)diffx.ImageData.ToPointer(); byte* ptrB = (byte*)diffy.ImageData.ToPointer(); byte* ptrC = (byte*)diffxy.ImageData.ToPointer(); float M, A, B, C; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { A = *(ptrA++); B = *(ptrB++); C = *(ptrC++); // Harris corner measure M = (A * B - C * C) - (k * ((A + B) * (A + B))); if (M > threshold) H[y, x] = M; else H[y, x] = 0; } ptrA += offset; ptrB += offset; ptrC += offset; } } // Free resources diffx.Dispose(); diffy.Dispose(); diffxy.Dispose(); if (image.PixelFormat != PixelFormat.Format8bppIndexed) grayImage.Dispose(); // 4. Suppress non-maximum points List cornersList = new List(); // for each row for (int y = r, maxY = height - r; y < maxY; y++) { // for each pixel for (int x = r, maxX = width - r; x < maxX; x++) { float currentValue = H[y, x]; // for each windows' row for (int i = -r; (currentValue != 0) && (i <= r); i++) { // for each windows' pixel for (int j = -r; j <= r; j++) { if (H[y + i, x + j] > currentValue) { currentValue = 0; break; } } } // check if this point is really interesting if (currentValue != 0) { cornersList.Add(new IntPoint(x, y)); } } } return cornersList; } /// /// Process image looking for corners. /// /// /// Source image data to process. /// /// Returns list of found corners (X-Y coordinates). /// /// /// The source image has incorrect pixel format. /// /// public List ProcessImage(BitmapData imageData) { return ProcessImage(new UnmanagedImage(imageData)); } /// /// Process image looking for corners. /// /// /// Source image data to process. /// /// Returns list of found corners (X-Y coordinates). /// /// /// The source image has incorrect pixel format. /// /// public List ProcessImage(Bitmap image) { // check image format if ( (image.PixelFormat != PixelFormat.Format8bppIndexed) && (image.PixelFormat != PixelFormat.Format24bppRgb) && (image.PixelFormat != PixelFormat.Format32bppRgb) && (image.PixelFormat != PixelFormat.Format32bppArgb) ) { throw new UnsupportedImageFormatException("Unsupported pixel format of the source"); } // lock source image BitmapData imageData = image.LockBits( new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat); List corners; try { // process the image corners = ProcessImage(new UnmanagedImage(imageData)); } finally { // unlock image image.UnlockBits(imageData); } return corners; } /// /// Process image looking for corners. /// /// /// Source image data to process. /// /// Returns list of found corners (X-Y coordinates). /// /// /// The source image has incorrect pixel format. /// /// public List ProcessImageMethod(Bitmap image) { // check image format if ( (image.PixelFormat != PixelFormat.Format8bppIndexed) && (image.PixelFormat != PixelFormat.Format24bppRgb) && (image.PixelFormat != PixelFormat.Format32bppRgb) && (image.PixelFormat != PixelFormat.Format32bppArgb) ) { throw new UnsupportedImageFormatException("Unsupported pixel format of the source"); } // lock source image BitmapData imageData = image.LockBits( new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat); List corners;// = new List(); try { // process the image corners = ProcessImageMethod(new UnmanagedImage(imageData)); } finally { // unlock image image.UnlockBits(imageData); } return corners; } /// /// Process image looking for corners. /// /// /// Source image data to process. /// /// Returns list of found corners (X-Y coordinates). /// /// /// The source image has incorrect pixel format. /// /// public List ProcessImageMethod(UnmanagedImage image) { // check image format if ( (image.PixelFormat != PixelFormat.Format8bppIndexed) && (image.PixelFormat != PixelFormat.Format24bppRgb) && (image.PixelFormat != PixelFormat.Format32bppRgb) && (image.PixelFormat != PixelFormat.Format32bppArgb) ) { throw new UnsupportedImageFormatException("Unsupported pixel format of the source image."); } // make sure we have grayscale image UnmanagedImage grayImage = null; if (image.PixelFormat == PixelFormat.Format8bppIndexed) { grayImage = image; } else { // create temporary grayscale image grayImage = Grayscale.CommonAlgorithms.BT709.Apply(image); } // get source image size int width = grayImage.Width; int height = grayImage.Height; int stride = grayImage.Stride; int offset = stride - width; // 1. Calculate partial differences UnmanagedImage diffx = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed); UnmanagedImage diffy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed); UnmanagedImage diffxy = UnmanagedImage.Create(width, height, PixelFormat.Format8bppIndexed); unsafe { // Compute dx and dy byte* src = (byte*)grayImage.ImageData.ToPointer(); byte* dx = (byte*)diffx.ImageData.ToPointer(); byte* dy = (byte*)diffy.ImageData.ToPointer(); byte* dxy = (byte*)diffxy.ImageData.ToPointer(); // for each line for (int y = 0; y < height; y++) { // for each pixel for (int x = 0; x < width; x++, src++, dx++, dy++) { // TODO: Place those verifications // outside the innermost loop if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { *dx = *dy = 0; continue; } int h = -(src[-stride - 1] + src[-1] + src[stride - 1]) + (src[-stride + 1] + src[+1] + src[stride + 1]); *dx = (byte)(h > 255 ? 255 : h < 0 ? 0 : h); int v = -(src[-stride - 1] + src[-stride] + src[-stride + 1]) + (src[+stride - 1] + src[+stride] + src[+stride + 1]); *dy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v); } src += offset; dx += offset; dy += offset; } // Compute dxy dx = (byte*)diffx.ImageData.ToPointer(); dxy = (byte*)diffxy.ImageData.ToPointer(); // for each line for (int y = 0; y < height; y++) { // for each pixel for (int x = 0; x < width; x++, dx++, dxy++) { if (x == 0 || x == width - 1 || y == 0 || y == height - 1) { *dxy = 0; continue; } int v = -(dx[-stride - 1] + dx[-stride] + dx[-stride + 1]) + (dx[+stride - 1] + dx[+stride] + dx[+stride + 1]); *dxy = (byte)(v > 255 ? 255 : v < 0 ? 0 : v); } dx += offset; dxy += offset; } } // 2. Smooth the diff images if (sigma > 0.0) { GaussianBlur blur = new GaussianBlur(sigma); blur.ApplyInPlace(diffx); blur.ApplyInPlace(diffy); blur.ApplyInPlace(diffxy); } // 3. Compute Harris Corner Response float[,] H = new float[height, width]; unsafe { byte* ptrA = (byte*)diffx.ImageData.ToPointer(); byte* ptrB = (byte*)diffy.ImageData.ToPointer(); byte* ptrC = (byte*)diffxy.ImageData.ToPointer(); float M, A, B, C; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { A = *(ptrA++); B = *(ptrB++); C = *(ptrC++); // Harris corner measure M = (A * B - C * C) - (k * ((A + B) * (A + B))); if (M > threshold) H[y, x] = M; else H[y, x] = 0; } ptrA += offset; ptrB += offset; ptrC += offset; } } // Free resources diffx.Dispose(); diffy.Dispose(); diffxy.Dispose(); if (image.PixelFormat != PixelFormat.Format8bppIndexed) grayImage.Dispose(); // 4. Suppress non-maximum points List cornersList = new List(); // for each row for (int y = r, maxY = height - r; y < maxY; y++) { // for each pixel for (int x = r, maxX = width - r; x < maxX; x++) { float currentValue = H[y, x]; // for each windows' row for (int i = -r; (currentValue != 0) && (i <= r); i++) { // for each windows' pixel for (int j = -r; j <= r; j++) { if (H[y + i, x + j] > currentValue) { currentValue = 0; break; } } } // check if this point is really interesting if (currentValue != 0) { cornersList.Add(new Point(x, y)); } } } return cornersList; } } }