图像处理之图像边缘检测算法
目录
1 图像边缘检测算法简介
2 Sobel边缘检测
3 经典的Canny边缘检测算法
4 演示Demo
4.1 开发环境
4.2 功能介绍
4.3 下载地址
参考
1 图像边缘检测算法简介
图像边缘检测是计算机视觉和图像处理中的基本问题,主要目的是提取图像中明暗变化明显的边缘细节信息。
图像边缘检测算法有很多,包括传统的模板算子(Sobel、Roberts、Prewitt、Laplace)、形态学边缘检测、经典的Canny边缘检测及基于深度学习的边缘检测算法等。
本文主要介绍Sobel模板算子和经典的Canny边缘检测算法。
2 Sobel边缘检测
Sobel模板算子是 Irwin Sobel 在1968年发表的论文 An Isotropic 3x3 Image Gradient Operator 中提出的一种一阶导数模板算子,用于计算图像灰度函数的近似梯度。
Sobel模板算子如下:
其中表示水平方向的卷积模板,
表示垂直方向的卷积模板。
对于图像中任何一点的像素P(i,j),使用水平和垂直卷积模板分别对图像进行卷积操作,得到水平梯度GX和垂直梯度GY,则梯度幅度计算如下:
完整梯度幅度计算公式如下:
Sobel边缘检测结果计算如下
其中,255白色表示边缘,0黑色表示背景。
C语言实现Sobel边缘检测算法代码如下:
/*************************************************
功 能:图像Sobel边缘检测
参 数:srcData - [输入/输出] 原始图像,格式为32位BGRA格式,执行后修为结果图像
width - [输入] 原始图像宽度
height - [输入] 原始图像高度
stride - [输入] 原始图像的Stride(也就是行字节数width*4)
threshold - [输入] 阈值
返 回: 0-成功,其他-失败.
*************************************************/
int sobel(unsigned char *srcData, int width, int height, int stride, int threshold)
{
int ret = 0;
unsigned char *dstData = (unsigned char*)malloc(sizeof(unsigned char) * height * stride);
memset(dstData, 255, sizeof(unsigned char) * height * stride);
int x, y, i, k, pos;
int hValue, vValue, value;
unsigned char *pSrcL0;
unsigned char *pSrcL1;
unsigned char *pSrcL2;
unsigned char *pDstL;
unsigned char SqrtValue[65026];
pSrcL0 = srcData;
pSrcL1 = srcData + stride;
pSrcL2 = srcData + stride * 2;
pDstL = dstData + stride;
for (i = 0; i < 65026; i++)
{
SqrtValue[i] = (unsigned char)(sqrt((float)i) < threshold ? 0 : 255);
}
for (y = 1; y < height - 1; y++)
{
for (x = 1; x < width - 1; x++)
{
pos = x * 4;
hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
k = hValue * hValue + vValue * vValue;
k = MIN2(k, 65025);
pDstL[pos] = SqrtValue[k];
pos++;
hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
k = hValue * hValue + vValue * vValue;
k = MIN2(k, 65025);
pDstL[pos] = SqrtValue[k];
pos++;
hValue = (-pSrcL0[pos - 4] + pSrcL0[pos + 4] - 2 * pSrcL1[pos - 4] + 2 * pSrcL1[pos + 4] - pSrcL2[pos - 4] + pSrcL2[pos + 4]);
vValue = (pSrcL0[pos - 4] + 2 * pSrcL0[pos] + pSrcL0[pos + 4] - pSrcL2[pos - 4] - 2 * pSrcL2[pos] - pSrcL2[pos + 4]);
k = hValue * hValue + vValue * vValue;
k = MIN2(k, 65025);
pDstL[pos] = SqrtValue[k];
}
pSrcL0 += stride;
pSrcL1 += stride;
pSrcL2 += stride;
pDstL += stride;
}
memcpy(srcData, dstData, sizeof(unsigned char) * height * stride);
free(dstData);
return ret;
}
3 经典的Canny边缘检测算法
Canny边缘检测是 John Canny 在1986年首次提出的一种改进的边缘检测方法。该方法主要通过图像信号函数的极大值来判断图像的边缘像素点,与基本的Sobel模板算子等相比,其具有低错误率、高定位性等优点,因此被广泛应用。
(1)高斯滤波平滑处理
由于图像中经常包含一些高斯噪声,因此,在边缘检测前,要先用高斯滤波器对其进行滤波。为了方便,这里使用如下高斯滤波器模板
(2)梯度计算
使用一阶导数算子(一般用Sobel模板算子)计算灰度图像中每个像素点在水平和垂直方向上的导数GX、GY,得出梯度向量(GX,GY),最后得到该像素点的梯度幅度G和相位角D
(3)非极大值抑制
对于上面计算得到的梯度值,其实是一个粗边缘信息,可以通过非极大值抑制去掉一些非边缘信息。这里将当前像素的梯度值与其在梯度方向上的邻域像素的梯度值做对比,如果当前像素的梯度值为最大值,则保留该点的梯度信息,否则将该点删除或将像素值置为9。
(4)双阈值边缘检测和边缘连接
由非极大值抑制得到的边缘信息中包含较多伪边缘信息,可通过设置高低双阈值的方法去除它们。
首先,设定两个阈值,一个高阈值,一个低阈值,阈值大小根据实际情况设置,一般高阈值为低阈值的2.5倍。
然后判断:梯度值大于高阈值的像素点一定是边缘点,将该点像素值置为255;梯度值小于低阈值的像素点一定不是边缘点,将该点像素值置为0;介于高低阈值之间的像素点为准边缘点,对于这些点,如果其像素点周围8邻域的梯度值都小于高阈值,则认为其不是边缘点,将该点像素值置为0,否则置为255。
C语言实现Cannyl边缘检测算法代码如下:
/*************************************************
功 能:图像Canny边缘检测
参 数:srcData - [输入/输出] 原始图像,格式为32位BGRA格式,执行后修为结果图像
width - [输入] 原始图像宽度
height - [输入] 原始图像高度
stride - [输入] 原始图像的Stride(也就是行字节数width*4)
highThreshold - [输入] 高阈值,范围为[0,255]
lowThreshold - [输入] 低阈值,范围为[0,255],默认值为0.4*highThreshold
返 回: 0-成功,其他-失败.
*************************************************/
//单通道灰度化
static int grayOneChannel(unsigned char* srcData, unsigned char* grayData, int width, int height, int stride)
{
int ret = 0;
int i, j, gray, offset;
offset = stride - (width * 4);
unsigned char* pSrc = srcData;
unsigned char* pGray = grayData;
for (j = 0; j < height; j++)
{
for (i = 0; i < width; i++)
{
gray = (pSrc[2] + pSrc[1] + pSrc[0]) / 3;
*pGray = gray;
pSrc += 4;
pGray++;
}
pSrc += offset;
}
return ret;
};
//梯度相位角获取
static void GetGradientDegree(unsigned char* srcBytes, int width, int height, float gradient[], unsigned char degree[], float* GradientMax)
{
float gx, gy;
int temp, pos;
float div;
float PI = 3.1415926f;
float t = 180.0f / PI;
for (int j = 1; j < height - 1; j++)
{
for (int i = 1; i < width - 1; i++)
{
pos = i + j * width;
gx = srcBytes[pos + 1 - width] + srcBytes[pos + 1] + srcBytes[pos + 1] + srcBytes[pos + 1 + width] - srcBytes[pos - 1 - width] - (srcBytes[pos - 1] + srcBytes[pos - 1]) - srcBytes[pos - 1 + width];
gy = srcBytes[pos - 1 - width] + srcBytes[pos - width] + srcBytes[pos - width] + srcBytes[pos + 1 - width] - srcBytes[pos - 1 + width] - (srcBytes[pos + width] + srcBytes[pos + width]) - srcBytes[pos + 1 + width];
gradient[pos] = (float)sqrt((float)(gx * gx + gy * gy));
if (*GradientMax < gradient[pos])
{
*GradientMax = gradient[pos];
}
if (gx == 0)
{
temp = (gy == 0) ? 0 : 90;
}
else
{
div = gy / gx;
if (div < 0)
{
temp = (int)(180 - atan(-div) * t);
}
else
{
temp = (int)(atan(div) * t);
}
if (temp < 22.5f)
{
temp = 0;
}
else if (temp < 67.5f)
{
temp = 45;
}
else if (temp < 112.5f)
{
temp = 90;
}
else if (temp < 157.5f)
{
temp = 135;
}
else
temp = 0;
}
degree[pos] = temp;
}
}
};
//非极大值抑制
static void NonMaxMini(unsigned char* srcBytes, int width, int height, float gradient[], float GradientMax, unsigned char degree[])
{
float leftPixel = 0, rightPixel = 0;
int pos;
for (int j = 1; j < height - 1; j++)
{
for (int i = 1; i < width - 1; i++)
{
pos = i + j * width;
switch (degree[pos])
{
case 0:
leftPixel = gradient[pos - 1];
rightPixel = gradient[pos + 1];
break;
case 45:
leftPixel = gradient[pos - 1 + width];
rightPixel = gradient[pos + 1 - width];
break;
case 90:
leftPixel = gradient[pos + width];
rightPixel = gradient[pos - width];
break;
case 135:
leftPixel = gradient[pos + 1 + width];
rightPixel = gradient[pos - 1 - width];
break;
default:
break;
}
if ((gradient[pos] < leftPixel) || (gradient[pos] < rightPixel))
{
srcBytes[pos] = 0;
}
else
{
srcBytes[pos] = (int)(255.0f * gradient[pos] / GradientMax);
}
}
}
};
//双阈值边缘判断
static void TwoThreshouldJudge(unsigned char* srcBytes, int width, int height, int highThreshold, int lowThreshould)
{
int pos = 0;
for (int j = 1; j < height - 1; j++)
{
for (int i = 1; i < width - 1; i++)
{
pos = i + j * width;
if (srcBytes[pos] > highThreshold)
{
srcBytes[pos] = 255;
}
else if (srcBytes[pos] < lowThreshould)
{
srcBytes[pos] = 0;
}
else
{
if (srcBytes[pos - 1 - width] < highThreshold && srcBytes[pos - width] < highThreshold && srcBytes[pos + 1 - width] < highThreshold && srcBytes[pos - 1] < highThreshold
&& srcBytes[pos + 1] < highThreshold && srcBytes[pos - 1 + width] < highThreshold && srcBytes[pos + width] < highThreshold && srcBytes[pos + 1 + width] < highThreshold)
{
srcBytes[pos] = 0;
}
else
srcBytes[pos] = 255;
}
}
}
};
int cannyEdgedetection(unsigned char* srcData, int width, int height, int stride, int highThreshold, int lowThreshold)
{
int ret = 0;
int i, j, offset, pos, temp, size;
unsigned char* pSrc = srcData;
size = width * height;
unsigned char* grayData = (unsigned char*)malloc(sizeof(unsigned char) * size);
memset(grayData, 0, sizeof(unsigned char) * size);
offset = stride - width * 4;
//gray
grayOneChannel(srcData, grayData, width, height, stride);
//gauss fiter
for (j = 0; j < height; j++)
{
for (i = 0; i < width; i++)
{
pos = i + j * width;
if (i == 0 || j == 0 || i == width - 1 || j == height - 1)
{
grayData[pos] = 0;
}
else
{
temp = ((grayData[pos] << 2) + grayData[pos - width - 1] + grayData[pos + 1 - width] + grayData[pos - 1 + width] + grayData[pos + 1 + width] + grayData[pos - width] + grayData[pos - width] + grayData[pos - 1] + grayData[pos - 1] + grayData[pos + width] + grayData[pos + width] + grayData[pos + 1] + grayData[pos + 1]) >> 4;
grayData[pos] = temp;
}
}
}
//gradient
float* gradient = (float*)malloc(sizeof(float) * size);
memset(gradient, 0, sizeof(float) * size);
unsigned char* degree = (unsigned char*)malloc(sizeof(unsigned char) * size);
memset(degree, 0, sizeof(unsigned char) * size);
float GradientMax = 0;
GetGradientDegree(grayData, width, height, gradient, degree, &GradientMax);
//none max value
NonMaxMini(grayData, width, height, gradient, GradientMax, degree);
//two threshold judgement
TwoThreshouldJudge(grayData, width, height, highThreshold, lowThreshold);
//recovery
for (j = 0; j < height; j++)
{
for (i = 0; i < width; i++)
{
pSrc[0] = pSrc[1] = pSrc[2] = grayData[i + j * width];
pSrc += 4;
}
pSrc += offset;
}
free(grayData);
free(gradient);
free(degree);
return ret;
};
4 演示Demo
4.1 开发环境
-
Windows 10 Pro x64
-
Visual Studio 2015
4.2 功能介绍
演示程序主界面如下图所示,具有图像读取、显示、保存、显示RGBA值、HSV调整、提取YUV分量、灰度化、二值化、直方图、亮度/对比度调整、饱和度调整、均值滤波、高斯滤波、拉普拉斯锐化、USM锐化、Sobel边缘检测、Canny边缘检测等功能。
原图
Sobel边缘检测(阈值为80 )效果图
Canny边缘检测(阈值为 8)效果图
4.3 下载地址
开发环境:
-
Windows 10 pro x64
-
Visual Studio 2015
下载地址:图像处理之图像边缘检测算法Demo
参考
图像视频滤镜与人像美颜美妆算法详解. 胡耀武、谭娟、李云夕. 电子工业出版社、2020-07