Canny边缘检测实现(Opencv C++)
Canny边缘检测是Canny在1986年提出来的,目前仍是图像边缘检测算法中最经典、先进的算法之一。canny方法基于如下三个基本目标:
1. 低错误率:所有边缘都应被找到,并且不应有虚假响应。
2. 最优定位:已定位的边缘必须尽可能接近真实边缘 。也就是说,由检测子标记为边缘的一点和真实边缘的中心之间的距离应最小。
3. 单个边缘点响应:对于每个真实的边缘点,检测子应只返回一个点。也就是说,真实边缘周围的局部最大数应该是最小的。这意味着检测子不应识别只存在单个边缘点的多个边缘像素。
canny的工作本质是,从数学上表达了前三个准则,并试图找到这些公式的最优解。
上篇博客介绍了边缘模型以及基本的边缘检测算法,接下来将了解Canny算法的步骤:
1. 使用高斯函数滤波器平滑输入图像
令f(x,y)表示输入图像,G(x,y)表示高斯函数:
我们用G和f卷积后得到平滑后的图像fs(x,y):
Mat gauImg;
GaussianBlur(src, gauImg, Size(13, 13), 2);
2. 计算梯度幅度和角度图像
计算梯度:
分别计算梯度幅度M的方向α:
代码实现:
Mat grad_x, grad_y, grad_xy;
Sobel(gauImg, grad_x, CV_32F, 1, 0, 3);
Sobel(gauImg, grad_y, CV_32F, 0, 1, 3);
Mat directImg, gradXY;
divide(grad_y, grad_x, directImg);
Mat gradX, gradY;
convertScaleAbs(grad_x, gradX);
convertScaleAbs(grad_y, gradY);
gradXY = gradX + gradY;
3. 非极大值抑制
利用非极大值抑制,细化梯度图像在局部极大值附近包含的一些宽脊。这种方法的实质是规定法线(梯度向量)的多个离散方向。例如,在一个3×3区域内,对于一个过该区域中心点的边缘,我们可以定义4个方向:水平,垂直,+45度和-45度,分别为d1,d2,d3,d4。对于任意点(x,y)为中心的3×3区域,我们将非极大值抑制方案表述如下:
1. 寻找最接近α(x,y)的方向dk。
2. 令K表示梯度幅值在(x,y)处的值。若K小于dk方向上的点(x,y)的一个或两个邻点处的幅度值,令g(x,y)=0(抑制);否则,令g(x,y)=K。
对x和y的所有值重复这一过程,产生一幅与梯度幅值图像大小相同的非极大值抑制图像g(x,y)。例如下图的边缘点,方向均是垂直方向,感兴趣的点为p2和p8,如果p5大于等于这两点,则保留该点,不然令该点为0(抑制)。
代码实现:将梯度法线分为4个方向,根据4个方向找相邻感兴趣的两个点的梯度幅值
void NMS(Mat& grad_x, Mat& grad_y,Mat& gradXY, Mat& directImg, Mat& dst) {
dst = gradXY.clone();
for (int i = 1; i < grad_x.rows - 1; i++) {
for (int j = 1; j < grad_x.cols - 1; j++) {
directImg.at<float>(i, j) = atan(directImg.at<float>(i, j));
int P1 = (int)gradXY.at<uchar>(i - 1, j - 1);
int P2 = (int)gradXY.at<uchar>(i - 1, j);
int P3 = (int)gradXY.at<uchar>(i - 1, j + 1);
int P4 = (int)gradXY.at<uchar>(i, j - 1);
int P5 = (int)gradXY.at<uchar>(i, j);
int P6 = (int)gradXY.at<uchar>(i, j + 1);
int P7 = (int)gradXY.at<uchar>(i + 1, j - 1);
int P8 = (int)gradXY.at<uchar>(i + 1, j);
int P9 = (int)gradXY.at<uchar>(i + 1, j + 1);
// 边缘法线水平 —
if (directImg.at<float>(i, j) <= CV_PI / 8 && directImg.at<float>(i, j) >= -CV_PI / 8)
if (P5 < P4 || P5 < P6)
dst.at<uchar>(i, j) = 0;
// 边缘法线45° \
if (directImg.at<float>(i, j) > CV_PI / 8 && directImg.at<float>(i, j) < CV_PI / 8 * 3)
if (P5 < P1 || P5 < P9)
dst.at<uchar>(i, j) = 0;
// 边缘法线垂直 |
if ((directImg.at<float>(i, j) >= CV_PI / 8 * 3 && directImg.at<float>(i, j) <= CV_PI / 2) ||
(directImg.at<float>(i, j) <= -CV_PI / 8 * 3 && directImg.at<float>(i, j) >= -CV_PI / 2))
if (P5 < P2 || P5 < P8)
dst.at<uchar>(i, j) = 0;
// 边缘法线-45° /
if (directImg.at<float>(i, j) < -CV_PI / 8 && directImg.at<float>(i, j) > -CV_PI / 8 * 3)
if (P5 < P3 || P5 < P7)
dst.at<uchar>(i, j) = 0;
}
}
}
通常为了更加精确的计算,在跨梯度方向的两个相邻像素之间使用线性插值来得到要比较的像素梯度。在讨论时分为下面4种情况:
代码实现:线性插值计算感兴趣两点的梯度幅值
void NMS(Mat& grad_x, Mat& grad_y, Mat& gradXY, Mat& dst) {
dst = gradXY.clone();
for (int i = 1; i < grad_x.rows - 1; i++) {
for (int j = 1; j < grad_x.cols - 1; j++) {
float gx = grad_x.at<float>(i, j);
float gy = grad_y.at<float>(i, j);
int g1, g2, g3, g4; float w;
if (abs(gy) > abs(gx)){
g2 = gradXY.at<uchar>(i - 1, j);
g3 = gradXY.at<uchar>(i + 1, j);
w = abs(gx / gy);
if (gx * gy > 0) {
g1= gradXY.at<uchar>(i - 1, j - 1);
g4 = gradXY.at<uchar>(i + 1, j + 1);
}
else {
g1 = gradXY.at<uchar>(i - 1, j + 1);
g4 = gradXY.at<uchar>(i + 1, j - 1);
}
}
else {
g2 = gradXY.at<uchar>(i, j - 1);
g3 = gradXY.at<uchar>(i, j + 1);
w = abs(gy / gx);
if (gx * gy > 0) {
g1 = gradXY.at<uchar>(i - 1, j - 1);
g4 = gradXY.at<uchar>(i + 1, j + 1);
}
else {
g1 = gradXY.at<uchar>(i + 1, j - 1);
g4 = gradXY.at<uchar>(i - 1, j + 1);
}
} float grad1 = g1 * w + g2 * (1 - w);
float grad2 = g4 * w + g3 * (1 - w);
if (gradXY.at<uchar>(i, j) < grad1 || gradXY.at<uchar>(i, j) < grad2)
dst.at<uchar>(i, j) = 0;
}
}
}
4. 使用双阈值处理和连通性分析来检测和连接边缘
双阈值处理是指设置两个阀值,分别为TL和TH。其中大于TH的都被检测为边缘,而低于TL的都被检测为非边缘。对于中间的像素点,如果与确定为边缘的像素点邻接,则判定为边缘;否则为非边缘。具体地说,高阈值得到的图像gH为强边缘,低阈值得到图像gL的弱边缘。强边缘均被认为是有效边缘,并被立即标记。较长的边缘容易被高阈值切断,可利用下面的步骤连接较长的边缘:
(a)在gH中定位下一个未被访问的边缘像素p;
(b)将gL(x,y)中中用8连通连接到p的所有若像素标记为有效边缘像素
(c)如果gH中的所有非零像素已被访问,则跳到步骤(d),否则返回步骤(a)
(d)将gL中未标记为有效边缘像素的所有像素设置为零
在这一过程的末尾,将来自gL(x,y)的所有非零像素附加到gH(x,y),形成canny算子输出的最终图像。
代码实现:利用了膨胀重建完成上面的过程
以gH为标记图,gL为模板,进行膨胀重建,若想了解形态学重建,移步链接
//膨胀重建
void dilateRestruct(Mat& mark, Mat& mould, Size dilate_size, Mat& dst) {
Mat dilateImg_pre = mark.clone();
Mat temp = mark.clone();
Mat cmp = Mat::zeros(mould.size(), CV_8UC1);
Mat element = getStructuringElement(MORPH_RECT, dilate_size);
int n = -1;
while (n != mould.cols * mould.rows) {
morphologyEx(dilateImg_pre, temp, MORPH_DILATE, element);
bitwise_and(temp, mould, temp);
compare(temp, dilateImg_pre, cmp, 0);
n = countNonZero(cmp);
dilateImg_pre = temp.clone();
}
dst = temp;
}
//双阈值
void double_threshold(Mat& src, int T_low, int T_high, Mat& dst) {
Mat g_h, g_l, g;
threshold(src, g_h, 65, 255, THRESH_BINARY);
threshold(src, g_l, 25, 255, THRESH_BINARY);
dilateRestruct(g_h, g_l, Size(3, 3), dst);
}
尽管非极大值抑制后的边缘要比原始梯度边缘细,但仍要粗于1像素。为得到1像素粗的边缘,通常要在步骤4后执行一次边缘细化算法(细化见链接)。
完整代码见链接
参考:
1. 冈萨雷斯《数字图像处理(第四版)》Chapter 10(所有图片可在链接中下载)
Canny边缘检测实现(Opencv C++)的更多相关文章
- OpenCV图像Canny边缘检测
Canny边缘检测 图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘函数原型: void cvCanny( ...
- OpenCV: Canny边缘检测算法原理及其VC实现详解(转载)
原文地址:http://blog.csdn.net/likezhaobin/article/details/6892176 原文地址:http://blog.csdn.net/likezhaobin/ ...
- openCV(四)---Canny边缘检测
图像的边缘检测的原理是检测出图像中所有灰度值变化较大的点,而且这些点连接起来就构成了若干线条,这些线条就可以称为图像的边缘. 直接上代码,函数简介都在代码注释中 //canny边缘检测 -(void) ...
- 基于opencv下对视频的灰度变换,高斯滤波,canny边缘检测处理,同窗体显示并保存
如题:使用opencv打开摄像头或视频文件,实时显示原始视频,将视频每一帧依次做灰度转换.高斯滤波.canny边缘检测处理(原始视频和这3个中间步骤处理结果分别在一个窗口显示),最后将边缘检测结果保存 ...
- Python+OpenCV图像处理(十三)—— Canny边缘检测
简介: 1.Canny边缘检测算子是John F. Canny于 1986 年开发出来的一个多级边缘检测算法. 2.Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是: 好的检测- ...
- OpenCV——边缘检测入门、Canny边缘检测
边缘检测的一般步骤: 最优边缘检测的三个评价标准: 低错误率:表示出尽可能多的实际边缘,同时尽可能地减少噪声产生的误报: 高定位性:标识出的边缘要与图像实际边缘尽可能接近: 最小响应:图像中的边缘只能 ...
- OpenCV学习代码记录——canny边缘检测
很久之前学习过一段时间的OpenCV,当时没有做什么笔记,但是代码都还在,这里把它贴出来做个记录. 代码放在码云上,地址在这里https://gitee.com/solym/OpenCVTest/tr ...
- openCV实例:Canny边缘检测
http://blog.sina.com.cn/s/blog_737adf530100z0jk.html 在第一次使用openCV程序成功对图像进行打开后,现在开始试验第二个例程试验:Canny边缘检 ...
- OpenCV学习笔记(11)——Canny边缘检测
了解Canny边缘检测的概念 1.原理 Canny边缘检测是一种非常流行的边缘检测算法,是 John F.Canny在1986年提出的.它是一个有很多步构成的算法 1)噪声去除 使用5*5的高斯滤波器 ...
- Canny边缘检测算法(基于OpenCV的Java实现)
目录 Canny边缘检测算法(基于OpenCV的Java实现) 绪论 Canny边缘检测算法的发展历史 Canny边缘检测算法的处理流程 用高斯滤波器平滑图像 彩色RGB图像转换为灰度图像 一维,二维 ...
随机推荐
- redis 简单整理——redis shell[九]
前言 简单介绍一下redis的shell命令. 正文 redis 提供了一些工具,如redis-cli.redis-server.redis-benchmark等. redis-cli -r 对red ...
- 简单介绍 Vue 3.0 项目创建
一.前期转杯 确保电脑上已安装 node.js. 可通过命令 npm --version进行查询,如果展示了版本号,则说明已安装,若提示 npm 不是有内部或外部命令,也不是可运行的程序,则说明未安装 ...
- 使用纯c#在本地部署多模态模型,让本地模型也可以理解图像
之前曾经分享过纯c#运行开源本地大模型Mixtral-8x7B 当时使用的是llamasharp这个库和Mixtral的模型在本地部署和推理,前段时间我看到llamasharp更新到了0.11.1版本 ...
- 力扣500(java&python)-键盘行(简单)
题目: 给你一个字符串数组 words ,只返回可以使用在 美式键盘 同一行的字母打印出来的单词.键盘如下图所示. 美式键盘 中: 第一行由字符 "qwertyuiop" 组成.第 ...
- 当 AI 邂逅绘画艺术,能迸发出怎样的火花?
简介: 2021年初,OpenAI 团队发布了能够根据文本描述生成图像的 DALL-E 模型.由于其强大的跨模态图像生成能力,引起自然语言和视觉圈技术爱好者的强烈追捧.仅仅一年多的时间,多模态图像生成 ...
- 直播回顾 | 云原生混部系统 Koordinator 架构详解(附完整PPT)
简介: 近期,来自 Koordinator 社区的两位技术专家从项目的架构和特性出发,分享了 Koordinator 是如何应对混部场景下的挑战,特别是提升混部场景下工作负载的运行的效率和稳定性,以及 ...
- 阿里云贾扬清:大数据+AI工程化,让数据从「成本」变为「资产」
简介: 近年来,数字经济发展迅速,企业转型背后频频涌现「数字力量」的身影.云计算.大数据.人工智能的快速融合形成了数字经济的新基建,也为数字经济发展带来了新的机遇. 5 月 20 日,阿里巴巴副总裁. ...
- 编译优化 | LLVM代码生成技术详解及在数据库中的应用
简介: 作者:长别 1. 前言 随着IT基础设施的发展,现代的数据处理系统需要处理更多的数据.支持更为复杂的算法.数据量的增长和算法的复杂化,为数据分析系统带来了严峻的性能挑战.近年来,我们可以在数据 ...
- [GPT] 用 document.querySelector('.xxx') 选择下级的第二个 div 要怎么写
要选择类名为 .xxx 的元素下的第二个子<div>元素,可以将 querySelectorAll()方法与CSS选择器一起使用. 以下是一个示例: const secondChild ...
- selenium项目中遇到的问题总结
问题:在pycharm中运行用例能成功,在命令行运行提示找不到com包解决办法:添加一个PYTHONPATH的环境变量,值为工程目录的路径 当要查找的文本前后有换行时,用如下方法解决//td[cont ...