Opencv入门系列八
主要内容:边缘检测,在图像处理中将相邻像素点的像素值的差值作为图像的梯度。同时有较大差值的部分也就是图像的边界。此处的算子就是滤波器,用于边缘检测。
- Sobel算子
- Scharr算子
- Laplacian算子
1.Sobel 算子
Sobel算子:一种离散的微分算子,结合了高斯平滑和微分运算。该算子利用局部差分寻找边缘,计算所得的是一个梯度的近似值。如下图是算子的提现:
左图是水平方向的算子,右图是垂直方向的算子。
①将水平方向的Sobel算子与原始图像src进行卷积计算,可以计算水平方向上的像素值变化情况。
如下是求一个像素点水平方向梯度的例子:
步骤如下:
首先:找出P5周围的所有点作为一个3×3的矩阵,我们称它为G。(注意:在图像边界的像素点,我们还需要选择一种边界填充的方式)
其次:将水平方向的Sobel算子与矩阵G进行卷积,卷积就是对应位置的点的乘积再求和。
最后:可以得到如下表达式。
②将垂直方向的Sobel算子与原始图像src进行卷积计算,可以计算水平方向上的像素值变化情况。
如下是求一个像素点垂直方向梯度的例子:
同理可得:
在Opencv中我们使用cv2.Sobel()函数实现如上运算,其语法如下:
● dst:处理后的图像。
● src:原图像。
● ddepth:输出图像的深度。 ddepth参数的值设置为-1时,处理结果与原始图像保持一致,但处理结果可能错误。通常要将函数cv2.Sobel()内参数ddepth的值设置为==“cv2.CV_64F”==,避免信息丢失。在不同的参数选择处理下可能会有不同的处理效果,一定要选择合适的参数。
图像深度可选参数如下:
● dx:x方向上的求导阶数。
● dy:y方向上的求导阶数。
● ksize:Sobel核的大小。该值为-1时,则会使用Scharr算子进行运算。
● scale:计算导数的缩放因子,默认情况下该值是1,即:没有缩放。
● delta:变换后要加上的值delta,默认为0。
● borderType:边界填充方式。
在OpenCV中,使用函数cv2.convertScaleAbs()对参数取绝对值,其语法格式为:
● dst:处理结果。= src*alpha + beta
● src:原始图像。
● alpha:调节系数,该值是可选值,默认为1。
● beta:调节亮度值,该值是默认值,默认为0。
下面实验程序在dx=1 dy=0 对比在ddepth默认CV_8U和CV_64F下的图像情况:
import cv2
import numpy as np
img = cv2.imread("Resources/12.jpg",cv2.IMREAD_GRAYSCALE)
imgSobel = cv2.Sobel(img,-1,1,0)
imgSobel2 = cv2.Sobel(img,cv2.CV_64F,1,0)
cv2.imshow("img",img)
cv2.imshow("imgSobel",imgSobel)
cv2.imshow("imgSobel2",imgSobel2)
cv2.waitKey(0)
通过图像我们可以看出,在默认条件下的图像右边的梯度无法表现出来,而ddepth=CV_64F时可以更好的表现出图像的边界。
下面实验程序在dx=0 dy=1 对比在ddepth默认CV_8U和CV_64F下的图像情况:
import cv2
import numpy as np
img = cv2.imread("Resources/12.jpg",cv2.IMREAD_GRAYSCALE)
imgSobel = cv2.Sobel(img,-1,0,1)
imgSobel2 = cv2.Sobel(img,cv2.CV_64F,0,1)
cv2.imshow("img",img)
cv2.imshow("imgSobel",imgSobel)
cv2.imshow("imgSobel2",imgSobel2)
cv2.waitKey(0)
通过图像我们可以看出,在默认条件下的图像下边的梯度无法表现出来,而ddepth=CV_64F时可以更好的表现出图像的边界。
下面实验程序在dx=1 dy=1 对比在ddepth默认CV_8U和CV_64F下的图像情况:
import cv2
import numpy as np
img = cv2.imread("Resources/12.jpg",cv2.IMREAD_GRAYSCALE)
imgSobel = cv2.Sobel(img,-1,1,1)
imgSobel2 = cv2.Sobel(img,cv2.CV_64F,1,1)
cv2.imshow("img",img)
cv2.imshow("imgSobel",imgSobel)
cv2.imshow("imgSobel2",imgSobel2)
cv2.waitKey(0)
2.Scharr算子
Scharr算子:在使用3×3的Sobel算子时,可能计算结果并不太精准。OpenCV提供了Scharr算子,该算子具有和Sobel算子同样的速度,且精度更高。如下图:
在Opencv中我们使用cv2.Scharr()函数实现如上运算,其语法如下:
● dst:输出图像。
● src:原图像。
● ddepth:输出图像深度。该值与函数cv2.Sobel()中的参数ddepth的含义相同。
● dx:x方向上的导数阶数。
● dy:y方向上的导数阶数。
● scale:同Sobel()算子。
● borderType:边界填充方式。
以下是Sobel()函数和Scharr()函数等价的情况:
注意:此函数中dx和dy不能同时为1。
如下程序展示Scharr()函数的边缘检测效果:
import cv2
import numpy as np
img = cv2.imread("Resources/12.jpg",cv2.IMREAD_GRAYSCALE)
imgScharrx = cv2.Scharr(img,cv2.CV_64F,0,1) #y方向上的Scharr
imgScharry = cv2.Scharr(img,cv2.CV_64F,1,0) #x方向上的Scharr
imgSobelToScharrxy = cv2.Sobel(img,cv2.CV_64F,1,1,-1) #使用Sobel函数实现x,y方向上的Scharr
# 合成前必须保证像素点为正数
imgScharrx = cv2.convertScaleAbs(imgScharrx)
imgScharry = cv2.convertScaleAbs(imgScharry)
imgScharrxy = cv2.addWeighted(imgScharrx,0.5,imgScharry,0.5,0)#第二种方式实现x,y方向的scharr
cv2.imshow("img",img)
cv2.imshow("imgScharrx",imgScharrx)
cv2.imshow("imgScharry",imgScharry)
cv2.imshow("imgSobelToScharrxy",imgSobelToScharrxy)
cv2.imshow("imgScharrxy",imgScharrxy)
cv2.waitKey(0)
3.Laplacian 算子
Laplacian(拉普拉斯)算子是一种二阶导数算子,其具有旋转不变性,可以满足不同方向的图像边缘锐化(边缘检测)的要求。如下图:算子系数之和为0。
如下是求一个像素点方向梯度的例子:
其结果可以表示为(具体要根据ksize的选取来看):
在OpenCV内使用函数cv2.Laplacian()实现Laplacian算子的计算,其语法格式为:
● dst:目标图像。
● src:原图像。
● ddepth:目标图像的深度。
● ksize:用于计算二阶导数的核尺寸大小。该值必须是正的奇数。
● scale:计算时的缩放因子。默认情况下,该值为1,即不进行缩放。
● delta:同前两种算子。
● borderType:边界填充方式。
该函数分别对x、y方向进行二次求导,具体为:
import cv2
import numpy as np
img = cv2.imread("Resources/12.jpg",cv2.IMREAD_GRAYSCALE)
imgLaplasian = cv2.Laplacian(img,cv2.CV_64F)
cv2.imshow("img",img)
cv2.imshow("imgLaplasian",imgLaplasian)
cv2.waitKey(0)
4.Canny边缘检测
Canny边缘检测:一种使用多级边缘检测算法检测边缘的方法。
Canny边缘检测分为如下几个步骤。
- 步骤1:去噪。噪声会影响边缘检测的准确性,因此首先要将噪声过滤掉。
- 步骤2:计算梯度的幅度与方向。
- 步骤3:非极大值抑制,即适当地让边缘“变瘦”。
- 步骤4:确定边缘。使用双阈值算法确定最终的边缘信息。
Opencv提供了函数cv2.Canny()来实现Canny边缘检测,其语法格式如下
● edges:边缘图像。
● image:8位的原图像。
● threshold1:处理过程中的第一个阈值。
● threshold2:处理过程中的第二个阈值。
● apertureSize:Sobel算子的孔径大小。
● L2gradient:计算梯度的方式。
默认值为False,直接将两个方向导数的绝对值相加计算导数。
如果为True,以两个方向的导数的平方和再开方计算导数。
一下是一个简单的使用程序:
import cv2
img = cv2.imread("Resources/12.jpg",cv2.IMREAD_GRAYSCALE)
imgCanny = cv2.Canny(img,32,168)
cv2.imshow("img",img)
cv2.imshow("imgCanny",imgCanny)
cv2.waitKey(0)
不难看出使用Canny边缘检测的效果比前三种要好得多。