opencv车道线检测

本次学习内容是通过opencv的库函数来实现对一段视频车道线的检测。

实现步骤分为:

1.相机标定矫正畸变图像

2.透视变化为了展现出感兴趣的区域(车道线区域)

3.通过hls图像提取白色车道线,通过lab图像提取黄色车道线

4.使用滑动窗口来识别车道线

5.二项式拟合求出车道线曲线

6.求出曲率半径和偏离位置

7.逆投影到原图上

8.显示检测过程

首先第一步是实现相机标定

将世界坐标系的坐标信息转到像素坐标系上,来确定空间3维几何位置与图像中对应点之间的关系。

涉及变换的坐标系有世界坐标系,相机坐标系,图像坐标系,像素坐标系。

首先是世界坐标系到相机坐标系

两个坐标系之间的变化为旋转和平移可以用旋转平移矩阵来表示\begin{pmatrix}Xc \\ Yc \\ Zc \end{pmatrix}=\begin{pmatrix}R &t \\ 0 & 1 \end{pmatrix}\cdot \begin{pmatrix}Xw \\ Yw \\ Zw \end{pmatrix}

其中R为旋转矩阵,t为平移矩阵。

接下来是相机坐标系到图像坐标系的变换

由相似三角形可以得出关系\frac{f}{Zc}=\frac{x}{Xc}=\frac{y}{Yc}从而写出矩阵形式为Zc\begin{pmatrix}x \\ y \\ 1 \end{pmatrix}=\begin{pmatrix}f & 0 &0 &0 \\ 0 & f & 0 &0 \\ 0 & 0 & 1 & 0 \end{pmatrix}\cdot \begin{pmatrix}Xc \\ Yc \\ Zc \\ 1 \end{pmatrix}f为焦距。

图像坐标系到像素坐标系

\begin{pmatrix}u \\ v \\ 1 \end{pmatrix}=\begin{pmatrix}\frac{x}{dx} &0 &u0 \\ 0 &\frac{y}{dy} &v0 \\ 0 & 0 & 1 \end{pmatrix}\cdot \begin{pmatrix}x \\ y \\ 1 \end{pmatrix}

综上所述变化为:Zc\begin{pmatrix}u \\ v \\ 1 \end{pmatrix}=\begin{pmatrix}\frac{x}{dx} &0 &u0 \\ 0 &\frac{y}{dy} &v0 \\ 0 & 0 & 1 \end{pmatrix}\cdot\begin{pmatrix}f & 0 &0 &0 \\ 0 & f & 0 &0 \\ 0 & 0 & 1 & 0 \end{pmatrix}\cdot \begin{pmatrix}R &t \\ 0 & 1 \end{pmatrix}\cdot \begin{pmatrix}Xw \\ Yw \\ Zw \end{pmatrix}

由于透镜的影响使得图片会产生畸变(主要分为两大类切向与径向畸变)

切向:x_{cor}=x+2p_{1}xy+p_{2}(r^{2}+2x^{2}) y_{cor}=y+2p_{2}xy+p_{1}(r^{2}+2y^{2})

径向:x_{cor}=x(1+k_{1}r^{2}+k_{2}r^{4}+k_{3}r^{6}) y_{cor}=y(1+k_{1}r^{2}+k_{2}r^{4}+k_{3}r^{6})

接着就是将纠正后的点通过内参到像素平面上

\left\{\begin{matrix}u=f_{x}x_{cor}+u_{0} \\ v=f_{y}y_{cor}+v_{0} \end{matrix}\right.其中fx为x/dx,fy同理

本文通过opencv函数实现相机标定与畸变校正

通过不同角度拍摄的棋盘格来完成相机的标定,本文的棋盘格数据集为如下图所示

paths=glob.glob(r'../IR_camera_calib_img/ca*.jpg')

由glob.glob()函数获得20张棋盘位置,函数返回值用列表形式储存

ret,corners=cv2.findChessboardCorners(gray,(9,6),None)

接下里用cv2.findChessboardCorners来找到棋盘格的内交点的像素坐标,由上图棋盘格能看出内角点为w=9,h=6 。

由于世界坐标能够自行进行设置,本文将世界坐标系的Z轴设为0。由此获得世界坐标系形式为(1,0,0),(2,0,0),(3,0,0)····为内交点的世界坐标用wld_object来保存,而通过cv2.findChessboardCorners获得的交点像素坐标系由img_object=[]保存,下一步带入cv2.calibrateCamera()来获得mtx是相机的内参矩阵;dist表述的相机畸变参数;rvecs表示旋转参数:;tvecs表示translation vectors,是平移参数。

最后通过cv2.undistort(img1,mtx,dis,None,mtx)来完成畸变校正

实现代码如下

worldobject=np.zeros((9*6,3),np.float32)
worldobject[:,:2]=np.mgrid[0:9,0:6].T.reshape(-1,2)

#
wld_object=[]
img_object=[]
paths=glob.glob(r'../IR_camera_calib_img/ca*.jpg')

#plt.figure(figsize=(10, 6))
#print(paths)
for path in paths:
    img= cv2.imread(path,flags=1)
    gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

    #找内角点
    ret,corners=cv2.findChessboardCorners(gray,(9,6),None)


    if ret == True:
        wld_object.append(worldobject)
        img_object.append(corners)
        i+=1
        #cv2.drawChessboardCorners(img,(11,8),corners,ret)
        #cv2.imshow('img',img)
        #plt.subplot(3,6,i)
        #plt.imshow(img)
        #cv2.waitKey(0)
        #cv2.destroyAllWindows()
#plt.show()

#img1=cv2.imread('100000.png')
ret,mtx,dis,rvecs,tvecs=cv2.calibrateCamera(wld_object,img_object,gray.shape[::-1],None,None)


#校正
img1=cv2.imread('straight_lines1.jpg',)
cv2.imshow('o',img1)
dst=cv2.undistort(img1,mtx,dis,None,mtx)
cv2.imshow('d',dst)
cv2.waitKey(0)
cv2.imwrite('correct.jpg',dst)
cv2.destroyAllWindows()

实现结果

第二步透视变化获得感兴趣的区域

在原图中车道线区域可以认为一个梯形,现在要把这个区域变成我们俯视时看到的场景便于车道线接下来的识别操作本文选取

scr=np.float32([[575,460],[700,460],[1096,720],[200,720]])
dst=np.float32([[200,0],[950,0],[950,720],[200,720]])

scr可以认为原图中的梯形区域dst可以认为我们俯视车道线是的矩形区域通过cv2.getPerspectiveTransform()来获得变换矩阵

再带入cv2.warpPerspective(img,M,(w,h),flags=cv2.INTER_LINEAR)完成透视变换。

import cv2
import numpy as np
import matplotlib.pyplot as plt

img=cv2.imread('correct.jpg')
h,w=img.shape[:2]
scr=np.float32([[575,460],[700,460],[1096,720],[200,720]])
dst=np.float32([[200,0],[950,0],[950,720],[200,720]])
M=cv2.getPerspectiveTransform(scr,dst,None)
Minv=cv2.getPerspectiveTransform(dst,scr,None)
img1=cv2.warpPerspective(img,M,(w,h),flags=cv2.INTER_LINEAR)
plt.figure(figsize=(10,6))
plt.subplot(121),plt.imshow(img)
plt.subplot(122),plt.imshow(img1)
plt.show()

接下来第三步开始识别车道线,由于车道线是黄色和白色,现在目的是将黄色线和白色线分别提出来最后在组合在一张图上完成检测

首先是通过HSL颜色空间提取出白色车线

hls=cv2.cvtColor(img1,cv2.COLOR_BGR2HLS)
lab=cv2.cvtColor(img1,cv2.COLOR_BGR2Lab)

#print(hls)
L_channel=hls[:,:,1]
L_channel=L_channel*(255/np.max(L_channel))
h,w=L_channel.shape[:2]

binary_out=np.zeros_like(L_channel)
for i in range(h):
    for j in range(w):
        if L_channel[i][j]>=220 and L_channel[i][j]<=255:
            binary_out[i][j]=1

 

 

 再通过Lab颜色空间提取黄色车道线

b_channel=lab[:,:,2]
if b_channel.max()>100:
    b_channel=b_channel*(255/np.max(b_channel))
lab_binary=np.zeros_like(b_channel)
for i in range(h):
    for j in range(w):
        if b_channel[i][j]>=195 and b_channel[i][j]<=255:
            lab_binary[i][j]=1

out=np.zeros_like(lab_binary)
for i in range(h):
    for j in range(w):
        if binary_out[i][j]==1 or lab_binary[i][j]==1:
            out[i][j]=1

 

 接下来是滑动窗口识别车道线

经过提取车道线后,可以的到非黑即白的二值化图像,可以利用直方图统计的方法来确定车道线在图像上的位置如下直方图所示横轴为图像水平坐标系,纵轴为像素个数

 实现代码

直方图统计是由np.sum()axis=0是压缩行,每一列的进行相加

histogram = np.sum(out[:,:], axis=0)
x=np.arange(1280)
plt.plot(x,histogram, linewidth=2)

接下来就是实现滑动窗口,首先定义窗口大小本次实验采用9个窗口那么高度为img.shape[0]/9

再找到图像中车道线最大值处(使用np.argmax),leftx_base,与rightx_base分别为第一个窗口的中点,将宽度设成100得到一个宽200高80的窗口。

接下来开始遍历图像寻找非0且的像素点,通过nonzero实现将其索引值保存在left_lane_inds(左车道线)右车道线同理

在寻找的过程中,如果非0的像素点超过一定的阈值那么将窗口中心点坐标改变。

以上就是滑动窗口的实现

leftx_base = np.argmax(histogram[:midpoint])
rightx_base = np.argmax(histogram[midpoint:]) + midpoint
#窗口
windows=9
windows_height=out.shape[0]//windows
margin=100
minpix=50
#统计
nonzero=out.nonzero()
nonzeroy=np.array(nonzero[0])
nonzerox=np.array(nonzero[1])

Left_lane_inds=[]
Right_lane_inds=[]
leftx_current = leftx_base
rightx_current = rightx_base

for window in range(windows):
    win_y_low=out.shape[0]-(window+1)*windows_height
    win_y_high = out.shape[0] - window  * windows_height
    win_xleft_low= leftx_current-margin
    win_xleft_high = leftx_current + margin
    win_xright_low = rightx_current - margin
    win_xright_high = rightx_current + margin

    #Draw
    cv2.rectangle(out_img,(win_xleft_low,win_y_low),(win_xleft_high,win_y_high),[0,255,0],2)
    cv2.rectangle(out_img, (win_xright_low, win_y_low), (win_xright_high, win_y_high), [0, 255, 0], 2)

    good_left_inds =((nonzerox>=win_xleft_low)&(nonzerox<win_xleft_high)&(nonzeroy>=win_y_low)&(nonzeroy<win_y_high)).nonzero()[0]
    good_right_inds =((nonzerox>=win_xright_low)&(nonzerox<win_xright_high)&(nonzeroy>=win_y_low)&(nonzeroy<win_y_high)).nonzero()[0]

    Left_lane_inds.append(good_left_inds)
    Right_lane_inds.append(good_right_inds)

    if len(good_left_inds)>minpix:leftx_current = np.int32(np.mean(nonzerox[good_left_inds]))
    if len(good_right_inds) > minpix: rightx_current = np.int32(np.mean(nonzerox[good_right_inds]))

Left_lane_inds=np.concatenate(Left_lane_inds)
Right_lane_inds=np.concatenate(Right_lane_inds)

lefttx=nonzerox[Left_lane_inds]
leftty=nonzeroy[Left_lane_inds]
righttx=nonzerox[Right_lane_inds]
rightty=nonzeroy[Right_lane_inds]


left_fit=np.polyfit(leftty,lefttx,2)
right_fit=np.polyfit(rightty,righttx,2)

ploty=np.linspace(0,out.shape[0]-1,out.shape[0])
left_fitx=left_fit[0]*ploty**2+ploty*left_fit[1]+left_fit[2]
right_fitx=right_fit[0]*ploty**2+ploty*right_fit[1]+right_fit[2]
out_img[leftty, lefttx] = [255, 0, 0]
out_img[rightty, righttx] = [0, 0, 255]

#plt.plot(left_fitx, ploty,  linewidth=2)
#plt.plot(right_fitx, ploty,  linewidth=2)

plt.subplot(121),plt.imshow(img1,'gray')
plt.subplot(122),plt.imshow(out_img,'gray')
plt.show()

 接着通过上一步得到的非0点的索引值(即像素的横纵坐标)来进行二项式多项式拟合得到下图

二次函数表示为y=ax**2+bx+c使用函数np.polyfit()带入坐标值返回[a,b,c]二次函数三个系数,而ploty可以认为表示的是自变量通过np.linspace得到

 通过上述得到的二次项系数在图像中分别获得二次项曲线的坐标(x,y)分别用pts_left与pts_right保存,通过cv2.fillpoly填充车道线之间颜色最后逆投影到原图像上

 ploty=np.linspace(0,binary_warped.shape[0]-1,binary_warped.shape[0])
    left_fitx = left_fit[0] * ploty ** 2 + left_fit[1] * ploty + left_fit[2]
    right_fitx = right_fit[0] * ploty ** 2 + right_fit[1] * ploty + right_fit[2]
    warp_zero = np.zeros_like(binary_warped).astype(np.uint8)
    color_warp=np.dstack((warp_zero,warp_zero,warp_zero))
    pts_left = np.array([np.transpose(np.vstack([left_fitx, ploty]))])
    pts_right = np.array([np.flipud(np.transpose(np.vstack([right_fitx, ploty])))])
    pts = np.hstack((pts_left, pts_right))
    cv2.fillPoly(color_warp,np.int_([pts]),(0,255,0))

    newwarp=cv2.warpPerspective(color_warp, Minv, (undist.shape[1], undist.shape[0]))
    result = cv2.addWeighted(undist, 1, newwarp, 0.3, 0)

还有曲率半径和偏离位置的求取

由于是二次函数y=ax**2+bx+c可以由公式求得曲率半径为\frac{(1+{y}'^{2})^{1.5}}{​{y}''}

而偏离位置为车道线之间的距离与图像中心距离之差

    ploty = np.linspace(0, binary_warped.shape[0] - 1, binary_warped.shape[0])
    leftx = left_fit[0] * ploty ** 2 + left_fit[1] * ploty + left_fit[2]
    rightx = right_fit[0] * ploty ** 2 + right_fit[1] * ploty + right_fit[2]

    ym_per_pix = 30 / 720  # meters per pixel in y dimension
    xm_per_pix = 3.7 / 700  # meters per pixel in x dimension
    y_eval = np.max(ploty)
    left_cur_fit=np.polyfit(ploty*ym_per_pix,leftx*xm_per_pix,2)
    right_cur_fit=np.polyfit(ploty*ym_per_pix,rightx*xm_per_pix,2)

    left_curverad=((1 + (2*left_cur_fit[0]*y_eval*ym_per_pix + left_cur_fit[1])**2)**1.5) / np.absolute(2*left_cur_fit[0])
    right_curverad=((1 + (2*right_cur_fit[0]*y_eval*ym_per_pix + right_cur_fit[1])**2)**1.5) / np.absolute(2*right_cur_fit[0])
    curverad=(left_curverad+right_curverad)/2.
    #
    lane_width=np.absolute(leftx[719]-rightx[719])
    lane_xm_per_pix=3.7/lane_width
    veh_pos=(leftx[719]+rightx[719])*lane_xm_per_pix/2.
    cen_pos=binary_warped.shape[1]*lane_xm_per_pix/2.
    distance_from_center=np.absolute(cen_pos-veh_pos)

最终实现视频

车道线检测

猜你喜欢

转载自blog.csdn.net/m0_59521425/article/details/130563081