Canny算子边缘检测与改进
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<opencv2opencv.hpp> #include<iostream> #include<math.h> #include<algorithm> 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<uchar>(i - 1, j - 1) * tem[0][0]) + fabs(pic.at<uchar>(i, j - 1) * tem[1][0]) + fabs(pic.at<uchar>(i + 1, j - 1)* tem[2][0]) + fabs(pic.at<uchar>(i - 1, j) * tem[0][1]) + fabs(pic.at<uchar>(i, j) * tem[1][1]) + fabs(pic.at<uchar>(i + 1, j) * tem[2][1]) + fabs(pic.at<uchar>(i - 1, j + 1) * tem[0][2]) +fabs( pic.at<uchar>(i, j + 1) * tem[1][2]) +fabs( pic.at<uchar>(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<uchar>(i, j) - pic.at<uchar>(i+ij,j+ji))*fabs(pic.at<uchar>(i, j) - pic.at<uchar>(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<uchar>(i - 1, j - 1) * temp[0][0] + pic.at<uchar>(i, j - 1) * temp[1][0] + pic.at<uchar>(i + 1, j - 1)* temp[2][0] + pic.at<uchar>(i - 1, j) * temp[0][1] + pic.at<uchar>(i, j) * temp[1][1] + pic.at<uchar>(i + 1, j) * temp[2][1] + pic.at<uchar>(i - 1, j + 1) * temp[0][2] + pic.at<uchar>(i, j + 1) * temp[1][2] + pic.at<uchar>(i + 1, j + 1) * temp[2][2]); b = b / sum; c.at<uchar>(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<uchar>(i - 1, j - 1) * temp[0][0] + pic.at<uchar>(i, j - 1) * temp[1][0] + pic.at<uchar>(i + 1, j - 1)* temp[2][0] + pic.at<uchar>(i - 1, j) * temp[0][1] + pic.at<uchar>(i, j) * temp[1][1] + pic.at<uchar>(i + 1, j) * temp[2][1] + pic.at<uchar>(i - 1, j + 1) * temp[0][2] + pic.at<uchar>(i, j + 1) * temp[1][2] + pic.at<uchar>(i + 1, j + 1) * temp[2][2]); c.at<uchar>(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 <h + 2; j++) if (c2.at<uchar>(i, j) !=0) { //该点设为边界点 c2.at<uchar>(i, j) = 0; c1.at<uchar>(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<uchar>(i - 1, j - 1); int a2 = c.at<uchar>(i - 1, j); int a3 = c.at<uchar>(i - 1, j + 1); int a4 = c.at<uchar>(i, j - 1); int a5 = c.at<uchar>(i, j); int a6 = c.at<uchar>(i, j + 1); int a7 = c.at<uchar>(i + 1, j - 1); int a8 = c.at<uchar>(i + 1, j); int a9 = c.at<uchar>(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<uchar>(i, j) = 0; } if (M[i][j] >= r1) { c1.at<uchar>(i, j) = 255; } else c1.at<uchar>(i, j) = 0; if (M[i][j] > r2&&M[i][j] < r1) { c2.at<uchar>(i, j) = M[i][j]; } else c2.at<uchar>(i, j) = 0; } } // 边缘连接 for (int i = 0; i < c1.rows; i++) { for (int j = 0; j < c1.cols; j++) { if (c1.at<uchar>(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; }