├── .gitignore ├── README.md ├── sample_lane_detection.py └── test.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.mp4 2 | source.txt -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 车道线检测 2 | 3 | ![](test.png) -------------------------------------------------------------------------------- /sample_lane_detection.py: -------------------------------------------------------------------------------- 1 | ''' 2 | xujing 3 | 2020-06-20 4 | 5 | 车道线检测 opencv 6 | 7 | 1、CCD视频摄像机校准 8 | 2、读视频,转成按每一帧读取,图像预处理 9 | 3、图像灰度化 10 | 4、高斯平滑,减少边缘干扰 11 | 5、利用canny算子,进行边缘检测 12 | 6、设定感兴趣区域,减少运算量 13 | 7、利用hough变换,进行直线检测 14 | 8、将检测成功的直线和原图像融合 15 | 16 | 17 | ''' 18 | 19 | import cv2 20 | import numpy as np 21 | import time 22 | 23 | 24 | #Canny算子或Sobel算子进行边缘检测 25 | def canny_func(blur_gray,canny_lthreshold=150,canny_hthreshold=250): 26 | canny_lthreshold = canny_lthreshold 27 | canny_hthreshold = canny_hthreshold 28 | edges = cv2.Canny(blur_gray,canny_lthreshold,canny_hthreshold) 29 | 30 | return edges 31 | 32 | 33 | #设置ROI区域,定义一个和输入图像同样大小的全黑图像mask 34 | def roi_mask(img,vertics): 35 | mask = np.zeros_like(img) 36 | #根据输入图像的通道数,忽略的像素点是多通道的白色,还是单通道的白色 37 | if len(img.shape) > 2: 38 | channel_count = img.shape[2] 39 | mask_color = (255,)*channel_count 40 | else: 41 | mask_color = 255 42 | cv2.fillPoly(mask,[vertics],mask_color) 43 | masked_img = cv2.bitwise_and(img,mask) 44 | return masked_img 45 | 46 | #Hough变换 47 | # https://blog.csdn.net/yuyuntan/article/details/80141392 48 | 49 | def hough_func(roi_image,rho=1,theta=np.pi/180,threshold=15,min_line_lenght=40,max_line_gap=20): 50 | rho = rho 51 | theta = theta 52 | threshold = threshold 53 | min_line_lenght = min_line_lenght 54 | max_line_gap = max_line_gap 55 | # line_img = hough_lines(roi_image,rho,theta,threshold,min_line_lenght,max_line_gap) 56 | line_img = cv2.HoughLinesP(roi_image,rho,theta,threshold,min_line_lenght,max_line_gap) 57 | 58 | return line_img 59 | 60 | 61 | # def draw_lines(img,lines,color = [0,0,255],thickness = 2): 62 | # for line in lines: 63 | # for x1,y1,x2,y2 in line: 64 | # cv2.line(img,(x1,y1),(x2,y2),color,thickness) 65 | 66 | 67 | # line_image = np.copy(img) # 复制一份原图,将线段绘制在这幅图上 68 | # draw_lines(line_image, lines, [255, 0, 0], 6) 69 | 70 | 71 | 72 | # 计算左右车道线的直线方程 73 | # 根据每个线段在图像坐标系下的斜率,判断线段为左车道线还是右车道线, 74 | # 并存于不同的变量中。随后对所有左车道线上的点、所有右车道线上的点做一次最小二乘直线拟合, 75 | # 得到的即为最终的左、右车道线的直线方程。 76 | # 最小二乘拟合讲解可参考:https://blog.csdn.net/nienelong3319/article/details/80894621 77 | # np.polyfit(X, Y, 1) #一次多项式拟合,相当于线性拟合 78 | 79 | # 计算左右车道线的上下边界 80 | # 考虑到现实世界中左右车道线一般都是平行的,所以可以认为左右车道线上最上和最下的点对应的y值, 81 | # 就是左右车道线的边界。 82 | def draw_lines(img, lines, color=[0, 0, 255], thickness=2): 83 | left_lines_x = [] 84 | left_lines_y = [] 85 | right_lines_x = [] 86 | right_lines_y = [] 87 | line_y_max = 0 88 | line_y_min = 999 89 | 90 | try: 91 | for line in lines: 92 | for x1,y1,x2,y2 in line: 93 | if y1 > line_y_max: 94 | line_y_max = y1 95 | if y2 > line_y_max: 96 | line_y_max = y2 97 | if y1 < line_y_min: 98 | line_y_min = y1 99 | if y2 < line_y_min: 100 | line_y_min = y2 101 | 102 | k = (y2 - y1)/(x2 - x1) 103 | 104 | if k < -0.3: 105 | left_lines_x.append(x1) 106 | left_lines_y.append(y1) 107 | left_lines_x.append(x2) 108 | left_lines_y.append(y2) 109 | elif k > 0.3: 110 | right_lines_x.append(x1) 111 | right_lines_y.append(y1) 112 | right_lines_x.append(x2) 113 | right_lines_y.append(y2) 114 | #最小二乘直线拟合 115 | left_line_k, left_line_b = np.polyfit(left_lines_x, left_lines_y, 1) 116 | right_line_k, right_line_b = np.polyfit(right_lines_x, right_lines_y, 1) 117 | 118 | #根据直线方程和最大、最小的y值反算对应的x 119 | cv2.line(img, 120 | (int((line_y_max - left_line_b)/left_line_k), line_y_max), 121 | (int((line_y_min - left_line_b)/left_line_k), line_y_min), 122 | color, thickness) 123 | cv2.line(img, 124 | (int((line_y_max - right_line_b)/right_line_k), line_y_max), 125 | (int((line_y_min - right_line_b)/right_line_k), line_y_min), 126 | color, thickness) 127 | # plot polygon 128 | zero_img = np.zeros((img.shape), dtype=np.uint8) 129 | polygon = np.array([ 130 | [int((line_y_max - left_line_b)/left_line_k), line_y_max], 131 | [int((line_y_max - right_line_b)/right_line_k), line_y_max], 132 | [int((line_y_min - right_line_b)/right_line_k), line_y_min], 133 | [int((line_y_min - left_line_b)/left_line_k), line_y_min] 134 | ]) 135 | # 用1填充多边形 136 | cv2.fillConvexPoly(zero_img, polygon, color=(0, 255, 0)) 137 | # zero_mask = cv2.rectangle(zero_img, (int((line_y_max - left_line_b)/left_line_k), line_y_max), 138 | # (int((line_y_min - right_line_b)/right_line_k), line_y_min), 139 | # color=(0, 255, 0), thickness=-1) 140 | 141 | alpha = 1 142 | # beta 为第二张图片的透明度 143 | beta = 0.2 144 | gamma = 0 145 | # cv2.addWeighted 将原始图片与 mask 融合 146 | img = cv2.addWeighted(img, alpha, zero_img, beta, gamma) 147 | 148 | except Exception as e: 149 | print(str(e)) 150 | print("NO detection") 151 | 152 | return img 153 | 154 | 155 | 156 | #-------------------------------------------------------------------------- 157 | 158 | # 开始检测 159 | video_file = "./test.mp4" 160 | cap=cv2.VideoCapture(video_file)#通过VideoCapture函数对视频进行读取操作 打开视频文件 161 | if cap.isOpened(): 162 | print("正确打开视频文件") 163 | else: 164 | print("没有正确打开视频文件") 165 | frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) 166 | width, height = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)),int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) 167 | fps = int(cap.get(cv2.CAP_PROP_FPS)) 168 | 169 | # save video 170 | fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') 171 | writer = cv2.VideoWriter("./output.mp4", fourcc, fps, (width, height), 1) 172 | 173 | 174 | while cap.isOpened(): 175 | 176 | try: 177 | ret,img = cap.read()#读取每一帧图片 flag表示是否读取成功 frame是图片 178 | 179 | 180 | start = time.time() 181 | #取图灰度化 182 | grap = cv2.cvtColor(img,cv2.COLOR_RGB2GRAY) 183 | blur_grap = cv2.GaussianBlur(grap,(3,3),0) 184 | canny_image = canny_func(blur_grap,canny_lthreshold=150,canny_hthreshold=250) 185 | 186 | #图像像素行数 rows = canny_image.shape[0] 720行 187 | #图像像素列数 cols = canny_image.shape[1] 1280列 188 | left_bottom = [0, canny_image.shape[0]] 189 | right_bottom = [canny_image.shape[1], canny_image.shape[0]] 190 | left_top = [canny_image.shape[1]/3,canny_image.shape[0]/1.5] 191 | right_top = [canny_image.shape[1]/3*2,canny_image.shape[0]/1.5] 192 | # apex = [canny_image.shape[1]/2, 290] 193 | # vertices = np.array([ left_bottom, right_bottom, apex ], np.int32) 194 | vertices = np.array([ left_top,right_top, right_bottom, left_bottom], np.int32) 195 | 196 | roi_image = roi_mask(canny_image, vertices) 197 | # roi_image = roi_mask(img, vertices) 198 | 199 | 200 | line_img = hough_func(roi_image,rho=1,theta=np.pi/180,threshold=15,min_line_lenght=40,max_line_gap=20) 201 | img = draw_lines(img,line_img) 202 | end = time.time() 203 | 204 | detect_fps = round(1.0/(end-start+0.00001),2) 205 | 206 | font = cv2.FONT_HERSHEY_SIMPLEX 207 | img = cv2.putText(img, 'Lane detect v1.0 | Xu Jing | FPS: {}'.format(detect_fps), 208 | (40, 40), font, 0.7, (0,0,0), 2) 209 | 210 | writer.write(img) 211 | 212 | 213 | cv2.imshow('lane_detect', img) 214 | 215 | if cv2.waitKey(25) & 0xFF == ord('q'): 216 | cap.release() 217 | writer.release() 218 | cv2.destroyAllWindows() 219 | break 220 | except Exception as e: 221 | print(str(e)) 222 | cap.release() 223 | writer.release() 224 | cv2.destroyAllWindows() 225 | 226 | cap.release() 227 | writer.release() 228 | cv2.destroyAllWindows() 229 | 230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DataXujing/lane-detect-opencv/835532ff811be3373f5e8e802732891cee3a8511/test.png --------------------------------------------------------------------------------