边缘检测就是找到图像中边缘像素点的过程,从而生成一副边缘图。一般来说边缘检测分为3步:
(1) 基于各种原理和方法找到潜在的边缘点;
(2) 选取阈值,生成二值边缘图;
(3) 有些算法还要进行边缘细化、连接等后续处理
边缘是图像中灰度值不连续的结果,这种不连续性常可以利用求导数的方法方便的检测到,一般常用已接到数和二阶导数来检测边缘。图像中目标的边缘可通过求取她们的导数来确定。导数可用微分算子来计算,而数字图像中求取导数是利用差分近似微分来进行的。在一阶导数方法中,对图像中两个正交方向分别求偏导数,然后对这两个偏导数取不同的范数做为边缘强度,即可得到边缘图像。在这里,我们最常用的以2为模的范数形式,即对这两个偏导数去平方和,再开平方。在实际计算中,求偏导数常常采用的是小区域模进行卷积来近似计算的一阶导数模板算子最常用的方法有Roberts算子法,Prewitt算子法和Sobel算子法。
在这仅介绍一阶导数模板算子,以下是Roberts、Prewitt、Sobel三种算子的模板
Roberts算子
Prewitt算子
Sobel算子
具体算法实现(代码中有形态学边缘检测部分,下篇博文再讲)
/// <summary>
/// 边缘检测
/// </summary>
/// <param name="srcBmp">原始图像</param>
/// <param name="edgeDetectors">边缘检测算子</param>
/// <param name="dstBmp">目标图像</param>
/// <param name="mask">模板</param>
/// <param name="T">阈值,当算子为拉普拉斯时有用</param>
/// <returns>处理成功 true 失败 false</returns>
public static bool Edge(Bitmap srcBmp, EdgeDetectors edgeDetectors, out Bitmap dstBmp, int[] mask = null,int T=0) {
if (srcBmp == null) {
dstBmp = null;
return false;
}
int[] template = new int[25];
if (mask != null) { template = mask; }
Bitmap tempSrcBmp = new Bitmap(srcBmp);//为形态学边缘检测所用
dstBmp = new Bitmap(srcBmp);
BitmapData bmpDataSrc = srcBmp.LockBits(new Rectangle(0, 0, srcBmp.Width, srcBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData bmpDataDst = dstBmp.LockBits(new Rectangle(0, 0, dstBmp.Width, dstBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
//double[] laplacianArray = new double[bmpDataSrc.Stride * bmpDataSrc.Height];//存储拉普拉斯算子中间结果
unsafe {
byte* ptrSrc = (byte*)bmpDataSrc.Scan0;
byte* ptrDst = (byte*)bmpDataDst.Scan0;
double gradX, gradY, grad;
switch (edgeDetectors) {
case EdgeDetectors.Roberts://Roberts算子
for (int i = 0; i < srcBmp.Height; i++) {
for (int j = 0; j < srcBmp.Width; j++) {
gradX = ptrSrc[i * bmpDataSrc.Stride + j * 3] - ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3];
gradY = ptrSrc[i * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] - ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + j * 3];
grad = Math.Sqrt(gradX * gradX + gradY * gradY);
grad = grad > 255 ? 255 : grad;
ptrDst[i * bmpDataDst.Stride + j * 3] = ptrDst[i * bmpDataDst.Stride + j * 3 + 1] = ptrDst[i * bmpDataDst.Stride + j * 3 + 2] = (byte)grad;
}
}
break;
case EdgeDetectors.Prewitt://prewitt算子
for (int i = 0; i < srcBmp.Height; i++) {
for (int j = 0; j < srcBmp.Width; j++) {
gradX = ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] +
ptrSrc[i * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] +
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] -
ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] -
ptrSrc[i * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] -
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3];
gradY = ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] +
ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + j * 3] +
ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] -
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] -
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + j * 3] -
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3];
grad = Math.Sqrt(gradX * gradX + gradY * gradY);
grad = grad > 255 ? 255 : grad;
ptrDst[i * bmpDataDst.Stride + j * 3] = ptrDst[i * bmpDataDst.Stride + j * 3 + 1] = ptrDst[i * bmpDataDst.Stride + j * 3 + 2] = (byte)grad;
}
}
break;
case EdgeDetectors.Sobel://solbel算子
for (int i = 0; i < srcBmp.Height; i++) {
for (int j = 0; j < srcBmp.Width; j++) {
gradX = ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] +
2 * ptrSrc[i * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] +
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] -
ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] -
2 * ptrSrc[i * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] -
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3];
gradY = ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] +
2 * ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + j * 3] +
ptrSrc[Math.Abs(i - 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3] -
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + Math.Abs(j - 1) % srcBmp.Width * 3] -
2 * ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + j * 3] -
ptrSrc[(i + 1) % srcBmp.Height * bmpDataSrc.Stride + (j + 1) % srcBmp.Width * 3];
grad = Math.Sqrt(gradX * gradX + gradY * gradY);
grad = grad > 255 ? 255 : grad;
ptrDst[i * bmpDataDst.Stride + j * 3] = ptrDst[i * bmpDataDst.Stride + j * 3 + 1] = ptrDst[i * bmpDataDst.Stride + j * 3 + 2] = (byte)grad;
}
}
break;
case EdgeDetectors.Morphology://形态学边缘检测
Bitmap erodeBmp = null, expBmp = null;
ImageFormats f = GetImageFormat(tempSrcBmp);
Erode(tempSrcBmp, ScanStruction.Custom, f, out erodeBmp, mask);
Expansion(tempSrcBmp, ScanStruction.Custom, f, out expBmp, mask);
BitmapData erodeBmpData = erodeBmp.LockBits(new Rectangle(0, 0, erodeBmp.Width, erodeBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData expBmpData = expBmp.LockBits(new Rectangle(0, 0, expBmp.Width, expBmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
unsafe {
int tempB, tempG, tempR;
byte* ptrErode = (byte*)erodeBmpData.Scan0;
byte* ptrExp = (byte*)expBmpData.Scan0;
for (int i = 0; i < expBmp.Height; i++) {
for (int j = 0; j < expBmp.Width; j++) {
tempB = (ptrExp[i * expBmpData.Stride + j * 3] - ptrErode[i * expBmpData.Stride + j * 3])/2;
tempG = (ptrExp[i * expBmpData.Stride + j * 3+1] - ptrErode[i * expBmpData.Stride + j * 3+1])/2;
tempR = (ptrExp[i * expBmpData.Stride + j * 3+2] - ptrErode[i * expBmpData.Stride + j * 3+2])/2;
tempB = tempB < 0 ? 0 : tempB;
tempG = tempG < 0 ? 0 : tempG;
tempR = tempR < 0 ? 0 : tempR;
ptrDst[i * bmpDataDst.Stride + j * 3] = (byte)tempB;
ptrDst[i * bmpDataDst.Stride + j * 3+1] = (byte)tempG;
ptrDst[i * bmpDataDst.Stride + j * 3+2] = (byte)tempR;
}
}
}
break;
default:
break;
}
}
srcBmp.UnlockBits(bmpDataSrc);
dstBmp.UnlockBits(bmpDataDst);
return true;
}
处理结果