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)
相关文章:

opencv c++ canny 实现 以及与halcon canny的对比
Opencv和C实现canny边缘检测_opencv边缘增强-CSDN博客 一、canny实现步骤 1、图像必须是单通道的,也就是说必须是灰度图像 2、图像进行高斯滤波,去掉噪点 3、sobel 算子过程的实现,计算x y方向 、梯度(用不到,但是…...

阿里云无影升级2.0 云电脑解决方案时代到来
10月31日,杭州云栖大会上,阿里云宣布无影全新升级2.0:从云电脑到云上解决方案,帮助中小企业更便捷地构建云上办公,并开放无影产品及解决方案能力,为生态合作伙伴提供企业云平台,帮助其打造定制化…...

【案例展示】多物理场仿真软件介绍
本期案例来自上海交通大学微波与射频研究中心团队,团队针对集成电路技术发展过程中面临的挑战,通过FastCAE平台对自研多物理场仿真求解器进行功能拓展与集成,使用户可基于不同的电、热、应力场景和仿真需求,灵活设定参数进行快速准…...

k8s的RBAC中,clusterrole, rolebinding 是什么关系谁先谁后
在Kubernetes中,Role-Based Access Control(RBAC)用于控制集群中不同用户、服务账号或组的访问权限。ClusterRole 和 RoleBinding 是两个关键的 RBAC 组件,它们之间的关系是 ClusterRole 定义了一组权限规则,而 RoleBi…...

myabtis流式查询
1、流式查询简介 流式处理在大数据方面应用比较广泛。随着数据的爆发式增长,流式处理的方式也被应用到日常的工具中,如JDK的对于集合处理的Stream流、Redis5.0新增的数据结构Stream专门来处理消息等。 流式查询指的是查询成功后不是返回一个集合而是返回…...

K8S的pod创建过程
创建流程图 用户发起请求创建deployment;apiserver收到创建资源的请求,apiserver对客户端操作进行身份认证,认证完成后接受该请求,并把相关的信息保存到etcd中,然后返回确认信息给客户端;apiserver向etcd…...

java修仙传之海岛奇遇
昨日开会, 商量了一下接口返回数据, 要求统一, 之前也同意,直接抛异常, 现在觉得之前那个异常不好, 看着不美观,对客户不友好 要求重新做。 大概要求如下: 要求1:范…...

电子商务平台对接电商供应链,不得不说的开放平台电商API接口
B2B电商开放平台的设计需要从以下几面去思考: 开放平台API接口的设计,主要是从功能需求的角度,设计满足业务需求的接口及对应的字段; 平台与商家之间信息的对接,对接的方法有哪些?对接过程中需要可能会遇到…...

【JAVA学习笔记】 57 - 本章作业
项目代码 https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter14/src/com/yinhai/homework 1. (1)封装个新闻类,包含标题和内容属性,提供get, set方法, 重写toString方法,打印对象时只打印标题; (2)只提供…...

【题解】[GenshinOI Round 3] P9816 少项式复合幂
题目链接 分析 首先这题给了很大的提示信息 注意 m 和 p 的范围 , 很自然的想到可以先把所有可能的 f ( x ) f(x) f(x) 算出来. 思维误区 有些人在算完 f ( x ) f(x) f(x) 之后可能就会去思考找环的问题,然后一些码力弱的大佬就会祭掉. 在经过仔细的观察之后…...

手写数字识别--神经网络实验
实验源码自取: 神经网络实验报告源码.zip - 蓝奏云 上深度学习的课程,老师布置了一个经典的实验报告,我做了好久才搞懂,所以把实验报告放到CSDN保存,自己忘了方便查阅,也为其他人提供借鉴 由于本人是小白…...

双11消费遇冷?如何让消费回归心智原点
近一年来,小红书话题「重新养育自己」引热议。直面成长缺憾,不少人探寻解决方案,即像对待新生命般,不论是衣食住行还是心灵,重新关照自己。 借此,本期千瓜将锁定小红书热门话题背后的消费观转变࿰…...

一分钟了解:什么是Image Matting?
1. 基本概念 Image Matting是图像处理领域的一个基本任务,意为“图像背景抠出”或者“抠图”。这项任务在图像处理、影视制作领域广泛应用。比如,拍电影时常用的扣绿,就是演员在绿幕前面表演,后期再把人物抠出来放到一个新的背景…...

微信小程序 跳转客服页面
前言 小程序 用户反馈 没有页面设计 可以直接跳转小程序指定客服页面 <button class"contactBtn"open-type"contact" contact"handleContact" session-from"sessionFrom">...

10个简单增删改查的免费Spring Boot源代码项目
此页面包含用于学习目的的免费 Spring boot 项目列表。每个 Spring boot 项目的源代码都托管在 GitHub 存储库上,因此您可以免费下载或克隆源代码并亲身体验 Spring boot 框架。 1.员工管理应用程序(ReactJS Spring Boot CRUD全栈应用程序) …...

mysql数据表设计
命名 mysql表名的命名规范为表名可以用 t_ 、tb_的前缀,或者是业务模块前缀。比如t_order。 有些项目也会使用 tt_、tm_、 ts_ 等前缀,根据项目的习惯命名就好了。 主键: AUTO_INCREMENT 表示自增,UNSIGNED 表示无符号…...

pytorch复现4_Resnet
ResNet在《Deep Residual Learning for Image Recognition》论文中提出,是在CVPR 2016发表的一种影响深远的网络模型,由何凯明大神团队提出来,在ImageNet的分类比赛上将网络深度直接提高到了152层,前一年夺冠的VGG只有19层。Image…...

【数据库】形式化关系查询语言(一):关系代数Relational Algebra:基本运算、附加关系代数、扩展的关系代数
目录 一、关系代数Relational Algebra 1. 基本运算 a. 选择运算(Select Operation) b. 投影运算(Project Operation) 组合 c. 并运算(Union Operation) d. 集合差运算(Set Difference Op…...

【计算机网络】计算机网络和因特网
一.基本术语介绍 端系统通过通信链路(communication link)和分组交换机(packet switch)连接到一起,连接这些端系统和分组交换机的物理媒体包括:同轴电缆,铜线,光纤和无线电频谱。而…...

JAVA面经整理(9)
一)什么是Spring?它有什么优点? spring是一款顶级的开源框架,他是包含了众多工具方法的IOC容器,Spring中包含了很多模块,比如说Spring-core,Spring-context,Spring-aop,Spring-web,…...

IPD(集成产品开发)模式下的产品研发流程
IPD(集成产品开发)涵盖了产品从创意提出到研发、生产、运营等,包含了产品开发到营销运营的整个过程。围绕产品(或项目)生命周期的过程的管理模式,是一套生产流程,更是时下国际先进的管理体系。I…...

Flutter GetX的使用
比较强大的状态管理框架 引入库: dependencies:get: ^4.6.6一.实现一个简单的demo 实现一个计数器功能 代码如下: import package:flutter/material.dart; import package:get/get.dart;void main() > runApp(const GetMaterialApp(home: Home()…...

【Amazon】AWS实战 | 快速发布安全传输的静态页面
文章目录 一、实验架构图二、实验涉及的AWS服务三、实验操作步骤1. 创建S3存储桶,存放网站网页2. 使用ACM建立域名证书3. 设置Cloudfront,连接S3存储桶✴️4. 设置Route53,解析域名服务5. 通过CLI工具上传网页更新内容【可选】 四、实验总结 …...

前后端登录的密码加密和解密
在一个典型的前后端应用中,前端对密码进行加密后传给后端,后端再进行解密或验证。这通常涉及前端加密、后端解密或验证的相互配合。下面是一个基本的流程: 前端加密: 前端可以使用各种加密库或算法对密码进行加密。常见的是使用哈…...

使用 Curl 和 DomCrawler 下载抖音视频链接并存储到指定文件夹
项目需求 假设我们需要从抖音平台上下载一些特定的视频,以便进行分析、编辑或其他用途。为了实现这个目标,我们需要编写一个爬虫程序来获取抖音视频的链接,并将其保存到本地文件夹中。 目标分析 在开始编写爬虫之前,我们需要了…...

取消Excel打开密码的两种方法
Excel设置了打开密码,想要取消打开密码是由两种方法的,今天分享这两种方法给大家。 想要取消密码是需要直到正确密码的,因为只有打开文件才能进行取消密码的操作 方法一: 是大家常见的取消方法,打开excel文件之后&a…...

多测师肖sir_高级金牌讲师_jmeter 反向代理录制脚本
jemeter自带的录制脚本功能,是利用代理服务器来进行录制的 1,新建一个线程组 2,新建一个代理服务器 右击工作台-添加-非测试元件-http代理服务器 3, 配置http代理服务器 端口: 默认为8888,可修改。但…...

网络取证-Tomcat-简单
题干: 我们的 SOC 团队在公司内部网的一台 Web 服务器上检测到可疑活动。为了更深入地了解情况,团队捕获了网络流量进行分析。此 pcap 文件可能包含一系列恶意活动,这些活动已导致 Apache Tomcat Web 服务器遭到破坏。我们需要进一步调查这一…...

3.Linux常用操作(传输、crontab定时、匹配日期删除文件等)
1. 服务器之间传输文件 1.1 传输文件到本服务器 scp -P 19622 -C dockeruser192.168.100.96:/home/dockeruser/lgr/lgr.dmp /home/dockeruser/lgr描述: 用dockeruser账号登录端口号为19622的192.168.100.96服务器,将此服务器的/home/dockeruser/lgr/l…...

ChatGPT对未来发展的影响?一般什么时候用到GPT
ChatGPT以其强大的自然语言处理能力对未来的发展具有重要影响。以下是ChatGPT的潜在影响和一般使用情况: 改善自然语言理解和生成:ChatGPT和类似的模型可以改善机器对人类语言的理解和生成。这将有助于改进各种应用领域,包括智能助手、聊天机…...