Skip to content

Instantly share code, notes, and snippets.

@goh-chunlin
Last active March 12, 2024 22:23
Show Gist options
  • Save goh-chunlin/50e9f6160052a5983fccf22b50593ab1 to your computer and use it in GitHub Desktop.
Save goh-chunlin/50e9f6160052a5983fccf22b50593ab1 to your computer and use it in GitHub Desktop.
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;
}
}
}
@MicaelDID
Copy link

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).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment