图像处理车道线检测_图像处理的车道线检测 - CSDN
  • 图像处理车道线检测

    千次阅读 2017-05-28 12:07:30
    看过几篇车道线检测的文章,有原理比较简单的,比如基于模板、霍夫变换,复杂的还有基于深度学习的,有检测直道、弯道的。推荐几篇文章。根据实际需求,我只是想能够比较粗略地得到视频流中的车道线信息,不需要特别...

    看过几篇车道线检测的文章,有原理比较简单的,比如基于模板、霍夫变换,复杂的还有基于深度学习的,有检测直道、弯道的。推荐几篇文章。根据实际需求,我只是想能够比较粗略地得到视频流中的车道线信息,不需要特别精确,所以本人是基于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-03-25 20:02:36
    车道检测(Advanced Lane Finding Project) 车道检测作为无人驾驶的基础技术,有必要单独进行研究下。我准备用两篇文章记录车道检测功能,本篇是基于传统算法进行车道检测,下篇将使用深度学习的算法进行分析。 ...

    车道检测(Advanced Lane Finding Project)

    车道检测作为无人驾驶的基础技术,有必要单独进行研究下。我准备用两篇文章记录车道检测功能,本篇是基于传统算法进行车道检测,下篇将使用深度学习的算法进行分析。

    é«éå¬è·¯åé弯éæ£æµææ

    占坑。。。。最近比较忙后续更新

    先看几个流程图:

     

     

     

     

    实现步骤:

    • 使用提供的一组棋盘格图片计算相机校正矩阵(camera calibration matrix)和失真系数(distortion coefficients).
    • 校正图片
    • 使用梯度阈值(gradient threshold),颜色阈值(color threshold)等处理图片得到清晰捕捉车道线的二进制图(binary image).
    • 使用透视变换(perspective transform)得到二进制图(binary image)的鸟瞰图(birds-eye view).
    • 针对不同颜色的车道线,不同光照条件下的车道线,不同清晰度的车道线,根据不同的颜色空间使用不同的梯度阈值,颜色阈值进行不同的处理。并将每一种处理方式进行融合,得到车道线的二进制图。
    • 提取二进制图中属于车道线的像素
    • 对二进制图片的像素进行直方图统计,统计左右两侧的峰值点作为左右车道线的起始点坐标进行曲线拟合。
    • 使用二次多项式分别拟合左右车道线的像素点(对于噪声较大的像素点,可以进行滤波处理,或者使用随机采样一致性算法进行曲线拟合)。
    • 计算车道曲率及车辆相对车道中央的偏离位置。
    • 效果显示(可行域显示,曲率和位置显示)。
       
    摄像机标定

    相信大家都多少听说过鱼眼相机,最常见的鱼眼相机是辅助驾驶员倒车的后向摄像头。也有很多摄影爱好者会使用鱼眼相机拍摄图像,最终会有高大上的大片效果,如下图所示。

    图片来源:优达学城(Udacity)无人驾驶工程师课程

    使用鱼眼相机拍摄的图像虽然高大上,但存在一个很大的问题——畸变(Distortion)。如上图所示,走道上的栏杆应该是笔直延伸出去的。然而,栏杆在图像上的成像却是弯曲的,这就是图像畸变,畸变会导致图像失真。

    使用车载摄像机拍摄出的图像,虽然没有鱼眼相机的畸变这么夸张,但是畸变是客观存在的,只是人眼难以察觉。使用有畸变的图像做车道线的检测,检测结果的精度将会受到影响,因此进行图像处理的第一步工作就是去畸变。

    为了解决车载摄像机图像的畸变问题摄像机标定技术应运而生。

    摄像机标定是通过对已知的形状进行拍照,通过计算该形状在真实世界中位置与在图像中位置的偏差量(畸变系数),进而用这个偏差量去修正其他畸变图像的技术。

    原则上,可以选用任何的已知形状去校准摄像机,不过业内的标定方法都是基于棋盘格的。因为它具备规则的、高对比度图案,能非常方便地自动化检测各个棋盘格的交点,十分适合标定摄像机的标定工作。如下图所示为标准的10x7(7行10列)的棋盘格。

    OpenCV库为摄像机标定提供了函数cv2.findChessboardCorners(),它能自动地检测棋盘格内4个棋盘格的交点(2白2黑的交接点)。我们只需要输入摄像机拍摄的完整棋盘格图像和交点在横纵向上的数量即可。随后我们可以使用函数cv2.drawChessboardCorners()绘制出检测的结果。

    棋盘格原图如下所示:

    图片出处:https://github.com/udacity/CarND-Advanced-Lane-Lines/blob/master/camera_cal/calibration2.jpg

    使用OpenCV自动交点检测的结果如下:

    获取交点的检测结果后,使用函数cv2.calibrateCamera()即可得到相机的畸变系数。

    我们使用cv2.calibrateCamera()进行标定,这个函数会返回标定结果、相机的内参数矩阵、畸变系数、旋转矩阵和平移向量。

    为了使摄像机标定得到的畸变系数更加准确,我们使用车载摄像机从不同的角度拍摄20张棋盘格,将所有的交点检测结果保存,再进行畸变系数的的计算。

    我们将读入图片、预处理图片、检测交点、标定相机的一系列操作,封装成一个函数,如下所示:

    1.计算摄像机畸变系数

    #################################################################
    # Step 1 : Calculate camera distortion coefficients
    #################################################################
    def getCameraCalibrationCoefficients(chessboardname, nx, ny):
        # prepare object points, like (0,0,0), (1,0,0), (2,0,0) ....,(6,5,0)
        objp = np.zeros((ny * nx, 3), np.float32)
        objp[:,:2] = np.mgrid[0:nx, 0:ny].T.reshape(-1,2)
        
        # Arrays to store object points and image points from all the images.
        objpoints = [] # 3d points in real world space
        imgpoints = [] # 2d points in image plane.
        
        images = glob.glob(chessboardname)
        if len(images) > 0:
            print("images num for calibration : ", len(images))
        else:
            print("No image for calibration.")
            return
        
        ret_count = 0
        for idx, fname in enumerate(images):
            img = cv2.imread(fname)
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            img_size = (img.shape[1], img.shape[0])
            # Finde the chessboard corners
            ret, corners = cv2.findChessboardCorners(gray, (nx, ny), None)
    
            # If found, add object points, image points
            if ret == True:
                ret_count += 1
                objpoints.append(objp)
                imgpoints.append(corners)
                
        ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints,     
        img_size, None, None)
        print('Do calibration successfully')
        return ret, mtx, dist, rvecs, tvecs

    调用之前封装好的函数,获取畸变参数。

    nx = 9 
    ny = 6
    ret, mtx, dist, rvecs, tvecs = getCameraCalibrationCoefficients('camera_cal/calibration*.jpg', nx, ny)

    随后,使用OpenCV提供的函数cv2.undistort(),传入刚刚计算得到的畸变参数,即可将畸变的图像进行畸变修正处理。

    #################################################################
    # Step 2 : Undistort image
    #################################################################
    def undistortImage(distortImage, mtx, dist):
        return cv2.undistort(distortImage, mtx, dist, None, mtx)

    以畸变的棋盘格图像为例,进行畸变修正处理

    # Read distorted chessboard image
    test_distort_image = cv2.imread('./camera_cal/calibration4.jpg')
    
    # Do undistortion
    test_undistort_image = undistortImage(test_distort_image, mtx, dist)

    筛选图像

    从我们作为输入的视频可以看出,车辆会经历颠簸、车道线不清晰、路面颜色突变,路边障碍物阴影干扰等复杂工况。因此,需要将这些复杂的场景筛选出来,确保后续的算法能够在这些复杂场景中正确地检测出车道线。

    使用以下代码将视频中的图像数据提取,进行畸变修正处理后,存储在名为original_image的文件夹中,以供挑选。

    video_input = 'project_video.mp4'
    cap = cv2.VideoCapture(video_input)
    count = 1
    while(True):
        ret, image = cap.read()
        if ret:
            undistort_image = undistortImage(image, mtx, dist)
            cv2.imwrite('original_image/' + str(count) + '.jpg', undistort_image)
            count += 1
        else:
            break
    cap.release()

     

    在original_image文件夹中,挑选出以下6个场景进行检测。这6个场景既包含了视频中常见的正常直道、正常弯道工况,也包含了具有挑战性的阴影、明暗剧烈变化的工况。如果后续的高级车道线检测算法能够完美处理以上六种工况,那将算法应用到视频中,也会得到完美的车道线检测效果。可见,如开篇所讲,本文完全是基于传统图像处理算法进行车道检测的,其依赖于高度定义化,手工特征提取和启发式方法,通常是需要后后处理技术,而这往往使得计算量大,不利于道路场景多变下的应用扩展。

    透视变换

    在完成图像的畸变修正后,就要将注意力转移到车道线。与《无人驾驶技术入门(十四)| 初识图像之初级车道线检测》中技术类似,这里需要定义一个感兴趣区域。很显然,我们的感兴趣区域就是车辆正前方的这个车道。为了获取感兴趣区域,我们需要对自车正前方的道路使用一种叫做透视变换的技术。

    “透视”是图像成像时,物体距离摄像机越远,看起来越小的一种现象。在真实世界中,左右互相平行的车道线,会在图像的最远处交汇成一个点。这个现象就是“透视成像”的原理造成的。

     

    以立在路边的交通标志牌为例,它在摄像机所拍摄的图像中的成像结果一般如下下图所示

     

    在这幅图像上,原本应该是正八边形的标志牌,成像成为一个不规则的八边形。

    通过使用透视变换技术,可以将不规则的八边形投影成规则的正八边形。应用透视变换后的结果对比如下:

     

    透视变换的原理:首先新建一幅跟左图同样大小的右图,随后在做图中选择标志牌位于两侧的四个点(如图中的红点),记录这4个点的坐标,我们称这4个点为src_points。图中的4个点组成的是一个平行四边形。

    由先验知识可知,左图中4个点所围成的平行四边形,在现实世界中是一个长方形,因此在右边的图中,选择一个合适的位置,选择一个长方形区域,这个长方形的4个端点一一对应着原图中的src_points,我们称新的这4个点为dst_points。

    得到src_points,dst_points后,我们就可以使用OpenCV中计算投影矩阵的函数cv2.getPerspectiveTransform(src_points, dst_points)算出src_points到dst_points的投影矩阵和投影变换后的图像了。

    使用OpenCV库实现透视变换的代码如下:

    #################################################################
    # Step 3 : Warp image based on src_points and dst_points
    #################################################################
    # The type of src_points & dst_points should be like
    # np.float32([ [0,0], [100,200], [200, 300], [300,400]])
    def warpImage(image, src_points, dst_points):
        image_size = (image.shape[1], image.shape[0])
        # rows = img.shape[0] 720
        # cols = img.shape[1] 1280
        M = cv2.getPerspectiveTransform(src, dst)
        Minv = cv2.getPerspectiveTransform(dst, src)
        warped_image = cv2.warpPerspective(image, M,image_size, flags=cv2.INTER_LINEAR)
        
        return warped_image, M, Minv

     

    同理,对于畸变修正过的道路图像,我们同样使用相同的方法,将我们感兴趣的区域做透视变换。

    如下图所示,我们选用一张在直线道路上行驶的图像,沿着左右车道线的边缘,选择一个梯形区域,这个区域在真实的道路中应该是一个长方形,因此我们选择将这个梯形区域投影成为一个长方形,在右图横坐标的合适位置设置长方形的4个端点。最终的投影结果就像“鸟瞰图”一样。

    使用以下代码,通过不断调整src和dst的值,确保在直线道路上,能够调试出满意的透视变换图像。

    test_distort_image = cv2.imread('test_images/test4.jpg')
    
    # 畸变修正
    test_undistort_image = undistortImage(test_distort_image, mtx, dist)
    
    # 左图梯形区域的四个端点
    src = np.float32([[580, 460], [700, 460], [1096, 720], [200, 720]])
    # 右图矩形区域的四个端点
    dst = np.float32([[300, 0], [950, 0], [950, 720], [300, 720])
    
    test_warp_image, M, Minv = warpImage(test_undistort_image, src, dst)

    最终,我们把筛选出的6幅图统一应用调整好的src、dst做透视变换。有点费力啊老兄!

    提取车道线

    在《无人驾驶技术入门(十四)| 初识图像之初级车道线检测》中,我们介绍了通过Canny边缘提取算法获取车道线待选点的方法,随后使用霍夫直线变换进行了车道线的检测。在这里,我们也尝试使用边缘提取的方法进行车道线提取。

    需要注意的是,Canny边缘提取算法会将图像中各个方向、明暗交替位置的边缘都提取出来,很明显,Canny边缘提取算法在处理有树木阴影的道路时,会将树木影子的轮廓也提取出来,这是我们不愿意看到的。

    因此我们选用Sobel边缘提取算法。Sobel相比于Canny的优秀之处在于,它可以选择横向或纵向的边缘进行提取。从投影变换后的图像可以看出,我们关心的正是车道线在横向上的边缘突变。

    封装一下OpenCV提供的cv2.Sobel()函数,将进行边缘提取后的图像做二进制图的转化,即提取到边缘的像素点显示为白色(值为1),未提取到边缘的像素点显示为黑色(值为0)。

    def absSobelThreshold(img, orient='x', thresh_min=30, thresh_max=100):
        # Convert to grayscale
        gray = cv2.cvtColor(img, cv2.COLOR_RGB2GRAY)
        # Apply x or y gradient with the OpenCV Sobel() function
        # and take the absolute value
        if orient == 'x':
            abs_sobel = np.absolute(cv2.Sobel(gray, cv2.CV_64F, 1, 0))
        if orient == 'y':
            abs_sobel = np.absolute(cv2.Sobel(gray, cv2.CV_64F, 0, 1))
        # Rescale back to 8 bit integer
        scaled_sobel = np.uint8(255*abs_sobel/np.max(abs_sobel))
        # Create a copy and apply the threshold
        binary_output = np.zeros_like(scaled_sobel)
        # Here I'm using inclusive (>=, <=) thresholds, but exclusive is ok too
        binary_output[(scaled_sobel >= thresh_min) & (scaled_sobel <= thresh_max)] = 1
    
        # Return the result
        return binary_output

    横向的Sobel边缘提取算法,无法很好地处理路面阴影、明暗交替的道路工况。

    无法使用边缘提取的方法提取车道线后,我们开始将颜色空间作为突破口。

    在以上6个场景中,虽然路面明暗交替,而且偶尔会有阴影覆盖,但黄色白色的车道线是一直都存在的。因此,我们如果能将图中的黄色和白色分割出来,然后将两种颜色组合在一幅图上,就能够得到一个比较好的处理结果。

    一幅图像除了用RGB(红绿蓝)三个颜色通道表示以外,还可以使用HSL(H色相、S饱和度、L亮度)和Lab(L亮度、a红绿通道、b蓝黄)模型来描述图像,三通道的值与实际的成像颜色如下图所示。

     

    我们可以根据HSL模型中的L(亮度)通道来分割出图像中的白色车道线,同时可以根据Lab模型中的b(蓝黄)通道来分割出图像中的黄色车道线,再将两次的分割结果,去合集,叠加到一幅图上,就能得到两条完整的车道线了。

    使用OpenCV提供的cv2.cvtColor()接口,将RGB通道的图,转为HLS通道的图,随后对L通道进行分割处理,提取图像中白色的车道线。

    使用OpenCV提供的cv2.cvtColor()接口,将RGB通道的图,转为Lab通道的图,随后对b通道进行分割处理,提取图像中黄色的车道线。

    根据以上试验可知,L通道能够较好地分割出图像中的白色车道线,b通道能够较好地分割出图像中的黄色车道线。即使面对树木阴影和路面颜色突变的场景,也能尽可能少地引入噪声。

    最后,我们使用以下代码,将两个通道分割的图像合并。

    hlsL_binary = hlsLSelect(test_warp_image)
    labB_binary = labBSelect(test_warp_image)
    combined_binary = np.zeros_like(hlsL_binary)
    combined_binary[(hlsL_binary == 1) | (labB_binary == 1)] = 1

    以上仅仅是车道线提取的方法之一。除了可以通过HSL和Lab颜色通道,这种基于规则的方法,分割出车道线外,还可以使用基于深度学习的方法。它们目的都是为了能够稳定地将车道线从图像中分割出来。

    车道线检测

    在检测车道线前,需要粗定位车道线的位置。为了方便理解,这里引入一个概念——直方图。

    以下面这幅包含噪点的图像为例,进行直方图的介绍

     

    我们知道,我们处理的图像的分辨率为1280*720,即720行,1280列。如果我将每一列的白色的点数量进行统计,即可得到1280个值。将这1280个值绘制在一个坐标系中,横坐标为1-1280,纵坐标表示每列中白色点的数量,那么这幅图就是“直方图”,如下图所示:

    图片出处:优达学城(Udacity)无人驾驶工程师学位

    将两幅图叠加,效果如下:

    图片出处:优达学城(Udacity)无人驾驶工程师学位

    找到直方图左半边最大值所对应的列数,即为左车道线所在的大致位置;找到直方图右半边最大值所对应的列数,即为右车道线所在的大致位置。


    使用直方图找左右车道线大致位置的代码如下,其中leftx_base和rightx_base即为左右车道线所在列的大致位置。

    跟踪车道线

    视频数据是连续的图片,基于连续两帧图像中的车道线不会突变的先验知识,我们可以使用上一帧检测到的车道线结果,作为下一帧图像处理的输入,搜索上一帧车道线检测结果附近的点,这样不仅可以减少计算量,而且得到的车道线结果也更稳定,如下图所示。

    图中的细黄线为上一帧检测到的车道线结果,绿色阴影区域为细黄线横向扩展的一个区域,通过搜索该区域内的白点坐标,即可快速确定当前帧中左右车道线的待选点。

    最终视频处理如下:

    nx = 9 
    ny = 6
    ret, mtx, dist, rvecs, tvecs = getCameraCalibrationCoefficients('camera_cal/calibration*.jpg', nx, ny)
    
    src = np.float32([[580, 460], [700, 460], [1096, 720], [200, 720]])
    dst = np.float32([[300, 0], [950, 0], [950, 720], [300, 720]])
    
    video_input = 'project_video.mp4'
    video_output = 'result_video.mp4'
    
    cap = cv2.VideoCapture(video_input)
    
    fourcc = cv2.VideoWriter_fourcc(*'XVID')
    out = cv2.VideoWriter(video_output, fourcc, 20.0, (1280, 720))
    
    detected = False
    
    while(True):
        ret, image = cap.read()
        if ret:
            undistort_image = undistortImage(image, mtx, dist)
            warp_image, M, Minv = warpImage(undistort_image, src, dst)
            hlsL_binary = hlsLSelect(warp_image)
            labB_binary = labBSelect(warp_image, (205, 255))
            combined_binary = np.zeros_like(sx_binary)
            combined_binary[(hlsL_binary == 1) | (labB_binary == 0)] = 1
            left_fit = []
            right_fit = []
            ploty = []
            if detected == False:
                out_img, left_fit, right_fit, ploty = fit_polynomial(combined_binary, nwindows=9, margin=80, minpix=40)
                if (len(left_fit) > 0 & len(right_fit) > 0) :
                    detected = True
                else :
                    detected = False
            else:
                track_result, left_fit, right_fit, ploty,  = search_around_poly(combined_binary, left_fit, right_fit)
                if (len(left_fit) > 0 & len(right_fit) > 0) :
                    detected = True
                else :
                    detected = False
            
            result = drawing(undistort_image, combined_binary, warp_image, left_fitx, right_fitx)
            
            out.write(result)
        else:
            break
            
    cap.release()
    out.release()

     

    不过每个相机的畸变参数需要重新标定,不是换个视频就能直接使用这套代码里面的参数,需要调整。效果不会差,因为中国的高速路况比这个视频里的路况要好。

     

    注:上述大部分参考知乎大神陈光的文章,如果有转载请注明出处,侵删!!

     

    其他

    车辆检测功能可参考这里

    更多资料详见这里:车道线检测最全资料集锦

    还有这里:基于摄像头的车道线检测方法一览

     

    参考文献:

    1.https://blog.csdn.net/weixin_38746685/article/details/81613065

    2.https://blog.csdn.net/xxxy502/article/details/81018106

    3.https://blog.csdn.net/xxxy502/article/details/81018859

    4.https://zhuanlan.zhihu.com/p/54866418

    5.https://zhuanlan.zhihu.com/c_147309339

    6.https://github.com/yang1688899/CarND-Advanced-Lane-Lines

    7.https://wenku.baidu.com/view/69dbf2a0f61fb7360b4c6577.html

     

    展开全文
  • 本课所讲的车道线检测来自于一个实际的项目,因为场景比较单一,而且硬件比较受限制,所以采用传统的图像处理方法来识别它们。 源图像 需求:要求识别出 黄色线道里外两条线, 一共有两根黄色车道线。 挑战性:...

    前言

    本课所讲的车道线检测来自于一个实际的项目,因为场景比较单一,而且硬件比较受限制,所以采用传统的图像处理方法来识别它们。

    源图像

    需求:要求识别出 黄色线道里外两条线, 一共有两根黄色车道线。

    挑战性:黄色车道线有些模糊不清,一定程度上和其周围背景区分不是很明显;由于摄像头安装位置不是正中间,所以拍出来的黄色车道线有些扭曲畸变,不是严格的直线,而是有些向左弯曲的直线。

    识别过程

    一)由于场景比较单一,所以先做一个roi,来尽量排除背景的干扰。同时在roi各取4个点(蓝色圆圈和红色圆圈表示),并求得蓝色点变换到红色点的投影矩阵。

     二)根据投影矩阵,将roi图像做一个透视变换,可理解为将倾斜视角拍的图片转换为鸟瞰图。鸟瞰图上黄色线道近似垂直线,从而可以大大提供识别准确度和鲁棒性,而且背景干扰部分进一步排除掉。

    三) 综合采用前面所学到的图像处理知识:高斯模糊,自己构造卷积模板来求黄色线道边缘;根据颜色特征区分开黄色车道和背景;最后采用腐蚀形态学方法去噪点得到比较理想的边缘点。这部分的具体内容会在视频课程中详细讲解。

    四)采用HoughLinesp来找直线,并且要求斜率近似于垂直。将多根挨在一起的直线上点进行直线拟合,得到四条最终的直线方程,并画在鸟瞰图上。

    五)最后一步就是将上面的图进行透视逆变换,并作平移就能在原始图上画出黄色车道线了。

    结论

    本课所涉及新的理论知识点比较少,侧重于实践,主要是综合应用前面所讲的图像处理方法来进行车道线识别。当然,如果场景比较复杂 比如光线忽亮忽暗等,还可以考虑语义分割模型,比如mask-rcnn deeplabv3等来进行识别。在识别推理前,需要收集大量的样本数据,并进行标注和训练才能得到算法模型。此外跑这种复杂的模型,对硬件要求比较高,一般需要GPU才能达到推理时间在毫秒级。

    展开全文
  • 目标:实际公路的车道线检测 素材中车道保持不变,车道线清晰明确,易于检测,是车道检测的基础版本,网上也有很多针对复杂场景的高级实现,感兴趣的朋友可以自行了解。 如果我们手动把这部分ROI区域抠出来,就会...

    目标:实际公路的车道线检测

    道路图像
    素材中车道保持不变,车道线清晰明确,易于检测,是车道检测的基础版本,网上也有很多针对复杂场景的高级实现,感兴趣的朋友可以自行了解。
    车道线位置基本固定在虚线框内
    如果我们手动把这部分ROI区域抠出来,就会排除掉大部分干扰。接下来检测直线肯定是用霍夫变换,但ROI区域内的边缘直线信息还是很多,考虑到只有左右两条车道线,一条斜率为正,一条为负,可将所有的线分为两组,每组再通过均值或最小二乘法拟合的方式确定唯一一条线就可以完成检测。总体步骤如下:
    1. 灰度化
    2. 高斯模糊
    3. Canny边缘检测
    4. 不规则ROI区域截取
    5. 霍夫直线检测
    6. 车道计算
    7. 对于视频来说,只要一幅图能检查出来,合成下就可以了,问题不大。

    图像预处理

    灰度化和滤波操作是大部分图像处理的必要步骤。灰度化不必多说,因为不是基于色彩信息识别的任务,所以没有必要用彩色图,可以大大减少计算量。而滤波会削弱图像噪点,排除干扰信息。另外,根据前面学习的知识,边缘提取是基于图像梯度的,梯度对噪声很敏感,所以平滑滤波操作必不可少。
    原图 vs 灰度滤波图
    这次的代码我们分模块来写,规范一点。其中process_an_image()是主要的图像处理流程:

    import cv2 as cv
    import numpy as np
    
    # 高斯滤波核大小
    blur_ksize = 5
    # Canny边缘检测高低阈值
    canny_lth = 50
    canny_hth = 150
    
    def process_an_image(img):
        # 1. 灰度化、滤波和Canny
        gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
        blur_gray = cv.GaussianBlur(gray, (blur_ksize, blur_ksize), 1)
        edges = cv.Canny(blur_gray, canny_lth, canny_hth)
    
    if __name__ == "__main__":
        img = cv.imread('test_pictures/lane.jpg')
        result = process_an_image(img)
        cv.imshow("lane", np.hstack((img, result)))
        cv.waitKey(0)
    

    边缘检测结果图

    ROI获取

    按照前面描述的方案,只需保留边缘图中的红线部分区域用于后续的霍夫直线检测,其余都是无用的信息:
    过滤掉红框以外的信息
    如何实现呢?我们可以创建一个梯形的mask掩膜,然后与边缘检测结果图混合运算,掩膜中白色的部分保留,黑色的部分舍弃。梯形的四个坐标需要手动标记:
    掩膜mask

    def process_an_image(img):
        # 1. 灰度化、滤波和Canny
    
        # 2. 标记四个坐标点用于ROI截取
        rows, cols = edges.shape
        points = np.array([[(0, rows), (460, 325), (520, 325), (cols, rows)]])
        # [[[0 540], [460 325], [520 325], [960 540]]]
        roi_edges = roi_mask(edges, points)
        
    def roi_mask(img, corner_points):
        # 创建掩膜
        mask = np.zeros_like(img)
        cv.fillPoly(mask, corner_points, 255)
    
        masked_img = cv.bitwise_and(img, mask)
        return masked_img
    

    这样,结果图”roi_edges”应该是:
    只保留关键区域的边缘检测图

    霍夫直线提取

    为了方便后续计算直线的斜率,我们使用统计概率霍夫直线变换(因为它能直接得到直线的起点和终点坐标)。霍夫变换的参数比较多,可以放在代码开头,便于修改:

    # 霍夫变换参数
    rho = 1
    theta = np.pi / 180
    threshold = 15
    min_line_len = 40
    max_line_gap = 20
    
    def process_an_image(img):
        # 1. 灰度化、滤波和Canny
    
        # 2. 标记四个坐标点用于ROI截取
    
        # 3. 霍夫直线提取
        drawing, lines = hough_lines(roi_edges, rho, theta, threshold, min_line_len, max_line_gap)
    
    def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
        # 统计概率霍夫直线变换
        lines = cv.HoughLinesP(img, rho, theta, threshold, minLineLength=min_line_len, maxLineGap=max_line_gap)
    
        # 新建一副空白画布
        drawing = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
        # draw_lines(drawing, lines)     # 画出直线检测结果
    
        return drawing, lines
    
    def draw_lines(img, lines, color=[0, 0, 255], thickness=1):
        for line in lines:
            for x1, y1, x2, y2 in line:
                cv.line(img, (x1, y1), (x2, y2), color, thickness)
    

    draw_lines()是用来画直线检测的结果,后面我们会接着处理直线,所以这里注释掉了,可以取消注释看下效果:
    霍夫直线检测结果图
    对本例的这张测试图来说,如果打印出直线的条数print(len(lines)),应该是有15条。

    车道计算

    这部分应该算是本实验的核心内容了:前面通过霍夫变换得到了多条直线的起点和终点,我们的目的是通过某种算法只得到左右两条车道线。
    第一步:根据斜率正负划分某条线是左车道还是右车道。
    斜率计算公式
    再次强调,斜率计算是在图像坐标系下,所以斜率正负/左右跟平面坐标有区别。
    第二步、迭代计算各直线斜率与斜率均值的差,排除掉差值过大的异常数据。

    注意这里迭代的含义,意思是第一次计算完斜率均值并排除掉异常值后,再在剩余的斜率中取均值,继续排除……这样迭代下去。

    第三步、最小二乘法拟合左右车道线。

    经过第二步的筛选,就只剩下可能的左右车道线了,这样只需从多条直线中拟合出一条就行。拟合方法有很多种,最常用的便是最小二乘法,它通过最小化误差的平方和来寻找数据的最佳匹配函数。

    具体来说,假设目前可能的左车道线有6条,也就是12个坐标点,包括12个x和12个y,我们的目的是拟合出这样一条直线:
    直线公式
    使得误差平方和最小:
    误差平方和公式
    Python中可以直接使用np.polyfit()进行最小二乘法拟合。

    def process_an_image(img):
        # 1. 灰度化、滤波和Canny
    
        # 2. 标记四个坐标点用于ROI截取
    
        # 3. 霍夫直线提取
    
        # 4. 车道拟合计算
        draw_lanes(drawing, lines)
    
        # 5. 最终将结果合在原图上
        result = cv.addWeighted(img, 0.9, drawing, 0.2, 0)
    
        return result
    
    def draw_lanes(img, lines, color=[255, 0, 0], thickness=8):
        # a. 划分左右车道
        left_lines, right_lines = [], []
        for line in lines:
            for x1, y1, x2, y2 in line:
                k = (y2 - y1) / (x2 - x1)
                if k < 0:
                    left_lines.append(line)
                else:
                    right_lines.append(line)
    
        if (len(left_lines) <= 0 or len(right_lines) <= 0):
            return
    
        # b. 清理异常数据
        clean_lines(left_lines, 0.1)
        clean_lines(right_lines, 0.1)
    
        # c. 得到左右车道线点的集合,拟合直线
        left_points = [(x1, y1) for line in left_lines for x1, y1, x2, y2 in line]
        left_points = left_points + [(x2, y2) for line in left_lines for x1, y1, x2, y2 in line]
        right_points = [(x1, y1) for line in right_lines for x1, y1, x2, y2 in line]
        right_points = right_points + [(x2, y2) for line in right_lines for x1, y1, x2, y2 in line]
    
        left_results = least_squares_fit(left_points, 325, img.shape[0])
        right_results = least_squares_fit(right_points, 325, img.shape[0])
    
        # 注意这里点的顺序,从左下角开始按照顺序构造梯形
        vtxs = np.array([[left_results[1], left_results[0], right_results[0], right_results[1]]])
        # d. 填充车道区域
        cv.fillPoly(img, vtxs, (0, 255, 0))
    
        # 或者只画车道线
        # cv.line(img, left_results[0], left_results[1], (0, 255, 0), thickness)
        # cv.line(img, right_results[0], right_results[1], (0, 255, 0), thickness)
        
    def clean_lines(lines, threshold):
        # 迭代计算斜率均值,排除掉与差值差异较大的数据
        slope = [(y2 - y1) / (x2 - x1) for line in lines for x1, y1, x2, y2 in line]
        while len(lines) > 0:
            mean = np.mean(slope)
            diff = [abs(s - mean) for s in slope]
            idx = np.argmax(diff)
            if diff[idx] > threshold:
                slope.pop(idx)
                lines.pop(idx)
            else:
                break
                
    def least_squares_fit(point_list, ymin, ymax):
        # 最小二乘法拟合
        x = [p[0] for p in point_list]
        y = [p[1] for p in point_list]
    
        # polyfit第三个参数为拟合多项式的阶数,所以1代表线性
        fit = np.polyfit(y, x, 1)
        fit_fn = np.poly1d(fit)  # 获取拟合的结果
    
        xmin = int(fit_fn(ymin))
        xmax = int(fit_fn(ymax))
    
        return [(xmin, ymin), (xmax, ymax)]
    

    这段代码比较多,请每个步骤单独来看。最后得到的是左右两条车道线的起点和终点坐标,可以选择画出车道线,这里我直接填充了整个区域:
    填充车道线结果
    搞定了一张图,视频也就没什么问题了,关键就是视频帧的提取和合成,为此,我们要用到Python的视频编辑包moviepy:
    pip install moviepy
    另外还需要ffmpeg,首次运行moviepy时会自动下载,也可手动下载。

    只需在开头导入moviepy,然后将主函数改掉就可以了,其余代码不需要更改:

    # 开头导入moviepy
    from moviepy.editor import VideoFileClip
    
    # 主函数更改为:
    if __name__ == "__main__":
        output = 'test_videos/output.mp4'
        clip = VideoFileClip("test_videos/cv2_white_lane.mp4")
        out_clip = clip.fl_image(process_an_image)
        out_clip.write_videofile(output, audio=False)
    

    本文实现了车道检测的基础版本,如果你感兴趣的话,可以自行搜索了解更多。

    实现功能完整代码

    import cv2 as cv
    import numpy as np
    
    # 高斯滤波核大小
    blur_ksize = 5
    
    # Canny边缘检测高低阈值
    canny_lth = 50
    canny_hth = 150
    
    # 霍夫变换参数
    rho = 1
    theta = np.pi / 180
    threshold = 15
    min_line_len = 40
    max_line_gap = 20
    
    
    def process_an_image(img):
        # 1. 灰度化、滤波和Canny
        gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
        blur_gray = cv.GaussianBlur(gray, (blur_ksize, blur_ksize), 1)
        edges = cv.Canny(blur_gray, canny_lth, canny_hth)
    
        # 2. 标记四个坐标点用于ROI截取
        rows, cols = edges.shape
        points = np.array([[(0, rows), (460, 325), (520, 325), (cols, rows)]])
        # [[[0 540], [460 325], [520 325], [960 540]]]
        roi_edges = roi_mask(edges, points)
    
        # 3. 霍夫直线提取
        drawing, lines = hough_lines(roi_edges, rho, theta,
                                     threshold, min_line_len, max_line_gap)
    
        # 4. 车道拟合计算
        draw_lanes(drawing, lines)
    
        # 5. 最终将结果合在原图上
        result = cv.addWeighted(img, 0.9, drawing, 0.2, 0)
    
        return result
    
    
    def roi_mask(img, corner_points):
        # 创建掩膜
        mask = np.zeros_like(img)
        cv.fillPoly(mask, corner_points, 255)
    
        masked_img = cv.bitwise_and(img, mask)
        return masked_img
    
    
    def hough_lines(img, rho, theta, threshold, min_line_len, max_line_gap):
        # 统计概率霍夫直线变换
        lines = cv.HoughLinesP(img, rho, theta, threshold,
                                minLineLength=min_line_len, maxLineGap=max_line_gap)
    
        # 新建一副空白画布
        drawing = np.zeros((img.shape[0], img.shape[1], 3), dtype=np.uint8)
        # 画出直线检测结果
        # draw_lines(drawing, lines)
        # print(len(lines))
    
        return drawing, lines
    
    
    def draw_lines(img, lines, color=[0, 0, 255], thickness=1):
        for line in lines:
            for x1, y1, x2, y2 in line:
                cv.line(img, (x1, y1), (x2, y2), color, thickness)
    
    
    def draw_lanes(img, lines, color=[255, 0, 0], thickness=8):
        # a. 划分左右车道
        left_lines, right_lines = [], []
        for line in lines:
            for x1, y1, x2, y2 in line:
                k = (y2 - y1) / (x2 - x1)
                if k < 0:
                    left_lines.append(line)
                else:
                    right_lines.append(line)
    
        if (len(left_lines) <= 0 or len(right_lines) <= 0):
            return
    
        # b. 清理异常数据
        clean_lines(left_lines, 0.1)
        clean_lines(right_lines, 0.1)
    
        # c. 得到左右车道线点的集合,拟合直线
        left_points = [(x1, y1) for line in left_lines for x1, y1, x2, y2 in line]
        left_points = left_points + [(x2, y2)
                                     for line in left_lines for x1, y1, x2, y2 in line]
    
        right_points = [(x1, y1)
                        for line in right_lines for x1, y1, x2, y2 in line]
        right_points = right_points + \
            [(x2, y2) for line in right_lines for x1, y1, x2, y2 in line]
    
        left_results = least_squares_fit(left_points, 325, img.shape[0])
        right_results = least_squares_fit(right_points, 325, img.shape[0])
    
        # 注意这里点的顺序
        vtxs = np.array(
            [[left_results[1], left_results[0], right_results[0], right_results[1]]])
        # d.填充车道区域
        cv.fillPoly(img, vtxs, (0, 255, 0))
    
        # 或者只画车道线
        # cv.line(img, left_results[0], left_results[1], (0, 255, 0), thickness)
        # cv.line(img, right_results[0], right_results[1], (0, 255, 0), thickness)
    
    
    def clean_lines(lines, threshold):
        # 迭代计算斜率均值,排除掉与差值差异较大的数据
        slope = [(y2 - y1) / (x2 - x1)
                 for line in lines for x1, y1, x2, y2 in line]
        while len(lines) > 0:
            mean = np.mean(slope)
            diff = [abs(s - mean) for s in slope]
            idx = np.argmax(diff)
            if diff[idx] > threshold:
                slope.pop(idx)
                lines.pop(idx)
            else:
                break
    
    
    def least_squares_fit(point_list, ymin, ymax):
        # 最小二乘法拟合
        x = [p[0] for p in point_list]
        y = [p[1] for p in point_list]
    
        # polyfit第三个参数为拟合多项式的阶数,所以1代表线性
        fit = np.polyfit(y, x, 1)
        fit_fn = np.poly1d(fit)  # 获取拟合的结果
    
        xmin = int(fit_fn(ymin))
        xmax = int(fit_fn(ymax))
    
        return [(xmin, ymin), (xmax, ymax)]
    
    
    if __name__ == "__main__":
        img = cv.imread('test_pictures/lane2.jpg')
        result = process_an_image(img)
        cv.imshow("lane", np.hstack((img, result)))
        cv.waitKey(0)
    

    左图:原图;右图:车道检测结果

    展开全文
  • 图像分割--车道线识别

    千次阅读 2018-09-24 11:29:41
    本文将简单介绍车道线检测的基本技术,将在python平台上使用opencv库进行测试。 二、车道线识别主要原理  本车道线识别的算法流程图如图1所示,本部分后面将对其各部分原理进行简要说明。  ...
  • opencv车道线检测

    万次阅读 多人点赞 2016-03-28 16:51:36
    车道线检测,需要完成以下功能: 图像裁剪:通过设定图像ROI区域,拷贝图像获得裁剪图像 反透视变换:用的是室外采集到的视频,没有对应的变换矩阵。所以建立二维坐标,通过四点映射的方法计算矩阵,进行反透视变化...
  • 车道线检测学习总结

    万次阅读 多人点赞 2018-09-14 11:31:38
    之前学习了一个非常酷炫的车道线检测项目:Advanced Lane Finding Project  现在写一个学习笔记,备注一下,方便以后复习:  项目总体流程如下: 第一步,采用棋盘对相机进行校正: 第二步,对图像进行...
  • 这个一个python实现的车道线识别程序,基于opencv库。压缩包内还附了测试用的图像和视频,适合进行python和图像处理学习。
  • matlab车道线检测

    2020-07-30 23:32:53
    车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测车道线检测...
  • 基于视觉的车道线检测完整代码

    热门讨论 2020-08-25 15:47:35
    基于视觉的曲线车道线检测完整代码,采用滑动窗口,详情见博客:http://blog.csdn.net/adamshan/article/details/78733302
  • 车道线检测paper

    2020-07-26 23:32:30
    车道线检测论文-paper.希望对这方面研究的相关人员有一定的帮助。
  • matlab 车道线检测

    热门讨论 2020-07-29 14:20:37
    matlab 车道线检测 基于改进的hough变换,区域生长法
  • 2.1、基于Hough变换的车道线检测 1)假设 在工程上(实际使用中或者demo中) 存在一些约束:前方存在左右两个车道线,并与当前正前方存在一定角度 2)主要思路 读取图像并选择感兴趣的区域ROI进行下一步处理  ...
  • awesome-lane-detection是Amusi 整理...据笔者了解,车道线检测解决方案主要分为传统图像处理和深度学习两种。看起来每个领域都可以这么说,但车道线检测与其它研究方向不太一样。因为检测的目标可能是直线也可能是...
  • 第七十篇:从ADAS到自动驾驶(三):车道检测

    万次阅读 多人点赞 2017-04-19 09:26:20
     车道线检测是我最先接触并自己实践了一把的ADAS算法,在研一入学的时候就完成了车道线的检测,车道线的检测对于无人驾驶的路径规划来讲特别重要,自动驾驶不仅要求能够避障和道路交通信息感知,还需要满足遵守交通...
  • 简介 无人车感知中的一个子问题,即车辆在道路上行驶过程中需要正确识别到车道线,并沿着车道线方向行驶。通常会给出车辆在车道线中所处的偏移量以及车道线自身的曲率属性。...传统车道线检测(二...
  • 第三类 图像特征分析和检测课程项目的设计和实现.基于霍夫变换的图像直线段检测——以车道线检测为例
  • 车道线检测最全资料集锦

    千次阅读 2018-12-27 15:46:57
    Summary:GitHub:车道线检测最全资料集锦 Author:Amusi Date:2018-12-27 微信公众号:CVer github:amusi/awesome-lane-detection 原文链接:GitHub:车道线检测最全资料集锦 知乎:...
  • 作者:liaojiacai 感谢作者!... 车道线检测是我最先接触并自己实践了一把的ADAS算法,车道线的检测对于无人驾驶的路径规划来讲特别重要,自动驾驶不仅要求能够避障和道路交通信息感知,还需要满足
  • 基于OPENCV的车道线检测,可以用来识别车道线。适合视觉开发及ADAS开发参考。
1 2 3 4 5 ... 20
收藏数 3,522
精华内容 1,408
关键字:

图像处理车道线检测