赞
踩
使用C++和opencv实现Canny算子进行边缘检测
先利用高斯函数对图像进行低通滤波;然后对图像中的每个像素进行处理,寻找边缘的位置及在该位置的边缘法向,并采用一种称之为“非极值抑制”的技术在边缘法向寻找局部最大值;最后对边缘图像做滞后阈值化处理,消除虚假响应。
Canny边缘检测算法可以分为以下5个步骤:
使用高斯滤波器,以平滑图像,滤除噪声。
计算图像中每个像素点的梯度强度和方向。
Gx = [ f(x+1,y-1)+2*f(x+1,y)+f(x+1,y+1)] - [f(x-1,y-1)+2*f(x-1,y)+f(x-1,y+1) ]
Gy = [ f(x-1,y-1) + 2f(x,y-1) + f(x+1,y-1)] - [f(x-1, y+1) + 2*f(x,y+1)+f(x+1,y+1)
梯度方向示意图:
应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
通过抑制孤立的弱边缘最终完成边缘检测。
// Canny.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include<string> #include<cmath> #include<vector> #include<gdal.h> #include<opencv2/highgui/highgui.hpp> #include<opencv2/opencv.hpp> #include "cpl_conv.h" #include<algorithm> #include<cstring> using namespace std; using namespace cv; #define PI 3.1415; void Canny(Mat img,Mat img2,string str2) { int r, c; int rows = img.rows - 1; int cols = img.cols - 1; int add, des; int IX, IY,gx,gy;//xy方向梯度 float m;//模, double angle_atanValue; double arctan;//arctan double angle;//方向 Mat Gauss(rows+1,cols+1,CV_32FC1,Scalar::all(0)); // Mat Gray(rows + 1, cols + 1, CV_8UC1, Scalar::all(0)); //= 0.299R + 0.587G + 0.114B //step1:高斯滤波 //Canny算子通常处理的图像是灰度图像,若不是灰度图像,先对彩色图像灰度化,再高斯滤波处理 //cvtColor(img, Gray, COLOR_BGR2GRAY);//灰度化 GaussianBlur(img, Gauss, Size(5, 5), 0,0); //imshow("Gauss", Gauss); //cv::waitKey(0); //step2:计算梯度和方向 Mat GX(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//x梯度矩阵 Mat GY(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//y梯度矩阵 Mat M(rows+1,cols+1,CV_32FC1,Scalar::all(0));//模矩阵 // Mat Angle(rows + 1, cols + 1, CV_32FC1, Scalar::all(0));//角度矩阵 Mat Direction(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//方向矩阵:0、1、2、3 for ( r = 0; r < rows+1; r++) { for ( c = 0; c < cols+1; c++) { add = 1, des = 1; if (r == 0 || c == 0) des = 0; if (r == rows || c == cols) add = 0; // Gx = [f(x + 1, y - 1) + 2 * f(x + 1, y) + f(x + 1, y + 1)] - [f(x - 1, y - 1) + 2 * f(x - 1, y) + f(x - 1, y + 1)] //Gy = [f(x - 1, y - 1) + 2f(x, y - 1) + f(x + 1, y - 1)] - [f(x - 1, y + 1) + 2 * f(x, y + 1) + f(x + 1, y + 1) gx = (Gauss.at<uchar>(r+add,c-des)+2* Gauss.at<uchar>(r+add,c)+ Gauss.at<uchar>(r+add,c+add))-(Gauss.at<uchar>(r -des, c - des) + 2 * Gauss.at<uchar>(r-des, c) + Gauss.at<uchar>(r-des, c + add)); gy = (Gauss.at<uchar>(r -des, c - des) + 2 * Gauss.at<uchar>(r , c-des) + Gauss.at<uchar>(r + add, c -des)) - (Gauss.at<uchar>(r -des, c +add) + 2 * Gauss.at<uchar>(r , c+add) + Gauss.at<uchar>(r + add, c + add)); GX.at<int>(r, c) = gx;//计算xy方向梯度 GY.at<int>(r, c) = gy; m = sqrt(pow(gx, 2) + pow(gy, 2));//计算模 M.at<float>(r, c) = m; //cout << "gx:" << gx<< "gy:" << gy <<"m:"<<m<< endl; //计算梯度方向:arctan(gx/gy):范围:-180~180° //注:0°、180°为0;45°、-135°为1;90°、-90°为2;135°、-45°为3; if (gy == 0 )//除数不能等于0 { angle = 0;//当gy=0时,角度是0度 } else { angle_atanValue = gx / gy; arctan =atan(angle_atanValue);//计算arctan值 //可以看出:atan函数输出的是弧度! 如果想进行atan运算得到角度,需要乘以(180/PI)把弧度转为角度" angle = arctan * 180 / PI; } // Angle.at<float>(r, c) = angle;//将角度存入矩阵 //cout << "angle:" << angle << endl; //角度近似,分成四个方向 if ((angle> -22.5 && angle<=22.5)||(angle >=-180 && angle < -157.5)||(angle>157.5 && angle<=180)) { Direction.at<int>(r, c) = 0;//-22.5°~22.5°和-157.5°~-180°、157.5~180方向设置为0 } else if ((angle > 22.5 && angle <= 67.5) || (angle >= -67.5 && angle < -22.5)) { Direction.at<int>(r, c) = 1;//22.5°~67.5°和-22.5°~-67.5°方向设置为1 } else if ((angle >67.5 && angle <= 112.5) || (angle >= -112.5 && angle < -67.5)) { Direction.at<int>(r, c) = 2;//67.5°~112.5°和247.5°~292.5°方向设置为2 } else { Direction.at<int>(r, c) = 3;//112.5°~157.5°和-112.5°~-157.5°方向设置为3 } //cout << "Direction.at<int>(r, c):"<< Direction.at<int>(r, c) <<endl; } } //step3:梯度方向局部非最大抑制 //至此我们得到了整幅图像上所有像素的方向 //int winR, winC;//窗口内的位置 //int size;//窗口大小 //Mat DX(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//用来存放梯度方向差值 for (r = 0; r < rows+1; r++) { for ( c = 0; c < cols+1; c++)//遍历整幅图像 { int d,d1,d2;//用来暂存梯度值 /*for ( winR = 0; winR < size; winR++)//窗口内比较梯度 { for ( winC = 0; winC < size; winC++) { */ //cout << "lalala" << endl; if (r == 0 || c == 0) des = 0; if (r == rows || c == cols) add = 0; if (Direction.at<int>(r, c) ==0)//如果是0方向上,对f(x,y),f(x,y+1),f(x,y-1)的灰度值进行比大小,看f(x,y)是不是最大值 { //cout << "Direction.at<int>(r, c)" << Direction.at<int>(r, c) << endl; d = Gauss.at<uchar>(r , c ); d1 = Gauss.at<uchar>(r , c - des); d2 = Gauss.at<uchar>(r , c + add); if (d<d1 || d<d2) { Gauss.at<uchar>(r , c ) = 0;//如果不是最大,将像素设为0 // cout << "r:"<<r<<"c:"<<c<<"Gauss.at<float>(r , c ):"<< Gauss.at<uchar>(r, c) << endl; } } else if (Direction.at<int>(r , c ) == 1)//如果是1方向上,对f(x,y),f(x+1,y-1),f(x-1,y+1)的灰度值进行比大小,看f(x,y)是不是最大值 { d = Gauss.at<uchar>(r , c ); d1 = Gauss.at<uchar>(r +add, c - des); d2 = Gauss.at<uchar>(r -des, c + add); if (d < d1 || d < d2) { Gauss.at<uchar>(r, c) = 0;//如果不是最大,将像素设为0 // cout << "r:" << r << "c:" << c << "Gauss.at<float>(r , c ):" << Gauss.at<uchar>(r, c) << endl; } } else if (Direction.at<int>(r , c ) == 2)//如果是2方向上,对f(x,y),f(x+1,y),f(x-1,y)的灰度值进行比大小,看f(x,y)是不是最大值 { d = Gauss.at<uchar>(r , c ); d1 = Gauss.at<uchar>(r + add, c ); d2 = Gauss.at<uchar>(r - des, c ); if (d < d1 || d < d2) { Gauss.at<uchar>(r, c) = 0;//如果不是最大,将像素设为0 //cout << "r:" << r << "c:" << c << "Gauss.at<float>(r , c ):" << Gauss.at<uchar>(r, c) << endl; } } else//如果是3方向上,对f(x,y),f(x+1,y+1),f(x-1,y-1)的灰度值进行比大小,看f(x,y)是不是最大值 { d = Gauss.at<uchar>(r, c ); d1 = Gauss.at<uchar>(r + add, c +add); d2 = Gauss.at<uchar>(r - des, c -des); if (d < d1 || d < d2) { Gauss.at<uchar>(r, c) = 0;//如果不是最大,将像素设为0 // cout << "r:" << r << "c:" << c << "img.at<float>(r , c ):" << Gauss.at<uchar>(r, c) << endl; } } // cout << "Gauss.at<float>(r, c):" << Gauss.at<uchar>(r, c) << endl; /* } }*/ } } //至此我们重新构造了图像,其中可疑边缘处的像素仍保留像素原值,非边缘位置像素为0 //step4:双阈值和链接边缘 cout << "step4" << endl; float high_value=30.0;//高阈值 float low_value=20.0;//低阈值 Mat Location(rows + 1, cols + 1, CV_32SC1, Scalar::all(0));//可疑边缘点的位置 //int add=1, des = 1; for ( r = 0; r < rows+1; r++) { for ( c = 0; c < cols+1; c++) { add = 1,des = 1; if (r == 0 || c == 0) des = 0; if (r == rows || c == cols) add = 0; float m1,m2,m3,m4,m5,m6,m7,m8,m9,maxm;//暂存梯度值 if (Gauss.at<uchar>(r, c) != 0)//是可疑点时 { //cout << "判断可疑点" << endl; m5 = M.at<float>(r, c); if (m5 >= high_value) { //强边缘点 Location.at<int>(r,c) = 1; } else if (m5 <= low_value) { //不是边缘点 Location.at<int>(r, c) =0; Gauss.at<uchar>(r, c) = 0; } else { //如果low_value < m <high_value m1= M.at<float>(r - des, c-des); m2= M.at<float>(r , c - des); m3 = M.at<float>(r + add, c - des); m4 = M.at<float>(r - des, c ); m6 = M.at<float>(r + add, c ); m7 = M.at<float>(r - des, c +add); m8 = M.at<float>(r , c + add); m9 = M.at<float>(r +add, c + add); maxm = MAX(m1,m2,m3,m4,m5,m6,m7,m8,m9); if (maxm>high_value) { Gauss.at<uchar>(r, c) = 1; } else { Gauss.at<uchar>(r, c) = 0; } } } } } namedWindow("Canny",WINDOW_NORMAL); imshow("Canny", Gauss); imwrite(str2, Gauss); cv::waitKey(0); } int main() { string str = "D://学习课件//数字摄影测量//实验指导//实验指导//实验数据//实验2//DJI_0011.JPG"; string str2= "D://学习课件//数字摄影测量//实验指导//实验指导//Canny4.JPG"; Mat img, img2; img = imread(str,IMREAD_GRAYSCALE); img2 = imread(str,IMREAD_COLOR); if (img.empty() || img2.empty()) { cout << "无法打开图像" << endl; } else { cout << "OKKKKKKKKK" << endl; } Canny(img,img2,str2); std::cout << "Hello World!\n"; } // 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单 // 调试程序: F5 或调试 >“开始调试”菜单 // 入门使用技巧: // 1. 使用解决方案资源管理器窗口添加/管理文件 // 2. 使用团队资源管理器窗口连接到源代码管理 // 3. 使用输出窗口查看生成输出和其他消息 // 4. 使用错误列表窗口查看错误 // 5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目 // 6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件
原图:
结果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。