文章

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)
  1. edge_img:要处理的图片
  2. 1:精度,值越大,考虑越多的线
  3. np.pi/180:精度,值越大,考虑越多的线
  4. 累加数阈值,值越小,考虑越少的线
  5. minLineLength:最短长度阈值,短于这个长度的线会被排除
  6. 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 值得改进的地方

  1. 边缘检测时,有很多弱边缘,可以尝试使用高斯模糊,或给它腐蚀一下,让弱边缘变得更模糊!这样就能有效过滤掉了。
  2. 左车道线只有一部分,很容易只画出一部分(如图),可以尝试使它延伸到图像边缘。

本文由作者按照 CC BY 4.0 进行授权

© Dignite. 保留部分权利。 由  提供CDN加速。

浙ICP备2023032699号 | 使用 Jekyll 主题 Chirpy