OpenCV 车道线识别
笔记整理自:https://www.bilibili.com/video/BV1qk4y1r7jw/
1 【基础操作】读取、展示、保存图片
1
2
3
4
5
6
7
8
9
10
11
12
import cv2
img = cv2.imread('img.jpg', cv2.IMREAD_COLOR) # 读取
print(type(img)) # 获取 img 的类型,是 numpy 的矩阵
print(img.shape) # 获取 img 的大小(长、宽),位深度
cv2.imshow('image', img) # 显示图片
if cv2.waitKey(0) == ord('q'): # 直到键盘按下某个值,退出显示图片。
cv2.destoryAllWindows()
cv2.imwrite('img_gray.jpg', img) # 保存图片
其中,IMGREAD_COLOR
是读取类型,是彩色 。还有其他的类型:
IMREAD_GRAYSCALE
:灰度图(那么 img.shape 将没有深度这一参数)
对于 imwrite()
,它会根据给定字符串中的后缀名,自动确定图片类型。必须给定合法的图片扩展名,否则会报错。
2 Canny 边缘检测
梯度是有方向的:
代码实现:
1
2
3
4
5
6
7
8
import cv2
img = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE)
edge_img = cv2.Canny(img, 50, 100) # 图片,上阈值,下阈值,需调节优化
cv2.imshow('edges', edge_img)
cv2.waitKey(0)
调高下边缘与上边缘,可以有效减小噪点数量。而车道线在灰度后,有非常强的边缘(黑与白),所以保持较高的阈值是方便的。
3 ROI mask - 获取要紧的区域
ROI - Region Of Interest,感兴趣的区域。
1
2
3
4
5
6
7
8
9
10
11
import cv2
import numpy as np
edge_img = cv2.imread('edges_img.jpg', cv2.IMREAD_GRAYSCALE)
mask = np.zero_like(edge_img)
cv2.fillPoly(mask, np.array([[[0, 368], [240, 210], [300, 210], [640,368]]]), color=255) # 假设已经获取到了顶点坐标
masked_edge_img = cv2.bitwise_and(edge_img, mask) # 布尔和运算
cv2.imshow('masked', masked_edge_img)
cv2.waitKey(0)
4 霍夫变换,获取直线
需要使用极坐标,$(r, \theta)$ 确定一条直线。
1
liens = cv2.HoughLinesP(edge_img, 1, np.pi / 180, 15, minLineLength=40, maxLineGap=20)
- edge_img:要处理的图片
- 1:精度,值越大,考虑越多的线
- np.pi/180:精度,值越大,考虑越多的线
- 累加数阈值,值越小,考虑越少的线
minLineLength
:最短长度阈值,短于这个长度的线会被排除maxLineLength
:同一直线两点之间最大距离
返回一个列表,里面是直线的两个端点的坐标。
5 离群值过滤
因为种种情况,可能有噪点被识别为车道线,而真正的车道线未被成功识别。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def reject_abnormal_lines(lines, threshold):
slopes = [calculate_slope(line) for line in lines]
while len(lines) > 0: # 不断重新计算斜率,直到群符合条件
mean = np.mean(slopes)
diff = [abs(s - mean) for s in slopes]
idx = np.argmax(diff) # 找到差值最大的下标
if diff[idx] > threshold:
slops.pop(idx)
lines.pop(idx)
else:
break
return lines
# 分别对左、右车道线进行过滤
reject_abnormal_lines(left_lines, threshold=0.2)
reject_abnormal_liens(right_lines, threshold=0.2)
过滤后的效果:
6 最小二乘拟合 - 最后一步
对于识别出来的这么多左车道线、右车道线,我们需要将它们合并成同一条完整的车道线。
1
2
3
4
5
import numpy as np
np.ravel() # 将高维数组压成一维函数(比如把矩阵变成数组)
poly = np.polyfit([0, 3, 6, 9], [0, 5, 9, 14], deg=1) # 多项式拟合,参数:几个 x 坐标,几个 y 坐标,多项式次数
np.polyval(poly, x0) # 多项式求值
ravel
示例:
7 车道线标注
绘制直线:cv2.line
1
2
cv2.line(img, (10, 10), (200, 100), 255, 3)
# 图像,一个端点,另一个端点,色彩值(彩色图为(r, g, b)),宽度
8 视频流处理
读取视频流:capture = cv2.VideoCapture('video.mp4')
,如果传入的是数字,就会使用相应序号的摄像头。
读取:ret, frame = capture.read()
,分别读取状态、图片帧。
1
2
3
4
5
6
7
capture = cv2.VideoCapture('video.mp4')
while True:
ret, frame = capture.read()
# frame = show_lane(frame)
cv2.imshow('frame', frame)
cv2.waitKey(100) # 等待 100 ms
9 值得改进的地方
- 边缘检测时,有很多弱边缘,可以尝试使用高斯模糊,或给它腐蚀一下,让弱边缘变得更模糊!这样就能有效过滤掉了。
- 左车道线只有一部分,很容易只画出一部分(如图),可以尝试使它延伸到图像边缘。
本文由作者按照
CC BY 4.0
进行授权