Last active
March 12, 2024 22:23
-
-
Save goh-chunlin/50e9f6160052a5983fccf22b50593ab1 to your computer and use it in GitHub Desktop.
Edge Detection with Sobel-Feldman Operator in C# (https://cuteprogramming.wordpress.com/2022/09/24/edge-detection-with-sobel-feldman-operator-in-c/)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Drawing; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Drawing.Imaging; | |
using System.Runtime.InteropServices; | |
namespace Lunar.SobelDetection | |
{ | |
public class SobelDetection | |
{ | |
public Bitmap ConvolutionFilter(Bitmap sourceImage) | |
{ | |
double[,] xSobel = new double[,] | |
{ | |
{ -1, 0, 1 }, | |
{ -2, 0, 2 }, | |
{ -1, 0, 1 } | |
}; | |
double[,] ySobel = new double[,] | |
{ | |
{ 1, 2, 1 }, | |
{ 0, 0, 0 }, | |
{ -1, -2, -1 } | |
}; | |
//Image dimensions stored in variables for convenience | |
int width = sourceImage.Width; | |
int height = sourceImage.Height; | |
//Lock source image bits into system memory | |
BitmapData srcData = sourceImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); | |
//Get the total number of bytes in our image - 32 bytes per pixel x image width x image height -> for 32bpp images | |
int bytes = srcData.Stride * srcData.Height; | |
//Create byte arrays to hold pixel information of our image | |
byte[] pixelBuffer = new byte[bytes]; | |
byte[] resultBuffer = new byte[bytes]; | |
//Get the address of the first pixel data | |
IntPtr srcScan0 = srcData.Scan0; | |
//Copy image data to one of the byte arrays | |
Marshal.Copy(srcScan0, pixelBuffer, 0, bytes); | |
//Unlock bits from system memory -> we have all our needed info in the array | |
sourceImage.UnlockBits(srcData); | |
//Convert our image to grayscale | |
float rgb = 0; | |
for (int i = 0; i < pixelBuffer.Length; i += 4) | |
{ | |
rgb = pixelBuffer[i] * .21f; | |
rgb += pixelBuffer[i + 1] * .72f; | |
rgb += pixelBuffer[i + 2] * .071f; | |
pixelBuffer[i] = (byte)rgb; | |
pixelBuffer[i + 1] = pixelBuffer[i]; | |
pixelBuffer[i + 2] = pixelBuffer[i]; | |
pixelBuffer[i + 3] = 255; | |
} | |
//Create variable for pixel data for each kernel | |
double xg = 0.0; | |
double yg = 0.0; | |
double gt = 0.0; | |
//This is how much our center pixel is offset from the border of our kernel | |
//Sobel is 3x3, so center is 1 pixel from the kernel border | |
int filterOffset = 1; | |
int calcOffset = 0; | |
int byteOffset = 0; | |
//Start with the pixel that is offset 1 from top and 1 from the left side | |
//this is so entire kernel is on our image | |
for (int OffsetY = filterOffset; OffsetY < height - filterOffset; OffsetY++) | |
{ | |
for (int OffsetX = filterOffset; OffsetX < width - filterOffset; OffsetX++) | |
{ | |
//reset rgb values to 0 | |
xg = yg = 0; | |
gt = 0.0; | |
//position of the kernel center pixel | |
byteOffset = OffsetY * srcData.Stride + OffsetX * 4; | |
//kernel calculations | |
for (int filterY = -filterOffset; filterY <= filterOffset; filterY++) | |
{ | |
for (int filterX = -filterOffset; filterX <= filterOffset; filterX++) | |
{ | |
calcOffset = byteOffset + filterX * 4 + filterY * srcData.Stride; | |
xg += (double)(pixelBuffer[calcOffset + 1]) * xSobel[filterY + filterOffset, filterX + filterOffset]; | |
yg += (double)(pixelBuffer[calcOffset + 1]) * ySobel[filterY + filterOffset, filterX + filterOffset]; | |
} | |
} | |
//total rgb values for this pixel | |
gt = Math.Sqrt((xg * xg) + (yg * yg)); | |
//set limits, bytes can hold values from 0 up to 255; | |
if (gt > 255) gt = 255; | |
else if (gt < 0) gt = 0; | |
//set new data in the other byte array for our image data | |
resultBuffer[byteOffset] = (byte)(gt); | |
resultBuffer[byteOffset + 1] = (byte)(gt); | |
resultBuffer[byteOffset + 2] = (byte)(gt); | |
resultBuffer[byteOffset + 3] = 255; | |
} | |
} | |
//Create new bitmap which will hold the processed data | |
Bitmap resultImage = new Bitmap(width, height); | |
//Lock bits into system memory | |
BitmapData resultData = resultImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); | |
//Copy from byte array that holds processed data to bitmap | |
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); | |
//Unlock bits from system memory | |
resultImage.UnlockBits(resultData); | |
//Return processed image | |
return resultImage; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Just a small comment (the code works well) in the article it says Luminosity = 0.21R + 0.72G + 0.07B (according to GIMP) but in the code its using 0,071 (without any explanation!) and also with the Marshal.Copy you get the bytes reversed so its B, G, R and last Alpha so I suppose line 57 and 59 should probably be the other way around.
What I found funny is to make the method use these arguments instead (and make sure they are within 0d-1d)
Bitmap sourceImage, double rr = 1d, double gg = 1d, double bb = 1d
then multiply them respectively at line 109-111 thus creating a result that instead of black/white become black/green or any other desired result (and without using the rr / gg / bb arguments the method works exactly like before).