opencv入门项目——车道线检测

文章目录

  • Canny 边缘检测
  • 小程序
  • roi_mask
  • 理论
  • 实现
  • 霍夫变换
  • 基本原理
  • API
  • 实现
  • 离群值过滤
  • 最小二乘拟合
  • API
  • 实现
  • 直线绘制
  • API
  • 视频流读写
  • API
  • 实现
  • Canny 边缘检测

    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)
    
    

    有小噪点,可通过调高阈值解决

    edge_img = cv2.Canny(img, 70, 120)
    
    cv2.imshow('edges', edge_img)
    cv2.waitkey(0)
    


    相比于之前,边缘明显减少

    小程序

    import cv2
    
    cv2.namedWindow('edge_detection')
    cv2.createTrackbar('minThreshold', 'edge_detection', 50, 1000, lambda x: x)
    cv2.createTrackbar('maxThreshold', 'edge_detection', 100, 1000, lambda x: x)
    img = cv2.imread('img.jpg', cv2.IMREAD_GRAYSCALE)
    
    while True:
    	minThreshold = cv2.getTrackbarPos('minThreshold', 'edge_detection')
    	maxThreshold = cv2.getTrackbarPos('maxThreshold', 'edge_detection')
    	edges = cv2.Canny(img, minThreshold, maxThreshold)
    	cv2.imshow('edge_detection', edges)
    	cv2.waitKey(10)
    


    阈值增加,强边缘减少,随之也会导致弱边缘减少

    roi_mask

    理论

    通过 Canny 边缘检测算法,获取了边缘信息。但还有很多无关信息,对于车道信息没有意义,还会造成干扰,所以要将其剔除。

    在C++版本中有实现ROI这个函数
    但在Python版本中,没有实现图片数据结构,而是将图片保存为 numpy 数组。
    所以在python 版本中实现ROI,有以上两种方法。
    数组切片,取出来的是个矩形区域

    实现

  • ROI(region of interest,感兴趣的区域)
  • 数组切片
  • 布尔运算(与运算)
  • 图像以矩阵 np.array 形式存储在内存中
  • np.zeros_like
  • cv2.fillPoly
  • cv2.bitwise_and / np.bitwise_and
  • import cv2
    import numpy as np
    
    edge_img = cv2.imread('edges_img.jpg', cv2.IMREAD_GRAYSCALE)
    mask = np.zeros_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)
    


    生成图片只保留车道线所需要的信息,当然 ROI 这里没有提取好。

    霍夫变换

    基本原理

    API


    官方文档

    实现

    import cv2
    import numpy as np
    
    img = np.zeros((200, 400))
    cv2.line(img, (10, 10), (200, 100), 255, 3)
    cv2.line(img, (30, 50), (350, 10), 255, 2)
    
    cv2.imshow('img', img)
    cv2.imwrite('lines.jpg', img)
    cv2.waitKey(0)
    

    霍夫变换只针对灰度图

    import cv2
    import numpy as np
    
    cv2.imread('lines.jpg', cv2.IMREAD_GRAYSCALE)
    lines = cv2.HoughLinesP(img, 1, np.pi/180, 15, minLineLength=40, maxLineGap=20)
    
    print(len(lines))
    

    32

    图像中只有两条直线,却找到32条直线。
    这是因为画出的直线是有一定宽度的,在这两条直线之间,拟合出来来了很多直线

    解决:
    根据斜率进行分类
    再通过最小二乘法拟合成一条直线

    def calculate_slope(line):
    	"""
    	计算线段line的斜率
    	:param line: np.array([[x_1, y_1, x_2, y_2]])
    	:return:
    	"""
    	x_1, y_1, x_2, y_2 = line[0]
    	if x_1 == x_2:
    		return (y_2-y_1) / (x_2-x_1+1)
    	else:
    		return (y_2-y_1) / (x_2-x_1)
    
    
    edge_img = cv2.imread('masked_edge_img.jpg', cv2.IMREAD_GRAYSCALE)
    # 获取所有线段
    lines = cv2.HoughlinesP(edge_img, 1, np.pi/180, 15, minLineLength=40, maxLineGap=20)
    # 按照斜率分成车道线
    left_lines = [line for line in lines if calculate_slope(line) > 0]
    right_lines = [line for line in lines if calculate_slope(line) < 0]
    

    离群值过滤

    因为误差,有些直线会被误识别,但他们可能不属于左直线和右直线。
    通过特征,来排除。直线的特征就是斜率。
    将与平均斜率相差太大的线排除。

    def reject_abnormal_lines(lines, threshold):
    	"""
    	剔除斜率不一致的线段
    	:param lines:线段集合,[np.array([[x_1, y_1, x_2, y_2]]), np.array([[x_1, y_1, x_2, y_2]])
    	"""
    	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:
    			slopes.pop(idx)
    			lines.pop(idx)
    		else:
    			break
    	return lines
    
    
    reject_abnormal_lines(left_lines, threshold=0.2)
    reject_abnormal_lines(right_lines, threshold=0.2)
    

    最小二乘拟合

    API

  • np.ravel 将高维数组拉成一维
  • np.polyfit 多项式拟合
  • np.polyval 多项式求值
  • a = np.array([[2, 3], [4, 5]])
    print(a)
    

    a.ravel()
    

    poly = np.polyfit([0, 3, 6, 9], [0, 5, 9, 14], deg=1)	# deg 为几维矩阵
    print(poly)
    

    得到一个数对,两个参数

    x = 0
    print(poly[0] * x + poly[1])
    np.polyval(poly, 10)	# 上下两种形式一样
    

    实现

    def least_squares_fit(lines):
    	"""
    	将lines中的线段拟合成一条线段
    	:param lines:线段集合,[np.array([[x_1, y_1, x_2, y_2]]), np.array([[x_1, y_1, x_2, y_2]]),...,np.array([[x_1, y_1, x_2, y_2]])]
    	:return:线段上的两点,np.array([[xmin, ymin], [xmax, ymax]])
    	"""
    
    	# 1. 去除所有坐标点
    	x_coords = np.ravel([[line[0][0], line[0][2]] for line in lines])
    	y_coords = np.ravel([[line[0][1], line[0][3]] for line in lines])
    	# 2. 进行直线拟合,得到多项式系数
    	poly = np.polyfit(x_coords, y_coords, deg=1)
    	# 3. 根据多项式系数,计算两个直线上的点,用于唯一确定这条直线
    	point_min = (np.min(x_coords), np.polyval(poly, np.min(x_coords)))
    	point_max = (np.max(x_coords), np.polyval(poly, np.max(x_coords)))
    	return np.array([point_min, point_max], dtype=np.int)
    
    
    print("left lane")
    print(least_squares_fit(left_lines))
    print("right lane")
    print(least_squares_fit(right_lines))
    

    直线绘制

    API

  • 绘制直线:cv2.line
  • img = cv2.imread('img.jpg', cv2.IMREAD_COLOR)
    cv2.line(img, tuple(left_line[0], tuple(left_line[1]), color=(0, 255, 255), thickness=5)
    cv2.line(img, tuple(right_line[0], tuple(left_line[1]), color=(0, 255, 255), thickness=5)
    
    cv2.imshow('lane', img)
    cv2.waitKey(0)
    

    视频流读写

    API

  • cv2.VideoCapture

  • capture.read
  • cv2.VideoWriter

  • writer.write
  • 如果要用C++或python 处理 视频流的话,建议使用ffmpeg

    实现

    def show_lane(color_img):
    	"""
    	在 color_img 上画出车道线
    	:param color_img:彩色图,channels=3
    	:return:
    	"""
    	edge_img = get_edge_img(color_img)
    	mask_gray_img = roi_mask(edge_img)
    	lines = get_lines(mask_gray_img)
    	draw_lines(color_img, lines)
    	return color_img
    
    
    capture = cv2.VideoCapture('video.mp4')
    # capture = cv2.VideoCapture(0)	# 读取当前设备
    
    while True:
    	ret, frame = capture.read()	# ret 表示当前图像是否关闭,frame 当前帧
    	frame = show_lane(frame)
    	cv2.imshow('frame', frame)
    	cv2.waitKey(10)	# 可以表示当前视频帧率
    
    物联沃分享整理
    物联沃-IOTWORD物联网 » opencv入门项目——车道线检测

    发表评论