车道线检测-方法1

在汽车自动驾驶过程中,你需要汽车能够对周围的世界进行很好的感知。我们人类通过使用眼睛来了解汽车跑的有多快,汽车的车道线在哪,明白在哪里可以进行转弯,汽车通过摄像头,毫米波雷达,mio等其它的传感器来帮助我们实现人眼的功能。
为了让车道线检测的结果能够上车,我并没有上神经网络,而是采用特征检测方法进行测量。在阅读相关论文的方法后,我按照以下的步骤进行实验:

  • 视频图片预处理。设置图片的ROI区域,对图片进行灰度转换。
  • 图片边缘处理。切割ROI为若干个子图,二值化后,计算子图的连通域。
  • 霍夫变换,计算图片灭点。
  • 直线特征筛选,ransac 直线拟合。
  • 绘制车道线。
视频图片预处理
frame.jpg

我们知道,车道线的特征很固定,它一定在地上,而不会跑到天上去。因此,在检测的第一步,我就把图片进行了裁剪,减少了天空,树等干扰,也减少了处理图片的时间。经裁剪后的ROI ,就要转换为灰度图。其实,看了许多论文后,发现在这个步骤前,还有的对图片进行色彩空间的转换,增强黄色和白色的特征(车道线为白色或者黄色)。我在灰度转换的时候,也没有直接使用opencv的转灰度图的api, 而是提取B,G,R 三个通道值,计算I = R+G-B。读者可以实践两种转灰度的方法,会发现后者得到的灰度图更加清晰。

cv::Mat img2gray(cv::Mat img){
    int height= img.rows;
    int width = img.cols;
    cv::Mat gray  (height, width, CV_8U,cv::Scalar(0,0,0));
    for (int i=0;i< height;i++) {
        for (int j = 0; j < width; j++) {
            int B = img.at<cv::Vec3b>(i, j)[0];
            int G = img.at<cv::Vec3b>(i, j)[1];
            int R = img.at<cv::Vec3b>(i, j)[2];
            int I = R + G -B;

            if (I > 255)
                I = 255;
            gray.at<uint8_t >(i, j) = I;
        }
    }
    return  gray;
}
图片的边缘处理

一提到车道线的边缘处理,一定会条件反射想到canny 边缘检测。如果只是单纯进行canny检测,那么检测结果不会太好。canny很依赖原始图片的质量,如果原来的图片车道线很分明,天气状况良好,那么canny的效果会好,但是如果图片质量不太好,那么干扰的点就会很多,这会直接影响霍夫变换的结果。因此我在canny之前对图像做了一个分割处理,对每个子图二值化,canny检测后,根据车道线的特征做连通域计算,最后将子图还原原图。


边缘化.png

可以看出,经过这样的处理后,车道线就很清晰的呈现出来了。

霍夫变换,灭点计算

在得到上述的二值化图后,紧接着就是上opencv概率霍夫变换,检测直线。调用opencv很简单,但是注意一些参数的设定就好。

直线特征筛选,ransac 直线拟合。

并不是所有霍夫变换得到的直线都是我们的候选线 ,我们有一些筛选条件。最直接的条件是,不经过灭点的直线,一定不是车道线。所以在,霍夫变换之后,我们需要计算图片的灭点。车道线有方向角的信息,也可以通过方向角进一步筛选。由于我在图像边缘计算的时候,已经在连通域计算时候考虑车道方向角,所以这里就不再计算了。

对于候选的直线,我采用ransac算法进行拟合。如果对ransac不是很了解的,可以先看看算法的描述,我这里直接上代码了。

void ransac (Mat X ,Mat Y,std::vector<Vec2d > &result ){
    int Size = X.rows;
    int iters = 400;
    double sigma = 0.25;
    double best_k = 0.0;
    double best_b = 0.0;
    double k,b;

    int pretotal = 2;
    double P = 0.90;
    int pos1,pos2;
    int total_inlier;
    default_random_engine e;
    uniform_int_distribution<unsigned> u(0, Size);
    int i;
    for (i=0;i<iters;i++){
            pos1= u(e);
            pos2 =u(e);
            int * x_1 = X.ptr<int>(pos1);
            int * x_2 = X.ptr<int>(pos2);
            int * y_1 = Y.ptr<int>(pos1);
            int * y_2 = Y.ptr<int>(pos2);

            k = (*(y_2) - *(y_1)) / (*(x_2) - *(x_1) + 0.0000001);
            b = *(y_1) - k * *(x_1);

            total_inlier= 0;
            for (int j=0;j<Size;j++){
                double  y_estimate = k * *(X.ptr<int>(j)) +b;
                if (fabs(y_estimate-*(Y.ptr<int>(j)))<sigma){
                     total_inlier+= 1;
                }
            }

            if (total_inlier>pretotal){
                iters = log(1-P)/log( 1-pow(total_inlier/(Size+0.0000001) ,2.0 ));
                pretotal = total_inlier;
                best_k = k;
                best_b = b;
            }

            if (total_inlier > (Size/2)){
                    break;
            }
    }

    result.push_back(Vec2d(best_k,best_b));
}

绘制直线

最重要的检测部分已经完成了,那么绘制车道线就很轻松了,通过简单的几行代码,就可以把我们的直线绘制出来了。

void draw_lane(std::vector<cv::Vec4i>coordinate)
{
      std::cout << coordinate.size()<< std::endl;
      for (auto it = coordinate.begin();it!= coordinate.end();it ++){
         line(img, cv::Point((*it)[0], (*it)[1]), cv::Point((*it)[2], (*it)[3]),       cv::Scalar(0,255,0), 3);
    }
     imshow("lane ",img);
       cv::waitKey(1)
}

下图就是最后的检测结果啦~。其中,红色圆点表示灭点位置。可看出,车道线已经比较好的拟合出来,但是左右两边还是有杂线,这就需要进一步筛选过滤了。

lane.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容