车道线检测(Finding Lane Lines)

车道线检测(Finding Lane Lines)

这篇文章是关于寻找车道线的,在自动驾驶中,让汽车保持在车道线内是非常重要的,所以这次我们来说说车道线的检测。

我们主要用到的是openCV, numpy, matplotlib几个库,如果要对视频进行处理的话,还要有moviepy和ffmpeg

要做到这主要是下面这么几个步骤

  • 颜色选择
  • 转化为灰度图
  • 高斯平滑
  • 边缘检测
  • 区域选择
  • 霍夫转换
  • 线条提取

我们都知道一般图像都是RGB图像,在计算机看来就是数组,转化为灰度图,可以直接将图像RGB值相加再除以255(当然,由于人眼对不同颜色敏感度不同,比如对绿色更敏感,对蓝色敏感低,也有进行加权平均的,这里不多说了)

用openCV很容易实现

cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)

然后我们进行高斯平滑,就是将图像变模糊,这样一些较小的不重要的线条就不会被检测出

cv2.GaussianBlur(img, (kernel_size, kernel_size), 0)

边缘检测我们用的是Canny Edge Detection,是一种边缘检测的算法,

cv2.Canny(img, low_threshold, high_threshold)

然后我们可以选择我们感兴趣的区域,可以提高准确度

这里使用的是cv2.fillPoly(),用它你需要在图像是选出你需要的所有顶点vertices

cv2.fillPoly(mask, vertices, ignore_mask_color)

检测出了边缘,我们就可以进行霍夫转换了,这也是本文的一个重点

我们普通图片是的线条可以表示为 y=m_0x+b_0 ,在霍夫(参数)空间就是一个点

相反,图片上的点在霍夫空间就可以表示为线,我们要检测线条的话,就可以把图像上的每个点转换到霍夫空间去,找到霍夫空间上线条相交的点,就可以确定参数m, b

但是我们知道如果是垂直线的话,它的斜率不存在,那又怎么办呢

这样就有了另一种直线表达方法 \rho_0=x\cos\theta_0+y\sin\theta_0 ,同理如下不多说了,如果你想了解更多可以看Understanding Hough Transform With Python

openCV已有相关的代码

cv2.HoughLinesP(img, rho, theta, threshold, np.array([]), minLineLength, maxLineGap)

上面的程序通过霍夫转化找出了图像空间是的线条,返回的是线段两个端点的(x,y)值

然后我们可以用cv2.line()画出线条

cv2.line(img, (left_x1, left_y1), (left_x2, left_y2), color, thickness)

再在原图是呈现出来

cv2.addWeighted(initial_img, α, img, β, λ)

效果如下(红色线条)


这样并没有结束

我们的目标是画出完整的线条,如题图

然后我进行了一定的数学运算,先把cv2.HoughLinesP()返回的两端点的(x,y)值提出来

根据斜率大于0小于0分为左车道和右车道,在分别对左右车道的线段的端点(由于程序返回了很多线,端点也很多)进行拟合,用到的是简单的参数估计法,最小二乘具体如下

b=\frac{n\sum xy-\sum x\sum y}{n\sum x^2-(\sum x)^2}\\ a=\frac{\sum y-b \sum x}{n}=\bar{y}-b\bar{x}

得出斜率b,截距a后,就可以根据图像大小画出线条了(下面是我画出的第一张图片,画面太丑不忍直视)

不要担心那超出的部分,只要调整下线条顶点就行了,不管了,先试下视频效果怎么样

(这里用到的主要是VideoFileClip)

https://www.zhihu.com/video/920707245350739968

可以看见很多时候还是不准确的,在真实情况下这可是很严重的

我觉得可能是某些极端值影响了线条的拟合,对此我们的处理方法是加权平均

先求出cv2.HoughLinesP()返回的每条线的斜率和截距,再以线段的长度为权重做加权平均,得出平均斜率和截距,好,直接看视频效果(为了美观,我把它弄成下面的样子)

https://www.zhihu.com/video/920709185048580096

嗯,不错,似乎一切都完美了,那结束了吗?

不对,我是不是少说了一个东西,文章开始处的第一个 颜色选择怎么没说呢?

没错,之前我并没有进行颜色选择,没有它程序似乎运行地也不错,直到遇见了Shadow

由于影子的存在,我一直无法成功生成那个challenge的视频,看来影子严重影响了我们的检测,在一阵纠结后,我终于看到了曙光——颜色选择(color select)

说到颜色选择,我们知道一般车道线的颜色是黄色和白色,如果我们将图片的黄色和白色都提取出来,后面进行车道线检测就会容易很多(还有一个就是可以将可能造成干扰的线条忽视掉,如影子)

这里我们用到了颜色检测,我们先把图片转为HSV或HSL(如果你要检测红绿蓝,当然可以直接用RGB图像),但考虑到HSL对白色检测更准确,我们用的是HSL,两者的区别可见下图

179下面给一段代码吧

def convert_image(image,cvt_type):
    if cvt_type == 'HSV':
        return cv2.cvtColor(image,cv2.COLOR_RGB2HSV)
    elif cvt_type=='HSL':
        return cv2.cvtColor(image,cv2.COLOR_RGB2HLS) # h l s

注意上面最后一行是RGB2HLS,顺序为h l s

然后我们选择我们所需要的颜色的阈值,至于HSL阈值的选择,可以使用微软的画图板

色调就是H值,注意这里的H值范围为到239,而openCV为0到179,所以注意缩放,乘以180/240

下面在给出一段代码

def select_line_color(image):
    cvt_image=convert_image(image,'HSL')
    # yellow mask
    yellow_lower = np.uint8([10,0,100]) # h l s
    yellow_upper = np.uint8([40,255,255]) 
    yellow_mask = cv2.inRange(cvt_image,yellow_lower,yellow_upper)
    # white mask
    white_lower = np.array([0,200,0])
    white_upper = np.array([255,255,255])
    white_mask = cv2.inRange(cvt_image,white_lower,white_upper)
    # combine mask
    mask = cv2.bitwise_or(yellow_mask,white_mask)
    return cv2.bitwise_and(image,image,mask=mask)

保留黄色和白色,效果如下

另外我又对选择区域,霍夫线条选择等进行了一些微调,好了,上视频

https://www.zhihu.com/video/920715518846054400

完美,结束了么,当然没有,上面不过是一堆像而已,要做的事还很多,如相机校准什么的,而要实现自动驾驶就更是任重道远咯,但本文就说到这里结束了,具体代码可以看我的github


参考资料

编辑于 2017-12-03