Canny算子边缘检测与改进
摘要:边缘检测是图像处理中很重要的一环,针对图像的原始特征对算法,提出了一种基于Canny算子边缘检测的算法。采用中值滤波代替高斯滤波去噪工作,因为中值滤波更能消除椒盐噪声。采用33的sobel算子代替了22的Reberts算子来计算梯度幅值和梯度。自适应双阈值的算法能够很好的根据图像选取双阈值,结果很好的检测到图像梯度,有效抑制假边缘和噪声边缘,提高边缘检测的性能。
1.引言
图像边缘检测是图像分析和图像识别的基础。常用的边缘检测微分算子有一阶(Reberts,sobel,Prewitt,Krish),二阶算子(laplacian)。这些算子都是通过原图像素和模板进行卷积来提取边缘,特点计算简单,易于实现,但常常会丢失一些边缘信息。
本文在传统Canny算法上的改进,1.用均值滤波对原图像滤波,2用sobel算子模板的一阶微分提取边缘,计算出梯度和梯度幅值。3.根据计算的梯度,和梯度幅值进行非极大值抑制。4自适应选取双阈值,对图像进行阈值化。5最后根据高低阈值处理后,进行边缘连接。
2传统的Canny算子
传统的Canny边缘检测算子的基本思想是先进行高斯滤波,高斯滤波是通过图像卷积进行的,然后通过一阶微分算子对边缘进行提取。然后通过梯度进行非极大值抑制,在一阶微分算子中只采用了水平,竖直放行而没考虑45°和135°方向来提取边缘。高低阈值需要进行自己设置,或者用outs方法进行双阈值的求取。根据高低阈值处理后的图像进行边缘连接。
3.本文canny算法的实现
滤波处理 滤波的方法。在我们接触过程中有很多种滤波,均值滤波,中值滤波,高斯滤波,和双边滤波。
每种滤波各有优缺点如下:
均值滤波在去噪声的同时会有如下缺点:边界模糊效应明显,细节丢失比较严重。
中值滤波在边界的保存方面优于均值滤波,是经常使用的一种滤波器。但是在模板逐渐变大时,依然会存在一定的边界模糊中值滤波对处理椒盐噪声非常有效,或者称为脉冲噪声。
高斯滤波是基于空间域的一直滤波,离中心越近权重越大,能很好的模糊图像,对边缘保存不是很好。
本文采用双边滤波,这里主要简单介绍一下Bilateral方法(双边滤波),这主要是由于前段时间做了SSAO,需要用bilateral blur 算法进行降噪。Bilateral blur相对于传统的高斯blur来说很重要的一个特性即可可以保持边缘(Edge Perseving),这个特点对于一些图像模糊来说很有用。一般的高斯模糊在进行采样时主要考虑了像素间的空间距离关系,但是却并没有考虑像素值之间的相似程度,因此这样我们得到的模糊结果通常是整张图片一团模糊。Bilateral blur的改进就在于在采样时不仅考虑像素在空间距离上的关系,同时加入了像素间的相似程度考虑,因而可以保持原始图像的大体分块进而保持边缘。在于游戏引擎的post blur算法中,bilateral blur常常被用到,比如对SSAO的降噪。
原理
首先介绍一下传统高斯滤波的原理
根据高斯公式求解出高斯模板,然后对模板进行归一化处理,归一化后和图像像素进行卷积处理。处理后就得到了高斯滤波的图像。
双边滤波原理
根据公式,先计算尺空间域的权重,在算出像素域的权重,算出模板进行归一化,然后卷积处理。
的自适应取值, 取值越小,图像边缘和细节越清楚,越大图像就越模糊边缘损失就会很大 ,所以根据图像的像素来设置取值能得到更好滤波。
梯度大小和方向计算
对像素进一阶求导,或者进行二阶求导。
求导模板的处理
这个模板比传统模板好,因为它更精确,对X,Y,45,135,方向进行了计算。
自适应双阈值处理
我采用的是OTSU最大类间方差的处理,找到双阈值,但是我算出的阈值对有些图像不是太合适,于是采取了一个滚动条,在求取出自适应双阈值的条件下微调,阈值达到最佳的效果。
非极大值抑制处理
图像梯度幅值矩阵中的元素值越大,说明图像中该点的梯度值越大,但这不不能说明该点就是边缘(这仅仅是属于图像增强的过程)。在Canny算法中,非极大值抑制是进行边缘检测的重要步骤,通俗意义上是指寻找像素点局部最大值,将非极大值点所对应的灰度值置为0,这样可以剔除掉一大部分非边缘的点。
C点是一个判断点,判断dTmp1 和dTmp2,进行判断,如果C大于两者就保留,不然就抑制说明不是边缘点。
边缘链接
方法 DFS深度优先搜索。
在大于高阈值的为真实边缘。
小于低阈值就丢弃,在非极大值抑制已经丢弃一部分。
介于高低阈值之间相当于伪边缘。
这部分伪边缘就是要操作的部分。
参考文献
代码如下
// AirTagetDetection.cpp: 定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include
#include
#include
#include
using namespace std;
using namespace cv;
Mat pic;
int y5=0,count1=0,maxc=0;
//双边滤波
Mat BilateralFilter(Mat pic)
{
Mat c(pic.size(), pic.type());
double temp[3][3], sum;
double d = 1, r ;
for (int i = 1; i < pic.rows - 1; i++)
{
for (int j = 1; j < pic.cols - 1; j++)
{
int tem[3][3] = {1,-2,1,-2,4,-2,1 - 2,1};
r = (int)(1.0* fabs(pic.at(i - 1, j - 1) * tem[0][0]) + fabs(pic.at(i, j - 1) * tem[1][0]) + fabs(pic.at(i + 1, j - 1)* tem[2][0]) + fabs(pic.at(i - 1, j) * tem[0][1]) + fabs(pic.at(i, j) * tem[1][1]) + fabs(pic.at(i + 1, j) * tem[2][1]) + fabs(pic.at(i - 1, j + 1) * tem[0][2]) +fabs( pic.at(i, j + 1) * tem[1][2]) +fabs( pic.at(i + 1, j + 1) * tem[2][2]));
r = 3*sqrt(3.14159*1.0 / 2) * 1 / (6 * (pic.rows - 2)*(pic.cols - 2))*r;
sum = 0;
for (int ij = -1, k1 = 0; ij < 2; ij++, k1++)
{
for (int ji = -1, k = 0; ji< 2; ji++, k++)
{
double t = (fabs(pic.at(i, j) - pic.at(i+ij,j+ji))*fabs(pic.at(i, j) - pic.at(i+ij, j+ji)))*1.0/ 2 / r / r;
double s = (ij*ij + ji*ji)*1.0 / 2 / d / d;
temp[k1][k] = exp(-s-t);
sum += temp[k1][k];
}
}
int b = (int)(1.0* pic.at(i - 1, j - 1) * temp[0][0] + pic.at(i, j - 1) * temp[1][0] + pic.at(i + 1, j - 1)* temp[2][0] + pic.at(i - 1, j) * temp[0][1] + pic.at(i, j) * temp[1][1] + pic.at(i + 1, j) * temp[2][1] + pic.at(i - 1, j + 1) * temp[0][2] + pic.at(i, j + 1) * temp[1][2] + pic.at(i + 1, j + 1) * temp[2][2]);
b = b / sum;
c.at(i, j) = b;
}
}
namedWindow("can", 1);
imshow("can", c);
return c;
}
//高斯滤波
Mat gaoshi(Mat pic)
{
Mat c(pic.size(), pic.type());
double temp[3][3];
double x = 0.8;
for (int ij = -1, k1 = 0; ij < 2; ij++, k1++)
{
for (int ji = -1, k = 0; ji< 2; ji++, k++)
{
temp[k1][k] = 1.0 / (2 * 3.14159*x*x)*exp(-(ij*ij + ji*ji)*1.0 / 2 / x / x);
}
}
double sum = 0;
for (int ij = 0; ij < 3; ij++)
{
for (int ji = 0; ji < 3; ji++)
{
sum += temp[ij][ji];
}
}
for (int ij = 0; ij < 3; ij++)
{
for (int ji = 0; ji < 3; ji++)
{
temp[ij][ji] /= sum;
cout << temp[ij][ji] << ends;
}
cout << endl;
}
for (int i = 1; i < pic.rows - 1; i++)
{
for (int j = 1; j < pic.cols - 1; j++)
{
int b = (int)(1.0* pic.at(i - 1, j - 1) * temp[0][0] + pic.at(i, j - 1) * temp[1][0] + pic.at(i + 1, j - 1)* temp[2][0] + pic.at(i - 1, j) * temp[0][1] + pic.at(i, j) * temp[1][1] + pic.at(i + 1, j) * temp[2][1] + pic.at(i - 1, j + 1) * temp[0][2] + pic.at(i, j + 1) * temp[1][2] + pic.at(i + 1, j + 1) * temp[2][2]);
c.at(i, j) = b;
}
}
namedWindow("can", 1);
imshow("can", c);
return c;
}
//DFS深度收索
void TraceEdge(int w, int h, int nThrHig, int nThrLow, Mat c1, Mat c2)
{
if (w > 0 && w < c1.rows - 1 && h>0 && h < c1.cols - 1)
{
//对8邻域像素进行查询
for (int i = w - 1; i< w + 2; i++)
{
for (int j = h - 1; j (i, j) !=0)
{
//该点设为边界点
c2.at(i, j) = 0;
c1.at(i, j) = 255;
//以该点为中心再进行跟踪
TraceEdge(i, j, nThrHig, nThrLow, c1, c2);
}
}
}
}
//R行,c列
void canny(Mat pic,int& y5,int& count,int& maxc)
{
Mat c;
c=BilateralFilter(pic);
//bilateralFilter(pic,c,0.4,0.4,3);
//imshow("cscs",c);
//c = gaoshi(pic);//高斯滤波
Mat c1(pic.size(), pic.type());
Mat c2(pic.size(), pic.type());
int maxM = 0;
int **M = new int*[c.rows];//储存每个点梯度幅值的数组
int **N = new int*[c.rows];
int **jiao = new int*[c.rows]; //储存每个点梯度角度的数组
double **g1 = new double *[c.rows];//储存每个点X的一阶偏导的数组
double **g2 = new double *[c.rows];//储存每个点y的一阶偏导的数组
double **g3 = new double *[c.rows];//储存每个点45的一阶偏导的数组
double **g4 = new double *[c.rows];//储存每个点135的一阶偏导的数组
for (int j = 0; j < c.rows; j++)
{
M[j] = new int[c.cols];
N[j] = new int[c.cols];
jiao[j] = new int[c.cols];
g1[j] = new double[c.cols];
g2[j] = new double[c.cols];
g3[j] = new double[c.cols];
g4[j] = new double[c.cols];
}
for (int i = 0; i < c.rows; i++)
{
for (int j = 0; j < c.cols; j++)
{
N[i][j] = 0;
M[i][j] = 0;
}
}
//梯度角度的计算并保存
for (int i = 1; i < c.rows - 1; i++)
{
for (int j = 1; j < c.cols - 1; j++)
{
int a1 = c.at(i - 1, j - 1);
int a2 = c.at(i - 1, j);
int a3 = c.at(i - 1, j + 1);
int a4 = c.at(i, j - 1);
int a5 = c.at(i, j);
int a6 = c.at(i, j + 1);
int a7 = c.at(i + 1, j - 1);
int a8 = c.at(i + 1, j);
int a9 = c.at(i + 1, j + 1);
g1[i][j] = a7 + 2 * a8 + a9 - a1 - 2 * a2 - a3;
g2[i][j] = a3 + 2 * a6 + a9 - a1 - 2 * a4 - a7;
g3[i][j] = a6 + 2 * a8 + a9 - a1 - 2 * a2 - a4;
g4[i][j] = a2 + 2 * a3 + a6 - a4 - 2 * a7 - a8;
M[i][j] = sqrt(g1[i][j] * g1[i][j] + g2[i][j] * g2[i][j]+ g3[i][j] * g3[i][j]+ g4[i][j] * g4[i][j]);
double hu = atan2(g2[i][j], g1[i][j]);
/*g1[i][j] = (a8 + a9 - a5 - a6) / 2;
g2[i][j] = (a9 + a6 - a5 - a8) / 2;
M[i][j] = sqrt(g1[i][j] * g1[i][j] + g2[i][j] * g2[i][j]);
double hu = atan2(g2[i][j], g1[i][j]);*/
if (M[i][j] > maxM)
{
maxM = M[i][j];
}
jiao[i][j] = hu * 180 / 3.14159;
if (jiao[i][j] < 0)
{
jiao[i][j] = 360 + jiao[i][j];
}
}
}
int y1 = 0;
int maxt = maxM / 255 + 1;
maxc = maxM / maxt;
if (count == 0)
{
//求取双阈值
double Host[255] = { 0 };
double p[255] = { 0 };
double v = 0;
double w = 0;
for (int i = 1; i < c.rows - 1; i++)
{
for (int j = 1; j < c.cols - 1; j++)
{
M[i][j] /= maxt;
Host[M[i][j]]++;
}
}
for (int i = 0; i <= maxc; i++)
{
p[i] = Host[i] / ((c.rows - 2)* (c.cols - 2));
w += i * p[i];
}
double maxv = 0;
for (int i = 0; i <= maxc; i++)
{
double p1 = 0;
double m1 = 0;
for (int k = 0; k < i; k++)
{
p1 += p[k];
m1 += k*p[k];
}
//m1 = m1*1.0 / p1;
for (int j = i; j <= maxc; j++)
{
double p2 = 0,p3=0,m2=0,m3=0;
for (int l = i; l < j; l++)
{
p2 += p[l];
m2 += l*p[l];
}
//m2 = m2*1.0 / p2;
p3 = 1 - p1 - p2;
m3 = w - m1 - m2;
//m3 = m3*1.0/p3;
v = p1*(m1 - w)*(m1 - w) + p2*(m2 - w)*(m2 - w)+p3*(m3 - w)*(m3 - w);
if (v >maxv)
{
maxv = v;
y1 = i;
y5 = j;
}
}
}
cout << y1 << " " << y5 << endl;
}
int r1 = y5*0.7, r2 = y5*0.4;
//对梯度幅值进行非极大值抑制
for (int i = 1; i < c.rows - 1; i++)
{
for (int j = 1; j < c.cols - 1; j++)
{
int y1 = 0, y2 = 0, y3 = 0, y4 = 0;
double dTmp1 = 0, dTmp2 = 0, dweight = 0;
if (M[i][j] == 0)
{
N[i][j] = 1;
}
else if (jiao[i][j] >= 0 && jiao[i][j]<45 || jiao[i][j] >= 180 && jiao[i][j]<225)
{
y1 = M[i - 1][j - 1];
y2 = M[i - 1][j];
y3 = M[i + 1][j];
y4 = M[i + 1][j + 1];
if (jiao[i][j] < 45) dweight = jiao[i][j] / 45;
else dweight = (jiao[i][j] - 180) / 45;
dTmp1 = y2*dweight + y1*(1 - dweight);
dTmp2 = y3* dweight + y4 * (1 - dweight);
}
else if (jiao[i][j] >= 45 && jiao[i][j]<90 || jiao[i][j] >= 225 && jiao[i][j]<270)
{
y1 = M[i - 1][j - 1];
y2 = M[i][j - 1];
y3 = M[i][j + 1];
y4 = M[i + 1][j + 1];
if (jiao[i][j] < 90) dweight = (jiao[i][j]-45) / 45;
else dweight = (jiao[i][j] - 225) / 45;
dTmp1 = y1*dweight + y2*(1 - dweight);
dTmp2 = y4* dweight + y3 * (1 - dweight);
}
else if (jiao[i][j] >= 135 && jiao[i][j]<180 || jiao[i][j] >= 315 && jiao[i][j]<360)
{
y1 = M[i - 1][j];
y2 = M[i - 1][j + 1];
y3 = M[i + 1][j];
y4 = M[i + 1][j - 1];
if (jiao[i][j] < 180) dweight = (jiao[i][j] - 135) / 45;
else dweight = (jiao[i][j] - 315) / 45;
dTmp1 = y2*dweight + y1*(1 - dweight);
dTmp2 = y4 * dweight + y3 * (1 - dweight);
}
else if (jiao[i][j] >= 90 && jiao[i][j]<135 || jiao[i][j] >= 270 && jiao[i][j]<315)
{
y1 = M[i - 1][j + 1];
y2 = M[i][j + 1];
y3 = M[i][j -1];
y4 = M[i+1][j-1];
if (jiao[i][j] < 135) dweight = (jiao[i][j] - 90) / 45;
else dweight = (jiao[i][j] - 270) / 45;
dTmp1 = y2*dweight + y1*(1 - dweight);
dTmp2 = y3 * dweight + y4 * (1 - dweight);
}
if (M[i][j] >= dTmp1&&M[i][j] >= dTmp2);
else N[i][j] = 1;
}
}
//int m,t1;
////对梯度幅值进行非极大值抑制
//for (int i = 1; i < c.rows - 1; i++)
//{
// for (int j = 1; j < c.cols - 1; j++)
// {
// double s = jiao[i][j];
// m = M[i][j];
// t1 = s / 22.5;
// switch (t1)
// {
// case 0:
// case 7:
// case 0+8:
// case 7+8:
// if (m < M[i + 1][j] || m < M[i - 1][j])
// {
// N[i][j] = 1;
// }
// else
// {
// N[i][j] = 0;
// }; break;
// case 1:
// case 2:
// case 1+8:
// case 2+8:
// if (m < M[i + 1][j + 1] || m < M[i - 1][j - 1])
// {
// N[i][j] = 1;
// }
// else
// {
// N[i][j] = 0;
// }; break;
// case 3:
// case 4:
// case 3+8:
// case 4+8:
// if (m < M[i][j + 1] || m < M[i][j - 1])
// {
// N[i][j] = 1;
// }
// else
// {
// N[i][j] = 0;
// }; break;
// case 5:
// case 6:
// case 5+8:
// case 6+8:
// if (m < M[i - 1][j + 1] || m < M[i + 1][j - 1])
// {
// N[i][j] = 1;
// }
// else
// {
// N[i][j] = 0;
// }; break;
// }
// }
//}
//去除非边缘点
for (int i = 1; i < c.rows - 1; i++)
{
for (int j = 1; j < c.cols - 1; j++)
{
if (N[i][j] == 1) M[i][j] = 0;
}
}
//利用高低阈值,找出分别处理后的图像
for (int i = 0; i < c1.rows; i++)
{
for (int j = 0; j < c1.cols; j++)
{
if (i == 0 || j == 0 || i == c1.rows - 1 || j == c1.cols)
{
c1.at(i, j) = 0;
}
if (M[i][j] >= r1)
{
c1.at(i, j) = 255;
}
else c1.at(i, j) = 0;
if (M[i][j] > r2&&M[i][j] < r1)
{
c2.at(i, j) = M[i][j];
}
else c2.at(i, j) = 0;
}
}
// 边缘连接
for (int i = 0; i < c1.rows; i++)
{
for (int j = 0; j < c1.cols; j++)
{
if (c1.at(i, j) == 255)
{
TraceEdge(i, j, r1, r2, c1, c2);
}
}
}
namedWindow("canny", 1);
imshow("canny", c1);
}
//回掉函数
void on_Trackbar(int, void*)
{
canny(pic,y5,count1,maxc);
}
int main()
{
pic = imread("D:/123.png", 0);
//resize(pic, pic, Size(500, 500));
namedWindow("src", 1);
imshow("src", pic);
if (pic.empty())
{
cout << "read picture wrong";
return 0;
}
copyMakeBorder(pic, pic, 1, 1, 1, 1, BORDER_REPLICATE);//扩充边界
Mat pdes(pic.size(), pic.type());
Canny(pic, pdes,70, 30);
namedWindow("dst", 1);
imshow("dst", pdes);
canny(pic, y5, count1,maxc);
count1++;
char TrackbarName[50];//窗口中的滑动条控件
sprintf(TrackbarName, "高阈值%d",y5);
createTrackbar(TrackbarName, "canny", &y5,maxc, on_Trackbar);
waitKey(0);
return 0;
}