(九)OpenCV Canny边缘检测
1.基础原理
参考自《数字图象处理》第十章
及OpenCV Tutorial Canny Edge Detector
1.1边缘检测概述
边缘检测是根据灰度突变来分割图像的一种常用方法。边缘模型可根据它们的灰度剖面来分类,通常分为台阶模型,斜坡模型和屋顶边缘模型。台阶模型多见于计算机生成的图像中,如实体建模和动画领域。实际图像中多为斜坡边缘模型。在使用二阶梯度来获取图像边缘时,二阶导数会产生一个局部极大正值和一个局部极小负值,在图像上体现为双线性效应。
1.2Canny边缘检测
1986年由John F. Canny
在A computational approach to edge detection.中提出,Canny
算法主要分为如下几个步骤:
- 使用高斯核平滑输入图像
- 计算梯度幅度图像和角度图像
- 对梯度幅度图像应用非极大值抑制
- 使用双阈值处理和连同性分析来检测和连接边缘
分别展开讨论,
-
高斯核平滑图像去除噪声
-
计算梯度幅度图和梯度方向图,令
f(x,y)
表示输入图像,梯度幅度和方向分别为:
M ( x , y ) = ∣ ∣ ▽ f ( x , y ) ∣ ∣ = g x 2 ( x , y ) + g y 2 ( x , y ) M(x,y)=||\triangledown f(x,y)||=\sqrt{g_x^2(x,y)+g_y^2(x,y)} M(x,y)=∣∣▽f(x,y)∣∣=gx2(x,y)+gy2(x,y)
α ( x , y ) = a r c t a n [ g y ( x , y ) g x ( x , y ) ] \alpha(x,y)=arctan[\frac{g_y(x,y)}{g_x(x,y)}] α(x,y)=arctan[gx(x,y)gy(x,y)],
其中, g x ( x , y ) = ∂ f ( x , y ) ∂ x g_x(x,y)=\frac{\partial f(x,y)}{\partial x} gx(x,y)=∂x∂f(x,y), g y ( x , y ) = ∂ f ( x , y ) ∂ y g_y(x,y)=\frac{\partial f(x,y)}{\partial y} gy(x,y)=∂y∂f(x,y),可使用(七)图像处理中常用算子Laplacian\Sobel\Roberts\Prewitt\Kirsch中的算子来求 g x ( x , y ) g_x(x,y) gx(x,y)和 g y ( x , y ) g_y(x,y) gy(x,y)。 -
对梯度幅度图像应用非极大值抑制,梯度图像 ∣ ∣ ▽ f ( x , y ) ∣ ∣ ||\triangledown f(x,y)|| ∣∣▽f(x,y)∣∣通常在局部极大值附近包含一些宽脊,可以使用非极大值抑制来细化这些宽脊。在一个
3x3
区域内,可以定义4个边缘法线(梯度向量)的4个方向,水平,垂直,+45度, -45度,每个方向包含的角度范围见下图,
使用
d
1
,
d
2
,
d
3
,
d
4
d_1,d_2,d_3,d_4
d1,d2,d3,d4表示前述3x3
区域的4个基本边缘方向,对以
α
\alpha
α中的任意点
(
x
,
y
)
(x,y)
(x,y)为中心的3x3
区域,对应的非极大值抑制方案为:
- 1)寻找
α
(
x
,
y
)
\alpha(x,y)
α(x,y)的方向
d
k
d_k
dk
- 2)令K表示
∣
∣
▽
f
∣
∣
||\triangledown f||
∣∣▽f∣∣ 在
(
x
,
y
)
(x,y)
(x,y)处的值。若K小于
d
k
d_k
dk方向上点(x,y)的一个或两个邻点处的
∣
∣
▽
f
∣
∣
||\triangledown f||
∣∣▽f∣∣值,则令
g
N
(
x
,
y
)
=
0
g_N(x,y)=0
gN(x,y)=0;否则,令
g
N
(
x
,
y
)
=
K
g_N(x,y)=K
gN(x,y)=K
对(x,y)的所有值重复这一过程,得到一幅与
f
(
x
,
y
)
f(x,y)
f(x,y)大小相同的非极大值抑制图像
g
N
(
x
,
y
)
g_N(x,y)
gN(x,y),图像
g
N
(
x
,
y
)
g_N(x,y)
gN(x,y)中只包含细化后的边缘。
- 使用双阈值处理和连同性分析来检测和连接边缘,得到
g
N
(
x
,
y
)
g_N(x,y)
gN(x,y)后对其进行阈值处理来求得边缘。
Canny
算法使用滞后阈值处理,设置两个阈值,低阈值 T L T_L TL和高阈值 T H T_H TH。可以将滞后阈值运算当作为创建两幅额外的图像:
g N H ( x , y ) = g N ( x , y ) ≥ T H g_{NH}(x,y)=g_N(x,y)\ge T_H gNH(x,y)=gN(x,y)≥TH
g N L ( x , y ) = g N ( x , y ) ≥ T L g_{NL}(x,y)=g_N(x,y)\ge T_L gNL(x,y)=gN(x,y)≥TL
与 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)相比 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)非零值更少, g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中的非零像素都在 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中,通过
g N L ( x , y ) = g N L ( x , y ) − g N H ( x , y ) g_{NL}(x,y) = g_{NL}(x,y) - g_{NH}(x,y) gNL(x,y)=gNL(x,y)−gNH(x,y)从 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中删除来自 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)的非零像素点。进行上述裁减后 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)和 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)分别表示“弱”边缘像素和“强”边缘像素。 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中的边缘像素被假设为有效的边缘像素,但其一般存在缝隙,通常需通过如下处理来形成更长的边缘:- 1)在 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中定位下一个未被访问的边缘像素p
- 2)将 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中与p 8 连通域中的所有弱像素点标记为有效边缘像素
- 3)若 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)中所有非零像素都已被访问,则转到4,否则返回1
- 4)将
g
N
L
(
x
,
y
)
g_{NL}(x,y)
gNL(x,y)中未标记为有效边缘像素的所有像素设置为0
经过上述步骤,将 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)中的所有非零像素附加到 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)上,形成Canny
算子输出的最终图像。
2.OpenCV API
void cv::Canny (
InputArray image,
OutputArray edges,
double threshold1,
double threshold2,
int apertureSize = 3,
bool L2gradient = false
)
image
:单通道灰度图edges
:存储边缘像素的图像threshold1
: 滞后阈值处理的超参数threshold2
: 滞后阈值处理的超参数apertureSize
:使用Sobel
算子时的卷积核大小L2gradient
:计算梯度幅值时是否使用 L 2 L_2 L2范数
3.示例
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat src, src_gray;
Mat dst, detected_edges;
int low_threshold = 0;
const int max_low_threshold = 100;
const int ratio = 3;
const int ks = 3;
const char *win_name = "Canny Edge Map";
static void CannyThreshold(int, void*)
{
blur(src_gray, detected_edges, Size(3,3));
Canny(detected_edges, detected_edges, low_threshold, low_threshold*ratio, ks);
dst = Scalar::all(0);
src.copyTo(dst, detected_edges);
imshow(win_name, dst);
imwrite("seg_res.png", dst);
}
int main(int argc, char **argv)
{
CommandLineParser parser( argc, argv, "{@input | fruits.jpg | input image}" );
src = imread( samples::findFile( parser.get<String>( "@input" ) ), IMREAD_COLOR ); // Load an image
if( src.empty() )
{
std::cout << "Could not open or find the image!\n" << std::endl;
std::cout << "Usage: " << argv[0] << " <Input image>" << std::endl;
return -1;
}
dst.create( src.size(), src.type() );
cvtColor( src, src_gray, COLOR_BGR2GRAY );
namedWindow( win_name, WINDOW_AUTOSIZE);
createTrackbar( "Min Threshold:", win_name, &low_threshold, max_low_threshold, CannyThreshold );
CannyThreshold(0, 0);
waitKey(0);
return 0;
}
相关文章
- 利用OpenCV进行边缘检测
- opencv学习笔记(六)直方图比较图片相似度
- OpenCV各个模块/各个文件夹的含义
- 基于OpenCV的灰度图像归一化到0~255(即对比度拉伸)的C++代码,并附原理介绍
- c#和c++的opencv位图数据参数互换问题解决方法
- OpenCV实现最大最小距离聚类算法
- Python数据处理Tips使用OpenCV预处理图像数据的10种操作
- 【毕业设计/课程设计】 基于opencv与SVM的车牌识别系统
- 【转载】 在PyTorch训练一个epoch时,模型不能接着训练,Dataloader卡死——在pytorch中尽量不要使用opencv而是使用PIL
- 第二章 Opencv图像处理基本操作
- 实战深度学习OpenCV(三):视频实时canny边缘检测
- 【pyqt5学习】——graphicView显示opencv图像