目录
一、边缘检测基础理论
1、作用:
图像边缘检测大幅度地减少了数据量,并且剔除了不相关的信息,保留了图像重要的结构属性。
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。边缘的表现形式如下图所示:
(纵轴:灰度值; 横轴:位置信息)
图像边缘检测大幅度地减少了数据量,并且剔除了不相关的信息,保留了图像重要的结构属性。有许多方法用于边缘检测,它们的绝大部分可以划分为两类∶基于搜索和基于零穿越。
2、分类
基于搜索:利用一阶导数最大值检测边缘
基于零穿越:利用二阶导数为0检测边缘
1、基于搜索
基于搜索:通过寻找图像一阶导数中的最大值来检测边界,然后利用计算结果估计边缘的局部方向,通常采用梯度的方向,并利用此方向找到局部梯度模的最大值,代表算法是Sobel算子和Scharr算子。
原理:边缘附近的像素值会有明显突变,即变化最大,也就是一阶导数最大。那么找到最大的一阶导数也就找到了像素变化最大的点,即边缘点。
2、基于零穿越
基于零穿越:通过寻找二阶导数零穿越来寻找边界,代表算法是Laplace算子。
原理:在一阶导数的基础上再求一次导,那么此时零点就是变化最大的点,即边缘点。
3、算子比较
其中,Canny算子永远的神!(最优边缘检测)
二、Sobel算子基础理论
1、作用
Sobel算子:用于边缘检测的离散微分算子。(一阶)(结合了高斯平滑和微分)
1、边缘检测: Gx 用于检测纵向边缘, Gy 用于检测横向边缘.
2、计算法线: Gx 用于计算法线的横向偏移, Gy 用于计算法线的纵向偏移.
Sobel边缘检测算法比较简单,实际应用中效率比canny边缘检测效率要高,但是边缘不如Canny检测的准确,但是很多实际应用的场合,sobel边缘却是首选,Sobel算子是高斯平滑与微分操作的结合体,所以其抗噪声能力很强,用途较多。尤其是效率要求较高,而对细纹理不太关心的时候。
(注:需要x方向图像就对y求微分,需要y方向图像就对x求微分,两者颠倒)
对x方向求导,得到的是y方向的边缘; 对y方向求导,得到的是x方向的边缘。
2、原理及推导
因为图像是二维的,所以需要在两个方向求导:
垂直方向的边缘在水平方向的梯度(偏导数)幅值较大
水平方向的边缘在垂直方向的梯度(偏导数)幅值较大
Sobel算子刚好能描述这个图像变化。
过程:
先分别求x和y方向sobel算子,再取绝对值(转换成uint8),然后线性混合。(如图)
梯度公式:
对于图像而言,它是离散的,所以h的最小值只能是1了,那么这意味着,图像中某个像素位置的梯度(以x方向为例)等于它左右两个像素点的像素之差除以2。
可以看出:后 - 前
所以:
综上求出:
例:假设有一行像素是这样分布的:
123 155 173
那么,像素值为155的像素位置x方向的梯度为(173 - 123)/2 = 25
Schar算子能够弥补Sobel内核为3时的误差:(更佳的3*3滤波器:Scahrr ()函数)
3、更详细推导
Prewitt算子:x方向和y方向卷积核分别为:
sobel算子:在此基础上加了一些权值,结合了高斯平滑和微分求导。
Sebel算子x方向和y方向的卷积核分别为:
求出了水平方向和竖直方向的梯度Gx和Gy之后
近似的梯度就可以用下面的方法算出来:
4、Sobel函数
void Sobel (InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT );
函数参数解释:
InputArray src:输入的原图像,Mat类型
OutputArray dst:输出的边缘检测结果图像,Mat型,大小与原图像相同。
int ddepth:输出图像的深度,针对不同的输入图像,输出目标图像有不同的深度,具体组合如下:
- 若src.depth() = CV_8U, 取ddepth =-1/CV_16S/CV_32F/CV_64F
- 若src.depth() = CV_16U/CV_16S, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_32F, 取ddepth =-1/CV_32F/CV_64F
- 若src.depth() = CV_64F, 取ddepth = -1/CV_64F
注:ddepth =-1时,代表输出图像与输入图像相同的深度。
int dx:int类型dx,x 方向上的差分阶数,1或0
int dy:int类型dy,y 方向上的差分阶数,1或0
其中,dx=1,dy=0,表示计算X方向的导数,检测出的是垂直方向上的边缘;dx=0,dy=1,表示计算Y方向的导数,检测出的是水平方向上的边缘。
int ksize:为进行边缘检测时的模板大小为ksize*ksize,取值为1、3、5和7,其中默认值为3。特殊情况:ksize=1时,采用的模板为3*1或1*3。
当ksize=3时,Sobel内核可能产生比较明显的误差,此时,可以使用 Scharr 函数,该函数仅作用于大小为3的内核。具有跟sobel一样的速度,但结果更精确,其内核为:
C++:
Sobel(gray, grad_x, CV_16S, 1, 0, 3); // x方向差分阶数 y方向差分阶数 核大小
python:
x = cv.Sobel(img, cv.CV_16S, 1, 0) y = cv.Sobel(img, cv.CV_16S, 0, 1) # 深度 x方向阶数 y方向阶数
二、实战
首先欣赏下原图及灰度图:
src = imread("Resource/test12.jpg");
imshow("原图", src);
cvtColor(src, gray, COLOR_RGB2GRAY); //转变为灰度图
imshow("灰度图", src);
1、对x方向微分
对x方向微分,得到的是y方向的边缘。
//对x方向微分
Sobel(gray, grad_x, CV_16S, 1, 0, 3);
// x方向差分阶数 y方向差分阶数 核大小
convertScaleAbs(grad_x, abs_grad_x); //可将任意类型的数据转化为CV_8UC1
imshow("【边缘图x】", abs_grad_x);
//边缘与梯度方向垂直,所以输出的边缘是和我们所计算的某一方向的梯度是垂直的
2、对y方向微分
对y方向微分,得到的是x方向的边缘。
//对y方向微分
Sobel(gray, grad_y, CV_16S, 0, 1, 3);
// x方向差分阶数 y方向差分阶数 核大小
convertScaleAbs(grad_y, abs_grad_y); //可将任意类型的数据转化为CV_8UC1
imshow("边缘图y", abs_grad_y);
3、线性混合
//图像的线性混合
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
imshow("线性混合", dst);
总代码
C++:
//Sobel算子(微分)
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
int main()
{
Mat src, dst, gray, grad_x, grad_y, abs_grad_x, abs_grad_y;
src = imread("Resource/test12.jpg");
imshow("原图", src);
cvtColor(src, gray, COLOR_RGB2GRAY); //转变为灰度图
imshow("灰度图", gray);
//对x方向微分
Sobel(gray, grad_x, CV_16S, 1, 0, 3);
// x方向差分阶数 y方向差分阶数 核大小
convertScaleAbs(grad_x, abs_grad_x); //可将任意类型的数据转化为CV_8UC1
imshow("边缘图x", abs_grad_x);
//边缘与梯度方向垂直,所以输出的边缘是和我们所计算的某一方向的梯度是垂直的
//对y方向微分
Sobel(gray, grad_y, CV_16S, 0, 1, 3);
// x方向差分阶数 y方向差分阶数 核大小
convertScaleAbs(grad_y, abs_grad_y); //可将任意类型的数据转化为CV_8UC1
imshow("边缘图y", abs_grad_y);
//图像的线性混合
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
imshow("线性混合", dst);
waitKey(0);
}
python:
# 边缘检测(Sobel、Laplace、Canny)
import cv2 as cv
# Sobel一阶微分算子
def Sobel():
# 1、对X和Y方向求微分
x = cv.Sobel(img, cv.CV_16S, 1, 0)
y = cv.Sobel(img, cv.CV_16S, 0, 1)
# 深度 x方向阶数 y方向阶数
# 2、取绝对值
absX = cv.convertScaleAbs(x) # 转回uint8
absY = cv.convertScaleAbs(y)
# 3、线性混合
dst = cv.addWeighted(absX, 0.5, absY, 0.5, 0)
# 比例 比例 常数
# 4、显示
cv.imshow("absX", absX)
cv.imshow("absY", absY)
cv.imshow("dst", dst)
if __name__ == '__main__':
# 读取图片
img = cv.imread("Resource/test5.jpg")
cv.imshow("img", img)
Sobel() #Sobel一阶微分算子
cv.waitKey(0)
其他应用
x方向梯度(使竖直的黑条特征变得明显):
参考资料
https://www.bilibili.com/video/BV1Fo4y1d7JL?p=34&spm_id_from=pageDriver