opencvc++canny实现以及与halconcanny的对比
- 开源代码
- 2025-08-15 01:36:02

Opencv和C++实现canny边缘检测_opencv边缘增强-CSDN博客 一、canny实现步骤
1、图像必须是单通道的,也就是说必须是灰度图像
2、图像进行高斯滤波,去掉噪点
3、sobel 算子过程的实现,计算x y方向 、梯度(用不到,但是可以看看xy 两个组合起来的结果)
以及梯度方向(很重要)
4、局部非极大值抑制
5、双阈值连接处理
具体可以分为上面的5个步骤,下面一起边看原理边实现。
二、原理与实现 1、图像灰度化如果是一张3通道的图像,也就是我们常见的彩色图,那么们就需要将其转换成一个灰度图,其规则如下:
1.浮点算法:Gray = R*0.3 + G*0.59 + B*0.11 2.整数方法:Gray = (R*30+G*59+B*11)/100 3.移位方法:Gray = (R*28+G*151+B*77)>> 8 4.平均值法:Gray = (R+G+B)/3 5.仅取绿色:Gray = G 但是通常我们自己实现一般都是拿第一种实现的。
OpenCV转灰度图像特别简单,只需调用函数 cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 即可。
code:
void ConvertRGB2GRAY(const Mat& image, Mat& imageGray) { if (!image.data || image.channels() != 3) { return; } // 创建一个单通道的灰度图像 imageGray = Mat::zeros(image.size(), CV_8UC1); // 取出存储图像的数组的指针 uchar* pointImage = image.data; uchar* pointImageGray = imageGray.data; int stepImage = image.step; int stepImageGray = imageGray.step; for (int i = 0; i < imageGray.rows; i++) { for (int j = 0; j < imageGray.cols; j++) { pointImageGray[i * stepImageGray + j] = 0.114 * pointImage[i * stepImage + 3 * j] + 0.587 * pointImage[i * stepImage + 3 * j + 1] + 0.299 * pointImage[i * stepImage + 3 * j + 2]; } } } 2、高斯滤波在高斯滤波的时候先要生成一个2元高斯核,然后进行高斯滤波,其作用是去掉噪点,其图像变的平滑起来
二元高斯函数随着sigma的增大,整个高斯函数的尖峰逐渐减小,整体也变的更加平缓,则对图像的平滑效果越来越明显。
高斯核代码里面最后一定要归一化
void CreateGaussianKernel(int kernel_size, int sigma, Mat& kernel) { const double PI = 3.1415926; int center = kernel_size / 2; kernel = Mat(kernel_size, kernel_size,CV_32FC1); float segma_pow = 2 * sigma * sigma; float sum = 0; // 二元高斯函数 for (size_t i = 0; i < kernel_size; i++) { for (size_t j= 0; j < kernel_size; j++) { float temp = ((i - center) * (i - center) + (j - center) * (j - center) )/ segma_pow; kernel.at<float>(i, j) = 1 / (PI * segma_pow) * exp(-temp); sum += kernel.at<float>(i, j); } } // 归一化 for (size_t i = 0; i < kernel_size; i++) { for (size_t j = 0; j < kernel_size; j++) { kernel.at<float>(i, j) = kernel.at<float>(i, j)/sum; } } }5*5 的高斯核,那个核数一般是不能超过11 ,超过11 其效果均值一样了
高斯滤波 //******************高斯滤波************************* //第一个参数imageSource是待滤波原始图像; //第二个参数imageGaussian是滤波后输出图像; //第三个参数 kernel 是一个指向含有N个double类型数组; //第四个参数size是滤波核的尺寸 //************************************************************* void GaussianFilter(const Mat& imageSource, Mat& imageGaussian, Mat& kernel, int size) { if (!imageSource.data|| imageSource.channels()!=1) { return; } imageGaussian = Mat::zeros(imageSource.size(),CV_8UC1); float gaussArray[100]; // 将 kernel 的方阵 变成一个一维度数组 这样在循环的时候啊就少了一次内循环 int m = 0; for (size_t i = 0; i < kernel.rows; i++) { for (size_t j = 0; j < kernel.cols; j++) { gaussArray[m] = kernel.at<float>(i,j); m++; } } //滤波 for (int i = 0; i < imageSource.rows; i++) { for (int j = 0; j < imageSource.cols; j++) { int k = 0; for (int l = -size / 2; l <= size / 2; l++) { for (int g = -size / 2; g <= size / 2; g++) { //以下处理针对滤波后图像边界处理,为超出边界的值赋值为边界值 int row = i + l; int col = j + g; row = row < 0 ? 0 : row; row = row >= imageSource.rows ? imageSource.rows - 1 : row; col = col < 0 ? 0 : col; col = col >= imageSource.cols ? imageSource.cols - 1 : col; //卷积和 imageGaussian.at<uchar>(i, j) += gaussArray[k] * imageSource.at<uchar>(row, col); k++; } } } } } void TestGaussian() { Mat kernel; CreateGaussianKernel(5, 1, kernel); // 打印 高斯核 for (int i = 0; i < kernel.rows; i++) { for (int j = 0; j < kernel.cols; j++) { cout << " " << kernel.at<float>(i, j); } cout << endl; } Mat src = imread("C:\\Users\\alber\\Desktop\\opencv_images\\529.jpg"); Mat dst, imageGaussian; ConvertRGB2GRAY(src, dst); imwrite("C:\\Users\\alber\\Desktop\\opencv_images\\1\\1.jpg", dst); GaussianFilter(dst, imageGaussian, kernel, 5); imwrite("C:\\Users\\alber\\Desktop\\GaussianFilter.jpg", imageGaussian); }3、实现sobel 算子 推导出X Y方向的核
【精选】Opencv 笔记5 边缘处理-canny、sobel、Laplacian、Prewitt_opencv 边缘处理_Σίσυφος1900的博客-CSDN博客
gradient =||dx||+||dy||
theta= atan(gradY / gradX) * 57.3 注意这里的角度转换
//******************Sobel算子计算X、Y方向梯度 以及 梯度方向角******************** //第一个参数imageSourc原始灰度图像; //第二个参数imageSobelX是X方向梯度图像; //第三个参数imageSobelY是Y方向梯度图像; //第四个参数 theta 是梯度方向角数组指针 下一步很重要 就是要用这个值来计算 //************************************************************* void SobelGradDirction(const Mat imageSource, Mat& imageX, Mat& imageY, Mat& gradXY, Mat& theta) { imageX = Mat::zeros(imageSource.size(), CV_32SC1); imageY = Mat::zeros(imageSource.size(), CV_32SC1); gradXY = Mat::zeros(imageSource.size(), CV_32SC1); theta = Mat::zeros(imageSource.size(), CV_32SC1); int rows = imageSource.rows; int cols = imageSource.cols; int stepXY = imageX.step; int step = imageSource.step; /* Mat.step参数指图像的一行实际占用的内存长度, 因为opencv中的图像会对每行的长度自动补齐(8的倍数), 编程时尽量使用指针,指针读写像素是速度最快的,使用at函数最慢。 */ uchar* PX = imageX.data; uchar* PY = imageY.data; uchar* P = imageSource.data; uchar* XY = gradXY.data; for (int i = 1; i < rows - 1; i++) { for (int j = 1; j < cols - 1; j++) { int a00 = P[(i - 1) * step + j - 1]; int a01 = P[(i - 1) * step + j]; int a02 = P[(i - 1) * step + j + 1]; int a10 = P[i * step + j - 1]; int a11 = P[i * step + j]; int a12 = P[i * step + j + 1]; int a20 = P[(i + 1) * step + j - 1]; int a21 = P[(i + 1) * step + j]; int a22 = P[(i + 1) * step + j + 1]; double gradY = double(a02 + 2 * a12 + a22 - a00 - 2 * a10 - a20); double gradX = double(a00 + 2 * a01 + a02 - a20 - 2 * a21 - a22); imageX.at<int>(i, j) = abs(gradX); imageY.at<int>(i, j) = abs(gradY); if (gradX == 0) { gradX = 0.000000000001; } theta.at<int>(i, j) = atan(gradY / gradX) * 57.3; theta.at<int>(i, j) = (theta.at<int>(i, j) + 360) % 360; gradXY.at<int>(i, j) = sqrt(gradX * gradX + gradY * gradY); //XY[i*stepXY + j*(stepXY / step)] = sqrt(gradX*gradX + gradY*gradY); } } convertScaleAbs(imageX, imageX); convertScaleAbs(imageY, imageY); convertScaleAbs(gradXY, gradXY); }
这个不明显,所以我打算换个图像test
4、局部非极大值抑制这里我们就要用到上面一步在sobel里面计算求得的x y 方向以及梯度方向的那些 东西了。
原理:拿到当前点的梯度方向[0,360],判断其在那个区域,计算梯度方向(一个方向,两个值)在不同权重下(w=dy/dx)的灰度值t1 t2, 最后判断当前点灰度值current 和t1 t2的大小比较,如果当前值current小于t1 t2中的任何一个那么,当前的点就不会是边缘的候选点,current=0;
下面我们看一下梯度的分布:
[0-45] U[180-225] [45-90] U[225-270] [90-135] U[270-315] [135-180] U[315-360] code: /// <summary> /// 局部极大值抑制 ,计算八领域 沿着该点梯度方向,比较前后两个点的幅值大小,若该点大于前后两点,则保留,若该点小于前后两点任意一点,则置为0; /// </summary> /// <param name="imageInput"> 输入的图像</param> /// <param name="imageOutput"></param> /// <param name="theta"></param> /// <param name="imageX"> </param> /// <param name="imageY"></param> void NonLocalMaxValue(const Mat imageInput, Mat& imageOutput, const Mat& theta, const Mat& imageX, const Mat& imageY) { if (!imageInput.data || imageInput.channels() != 1) { return; } imageOutput = imageInput.clone(); int rows = imageOutput.rows; int cols = imageOutput.cols; int g00, g01, g02, g10, g11, g12, g20, g21, g22; int g1, g2, g3, g4; for (size_t i = 1; i < rows-1; i++) { for (size_t j = 1; j < cols-1; j++) { // 第一行 g00 = imageOutput.at<uchar>(i - 1, j - 1); g01 = imageOutput.at<uchar>(i - 1, j); g02 = imageOutput.at<uchar>(i - 1, j+1); // 第二行 g10 = imageOutput.at<uchar>(i , j - 1); g11 = imageOutput.at<uchar>(i , j); g12 = imageOutput.at<uchar>(i, j + 1); // 第三行 g20 = imageOutput.at<uchar>(i+1, j - 1); g21 = imageOutput.at<uchar>(i+1, j); g22 = imageOutput.at<uchar>(i+1, j + 1); // 当前点的梯度方向 int direction = theta.at<int>(i, j); g1 = 0; g2 = 0; g3 = 0; g4 = 0; // 保存亚像素点插值得到的灰度值 double t1 = 0; double t2 = 0; // 计算权重 double w = fabs((double)imageY.at<uchar>(i,j) / (double)imageX.at<uchar>(i, j)); if (w==0) { w = 0.0000001; } if (w>1) { w = 1 / w; } // g00 g01 g02 // g10 g11 g12 // g20 g21 g22 // ================================ if ((0 <= direction && direction < 45) || 180 <= direction && direction < 225) { t1 = g10 * (1 - w) + g20 * (w); t2 = g02 * (w)+g12 * (1 - w); } if ((45 <= direction && direction < 90) || 225 <= direction && direction < 270) { t1 = g01 * (1 - w) + g02 * (w); t2 = g20 * (w)+g21 * (1 - w); } if ((90 <= direction && direction < 135) || 270 <= direction && direction < 315) { t1 = g00 * (w)+g01 * (1 - w); t2 = g21 * (1 - w) + g22 * (w); } if ((135 <= direction && direction < 180) || 315 <= direction && direction < 360) { t1 = g00 * (w)+g10 * (1 - w); t2 = g12 * (1 - w) + g22 * (w); } if (imageInput.at<uchar>(i,j)<t1 || imageInput.at<uchar>(i, j) < t2) { imageOutput.at<uchar>(i, j) = 0; } } } } 5、 双阈值连接处理 双阈值处理给定一个高阈值high 一个低阈值low, low*[1.5,2]=high 这个是给定规则
判断条件就是
当前current<low ,那么current=0
low<current<hight current 不处理
current>hight current=255
/// <summary> /// 双阈值原理: /// 制定一个低阈值 L 一个 高阈值 H,一般取H为整体图像灰度分布的 7成 并且H为1.5-2L /// 灰度值<L gray=0, gray>H gray=255; /// </summary> /// <param name="imageIn"></param> /// <param name="low"></param> /// <param name="hight"></param> void DoubleThreshold(Mat& imageIn, const double low, const double hight) { if (!imageIn.data || imageIn.channels() != 1) { return; } int rows = imageIn.rows; int cols = imageIn.cols; double gray; for (size_t i = 0; i < rows ; i++) { for (size_t j = 0; j < cols ; j++) { gray = imageIn.at<uchar>(i, j); gray = gray > hight ? (255) : (gray < low) ? (0) : gray; imageIn.at<uchar>(i, j) = gray; } } }将边缘链接起来
经过上每一步的双阈值处理,我们基本上已经拿到了边缘点的候选点,下一步就是将这些边缘点联合起来,组成一个边缘轮廓
这里我们再次使用双阈值的机制 low 和 hight 和当前点的灰度值current
规则如下: current 的8邻域的灰度值 M介于【low,hight】中,有,可能是边缘点,这个领域的点M=255 ,并且回退 , 如果领域类没有 说明这个点是一个孤立的点 不做处理,
最后判断图像中所有的点,不是255 就是0 ,生成边缘
void DoubleThresholdLink(Mat& imageInput, double lowTh, double highTh) { if (!imageInput.data || imageInput.channels() != 1) { return; } int rows = imageInput.rows; int cols = imageInput.cols; double gray; for (size_t i = 1; i < rows-1; i++) { for (size_t j = 1; j < cols-1; j++) { gray = imageInput.at<uchar>(i, j); if (gray==255) { continue; } bool reback = false; // 寻找8领域中是否有介于low 和hight 的值 for (size_t k = -1; k < 2; k++) { for (size_t l= -1; l < 2; l++) { if (k == 0 && l == 0) //当前点 { continue; } double t = imageInput.at<uchar>(i + k, j + l); if (t>= lowTh&& t<highTh) { imageInput.at<uchar>(i + k, j + l) = 255; reback = true; } } } // 回退 if (reback) { if (i > 1) i--; if (j > 2)j -= 2; } } } // 最后调整 for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (imageInput.at<uchar>(i, j) != 255) { imageInput.at<uchar>(i, j) = 0; } } } }opencv 库结果:
还是用opencv库吧,结果比这个好多了
三、halcon 效果对比halcon的效果更好
code
read_image (Grayimage, 'C:/Users/alber/Desktop/opencv_images/1/grayImage.jpg') edges_sub_pix (Grayimage, Edges, 'canny', 1, 20, 40)
opencvc++canny实现以及与halconcanny的对比由讯客互联开源代码栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“opencvc++canny实现以及与halconcanny的对比”
上一篇
GaussDB数据库管理系统介绍
下一篇
BUUCTFningen1