10.2.7连接边缘点
笔记地址:https://github.com/cantonice/DIP/tree/feat/10.2.7
连接边缘点
Q: 为什么要这样连接边缘点呢?
A: 因为在边缘检测算法中,理想情况就是边缘就是我们想要的区域的边界像素组合,但是实际情况是由于光照不均匀加上有噪点影响导致实际边缘有断裂,我们可能得到的边缘点只是部分真边缘点和有一些噪声误检成假边缘点,所以要利用连接算法将检出的边缘点“去伪存真”组合成有意义的边界。连接算法暂时先介绍两种主流的算法,分别是局部处理的算法和全局处理算法。
局部处理
核心思想:是邻域内计算相似性高的像素然后将他们归为边缘点,能够处理断裂问题。在实际工程中计算邻域(3x3)的计算量非常大,所以采用的是行内扫描加旋转的方式来处理不同方向的连接。
具体算法:
一、首先使用梯度差分算子求出图像的梯度幅值,然后根据梯度幅值来计算梯度方向。其中梯度差分算子一般采用sobel算子来计算梯度幅值,sobel算子如下所示
x方向的sobel卷积核:
| -1 | -2 | -1 |
|---|---|---|
| 0 | 0 | 0 |
| 1 | 2 | 1 |
y方向的sobel卷积核:
| -1 | 0 | 1 |
|---|---|---|
| -2 | 0 | 2 |
| -1 | 0 | 1 |

二、使用相似性条件来判断得到一幅二值图像\(g(x,y)\),判断条件是梯度幅值$M(x,y) \(是否大于某个阈值\)T_M \(且梯度方向\)α(x,y)\(在某个规定范围\)T_A$内:
三、在行内做扫描,如果间隙长度(0的长度)小于预设的\(L\),那么就填充为1作为边缘。
四、如果需要垂直方向的边缘点连接,那么就旋转90°,做完第三步的行内扫描后再旋转回来,同理可以得到任意\(θ\)角度的边缘点连接。
全局处理(霍夫变换)
霍夫变换的核心观点就是通过变换到参数空间,通过离散化的网格来统计峰值,来判别哪些点能组成一条直线或者一个圆。霍夫变换适合用于直线检测和圆检测,下面我们以检测直线为例来解释。
问题引出:如果我们现在通过边缘检测找到了n个边缘点,那么我们要如何确定哪些点属于一条直线呢?根据两点确定一条直线来看,我们能够有\(C_n^2 = \frac {n(n-1)}2\)条直线,但是我们希望尽可能将这n个点收敛于真实的线,那么我们想找出点最多那条直线,这条直线就是n个点最有代表性的一条线。如何找到这条线呢?在图像空间是不好看出来的,所以我们可以换一个参数空间方便我们观察。
一、变换坐标空间
假设我们已知一条直线方程是\(y= kx +b\),经过点\((x_0, y_0)\),那么待入可以得到:
这个方程在\(k-b\)坐标系中也是一条直线,而且是斜率为\(-x_0\),截距为\(y_0\)的一条直线,这其实就是可以说明在\(x-y\)坐标系中过定点的直线其实在\(k-b\)坐标系是斜率和截距相同的直线,同理,在\(x-y\)坐标系斜率和截距相同的直线(确定的直线)其实在\(k-b\)坐标系过定点的直线,那么就只要知道在\(k-b\)坐标系中有多少条相交与同一点的直线就能知道在\(x-y\)坐标系中有多少点共线,找出这些在\(k-b\)坐标系中“高频”交点就是找出在\(x-y\)坐标系中的回归线。这里有一个问题需要指出,当\(x = C\),平行于\(y\)轴的线是无法表示的,因为\(k\)趋近于无穷大,所以可以采用\(ρ-θ\)坐标系来解决。
综上来说,如果多个点在同一条直线上,那么它们对应的曲线会在某个\((ρ,θ)\)处相交。
二、网格离散化投票

每个点转到\(ρ-θ\)中都对应一条曲线,然后将\(ρ-θ\)坐标空间用网格离散化,每个离散化的网格中的曲线数量近似表示曲线“相交”的数量,可以推想当网格无限小的时候就是严格意义上的点交,但是为了方便计算,采用离散网格来累加表示共线点的数量。最后累加器中会有每个\(ρ-θ\)坐标中格子的“票数”,找出累加器中哪一个格子(\(ρ-θ\)坐标)最大,也就是所谓的峰值,取这个坐标作为结果映射回\(x-y\)坐标系,就可以得到一条具体的直线方程,那么这个方程就是回归大部分点的直线。
三、代码处理示例
下面用Python / OpenCV实际演示一下如何运用霍夫变换检测车道
点击查看代码
import cv2
import numpy as np
def color_filter(imgsrc):
"""
颜色过滤器
将车道线图像传入后通过hsv颜色空间来过滤无关车道线的颜色
Args:
imgsrc:车道原图
Return:
过滤后的车道图
"""
hsv = cv2.cvtColor(imgsrc, cv2.COLOR_BGR2HSV)
white_mask = cv2.inRange(hsv, (0,0,200), (180,30,255))
yellow_mask = cv2.inRange(hsv, (15,80,80), (30,255,255))
color_mask = cv2.bitwise_or(white_mask,yellow_mask)
return cv2.bitwise_and(imgsrc, imgsrc, mask=color_mask)
img = cv2.imread(r".\images\10.2.7\lane.jpg")
print("img is not exist")if img is None else print("img is exist")
cv2.imshow('img', img)
h, w = img.shape[:2]
roi_mask = np.zeros_like(img)
# 梯形:只保留下方车道区域(根据你的图片调整)
points = np.array([[
(0, h), # 左下角
(w*3//7, h*2//5), # 自定义左上
(w*2//3, h*2//5), # 自定义右上
(w, h) # 右下角
]], dtype=np.int32)
# 车道线检测roi
cv2.fillPoly(roi_mask, points, (255,255,255))
roi_img = cv2.bitwise_and(img, roi_mask)
cv2.imshow('roi_img', roi_img)
cv2.imwrite(r'./images/10.2.7/roi_img.jpg', roi_img)
img_filter = color_filter(roi_img) # 颜色过滤
cv2.imshow('img_filter', img_filter)
cv2.imwrite(r'./images/10.2.7/img_filter.jpg', img_filter)
img_gray = cv2.cvtColor(img_filter, cv2.COLOR_BGR2GRAY) # 转灰度
cv2.imshow('img_gray', img_gray)
cv2.imwrite(r'./images/10.2.7/img_gray.jpg', img_gray)
bulr = cv2.GaussianBlur(img_gray, (3, 3), 0) # 高斯滤波
cv2.imshow('bulr', bulr)
cv2.imwrite(r'./images/10.2.7/bulr.jpg', bulr)
edges = cv2.Canny(bulr, 80, 120) # Canny边缘点检测
cv2.imshow('edges', edges)
cv2.imwrite(r'./images/10.2.7/edges.jpg', edges)
# 使用霍夫变换检测直线并画线
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 20, minLineLength=0, maxLineGap=5)
res_img = img
if lines is not None:
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(res_img, (x1, y1), (x2, y2), (0,0,255), 2, cv2.LINE_8)
cv2.imshow('res_img', res_img) # 标记结果显示
cv2.imwrite(r'./images/10.2.7/res_img.jpg', res_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
图1 车道线检测原始图像
图2 车道线检测roi图
图2 车道线颜色过滤图
图3 灰度roi图
图4 边缘检测轮廓图
图5 车道线霍夫检测结果图

浙公网安备 33010602011771号