欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

图像处理——边缘检测

程序员文章站 2024-01-28 08:59:58
...

图像处理——边缘检测

边缘检测是为了识别物体的边缘,而边缘是由数字图像中亮度变化明显的点连接而成的,主要可以通过基于图像强度的一阶和二阶导数来寻找到这些点。

边缘检测可以分成三个步骤:

  1. 滤波。由于导数对噪声敏感,所以在边缘检测之前可以先试着降低图片的噪声,常用的是高斯滤波。
  2. 增强。增强算法可以将图像灰度点邻域强度值有显著变化的点凸显出来,可以通过计算梯度幅值来确定。
  3. 检测。根据梯度幅值就可以检测出物体的边缘,由于经过图像增强,有些店并不是要找的边缘值,我们还可以通过阈值化的方式来筛选。

canny

Canny边缘检测算子是澳洲计算机科学家约翰·坎尼(John F. Canny)于1986年开发出来的一个多级边缘检测算法。更为重要的是Canny创立了“边缘检测计算理论”(computational theory of edge detection)解释这项技术如何工作。

Canny的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:

  • 好的检测 - 算法能够尽可能多地标识出图像中的实际边缘。
  • 好的定位 - 标识出的边缘要与实际图像中的实际边缘尽可能接近。
  • 最小响应 - 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。

为了满足这些要求Canny使用了变分法,这是一种寻找满足特定功能函数的方法。最优检测使用四个指数函数项的和表示,但是它非常近似于高斯函数的一阶导数

—— *

我们可以使用 OpenCV 中的 Canny() 来做边缘检测。

1
2
3
4
5
6
7
void cv::Canny	(	InputArray 	image,
					OutputArray 	edges,
					double 	threshold1,
					double 	threshold2,
					int 	apertureSize = 3,
					bool 	L2gradient = false 
)
  • src: 源图像,8位即可
  • edges: 输出的边缘图,要求与源图像保持一样的尺寸和类型
  • threshold1: 第一个滞后性阈值
  • threshold2: 第二个滞后性阈值
  • apertureSize: 表示应该用 Sobel 算子的空间大小,默认为 3
  • L2gradient: 计算图像梯度幅值的标识,默认为 false

注:threshold1 和 threshold2 中较小的用于边缘连接,交大的用来控制强边缘的初始段,推荐的高低阈值比在 2:1 到 3:1 之间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include "opencv2/highgui.hpp"
#include "opencv2/imgcodecs.hpp"
#include "opencv2/imgproc.hpp"

int main()
{
    cv::Mat src = cv::imread("../images/persimmon.jpg", cv::IMREAD_COLOR);

    cv::Mat dst, edge, gray;

    // 【1】创建与src同类型和大小的矩阵(dst)
    dst.create(src.size(), src.type());

    // 【2】将原图像转换为灰度图像
    cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);

    // 【3】运行Canny算子
    cv::Canny(gray, edge, 150, 50, 3);

    // 【4】显示效果图
    cv::namedWindow("【原图】边缘检测", cv::WINDOW_NORMAL);
    cv::namedWindow("【效果图】Canny边缘检测", cv::WINDOW_NORMAL);

    imshow("【原图】边缘检测", gray);
    imshow("【效果图】Canny边缘检测", edge);

    cv::waitKey(0);
}

sobel

索伯算子(Sobel operator)图像处理中的算子之一,有时又称为索伯-费德曼算子索贝滤波器,在影像处理电脑视觉领域中常被用来做边缘检测。索伯算子最早是由美国计算机科学家艾尔文·索伯(Irwin Sobel)及盖瑞·费德曼(Gary Feldman)于1968年在史丹佛大学的人工智能实验室(SAIL)所提出,因此为了表扬他们的贡献,才用他们的名字命名。在技术上,它是一离散性差分算子,用来运算图像亮度函数的梯度之近似值。在图像的任何一点使用此算子,索伯算子的运算将会产生对应的梯度向量或是其范数。概念上,索伯算子就是一个小且是整数的滤波器对整张影像在水平及垂直方向上做卷积,因此它所需的运算资源相对较少,另一方面,对于影像中的频率变化较高的地方,它所得的梯度之近似值也比较粗糙。

—— *

Sobel 函数使用扩展的 Sobel 算子来计算一阶、二阶、三阶或者混合图像的差分,来看一下 OpenCV 中的 Sobel()。

1
2
3
4
5
6
7
8
9
10
void cv::Sobel	(	InputArray 	src,
					OutputArray 	dst,
					int 	ddepth,
					int 	dx,
					int 	dy,
					int 	ksize = 3,
					double 	scale = 1,
					double 	delta = 0,
					int 	borderType = BORDER_DEFAULT 
)
  • src: 源图像,Mat 类型
  • dst: 输出图像,尺寸和类型需要与 src 相同
  • ddepth: 输出图像深度,支持如下 src.depth() 和 ddepth 的组合
  • dx: x 方向上的差分阶数
  • dy: y 方向上的差分阶数
  • ksize: Sobel 卷积核的代销,默认值为 3,必须取 1、3、5 或 7
  • scale: 计算导数值时可选的缩放因子,默认值是 1,表示默认情况下是没有应用缩放的,更多可以查看文档
  • delta: 表示结果存入输出图像之前可选的 delta 值,默认为 0
  • borderType: 推断图像外包部像素的某种边界模式,一般用默认的即可

注:

  1. 当内核大小为 3 时,Sobel 内核可能产生比较明显的误差。为此 OpenCV 提供了结果更加精确的 Scharr() ,它仅作用于大小为 3 的卷积核,运行速度与 Sobel() 一样快,当结果更精确。
  2. 计算图像 X 方向导数是可以取 xorder = 1, uorder = 0, ksize = 3
  3. 计算图像 Y 方向导数是可以取 xorder = 0, uorder = 1, ksize = 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//---------------【边缘检测】----------------
// 描述:sobel 函数用法示例
//------------------------------------------

#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

using namespace cv;

int main()
{
    //【0】创建 grad_x 和 grad_y 矩阵
	Mat grad_x, grad_y;
	Mat abs_grad_x, abs_grad_y,dst;

	//【1】载入原始图  
	Mat src = imread("images/2.jpg");  //工程目录下应该有一张名为1.jpg的素材图

	//【2】显示原始图 
	imshow("【原始图】sobel边缘检测", src); 

	//【3】求 X方向梯度
	Sobel( src, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT );
	convertScaleAbs( grad_x, abs_grad_x );
	imshow("【效果图】 X方向Sobel", abs_grad_x); 

	//【4】求Y方向梯度
	Sobel( src, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT );
	convertScaleAbs( grad_y, abs_grad_y );
	imshow("【效果图】Y方向Sobel", abs_grad_y); 

	//【5】合并梯度(近似)
	addWeighted( abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst );
	imshow("【效果图】整体方向Sobel", dst); 

	waitKey(0); 
	return 0; 
}

Laplacian

数学以及物理中,拉普拉斯算子或是拉普拉斯算符(英语:Laplace operator, Laplacian)是由欧几里得空间中的一个函数的梯度散度给出的微分算子,通常写成 图像处理——边缘检测图像处理——边缘检测图像处理——边缘检测

这名字是为了纪念法国数学家皮埃尔-西蒙·拉普拉斯(1749–1827)而命名的。他在研究天体力学在数学中首次应用算子,当它被施加到一个给定的重力位(Gravitational potential)的时候,其中所述算子给出的质量密度的常数倍。经拉普拉斯算子运算为零∆f=0的函数称为调和函数,现在称为拉普拉斯方程,和代表了在*空间中的可能的重力场。

拉普拉斯算子有许多用途,此外也是椭圆算子中的一个重要例子。

拉普拉斯算子出现描述许多物理现象的微分方程里。例如,常用于波方程数学模型热传导方程流体力学以及亥姆霍兹方程。在静电学中,拉普拉斯方程泊松方程的应用随处可见。在量子力学中,其代表薛定谔方程中的动能项。

拉普拉斯算子是最简单的椭圆算子,并且拉普拉斯算子是霍奇理论的核心,并且是德拉姆上同调的结果。在图像处理计算机视觉中,拉普拉斯算子已经被用于诸如斑点检测边缘检测等的各种任务。

Laplacian 使用了图像梯度,它内部的代码调用了 Sobel 算子。让一幅图像减去它的 Laplacian 算子可以增强对比度。

1
2
3
4
5
6
7
8
9

void cv::Laplacian	(	InputArray 	src,
						OutputArray 	dst,
						int 	ddepth,
						int 	ksize = 1,
						double 	scale = 1,
						double 	delta = 0,
						int 	borderType = BORDER_DEFAULT 
)
  • src: 源图像,Mat 类型,需要为单通道 8 位图像
  • dst: 输出图像,尺寸和类型需要与 src 相同
  • ddepth: 输出图像深度
  • ksize: 用于计算二阶大数的滤波器的孔径尺寸,大小必须正奇数,默认值为 1
  • scale: 计算拉普拉斯值的时候可选的比例因子,默认值为 1
  • delta: 在结果存入输出图像全可选的 delta 值,默认值为 0
  • borderType: 推断图像外包部像素的某种边界模式,一般用默认的即可

注:Laplacian() 函数使用 sobel 运算,加上 sobel 算子运算出图像 dx 和 dy,来得到载入图像的拉普拉斯变换的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//---------------【边缘检测】----------------
// 描述:laplacian 函数用法示例
//------------------------------------------
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>

int main()
{
    cv::Mat src, src_gray, dst, abs_dst;

    // 载入原图像
    src = cv::imread("images/3.jpg", cv::IMREAD_COLOR);;
    cv::imshow("【原图】", src);

    // 使用高斯滤波降噪
    cv::GaussianBlur(src, src, cv::Size(3, 3), 0, 0, cv::BORDER_DEFAULT);

    // 转换为灰度图
    cv::cvtColor(src, src_gray, cv::COLOR_BGR2GRAY);

    // 使用 Laplacian 函数
    cv::Laplacian(src_gray, dst, CV_16S, 3, 1, 0, cv::BORDER_DEFAULT);

    //【6】计算绝对值,并将结果转换成8位
	convertScaleAbs( dst, abs_dst );

	//【7】显示效果图
	cv::imshow( "【效果图】图像Laplace变换", abs_dst );

	cv::waitKey(0); 

    return 0;
}