halcon canny 和opencv c++ canny 实现对比

news/2025/1/17 22:03:01/

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

 

三、halcon 效果对比

 

四、canny 的优劣性


http://www.ppmy.cn/news/1187060.html

相关文章

day55--动态规划13

300.最长递增子序列 674. 最长连续递增序列 718. 最长重复子数组 第一题&#xff1a;最长递增子序列 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而…

腾讯云轻量应用服务器“镜像”怎么选择合适?

腾讯云轻量应用服务器镜像怎么选择&#xff1f;如果是用来搭建网站可以选择宝塔Linux面板腾讯云专享版&#xff0c;镜像系统根据实际使用来选择&#xff0c;腾讯云百科txybk.com来详细说下腾讯云轻量应用服务器镜像的选择方法&#xff1a; 腾讯云轻量应用服务器镜像选择 轻量…

Linux mv命令:移动文件或改名

mv 命令&#xff08;move 的缩写&#xff09;&#xff0c;既可以在不同的目录之间移动文件或目录&#xff0c;也可以对文件和目录进行重命名。该命令的基本格式如下&#xff1a; [rootlocalhost ~]# mv 【选项】 源文件 目标文件 -f&#xff1a;强制覆盖&#xff0c;如果目标文…

部署私有仓库(笔记docker应用)

二&#xff1a;部署私有仓库 docker pull daocloud.io/library/registry:latest docker run --restartalways -d -p 5000:5000 daocloud.io/library/registry systemctl stop firewalld systemctl restart docker 宿主机ip端口 curl -I 127.0.0.1:5000 将镜像存放在仓…

稿C语言基础 -- scanf函数的工作原理

一、前言、基本概念和术语 在C语言中&#xff0c;输入主要是靠标准输入函数&#xff0c;也就是scanf函数来完成的。要正确的调用scanf函数来完成输入&#xff0c;需要了解scanf的工作原理。为了讲清楚原理&#xff0c;我先铺垫一下&#xff0c;介绍几个概念。 &#xff08;1&…

http1,https,http2,http3总结

1.HTTP 当我们浏览网页时&#xff0c;地址栏中使用最多的多是https://开头的url&#xff0c;它与我们所学的http协议有什么区别&#xff1f; http协议又叫超文本传输协议&#xff0c;它是应用层中使用最多的协议&#xff0c; http与我们常说的socket有什么区别吗&#xff1f; …

Egg.js使用MySql数据库

最近在接手一个项目&#xff0c;vuenuxtegg&#xff0c;我也是刚开始学习egg.js&#xff0c;所以会将自己踩的坑都记录下来。 安装mysql 使用sequelize连接数据库&#xff0c;首先安装egg-sequelize和mysql2。 npm install --save egg-sequelize mysql2打开package.json文件…

[PHP]Tpflow工作流引擎 v6.0.9

源码下载:https://download.csdn.net/download/m0_66047725/88452225 TpFlow工作流引擎是一套规范化的流程管理系统&#xff0c;基于业务而驱动系统生命力的一套引擎。 彻底释放整个信息管理系统的的活力&#xff0c;让系统更具可用性&#xff0c;智能应用型&#xff0c;便捷…