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

news2024/12/23 22:40:18

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.coloradmin.cn/o/1153133.html

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

相关文章

如何选购适合自己的内衣洗衣机?性价比高内衣洗衣机推荐

内衣洗衣机&#xff0c;在几年前或许是个新事物&#xff0c;但近两年却是成为了很普遍的家电产品&#xff0c;由于近几年内衣洗衣机需求量的增加&#xff0c;我们在商场的电子产品区都能见到它&#xff0c;就像普通的家庭洗衣机那样&#xff0c;直接摆在展台上销售&#xff0c;…

分布式:一文掌握分布式ID生成方案

目录 背景1、UUID2、数据库自增ID2.1、主键表2.2、ID自增步长设置 3、号段模式4、Redis INCR5、雪花算法6、美团(Leaf)7、百度(Uidgenerator)8、滴滴(TinyID)总结比较 背景 在复杂的分布式系统中&#xff0c;往往需要对大量的数据进行唯一标识&#xff0c;比如在对一个订单表进…

从零开始的目标检测和关键点检测(一):用labelme标注数据集

从零开始的目标检测和关键点检测&#xff08;一&#xff09;&#xff1a;用labelme标注数据集 1、可视化标注结果2、划分数据集3、Lableme2COCO&#xff0c;将json文件转换为MS COCO格式 前言&#xff1a;前段时间用到了mmlab的mmdetction和mmpose&#xff0c;因此以一个小的数…

python练习(猜数字,99乘法表)

python练习(猜数字&#xff0c;99乘法表) 猜数字 import random num1random.choice(range(1,101))for i in range(11):num2input("plz input a number:")num2int(num2)if num1<num2:print("太大了&#xff0c;小一点")elif num1>num2:print("…

【每日一题】2003. 每棵子树内缺失的最小基因值-2023.10.31

题目&#xff1a; 2003. 每棵子树内缺失的最小基因值 有一棵根节点为 0 的 家族树 &#xff0c;总共包含 n 个节点&#xff0c;节点编号为 0 到 n - 1 。给你一个下标从 0 开始的整数数组 parents &#xff0c;其中 parents[i] 是节点 i 的父节点。由于节点 0 是 根 &#xf…

可视化 | 数据可视化降维算法梳理

文章目录 &#x1f4da;数据描述&#x1f407;iris&#x1f407;MNIST &#x1f4da;PCA&#x1f407;算法流程&#x1f407;图像描述 &#x1f4da;Kernel-PCA&#x1f407;算法流程&#x1f407;图像描述 &#x1f4da;MDS&#x1f407;算法流程&#x1f407;图像描述 &#…

从初级测试工程师到测试专家,你的晋升路线是什么?

最近&#xff0c;我们讨论了软件测试工程的的分级&#xff0c;大家都贡献了自己的想法。 对于大家来说&#xff0c;软件测试人的分级其实也代表了我们的进阶方向&#xff0c;职业发展。总体来说&#xff0c;测试工程师未来发展有三个方向&#xff1a; 技术精英 行业专家 管理…

【Proteus仿真】【Arduino单片机】SG90舵机控制

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用SG90舵机等。 主要功能&#xff1a; 系统运行后&#xff0c;舵机开始运行。 二、软件设计 /* 作者&#xff1a;嗨小易&#xff08;QQ&#x…

pytorch复现3_GoogLenet

背景&#xff1a; GoogLeNeta是2014年提出的一种全新的深度学习结构&#xff0c;在这之前的AlexNet、VGG等结构都是通过增大网络的深度(层数)来获得更好的训练效果&#xff0c;但层数的增加会带来很多负作用&#xff0c;比如overfit、梯度消失、梯度爆炸等。GoogLeNet通过引入i…

代码精简10倍,责任链模式yyds

1 推荐看的文章1 责任链设计——责任链验证推翻 if-else 炼狱 2 推荐看的文章2 代码精简10倍&#xff0c;责任链模式yyds

引入了mybatis-spring-boot-starter,还需要引入mysql-connector-java吗?

spring boot集成mybatis&#xff0c;是需要引入mybatis-spring-boot-starter&#xff0c;有文章说不需要引入mysql-connector-java&#xff0c;但实际用下来并不行&#xff0c;我看了里面的pom文件&#xff0c;终于知道怎么一回事。 <!--引入mybatis的依赖--><depende…

HTTP协议说明

1.用于HTTP协议交互的信息被称为HTTP报文。请求端&#xff08;客户端&#xff09;的HTTP报文叫做请求报文&#xff0c;响应端&#xff08;服务器端&#xff09;的叫做响应报文。HTTP 报文本身是由多行&#xff08;用 CRLF 作换行符&#xff09;数据构成的字符串文本。 HTTP报文…

[Linux C] signal 的使用

前言&#xff1a; signal 是一种通信机制&#xff0c;可以跨进程发送&#xff0c;可以同进程跨线程发送&#xff0c;可以不同进程向指定线程发送。 信号的创建有两套api&#xff0c;一个是signal&#xff0c;一个是sigaction&#xff0c;signal缺陷很多&#xff0c;比如没有提…

亚马逊美国站衣物收纳商品合规标准是什么?如何办理?

随着秋季的来临&#xff0c;不少人翻箱倒柜地寻找换季用品。相信现在很多人都和小编一样&#xff0c;出门时打算找个外套穿上&#xff0c;但想到要去柜子里翻半天&#xff0c;就立刻打消了想要出门的念头。 但当翻箱倒柜地找到了换季用品&#xff0c;却又要一件一件地把翻出来…

Variations-of-SFANet-for-Crowd-Counting可视化代码

前文对Variations-of-SFANet-for-Crowd-Counting做了一点基础梳理&#xff0c;链接如下&#xff1a;Variations-of-SFANet-for-Crowd-Counting记录-CSDN博客 本次对其中两个可视化代码进行梳理 1.Visualization_ShanghaiTech.ipynb 不太习惯用jupyter notebook, 这里改成了p…

spring解决后端显示时区的问题

spring解决后端显示时区的问题 出现的问题&#xff1a; 数据库中的数据&#xff1a; 解决方法 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: Asia/Shanghai

vscode前端必备插件

安装插件的位置如下&#xff1a; 1、Chinese (Simplified) Language Pack 中文简体插件 2、Vetur Vue官方钦定插件&#xff0c;包括&#xff1a;语法高亮&#xff0c;智能提示&#xff0c;错误提示&#xff0c;格式化&#xff0c;自动补全等等 3、ESLint 语法检查工具&#…

客户端性能测试基础知识

目录 1、客户端性能 1.1、客户端性能基础知识 2、客户端性能工具介绍与环境搭建 2.1.1、perfdog的使用 2.1.2、renderdoc的使用 1、客户端性能 1.1、客户端性能基础知识 客户端性能知识这里对2D和3D类游戏进行展开进行&#xff0c;讲述的有内存、CPU、GPU、帧率这几个模块…

云栖大会十五年:开放创新,未来愿景

时光荏苒&#xff0c;转眼间云栖大会已经走过了十五个年头&#xff0c;这一场中国云计算行业的盛会已经成为业内不可或缺的一部分。在这个特殊的时刻&#xff0c;我想分享一些对未来云栖大会的期待与建议&#xff0c;希望这个盛会能够继续推动云计算领域的创新和发展。 云栖大会…

数据库深入浅出,数据库介绍,SQL介绍,DDL、DML、DQL、TCL介绍

一、基础知识&#xff1a; 1.数据库基础知识 数据(Data)&#xff1a;文本信息(字母、数字、符号等)、音频、视频、图片等&#xff1b; 数据库(DataBase)&#xff1a;存储数据的仓库&#xff0c;本质文件&#xff0c;以文件的形式将数据保存到电脑磁盘中 数据库管理系统(DBMS)&…