2019-02-20 21:38:21 haimianjie2012 阅读数 460
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    414 人正在学习 去看看 刘山

awesome-lane-detection是Amusi 整理的关于车道线检测的资料合集,已经发布到github上,欢迎star、fork:https://github.com/amusi/awesome-lane-detection

据笔者了解,车道线检测解决方案主要分为传统图像处理和深度学习两种。看起来每个领域都可以这么说,但车道线检测与其它研究方向不太一样。因为检测的目标可能是直线也可能是曲线,而且只是"线"而已,目前很多公司还在用传统图像处理方法来解决。

论文精读 | LaneNet 端到端车道线检测的实例分割方法

 

2018-12-27 15:09:32 amusi1994 阅读数 2883
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    414 人正在学习 去看看 刘山

Summary:GitHub:车道线检测最全资料集锦
Author:Amusi
Date:2018-12-27
微信公众号:CVer
github:amusi/awesome-lane-detection
原文链接:GitHub:车道线检测最全资料集锦
知乎:https://zhuanlan.zhihu.com/c_172507674

前言

这篇文章,搁置了至少5个月。关于车道线检测,CVer曾于2018-06-07转载了一篇文章:论文精读 | LaneNet 端到端车道线检测的实例分割方法

前几天又有同学在AI求职大本营里咨询是否有车道线检测方向的资料,笔者就想正好整理一下资料并po出来,也许可以帮助到其它人。

首先申明笔者对这个方向不熟,只是比较感兴趣,并没有深入研究。但这份资料,笔者可以保证是目前网上关于车道线检测最全的资料合集(如果你知道有更棒的,欢迎评论推荐)。

一幅图理解一下车道检测是干嘛的:

file

awesome-lane-detection

awesome-lane-detection是Amusi 整理的关于车道线检测的资料合集,已经发布到github上,欢迎star、fork:https://github.com/amusi/awesome-lane-detection

据笔者了解,车道线检测解决方案主要分为传统图像处理和深度学习两种。看起来每个领域都可以这么说,但车道线检测与其它研究方向不太一样。因为检测的目标可能是直线也可能是曲线,而且只是"线"而已,目前很多公司还在用传统图像处理方法来解决。

这个开源库主要包括以下内容:

  • 论文(以2017之后为主)
  • 开源代码
  • 博客
  • 数据集

论文

如图所示,尽可能涵盖了两年内车道线检测(lane detection)方向的论文。这里点名推荐一篇IEEE IV 2018的优秀论文《Towards End-to-End Lane Detection an Instance Segmentation Approach》和一篇AAAI 2018《Spatial As Deep: Spatial CNN for Traffic Scene Understanding》

file

开源代码

网上关于车道线检测的开源代码,最著名的应该是Udacity课程中项目实例:CarND-Advanced-Lane-Lines

file

博客和数据集

数据集这一块内容还很少,不过每篇论文中多少会说明在哪些数据集上训练/测试的。后续会继续补充,也欢迎大家push。

file

侃侃

个人觉得车道线检测其实是个很有意思、具有难度且有需求的研究方向。你既可以了解传统图像处理如何解决这个问题,也可以通过深度学习来解决,可谓一举两得。

国内做自动驾驶方向的公司很多,而且这个方向与公司业务也是强关联的,笔者认为很有助于找工作。

awesome-lane-detection已经发布到github上,欢迎各位CVers来star、fork,点击链接可以直接进入:https://github.com/amusi/awesome-lane-detection

同时,欢迎下方评论交流

2017-05-28 12:07:38 chentravelling 阅读数 3530
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    414 人正在学习 去看看 刘山

看过几篇车道线检测的文章,有原理比较简单的,比如基于模板、霍夫变换,复杂的还有基于深度学习的,有检测直道、弯道的。推荐几篇文章。根据实际需求,我只是想能够比较粗略地得到视频流中的车道线信息,不需要特别精确,所以本人是基于OpenCV,通过霍夫变换实现的车道线检测。
直接看代码和结果吧:
代码部分:

/*
-功能:车道线检测
-输入:
        IplImage * curImage      //图像
-返回:
        //本文中保存车道线起点和终点的参数是全局变量
*/
void getRoad(IplImage * curImage)
{
    IplImage *temp = cvCreateImage(cvGetSize(curImage), IPL_DEPTH_8U, 1);
    //cvCvtColor(curImage, temp, CV_BGR2GRAY);
    cvThreshold(curImage, temp, 90, 255.0, CV_THRESH_BINARY);       //200
    cvErode(temp, temp, NULL, 1);
    cvDilate(temp, temp, NULL, 1);
    cvCanny(temp, temp, 50, 120);
    //霍夫变换
    CvSeq *lines = NULL;

    CvMemStorage *storage = cvCreateMemStorage(0);
    lines = cvHoughLines2(
        temp, 
        storage, 
        CV_HOUGH_PROBABILISTIC,         //method            概率
        1.0,                            //rho
        CV_PI / 180,                    //theta
        40,                             //threshold     像素点个数阈值
        20,                             //param1        最小线段长度
        10);                            //param2        线段的最大间隔
    int length = lines->total;
    vector<CvPoint*> lRoadV, rRoadV;//候选车道线线段
    for (size_t i = 0; i < length; i++)
    {
        CvPoint *points = (CvPoint*)cvGetSeqElem(lines, i);
        double k = (points[0].y - points[1].y)*1.0 / (points[0].x - points[1].x);
        if (k > -1.5&&k < -0.5)             //左车道
        {
            lRoadV.push_back(points);
        }
        else if (k > 0.5 && k < 1.5)        //右车道
        {
            rRoadV.push_back(points);
        }   
    }
        selectRoad(lRoadV, rRoadV);//进一步筛选车道线
}

效果如下
这里写图片描述

2019-10-25 20:00:02 weixin_40279184 阅读数 275
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    414 人正在学习 去看看 刘山

车道线检测是图像处理运用到无人驾驶的一项技术,目前也过渡到了部分汽车上,高速公路的自动车道保持就是一个应用。
最近研究了两个基于opencv的车道检的代码,先放链接:
A.Udacity车道线检测代码

高速车道线检测

B.基于霍夫变换和kalman滤波车道线检测

夜视情况下车道线检测

 *****A***中处理步骤如下:**
 
 1.使用提供的一组棋盘格图片计算相机校正矩阵(camera calibration matrix)和失真系数(distortion coefficients).这里主要是考虑到一些鱼眼相机。可以通过棋盘图片来获取校正矩阵进而完成相角拍摄图片的校正。
 2.使用梯度阈值(gradient threshold),颜色阈值(color threshold)等处理图片得到清晰捕捉车道线的二进制图(binary image).
 3.使用透视变换(perspective transform)得到二进制图(binary image)的鸟瞰图(birds-eye view).
 进行透视变化只要是为了找到左右车道线的起点(这里将透视变化后的图片中的像素点投影到x轴,像素多的点对应为左右车道线的起点)
 4.检测属于车道线的像素并用它来测出车道边界.
 5.计算车道曲率及车辆相对车道中央的位置.
 6.处理图片展示车道区域,及车道的曲率和车辆位置.

A中各个步骤的处理细节,效果对比在.
Github中README.md中介绍很详细;A中主要缺点就是实时性还不够(每一帧需要1.05秒),对于弯道较多的路段还是很难适用。

***B***中对于夜视情况下检测效果非常好,且实时性较好。
         步骤如下:1.对视频中的图片进行gamma校正,gamma校正相当于进行直方图均匀化。将夜视下的图片增量对比度提高。代码如下:`
def gamma_correction_auto(RGBimage, equalizeHist=False):  # 0.35
    originalFile = RGBimage.copy()
    red = RGBimage[:, :, 2]
    green = RGBimage[:, :, 1]
    blue = RGBimage[:, :, 0]
    forLuminance = cv2.cvtColor(originalFile, cv2.COLOR_BGR2YUV)
    Y = forLuminance[:, :, 0]
    totalPix = vidsize[0] * vidsize[1]
    summ = np.sum(Y[:, :])
    Yaverage = np.divide(totalPix, summ)
    # Yclipped = np.clip(Yaverage,0,1)
    epsilon = 1.19209e-007
    correct_param = np.divide(-0.3, np.log10([Yaverage + epsilon]))
    correct_param = 0.7 - correct_param
    red = red / 255.0
    red = cv2.pow(red, correct_param)
    red = np.uint8(red * 255)
    if equalizeHist:
        red = cv2.equalizeHist(red)
    green = green / 255.0
    green = cv2.pow(green, correct_param)
    green = np.uint8(green * 255)
    if equalizeHist:
        green = cv2.equalizeHist(green)
    blue = blue / 255.0
    blue = cv2.pow(blue, correct_param)
    blue = np.uint8(blue * 255)
    if equalizeHist:
        blue = cv2.equalizeHist(blue)
    output = cv2.merge((blue, green, red))
    # print(correct_param)
    return output
 2.将图片转化到hsv空间主要筛选出白色和黄色区域:
       hsv空间中黄色所在区间(yellow)
       min_val_y = np.array([15, 80, 190])
       max_val_y = np.array([30, 255, 255])
       hsv空间中白色色所在区间(white)
       min_val_w = np.array([0, 0, 195])
       max_val_w = np.array([255, 80, 255])

代码如下:

def hsv_filter(image, min_val_y, max_val_y, min_val_w, max_val_w):#因为车道线只有黄白两种颜色所以主要对这两种颜色进行处理
    """
    A function returning a mask for pixels within min_val - max_val range
    Inputs:
    - image - a BGR image you want to apply function on
    - min_val_y - array of shape (3,) giving minumum HSV values for yellow color
    - max_val_y - array of shape (3,) giving maximum HSV values for yellow color
    - min_val_w - array of shape (3,) giving minumum HSV values for white color
    - max_val_w - array of shape (3,) giving maximum HSV values for white color
    Returns:
    - img_filtered - image of pixels being in given threshold
    """

    hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
    mask_yellow = cv2.inRange(hsv, min_val_y, max_val_y) #黄
    mask_white = cv2.inRange(hsv, min_val_w, max_val_w)  #白
    mask = cv2.bitwise_or(mask_yellow, mask_white)
    img_filtered = cv2.bitwise_and(image, image, mask=mask)
    return img_filtered
 3.霍夫变换检检测出直线。霍夫变换可以检测直线和圆。原理可以查看下相关资料,运算量算小。所以实时性不错。对于一些弯道较多的路段挑战较大。代码如下:代码中进行了关于角度的滤波,因为霍夫变换检测出来的直线也较多所以可以去掉一些角度不在范围内的,挑选出左右车道线。
def hough_transform(original, gray_img, threshold, discard_horizontal=0.4):
    """
    A function fitting lines that intersect >=threshold white pixels
    Input:
    - original - image we want to draw lines on
    - gray_img - image with white/black pixels, e.g. a result of Canny Edge Detection
    - threshold - if a line intersects more than threshold white pixels, draw it
    - discard_horizontal - smallest abs derivative of line that we want to take into account
    Return:
    - image_lines - result of applying the function
    - lines_ok - rho and theta
    """
    lines = cv2.HoughLines(gray_img, 0.5, np.pi / 360, threshold)
    image_lines = original
    lines_ok = []  # list of parameters of lines that we want to take into account (not horizontal)
    if lines is not None:
        for i in range(0, len(lines)):
            rho = lines[i][0][0]
            theta = lines[i][0][1]
            # discard horizontal lines
            m = -math.cos(theta) / (math.sin(theta) + 1e-10)  # adding some small value to avoid dividing by 0
            if abs(m) < discard_horizontal:
                continue
            else:
                a = math.cos(theta)
                b = math.sin(theta)
                x0 = a * rho
                y0 = b * rho
                pt1 = (int(x0 + 1000 * (-b)), int(y0 + 1000 * (a)))
                pt2 = (int(x0 - 1000 * (-b)), int(y0 - 1000 * (a)))
                cv2.line(image_lines, pt1, pt2, (0, 0, 255), 2, cv2.LINE_AA)
                lines_ok.append([rho, theta])
    lines_ok = np.array(lines_ok)
    return image_lines, lines_ok
  4.检测出直线后为了防止车道线抖动,这里采用了kalman滤波对车道线进行跟踪。kalman运算量较小,广泛运用于目标跟踪。当然还有粒子滤波有兴趣可以了解下,这里最关键是怎样对kalman的初试矩阵进行初始化。kalman滤波主要运用了5个方程。2个用于预测,3个用于更新参数;直接贴代码吧
class LaneTracker:
    def __init__(self, n_lanes, proc_noise_scale, meas_noise_scale, process_cov_parallel=0, proc_noise_type='white'):
        '''
        n_lanes=2
        proc_noise_scale=0.1
        meas_noise_scale=15
        predict():  基于k-1去预测值
                     x^k- =Ax^(k-1)        #估计的状态       x^k- 16*1    A.shape 16*16      x^(k-1) 16*1
                     p^k-  =A(p^k)AT+Q     #估计的协方差     16*16 =16*16 * 16*16*16*16 +16*16

        correct(): 估计值 基于zk测量值和x^k- 去估计
                  Kk=((p^k-).HT)/(H(p^k-)HT+R) 16*8=(16*16*16*8)/(8*16 *16*16 *16*8 +8*8)
                  x^k=x^k- +Kk(zk-H(x^k-))      #更新的状态#结合测量和预测后验估计    精确  16*1=16*1-16*8* (8*1-8*16*16*1)
                  p^k=(I-KkH)(p^k- )            #更新的协方差#结合测量和预测后验估计  精确  16*16=( 16*16- 16*8*8*16)*16*16

        x^k-          为k时刻先验状态估计
        x^k、x^(k-1)  为后验  转态估计
        p^k-          为k时刻 先验估计协方差 初始化为0 容易收敛
        Q             过程噪声协方差  表达了状态矩阵和实际过程状态矩阵的误差 是估计的关键

        Kk 卡尔曼协方差
        zk 为测量值 m阶向量
        H  为测量转移矩阵
        R为 测量噪声协方差 R越小越收敛快
        '''
        self.n_lanes = n_lanes#2
        self.meas_size = 4 * self.n_lanes     #8 测量维度 一条车道线四个点  x1,y1,x2,y2, x1',y1',x2',y2'
        self.state_size = self.meas_size * 2  #16   转态转移维度  测量维度*2  x1,dx1,y1,dy1,x2,dx2,y2,dy2,  x1',dx1',y1',dy1',x2',dx2',y2',dy2'
        self.contr_size = 0
        self.kf = cv2.KalmanFilter(self.state_size, self.meas_size, self.contr_size)        #(16, 8,0)转移 测量控制矩阵维度
        self.kf.transitionMatrix = np.eye(self.state_size, dtype=np.float32)                #(16,16)单位矩阵转移矩阵初始化A
        self.kf.measurementMatrix = np.zeros((self.meas_size, self.state_size), np.float32) #(8,16)测量矩阵初始化H
        for i in range(self.meas_size):
            self.kf.measurementMatrix[i, i*2] = 1
        if proc_noise_type == 'white':#处理的白噪声
            block = np.matrix([[0.25, 0.5],
                               [0.5, 1.]], dtype=np.float32)
            self.kf.processNoiseCov = block_diag(*([block] * self.meas_size)) * proc_noise_scale#过程噪声协方差Q
        if proc_noise_type == 'identity':
            self.kf.processNoiseCov = np.eye(self.state_size, dtype=np.float32) * proc_noise_scale#(16,16)

        for i in range(0, self.meas_size, 2):
            for j in range(1, self.n_lanes):#8*2
                self.kf.processNoiseCov[i, i+(j*8)] = process_cov_parallel  #process_cov_parallel=0
                self.kf.processNoiseCov[i+(j*8), i] = process_cov_parallel
        self.kf.measurementNoiseCov = np.eye(self.meas_size, dtype=np.float32) * meas_noise_scale#(8,8)测量噪声协方差R
        self.kf.errorCovPre = np.eye(self.state_size)#P^k 错误协方差(16,16)的单位矩阵
        self.meas = np.zeros((self.meas_size, 1), np.float32)#(8,1)测量值 zk
        self.state = np.zeros((self.state_size, 1), np.float32)#(16,1)状态值 x^k-
        self.first_detected = False
        
    def _update_dt(self, dt):#更新A(16,16)
        for i in range(0, self.state_size, 2):
            self.kf.transitionMatrix[i, i+1] = dt  #速度 dt为视频处理速度?

    def _first_detect(self, lanes):#初始化self.state
        for l, i in zip(lanes, range(0, self.state_size, 8)):
            self.state[i:i+8:2, 0] = l#先验转态x^k-
        self.kf.statePost = self.state
        self.first_detected = True

    def update(self, lanes):
        if self.first_detected:
            for l, i in zip(lanes, range(0, self.meas_size, 4)):
                if l is not None:
                    self.meas[i:i+4, 0] = l#测量值更新
            self.kf.correct(self.meas)#根据测量值更新    Kk x^k p^k 为下次预测做准备

        else:
            if lanes.count(None) == 0:
                self._first_detect(lanes)

    def predict(self, dt):
        if self.first_detected:
            self._update_dt(dt)
            state = self.kf.predict()#预测值x^k-
            lanes = []
            for i in range(0, len(state), 8):
                lanes.append((state[i], state[i+2], state[i+4], state[i+6]))# x1,dx1,y1,dy1,x2,dx2,y2,dy2
            return lanes
        else:
            return None

整体效果大家可以运行Github代码查看。夜视下以及实时性很nice。
A,B主要采用opencv方式完成的车道线检测方法。接下来作者将继续去研究基于特征的深度学习方法完成车道检测。

2018-10-03 20:34:43 aiailab 阅读数 4444
  • Opencv4图像分割和识别实战课程

    本课程专注于使用opencv4 APIs来解决实际工作中经常遇到的图像分割、识别问题。精心挑选的8个例子涵盖了图片滤镜特效,验证码识别,条形码和二维码定位和识别,试卷答题线检测、瓶盖缺陷识别以及车道线检测等方面。课程中既有原理讲解也有实战代码演示,更重要的是每节课后都布置有作业来提供动手机会,使得学员们通过实操来更好地消化和掌握课堂知识。

    414 人正在学习 去看看 刘山

基于图像处理相关技术的高级车道线检测(可适用于弯道,车道线颜色不固定,路面阴影,亮光)
pipeline:
1.校准摄像头的畸变,使拍摄照片能够较完整的反映3D世界的情况
2.对每一帧图片做透视转换(perspective transform),将摄像头的照片转换到鸟瞰图视角(图片见正文),方便计算车道线曲率,从而控制车辆运动
3.对鸟瞰图二值化,通过二值的像素点进一步区分左右两条车道线,从而拟合出车道线曲线
4.用滑窗的方法检测第一帧的车道线像素点,然后拟合车道线曲线
5.从第一帧的曲线周围寻找接下来的车道线像素点,然后拟合车道线曲线,这样可以节约计算资源,加快检测速度
6.有了车道线曲线方程之后,可以计算斜率和车道线偏离中心的位置

正文
1.校准摄像头畸变
摄像头畸变主要分两种,径向畸变和切向畸变,径向畸变是由于光线经过摄像机的镜头时,边缘的光线会更多或更少的弯曲,所以边缘的物体成像时会有畸变。切向畸变主要是由于镜头和成像胶卷或传感器不平行导致的。
关于由5个参数矫正畸变,k1-k5,畸变越严重,所需参数越多,径向畸变矫正公式如下:在这里插入图片描述
切向畸变矫正公式如下:
在这里插入图片描述
其中x,y为原图任一点,x,y(corrected)为其对应的没有畸变的图像上的坐标,r为点到中心的距离,K1,K2,K3为径向畸变参数,P1,P2为切向畸变参数,矫正参数由opencv的API来获得。这里用棋盘图的原因是计算点坐标相对容易,一般计算要多拍一些棋盘图在不同角度的照片,找到每一张棋盘图的角点坐标(图中的全部(x,y)和(x,y)(corrected)坐标),和其对应的未畸变的角点坐标,然后计算矫正参数进行矫正。

#创建objpoints和imgpoints来接收来自无畸变图片和相机拍摄畸变图片的角点,存储多张图片角点增加矫正的准确率。
def calibrate_camera(nx,ny):
    objp = np.zeros((nx*ny,3), np.float32)
    objp[:,:2] = np.mgrid[0:nx,0:ny].T.reshape(-1,2)

    objpoints = []
    imgpoints = []

    images = glob.glob('camera_cal/calibration*.jpg')

    for idx, fname in enumerate(images):
        #convert image to gray that the 'cv2.findChessboardCorners' needed
        img = mpimg.imread(fname)
        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        #Finding chessboard corners (for an 9×️6 board)
        ret, corners = cv2.findChessboardCorners(gray, (nx,ny), None)

        if ret == True:
            objpoints.append(objp)
            imgpoints.append(corners)
            #mtx represents 3D to 2D transformation, dist represents undistortion coef, rvecs the spin of camera
            #and tvecs the offset(偏移量)of the camera in the real world.
    return objpoints, imgpoints

有了目标点和图像点的角点信息之后,利用opencv的API自动计算矫正系数来矫正每一帧图片,返回值是无畸变图像,图1为矫正前图像,图2为矫正后图像。

def undistort_image(img):
    objectpoints, imagepoints = calibrate_camera(9,6)
    img_size = img.shape[1::-1]
    #get the distortion coef and other parameters
    ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objectpoints, imagepoints, img_size, None, None)
    #Undistort a image
    dst = cv2.undistort(img, mtx, dist, None, mtx)
    return dst

矫正前

矫正后

接着就是为了计算曲率,将摄像头的视角进行转换,转换的结果最好是转换成从上向下的视角,造成摄像机从空中垂直拍摄车道线的效果,使用转换前图片任意多边形的边界点和转换以后多边形的边界点作为输入,调用opencv的cv2.getPerspectiveTransform函数可以返回转换的矩阵M,Minv是反转换矩阵,warped就是转换后车道线鸟瞰图。

def perspective_transform(img):
    # Vertices extracted manually for performing a perspective transform
    leftupperpoint = [568, 470]
    rightupperpoint = [717, 470]
    leftlowerpoint = [260, 680]
    rightlowerpoint = [1043, 680]

    src = np.float32([leftupperpoint, leftlowerpoint, rightupperpoint, rightlowerpoint])
    dst = np.float32([[200, 0], [200, 680], [1000, 0], [1000, 680]])

    img_size = img.shape[1::-1]
    #Compute the perspective transform, M, given source and destination points
    M = cv2.getPerspectiveTransform(src, dst)
    #Compute the inverse perspective transform
    Minv = cv2.getPerspectiveTransform(dst, src)
    #Warp an image using the perspective transform, M
    warped = cv2.warpPerspective(img, M, img_size, flags=cv2.INTER_LINEAR)
    return M, Minv, warpeddef perspective_transform(img):
   

在这里插入图片描述
有了鸟瞰图,还是要让程序自己辨别左右两条车道线,车道线在图像上是两条垂直线,可以用边缘检测的方法检测出,关于Sobel边缘检测,用sobel算子在x方向求导数可以很好的检测出垂直的车道线,但是为了让车道线检测效果更鲁棒,这里还需要结合一些图像颜色空间的知识,实验表明,HLS颜色中的S(饱和度)空间对阴影,光照的结果很鲁棒,因为饱和度通常反应物体颜色的鲜艳程度,与物体颜色及颜色亮暗无关,所以采用x方向的sobel边缘检测和饱和度阈值结合的方法可以使车道线检测结果更鲁棒
在这里插入图片描述

def pipeline(img, s_thresh=(170, 255), sx_thresh=(20, 100)):
    img = np.copy(img)
    # Convert to HLS color space and separate the V channel
    hls = cv2.cvtColor(img, cv2.COLOR_RGB2HLS)
    l_channel = hls[:,:,1]
    s_channel = hls[:,:,2]
    # Sobel x
    sobelx = cv2.Sobel(l_channel, cv2.CV_64F, 1, 0) # Take the derivative in x
    abs_sobelx = np.absolute(sobelx) # Absolute x derivative to accentuate lines away from horizontal
    scaled_sobel = np.uint8(255*abs_sobelx/np.max(abs_sobelx))
    
    # Threshold x gradient
    sxbinary = np.zeros_like(scaled_sobel)
    sxbinary[(scaled_sobel >= sx_thresh[0]) & (scaled_sobel <= sx_thresh[1])] = 1
    
    # Threshold color channel
    s_binary = np.zeros_like(s_channel)
    s_binary[(s_channel >= s_thresh[0]) & (s_channel <= s_thresh[1])] = 1
    # Stack each channel
    color_binary = np.dstack(( np.zeros_like(sxbinary), sxbinary, s_binary)) *255
    return color_binary

有了二进制的车道线图像,就可以进一步拟合车道线曲线,首先画车道线二进制图的像素直方图,取下半张图片,像素最多的位置作为车道线的起始位置,然后自定义窗口大小和个数向上做滑窗操作,求出每个窗口中像素点的x和y坐标作为车道线的x,y坐标,当前窗口像素的平均x坐标作为下一个滑窗的中心位置,有了全部滑窗和车道线坐标用cv2.fitpoly函数拟合车道线曲线方程
在这里插入图片描述

def find_line_pixels(binary_warped):
    # Take a histogram of the bottom half of the image
    histogram = np.sum(binary_warped[binary_warped.shape[0] // 2:, :], axis=0)
    # Find the peak of the left and right halves of the histogram
    # These will be the starting point for the left and right lines
    midpoint = np.int(histogram.shape[0] / 2)
    leftx_base = np.argmax(histogram[:midpoint])
    rightx_base = np.argmax(histogram[midpoint:]) + midpoint

    # Choose the number of sliding windows
    nwindows = 9
    # Set height of windows
    window_height = np.int(binary_warped.shape[0] / nwindows)
    # Identify the x and y positions of all nonzero pixels in the image
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    # Current positions to be updated for each window
    leftx_current = leftx_base
    rightx_current = rightx_base
    # Set the width of the windows +/- margin
    margin = 100
    # Set minimum number of pixels found to recenter window
    minpix = 50
    # Create empty lists to receive left and right lane pixel indices
    left_lane_inds = []
    right_lane_inds = []

    # Step through the windows one by one
    for window in range(nwindows):
        # Identify window boundaries in x and y (and right and left)
        win_y_low = binary_warped.shape[0] - (window + 1) * window_height
        win_y_high = binary_warped.shape[0] - window * window_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
        # Identify the nonzero pixels in x and y within the window
        good_left_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
                          (nonzerox >= win_xleft_low) & (nonzerox < win_xleft_high)).nonzero()[0]
        good_right_inds = ((nonzeroy >= win_y_low) & (nonzeroy < win_y_high) &
                           (nonzerox >= win_xright_low) & (nonzerox < win_xright_high)).nonzero()[0]
        # Append these indices to the lists
        left_lane_inds.append(good_left_inds)
        right_lane_inds.append(good_right_inds)
        # If you found > minpix pixels, recenter next window on their mean position
        if len(good_left_inds) > minpix:
            leftx_current = np.int(np.mean(nonzerox[good_left_inds]))
        if len(good_right_inds) > minpix:
            rightx_current = np.int(np.mean(nonzerox[good_right_inds]))

    # Concatenate the arrays of indices
    left_lane_inds = np.concatenate(left_lane_inds)
    right_lane_inds = np.concatenate(right_lane_inds)

    # Extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds]
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]

    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)


    return left_fit, right_fit, left_lane_inds, right_lane_inds, lefty, leftx, righty, rightx


    

滑窗的方法通常用于第一帧或者检测失败重新开始的检测,因为对计算资源浪费过多,检测时间长,由于连续帧图像之间相差不大,之后几帧的图像可以只对第一帧拟合的曲线周围检测,设置周围的margin,然后在该范围内寻找下一帧曲线的像素点从而拟合曲线。但是再最后处理每一帧图像时要设置标志位检测是否检测到拟合的曲线,检测到的话用search_from_previous方法,否则的话要用滑窗的方法重新寻找车道线。
在这里插入图片描述


def search_from_previous(binary_warped, left_fit, right_fit):
    nonzero = binary_warped.nonzero()
    nonzeroy = np.array(nonzero[0])
    nonzerox = np.array(nonzero[1])
    margin = 100
    left_lane_inds = ((nonzerox > (left_fit[0] * (nonzeroy ** 2) + left_fit[1] * nonzeroy +
                                   left_fit[2] - margin)) & (nonzerox < (left_fit[0] * (nonzeroy ** 2) +
                                                                         left_fit[1] * nonzeroy + left_fit[
                                                                             2] + margin)))

    right_lane_inds = ((nonzerox > (right_fit[0] * (nonzeroy ** 2) + right_fit[1] * nonzeroy +
                                    right_fit[2] - margin)) & (nonzerox < (right_fit[0] * (nonzeroy ** 2) +
                                                                           right_fit[1] * nonzeroy + right_fit[
                                                                               2] + margin)))

    # Again, extract left and right line pixel positions
    leftx = nonzerox[left_lane_inds]
    lefty = nonzeroy[left_lane_inds]
    rightx = nonzerox[right_lane_inds]
    righty = nonzeroy[right_lane_inds]
    # Fit a second order polynomial to each
    left_fit = np.polyfit(lefty, leftx, 2)
    right_fit = np.polyfit(righty, rightx, 2)
    return left_fit, right_fit, left_lane_inds, right_lane_inds,lefty,leftx,righty,rightx

有了曲线的方程,则可以根据曲线曲率半径的公式计算曲率,dx/dy是因为我们拟合的是x对y的方程,因为车道线基本垂直,y对x的函数可能会存在一个y队形多个x的情况,这里还要注意自己计算的车道线曲线是根据像素值计算的,还要转换为其对应的实际道路距离(m)。
对于车辆的偏移,我们可以假设摄像头安装在车辆的正中心,那么道路中心就是检测到的图像中两条车道线的中点,车道线中心和图像中心的偏移就是车辆相对于车道线的偏移。同样记得把像素值转换为实际道路记录(m)。

在这里插入图片描述


  def measure_radius_of_curvature(warped_img, lefty, leftx, righty, rightx):
    ym_per_pix = 20 / 720  # meters per pixel in y dimension
    xm_per_pix = 3.7 / 800  # meters per pixel in x dimension

    # Fit a second order polynomial to pixel positions in each fake lane line
    # Fit new polynomials to x,y in world space
    left_fit_cr = np.polyfit(lefty*ym_per_pix, leftx*xm_per_pix, 2)
    right_fit_cr = np.polyfit(righty*ym_per_pix, rightx*xm_per_pix, 2)

    ploty = np.linspace(0, warped_img.shape[0] - 1, warped_img.shape[0])

    # Define y-value where we want radius of curvature
    # We'll choose the maximum y-value, corresponding to the bottom of the image
    y_eval = np.max(ploty)

    #Implement the calculation of R_curve (radius of curvature)
    left_curverad = ((1 + (2 * left_fit_cr[0] * y_eval * ym_per_pix + left_fit_cr[1]) ** 2) ** 1.5) / np.absolute(
        2 * left_fit_cr[0])
    right_curverad = ((1 + (2 * right_fit_cr[0] * y_eval * ym_per_pix + right_fit_cr[1]) ** 2) ** 1.5) / np.absolute(
        2 * right_fit_cr[0])

    left_lane_bottom = left_fit_cr[0] * (y_eval* ym_per_pix) ** 2 + left_fit_cr[0] * y_eval* ym_per_pix + left_fit_cr[2]
    right_lane_bottom = right_fit_cr[0] * (y_eval* ym_per_pix) ** 2 + right_fit_cr[0] * y_eval* ym_per_pix + right_fit_cr[2]

    # Lane center as mid of left and right lane bottom
    lane_center = (left_lane_bottom + right_lane_bottom) / 2.
    center_image = np.float(1280/2*xm_per_pix)
    center = lane_center - center_image  # Convert to meters
    position = "left" if center < 0 else "right"
    center = "Vehicle is {:.2f}m {}".format(center, position)

    # Now our radius of curvature is in meters
    return left_curverad, right_curverad, center

这就是完整的车道线检测流程了,对于一些连续弯道导致车道线超出图像边界的情况日后解决了会更新。

车道线检测学习总结

阅读数 14529

没有更多推荐了,返回首页