B07 - 图像处理 - 图像梯度与边缘检测
通过图像梯度检测边缘的算子集合,一般是先转为二值图再进行边缘寻找
图像上的边缘类型?
- 台阶模型,对应于阶跃信号,边缘模型是灰度级在相邻像素点发生垂直的台阶突变;
- 斜坡模型,对应于斜变信号,边缘模型是灰度级变化的斜坡
- 屋顶模型,是线的模型,用于细特征的建模
- 模拟生成以上边缘类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19h ,w = 100, 600
onesW = np.ones((h, 1), dtype=np.int)
x = range(w)
# 台阶模型 (step edge)
imgStep = np.zeros((h, w), np.uint8)
imgStep[:, 300:600] = 255
print(imgStep.max(), imgStep.min())
# 斜坡模型 (ramp edge)
imgRamp = np.zeros((h,w), np.uint8)
for i in range(200, 400):
imgRamp[:, i] = 255 * (i-199) / 200
imgRamp[:, 400:] = 255
# 屋顶模型 (roof edge)
imgRoof = np.zeros((h,w), np.uint8)
for i in range(250, 300):
imgRoof[:, i] = 255 * (i-249) / 50
for i in range(300, 350):
imgRoof[:, i] = 255 - 255 * (i-299) / 50
imgRoof[:, 350:] = 0
图像边缘检测原理?
- 用于识别对象的边界(边缘)或图像中的区域,边缘特征是像素强度的突然变化,要检测边缘,需要在相邻像素中寻找这样的变化,也就是梯度
- 红色区曲线表示原始边缘,绿色曲线表示一阶微分结果,蓝色区域表示二级微分结果。可知一阶微分算子可以检测图像边缘。对于剧烈变化的图像边缘,一阶微分效果比较理想。但对于缓慢变化的图像边缘,通过对二阶微分并寻找过零点可以很精确的定位边缘中心
Sobel 梯度算子边缘检测算法?
- (1) 定义 Sobel 卷积因子: 该算子包含两组 3x3 的矩阵,分别为横向及纵向,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似
- (2) 计算某一点的灰度值:分别使用计算 y 方向的卷积和、x 方向的卷积核,然后使用公式求灰度
Scharr 梯度算子边缘检测算法?
- Scharr 算子是 Soble 算子在 ksize=3 时的优化,与 Soble 的速度相同,且精度更高。Scharr 算子与 Sobel 算子的不同点是在平滑部分,其中心元素占的权重更重,相当于使用较小标准差的高斯函数,也就是更瘦高的模板
- Scharr 算子的卷积核
Canny 边缘检测算法?
- Canny 边缘检测算法是目前最优秀和最流行的边缘检测算法之一。算法不容易受噪声影响,能够识别图像中的弱边缘和强边缘,并能结合强弱边缘的位置关系给出图像整体的边缘信息
- 步骤:(1) 用高斯滤波器平滑图像;(2) 用 Sobel 等梯度算子计算梯度幅值和方向: 图像灰度值的梯度可以使用最简单的一阶有限差分来进行近似,保留这四个方向的梯度:0°/45°/90°/135;(3) 对梯度幅值进行非极大值抑制: 沿着该点梯度方向,比较前后两个点的幅值大小,若该点大于前后两点,则保留,若该点小于前后两点,则置为 0;(4) 用双阈值算法检测和连接边缘: 指定一个低阈值 A,一个高阈值 B,一般取 B 为图像整体灰度级分布的 70%,且 B 为 1.5 到 2 倍大小的 A;灰度值介于 A 和 B 之间的,考察改像素点临近的 8 像素是否有灰度值为 255 的,若没有 255 的,表示这是一个孤立的局部极大值点,予以排除,置为 0;若有 255 的,表示这是一个跟其他边缘有 “接壤” 的可造之材,置为 255,之后重复执行该步骤,直到考察完之后一个像素点
1
2
3
4
5
6
7
8
9
10
11img = cv2.imread("../images/imgLena.tif", flags=0) # flags=0 读取为灰度图像
# 高斯核低通滤波器,sigmaY 缺省时 sigmaY=sigmaX
kSize = (5, 5)
imgGauss1 = cv2.GaussianBlur(img, kSize, sigmaX=1.0) # sigma=1.0
imgGauss2 = cv2.GaussianBlur(img, kSize, sigmaX=10.0) # sigma=2.0
# 高斯差分算子 (Difference of Gaussian)
imgDoG = imgGauss2 - imgGauss1 # sigma=1.0, 10.0
# Canny 边缘检测, kSize 为高斯核大小,t1,t2为阈值大小
t1, t2 = 50, 150
imgCanny = cv2.Canny(imgGauss1, t1, t2)
show_images([img,imgDoG,imgCanny])
Laplacian 边缘检测算法?
- Laplacian 算子具有各方向同性的特点,能够对任意方向的边缘进行提取,具有无方向性的优点,因此提取边缘不需要分别检测 X 方向的边缘和 Y 方向的边缘,只需要一次边缘检测即可
- Laplacian 算子是一种二阶导数算子,对噪声比较敏感,因此常需要配合高斯滤波一起使用
1
2
3
4
5
6
7
8
9
10
11
12
13imgGray = cv2.imread("../images/Fig0905a.tif", flags=0)
# scipy.signal 实现卷积运算 (注意:不能用 cv2.filter2D 处理)
from scipy import signal
kernelLaplace = np.array([[1, 1, 1], [1, -8, 1], [1, 1, 1]]) # Laplacian kernel
imgLaplace = signal.convolve2d(imgGray, kernelLaplace, boundary='symm', mode='same') # same 卷积
kernel1 = np.array([[-1, -1, -1], [2, 2, 2], [-1, -1, -1]]) # 0 degree, horizontal
kernel2 = np.array([[2, -1, -1], [-1, 2, -1], [-1, -1, 2]]) # +45 degree
kernel3 = np.array([[-1, 2, -1], [-1, 2, -1], [-1, 2, -1]]) # 90 degree, vertical
kernel4 = np.array([[-1, -1, 2], [-1, 2, -1], [2, -1, -1]]) # -45 degree
imgLine1 = signal.convolve2d(imgGray, kernel1, boundary='symm', mode='same') # horizontal kernel
imgLine2 = signal.convolve2d(imgGray, kernel2, boundary='symm', mode='same')
imgLine3 = signal.convolve2d(imgGray, kernel3, boundary='symm', mode='same') # vertical kernel
imgLine4 = signal.convolve2d(imgGray, kernel4, boundary='symm', mode='same')
LoG 边缘检测算子?
- 平滑算子与 Laplace 算子的结合,因而兼具平滑和二阶微分的作用,其定位精度高,边缘连续性好,计算速度快
- 具体地,可以先对图像做 Gauss 平滑,再做 Laplace 变换;也可以预先算出高斯拉普拉斯卷积核 LoG,直接与图像卷积来实现,提高计算速度
- Marr-Hildreth 算法让 LoG 核与原图像卷积,然后寻找过零点来确定边缘的位置
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
35def ZeroDetect(img): # 判断零交叉点
h, w = img.shape[0], img.shape[1]
zeroCrossing = np.zeros_like(img, np.uint8)
for x in range(0, w-1):
for y in range(0, h-1):
if img[y][x] < 0:
if (img[y][x-1] > 0) or (img[y][x+1] > 0)\
or (img[y-1][x] > 0) or (img[y+1][x] > 0):
zeroCrossing[y][x] = 255
return zeroCrossing
from scipy import signal
img = cv2.imread("Fig1016a.tif", flags=0) # flags=0 读取为灰度图像
imgBlur = cv2.blur(img, (3,3)) # Blur 平滑后再做 Laplacian 变换
# 近似的 Marr-Hildreth 卷积核 (5*5)
kernel_MH5 = np.array([
[0, 0, -1, 0, 0],
[0, -1, -2, -1, 0],
[-1, -2, 16, -2, -1],
[0, -1, -2, -1, 0],
[0, 0, -1, 0, 0]])
imgMH5 = signal.convolve2d(imgBlur, kernel_MH5, boundary='symm', mode='same')
zeroMH5 = ZeroDetect(imgMH5) # 判断零交叉点
# 由 Gauss 标准差计算 Marr-Hildreth 卷积核
sigma = 3 # Gauss 标准差,输入参数
size = int(2 * round(3 * sigma)) + 1 # 根据标准差确定窗口大小,3*sigma 占比 99.7%
print("sigma={:d}, size={}".format(sigma, size))
x, y = np.meshgrid(np.arange(-size/2+1, size/2+1), np.arange(-size/2+1, size/2+1)) # 生成网格
norm2 = np.power(x, 2) + np.power(y, 2)
sigma2, sigma4 = np.power(sigma, 2), np.power(sigma, 4)
kernelLoG = ((norm2 - (2.0 * sigma2)) / sigma4) * np.exp(- norm2 / (2.0 * sigma2)) # 计算 LoG 卷积核
# Marr-Hildreth 卷积运算
imgLoG = signal.convolve2d(imgBlur, kernelLoG, boundary='symm', mode='same')
# 判断零交叉点
zeroCrossing = ZeroDetect(imgLoG)
show_images([imgMH5,imgLoG,zeroMH5,zeroCrossing])
DoG 边缘检测算子?
- 高斯差分算子 DoG(Difference of Gaussian)是两个不同尺度的高斯滤波器之差,可以实现 LoG 算子的近似。在具体处理中,DoG 算子就是将图像在不同参数下的高斯滤波结果相减
- DoG 算子不仅实现简单、计算速度快,而且对噪声、尺度、仿射变化和旋转等具有很强的鲁棒性,能够提供丰富的局部特征信息
1
2
3
4
5
6
7
8
9
10
11
12
13img = cv2.imread("../images/Fig1016a.tif", flags=0) # flags=0 读取为灰度图像
# 高斯核低通滤波器,sigmaY 缺省时 sigmaY=sigmaX
kSize = (5, 5)
imgGaussBlur1 = cv2.GaussianBlur(img, (5,5), sigmaX=1.0) # sigma=1.0
imgGaussBlur2 = cv2.GaussianBlur(img, (5,5), sigmaX=2.0) # sigma=2.0
imgGaussBlur3 = cv2.GaussianBlur(img, (5,5), sigmaX=4.0) # sigma=4.0
imgGaussBlur4 = cv2.GaussianBlur(img, (5,5), sigmaX=16.0) # sigma=16.0
show_images([imgGaussBlur2,imgGaussBlur3,imgGaussBlur4])
# 高斯差分算子 (Difference of Gaussian)
imgDoG1 = imgGaussBlur2 - imgGaussBlur1 # sigma=1.0,2.0
imgDoG2 = imgGaussBlur3 - imgGaussBlur2 # sigma=2.0,4.0
imgDoG3 = imgGaussBlur4 - imgGaussBlur3 # sigma=4.0,16.0
show_images([imgDoG1,imgDoG2,imgDoG3])
局部处理连接边缘?
- 在实际应用中,由于噪声、光照等原因引起的边缘断裂,使边缘检测的结果并不是完全的、完整的边缘,通常要通过边缘连接算法,将边缘像素组合为有意义的边缘或区域边界
- 边缘连接方分为局部处理方法和全局处理方法。边缘连接的局部处理方法:在局部分析中,主要基于梯度向量的幅值和方向进行边缘像素的相似性判断。E 是梯度向量的幅度阈值,A 是角度阈值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24img = cv2.imread("../images/imgLena.tif", flags=0) # flags=0 读取为灰度图像
# img16 = np.array(img, dtype='uint16')
hImg, wImg = img.shape#[0], img.shape[1]
# Sobel 梯度算子
kSobelX = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]])
kSobelY = np.array([[1, 2, 1], [0, 0, 0], [-1, -2, -1]])
gx = cv2.filter2D(img, cv2.CV_64F, kSobelX) # SobelX 水平梯度
gy = cv2.filter2D(img, cv2.CV_64F, kSobelY) # SobelY 垂直梯度
# 计算梯度向量的幅值 mag 与角度 angle
# magn = np.sqrt(np.power(gx,2) + np.power(gy,2)) # 梯度向量的幅值
magn = cv2.normalize(abs(gx)+abs(gy), None, 0, 255, cv2.NORM_MINMAX) # 用绝对值近似梯度幅值
gxFlat, gyFlat = gx.flatten(), gy.flatten() # 展平为一维,便于计算角度
angleFlat = np.arctan2(gy, gx) * 180 / np.pi # 梯度向量的角度,将弧度转为角度: (-180, 180)
angle = angleFlat.reshape(hImg, wImg)
# 边缘像素相似性判断
edge = np.zeros((hImg,wImg), np.uint8)
for h in range(1, hImg-1): # 对边界点不判断
for w in range(1, wImg-1):
if (abs(magn[h,w]-magn[h-1,w-1])<=30) and (abs(angle[h,w]-angle[h-1,w-1])<=15)\
or (abs(magn[h,w]-magn[h-1,w+1])<=30) and (abs(angle[h,w]-angle[h-1,w+1])<=15)\
or (abs(magn[h,w]-magn[h+1,w-1])<=30) and (abs(angle[h,w]-angle[h+1,w-1])<=15)\
or (abs(magn[h,w]-magn[h+1,w+1])<=30) and (abs(angle[h,w]-angle[h+1,w+1])<=15):
edge[h,w] = magn[h,w]
show_images([img,magn,edge])
什么是凸缺陷?
- 凸缺陷: 对象上的任何凹陷都被成为凸缺陷
- Opencv 使用 cv: : convexityDefects 检测凸缺陷
参考:
- 【youcans 的 OpenCV 例程 200 篇】148. 图像分割之线检测_youcans_的博客 - CSDN 博客
- 【youcans 的 OpenCV 例程 200 篇】149. 图像分割之边缘模型_youcans_的博客 - CSDN 博客
- 【youcans 的 OpenCV 例程 200 篇】150. 边缘检测梯度算子_梯度算子检测边缘的模板_youcans_的博客 - CSDN 博客
- 【OpenCV 例程 300 篇】250. 梯度算子的传递函数_youcans_的博客 - CSDN 博客
- 图像矩 - 知乎
- Shape Matching using Hu Moments (C++ / Python) | LearnOpenCV
- Image moment - Wikipedia
- 【从零学习 OpenCV 4】图像矩的计算与应用 - 知乎
- 【youcans 的 OpenCV 例程 200 篇】152. 边缘检测之 LoG 算子_opencv log 算子_youcans_的博客 - CSDN 博客
- 【youcans 的 OpenCV 例程 200 篇】153. 边缘检测之 DoG 算子_dog 算法_youcans_的博客 - CSDN 博客
- 【youcans 的 OpenCV 例程 200 篇】154. 边缘检测之 Canny 算子_youcans_的博客 - CSDN 博客
- 【youcans 的 OpenCV 例程 200 篇】155. 边缘连接的局部处理方法_局部处理的边缘连接_youcans_的博客 - CSDN 博客