运行环境:python3.8, numpy=1.19.5,opencv-python=4.5.1
算法细节解释参考:https://zhuanlan.zhihu.com/p/52623916
代码文件:见文章尾部
效果展示:
一、文章目录
1、图片读取
2、BGR转灰度图
3、图片边缘提取
4、感兴趣区域选择
5、霍夫变换
6、直线拟合与后处理
7、视频车道线提取
二、实操
注意:后一节代码是在上一节代码的基础上进行修改
1、图片读取
import cv2
img = cv2.imread('whiteCarLaneSwitch.jpg')
cv2.imshow('lane', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2、BGR转灰度图
import cv2
img = cv2.imread('whiteCarLaneSwitch.jpg')
# BGR转换灰度图,opencv中为BGR格式
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.imshow('lane', gray_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
3、图片边缘提取(横线太多可以选择降低low_threshold值)
import cv2
img = cv2.imread('whiteCarLaneSwitch.jpg')
# BGR转换灰度图,opencv中为BGR格式
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# canny算子进行边缘提取,70没有横线遗留,40有
low_threshold = 40
high_threshold = 150
eager_img = cv2.Canny(gray_img, low_threshold, high_threshold)
cv2.imshow('lane', eager_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
4、感兴趣区域选择
作用:选择一个多边形区域,只保留区域内的线条
import cv2
import numpy as np
# 定义一个感兴趣区域
def region_interest(img, region):
# 创立一个掩码
mask = np.zeros_like(img)
# 多通道
if len(img.shape) > 2:
channel_count = img.shape[2]
ignore_mask_color = (255,)*channel_count
# 单通道
else:
ignore_mask_color = 255
# 图像填充,全白
cv2.fillPoly(mask, region, ignore_mask_color)
# 进行与操作
mask_img = cv2.bitwise_and(img, mask)
return mask_img
if __name__ == '__main__':
img = cv2.imread('whiteCarLaneSwitch.jpg')
# BGR转换灰度图,opencv中为BGR格式
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# canny算子进行边缘提取
low_threshold = 40
high_threshold = 150
eager_img = cv2.Canny(gray_img, low_threshold, high_threshold)
# 感兴趣区域选择
left_bottom = [0, img.shape[0]]
right_bottom = [img.shape[1], img.shape[0]]
apex = [img.shape[1]/2, 310]
# 一个多边形为2维数组,多个多边形为3维数组
region = np.array([[left_bottom, right_bottom, apex]], dtype=np.int32)
print(region)
mask_img = region_interest(eager_img, region)
cv2.imshow('lane', mask_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
5、霍夫变换
作用:能够提取一些直线
import cv2
import numpy as np
# 定义一个感兴趣区域
def region_interest(img, region):
# 创立一个掩码
mask = np.zeros_like(img)
# 多通道
if len(img.shape) > 2:
channel_count = img.shape[2]
ignore_mask_color = (255,)*channel_count
# 单通道
else:
ignore_mask_color = 255
# 图像填充,全白
cv2.fillPoly(mask, region, ignore_mask_color)
# 进行与操作
mask_img = cv2.bitwise_and(img, mask)
return mask_img
if __name__ == '__main__':
img = cv2.imread('whiteCarLaneSwitch.jpg')
# BGR转换灰度图,opencv中为BGR格式
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# canny算子进行边缘提取
low_threshold = 40
high_threshold = 150
eager_img = cv2.Canny(gray_img, low_threshold, high_threshold)
# 感兴趣区域选择
left_bottom = [0, img.shape[0]]
right_bottom = [img.shape[1], img.shape[0]]
apex = [img.shape[1]/2, 310]
# 一个多边形为2维数组,多个多边形为3维数组
region = np.array([[left_bottom, right_bottom, apex]], dtype=np.int32)
print(region)
mask_img = region_interest(eager_img, region)
# 霍夫变换->检测直线
rho = 2 # distance resolution in pixels of the Hough grid
theta = np.pi/180 # angular resolution in radians of the Hough grid
threshold = 15 # minimum number of votes (intersections in Hough grid cell)
min_line_length = 40 # minimum number of pixels making up a line
max_line_gap = 20 # maximum gap in pixels between connectable line segments
# Hough Transform 检测线段,线段两个端点的坐标存在lines中
lines = cv2.HoughLinesP(mask_img, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
# 复制一个原图
img_copy = np.copy(img)
# 绘制变换后的线
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img_copy, (x1, y1), (x2, y2), color=[255, 0, 0], thickness=6) # 将线段绘制在img上
# 显示
cv2.imshow('lane', img_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()
6、直线拟合与后处理
import cv2
import numpy as np
# 定义一个感兴趣区域
def region_interest(img, region):
# 创立一个掩码
mask = np.zeros_like(img)
# 多通道
if len(img.shape) > 2:
channel_count = img.shape[2]
ignore_mask_color = (255,)*channel_count
# 单通道
else:
ignore_mask_color = 255
# 图像填充,全白
cv2.fillPoly(mask, region, ignore_mask_color)
# 进行与操作
mask_img = cv2.bitwise_and(img, mask)
return mask_img
# 计算左右车道线直线方程,计算左右车道线的上下边界
def draw_lines(img, lines, color, thickness):
left_lines_x = []
left_lines_y = []
right_lines_x = []
right_lines_y = []
line_y_max = 0
line_y_min = 999
for line in lines:
for x1, y1, x2, y2 in line:
if y1 > line_y_max:
line_y_max = y1
if y2 > line_y_max:
line_y_max = y2
if y1 < line_y_min:
line_y_min = y1
if y2 < line_y_min:
line_y_min = y2
k = (y2 - y1)/(x2 - x1)
if k < -0.3:
left_lines_x.append(x1)
left_lines_y.append(y1)
left_lines_x.append(x2)
left_lines_y.append(y2)
elif k > 0.3:
right_lines_x.append(x1)
right_lines_y.append(y1)
right_lines_x.append(x2)
right_lines_y.append(y2)
# 最小二乘直线拟合
left_line_k, left_line_b = np.polyfit(left_lines_x, left_lines_y, 1)
right_line_k, right_line_b = np.polyfit(right_lines_x, right_lines_y, 1)
# 根据直线方程和最大、最小的y值反算对应的x
cv2.line(img,
(int((line_y_max - left_line_b)/left_line_k), line_y_max),
(int((line_y_min - left_line_b)/left_line_k), line_y_min),
color, thickness)
cv2.line(img,
(int((line_y_max - right_line_b)/right_line_k), line_y_max),
(int((line_y_min - right_line_b)/right_line_k), line_y_min),
color, thickness)
if __name__ == '__main__':
img = cv2.imread('whiteCarLaneSwitch.jpg')
# BGR转换灰度图,opencv中为BGR格式
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# canny算子进行边缘提取,有横线遗留加大low_threshold的值
low_threshold = 70
high_threshold = 150
eager_img = cv2.Canny(gray_img, low_threshold, high_threshold)
# 感兴趣区域选择
left_bottom = [0, img.shape[0]]
right_bottom = [img.shape[1], img.shape[0]]
apex = [img.shape[1]/2, 310]
# 一个多边形为2维数组,多个多边形为3维数组
region = np.array([[left_bottom, right_bottom, apex]], dtype=np.int32)
mask_img = region_interest(eager_img, region)
# 霍夫变换->检测直线
rho = 2 # distance resolution in pixels of the Hough grid
theta = np.pi/180 # angular resolution in radians of the Hough grid
threshold = 15 # minimum number of votes (intersections in Hough grid cell)
min_line_length = 40 # minimum number of pixels making up a line
max_line_gap = 20 # maximum gap in pixels between connectable line segments
# Hough Transform 检测线段,线段两个端点的坐标存在lines中
lines = cv2.HoughLinesP(mask_img, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
# 复制一个原图
img_copy = np.copy(img)
# 绘制变换后的线(霍夫变换)
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img_copy, (x1, y1), (x2, y2), color=[255, 0, 0], thickness=6) # 将线段绘制在img上
# 拟合左右车道线方程
draw_lines(img_copy, lines, color=[255, 0, 0], thickness=6)
# 显示
cv2.imshow('lane', img_copy)
cv2.waitKey(0)
cv2.destroyAllWindows()
7、视频车道线提取
import cv2
import numpy as np
# 定义一个感兴趣区域
def region_interest(img, region):
# 创立一个掩码
mask = np.zeros_like(img)
# 多通道
if len(img.shape) > 2:
channel_count = img.shape[2]
ignore_mask_color = (255,)*channel_count
# 单通道
else:
ignore_mask_color = 255
# 图像填充,全白
cv2.fillPoly(mask, region, ignore_mask_color)
# 进行与操作
mask_img = cv2.bitwise_and(img, mask)
return mask_img
# 计算左右车道线直线方程,计算左右车道线的上下边界
def draw_lines(img, lines, color, thickness):
left_lines_x = []
left_lines_y = []
right_lines_x = []
right_lines_y = []
line_y_max = 0
line_y_min = 999
for line in lines:
for x1, y1, x2, y2 in line:
if y1 > line_y_max:
line_y_max = y1
if y2 > line_y_max:
line_y_max = y2
if y1 < line_y_min:
line_y_min = y1
if y2 < line_y_min:
line_y_min = y2
k = (y2 - y1)/(x2 - x1)
if k < -0.3:
left_lines_x.append(x1)
left_lines_y.append(y1)
left_lines_x.append(x2)
left_lines_y.append(y2)
elif k > 0.3:
right_lines_x.append(x1)
right_lines_y.append(y1)
right_lines_x.append(x2)
right_lines_y.append(y2)
# 最小二乘直线拟合
left_line_k, left_line_b = np.polyfit(left_lines_x, left_lines_y, 1)
right_line_k, right_line_b = np.polyfit(right_lines_x, right_lines_y, 1)
# 根据直线方程和最大、最小的y值反算对应的x
cv2.line(img,
(int((line_y_max - left_line_b)/left_line_k), line_y_max),
(int((line_y_min - left_line_b)/left_line_k), line_y_min),
color, thickness)
cv2.line(img,
(int((line_y_max - right_line_b)/right_line_k), line_y_max),
(int((line_y_min - right_line_b)/right_line_k), line_y_min),
color, thickness)
if __name__ == '__main__':
# 读取视频
cap = cv2.VideoCapture("solidYellowLeft.mp4")
ret, frame = cap.read()
# 结果写入视频
out = cv2.VideoWriter('output.mp4', cv2.VideoWriter_fourcc('m', 'p', '4', 'v'), 25, (frame.shape[1], frame.shape[0]))
while ret:
img = frame
# BGR转换灰度图,opencv中为BGR格式
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# canny算子进行边缘提取,"有横线遗留"加大low_threshold的值,40->100
low_threshold = 100
high_threshold = 150
eager_img = cv2.Canny(gray_img, low_threshold, high_threshold)
# 感兴趣区域选择,报TypeError: expected non-empty vector for x错,将apex的第二个值降低,310->300
left_bottom = [0, img.shape[0]]
right_bottom = [img.shape[1], img.shape[0]]
apex = [img.shape[1]/2, 305]
# 一个多边形为2维数组,多个多边形为3维数组
region = np.array([[left_bottom, right_bottom, apex]], dtype=np.int32)
mask_img = region_interest(eager_img, region)
# 霍夫变换->检测直线
rho = 2 # distance resolution in pixels of the Hough grid
theta = np.pi/180 # angular resolution in radians of the Hough grid
threshold = 15 # minimum number of votes (intersections in Hough grid cell)
min_line_length = 40 # minimum number of pixels making up a line
max_line_gap = 20 # maximum gap in pixels between connectable line segments
# Hough Transform 检测线段,线段两个端点的坐标存在lines中
lines = cv2.HoughLinesP(mask_img, rho, theta, threshold, np.array([]),
min_line_length, max_line_gap)
# 复制一个原图
img_copy = np.copy(img)
# 绘制变换后的线(霍夫变换)
for line in lines:
for x1, y1, x2, y2 in line:
cv2.line(img_copy, (x1, y1), (x2, y2), color=[255, 0, 0], thickness=6) # 将线段绘制在img上
# 拟合左右车道线方程
draw_lines(img_copy, lines, color=[255, 0, 0], thickness=6)
# 显示
cv2.imshow('lane', img_copy)
cv2.waitKey(1)
# 将处理后的帧数写到视频
out.write(img_copy)
# 播放结束跳出循环
ret, frame = cap.read()
cap.release()
cv2.destroyAllWindows()
异常:np.polyfit(right_lines_x, right_lines_y, 1),TypeError: expected non-empty vector for x
解决:感兴趣区域范围改变,降低apex第二个值(如310->305)
总结:
处理速度较快,但是只适合直线处理,并且对图片明亮度有一定要求;
以上代码:
链接:https://pan.baidu.com/s/1uaoMebL2dBrKyTLsnGcdnQ
提取码:vxn1