opencv c++ canny 实现 以及与halcon canny的对比

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)


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/126249.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

预安装win11的电脑怎么退回正版win10?

对于新购的笔记本 通常来讲预装的系统是全新安装的&#xff0c;是没有之前Windows10系统文件的&#xff0c;无法回退。 可以打开设置-----系统----恢复-----看下是否有该选项。 ------------------------------------------------------------------------------- 若是在上述…

Vue.js中的双向数据绑定(two-way data binding)

聚沙成塔每天进步一点点 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 欢迎来到前端入门之旅&#xff01;感兴趣的可以订阅本专栏哦&#xff01;这个专栏是为那些对Web开发感兴趣、刚刚踏入前端领域的朋友们量身打造的。无论你是完全的新手还是有一些基础的开发…

Redis统计大法:挖掘数据的四重宝藏【redis第五部分】

Redis统计大法&#xff1a;挖掘数据的四重宝藏 前言第一&#xff1a;redis集合统计简介第二&#xff1a;聚合统计->数据的综合分析总和&#xff08;Sum&#xff09;&#xff1a;平均值&#xff08;Average&#xff09;中位数&#xff08;Median&#xff09; 第三&#xff1a…

RuoYi若依源码分析1 - security

Security springsecurity配置文件夹 security springsecurity总配置类 SecurityConfig.java SecurityConfig 总配置分析 首先看一下总配置&#xff0c;我们可以从总配置项里面大体的总结出springsecurity鉴权在ruoyi框架里面是如何执行的 自动装配关键处理类以及过滤器等…

oracle 将数据库中聚合的id按指定的符号分割后合并为一列

在这里插入图片描述 数据库中存在这样的数据是clob字段并且是多个先将其拆分聚合为一列成 SELECT distinct REGEXP_SUBSTR(to_char(p.ids), [^;], 1, LEVEL) AS id FROM xxxxx p CONNECT BY REGEXP_SUBSTR(to_char(p.ids), [^;], 1, LEVEL) IS NOT NULL

力扣第62题 不同路径 c++ 动态规划 dp二维 + dp一维 解法

题目 62. 不同路径 中等 相关标签 数学 动态规划 组合数学 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Fini…

【QT】鼠标常用事件

新建项目 加标签控件 当鼠标进去&#xff0c;显示【鼠标进入】&#xff0c;离开时显示【鼠标离开】 将QLable提升成自己的控件&#xff0c;然后再去捕获 添加文件 改继承的类名 提升类 同一个父类&#xff0c;可以提升 效果 现在代码就和Qlabel对应起来了。 在.h中声明&…

科研迷雾:读研以来,我发现的科研界“怪象”

1 引言 随着读论文和做实验的增多&#xff0c;我发现了sci的很多猫腻经不起细细推敲&#xff0c;原来科研并不如我想象的神圣&#xff0c;还不如工业界来的实在&#xff0c;因为在工业界做项目出现问题&#xff0c;客户是验收不了不给付钱的。所以论文只是一个玩具。 2 常见的…

大数据预处理与采集实验三:Urllib的GET和POST请求(1)

目录 Urllib基本操作-GET ➢没有进行utf-8编码的输出 ➢经过utf-8decode之后的输出 ➢ Timeout参数&#xff1a;捕获由于连接超时而引发的异常 ◆Urllib基本操作-定制请求头 ➢ 在GET请求中加入多个访问参数 ◆Urllib基本操作-POST ➢有道词典网页爬取&#xff1a;找到…

libpcap获取数据包

一、用户空间 以Linux以及TPACKET_V3为例。 调用pcap_dispatch获取数据包&#xff0c;然后回调用户传递的数据包处理函数。 read_op实际调用的是pcap_read_linux_mmap_v3 // pcap.c int pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user) {return (p-…

钡铼技术助力ARM工控机在智慧交通中的创新应用

在交通运输领域&#xff0c;钡铼技术ARM工控机可以实现以下功能&#xff1a; 实时监控和管理&#xff1a;利用钡铼技术ARM工控机&#xff0c;可以对交通运输中的车辆、船只、飞机等进行实时监测和管理&#xff0c;帮助调度员提高车辆调度和路线规划的准确性和效率。 安全保障&…

Vue 3.0中Treeshaking特性是什么?

一、是什么 Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术&#xff0c;专业术语叫 Dead code elimination 简单来讲&#xff0c;就是在保持代码运行结果不变的前提下&#xff0c;去除无用的代码 如果把代码打包比作制作蛋糕&#xff0c;传统的方式是把鸡…

js实现容器之间交换

&#x1f525;博客主页&#xff1a; 破浪前进 &#x1f516;系列专栏&#xff1a; Vue、React、PHP ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ JavaScript是一种非常流行和常用的编程语言&#xff0c;它在web开发中起着至关重要的作用&#xff0c;在实现网页动态交互、数据…

实时检测并识别视频中的汽车车牌

对于基于摄像头监控的安全系统来说,识别汽车牌照是一项非常重要的任务。我们可以使用一些计算机视觉技术从图像中提取车牌,然后我们可以使用光学字符识别来识别车牌号码。在这里,我将引导您完成此任务的整个过程。 要求: import cv2import numpy as npfrom skimage impor…

黑马 小兔鲜儿 uniapp 小程序开发- 商品详情模块- day05

黑马 小兔鲜儿 uniapp 小程序开发- 分类模块- day04-CSDN博客 小兔鲜儿 - 商品详情(登录前)-day05 商品详情页分为两部分讲解&#xff1a; 登录前&#xff1a;展示商品信息&#xff0c;轮播图交互&#xff08;当前模块&#xff09;登录后&#xff1a;加入购物车&#xff0c;立…

Xcode中如何操作Git

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的编码爱好者 大家好&#xff0c;我是 DevO…

[云原生案例1.] 构建LNMP架构并运行Wordpress个人博客平台

文章目录 1. 当前需求2. 前置准备3. 搭建过程3.1 创建自定义网络3.2 部署并配置nginx3.2.1 创建工作目录并上传相关软件包3.2.2 解压缩相关软件包3.2.3 编写Dockerfile文件3.2.4 编写nginx.conf文件3.2.5 创建nginx镜像3.2.6 运行容器 3.3 部署并配置mysql3.3.1 创建工作目录3.…

Python机器学习基础(二)---数据可视化

一.简单图形生成 1.Pandas生成折线图 import pandas as pd import numpy as np from matplotlib import pyplot #生成10行4列 标准正态分布的数据 df pd.DataFrame(np.random.randn(10,4),indexpd.date_range(1/1/2000,periods10), columnslist(ABCD)) df.plot() print(np.r…

分布式理论和分布式锁知识点总结

文章目录 (一) 分布式理论算法和协议1&#xff09;CAP理论总结 2&#xff09;BASE理论BASE 理论的核心思想基本可用软状态最终一致性 3&#xff09;Paxos算法Basic Paxos 算法4&#xff09; Raft算法1 拜占庭将军 5&#xff09;Gossip协议 (二) 分布式锁分布式锁应该具备哪些条…

89 柱状图中最大的矩形

柱状图中最大的矩形 类似接雨水&#xff08;反过来&#xff0c;相当于找接雨水最少的一段&#xff09;题解1 暴力搜索&#xff08;超时&#xff09; O ( N 2 ) O(N^2) O(N2)另一种 题解2 单调栈【重点学习】常数优化 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的…