opencv识别车道线(霍夫线变换)

Posted weixin_45001971

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了opencv识别车道线(霍夫线变换)相关的知识,希望对你有一定的参考价值。

目录


1、前言

最近学习opencv学到了霍夫线变换,霍夫线变换是一个查找图像中直线的算法,它的其中一种应用场景就是识别车道,本文以识别车道为例,介绍霍夫线的简单用法。

2、霍夫线变换

2.1、霍夫线变换是什么?

下面是chatGPT给出的说明:

霍夫线变换(Hough Line Transform)是一种图像处理技术,可以用于检测图像中的直线。它的基本思想是,将直线转换为参数空间,并在参数空间中寻找与图像中的边缘相对应的点,从而找到这些直线。霍夫线变换常用于计算机视觉领域,例如在车道线检测、图像拼接、人脸识别等方面应用广泛。

原理性的东西这里不讲,因为有点复杂,我看得也有点懵。

2.2、在opencv中的基本用法

2.2.1、HoughLinesP函数定义

opencv实现霍夫线变换的函数是HoughLinesP,它的定义如下。

void HoughLinesP( InputArray image, OutputArray lines,
                  double rho, double theta, int threshold,
                  double minLineLength = 0, double maxLineGap = 0 );

它的参数的含义如下:
image:8位、单通道二进制源图像。
lines:输出线的矢量。每条线由一个4元素矢量表示,可以传入vector< cv::Vec4i>类型。
控制精度:
rho:累加器的距离分辨率(以像素为单位)。
theta:累加器的角度分辨率(弧度)。
过滤:
threshold:累加器阈值参数。
minLineLength:最小行长度。小于该长度的线段将被拒绝。
maxLineGap:同一条线上链接点的最大允许间隙。

2.2.2、用法

因为HoughLinesP传入的图像必须是8位、单通道二进制源图像,所以在传入图像之前,需要做转灰度图-》转二进制图的操作。
opencv提供了一些转二进制图的方法,因为HoughLinesP的目的是找到直线,而直线其实也是轮廓的一部分,所以一般我们采用Canny算法来把灰度图转为二进制图。
例程:

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace cv;

int main() 
    Mat src = imread("road.png");
    imshow("src", src);
    
    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    imshow("gray", gray);

    // Apply Canny edge detection
    Mat edges;
    Canny(gray, edges, 50, 150);
    imshow("canny", edges);

    // Perform Hough transform to find lines
    std::vector<Vec4i> lines;
    HoughLinesP(gray, lines, 1, CV_PI / 180, 50, 50, 10);

    // Draw lines on output image
    Mat dst = src.clone();
    for (size_t i = 0; i < lines.size(); i++) 
        Vec4i vline = lines[i];
        line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
    
    imshow("dst", dst);

    waitKey(0);


3、识别车道

首先准备一张图片,如下图所示,要识别出它的白色车道线。

我们直接使用上一节的例程,效果如下。

发现虽然车道是识别出来了,但是环境中的纹理也被误认为车道,所以要做进一步优化。

3.1、优化

3.1.1、降噪

从上面的Canny图可以看到,环境中的树木形成了密密麻麻的纹理,这些就是影响效果的因素之一。
经过测试,我选用了“二值化 - 》腐蚀 - 》膨胀”的方式来完成降噪,经过优化后的代码如下:

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>

using namespace cv;

int main() 
    Mat src = imread("/road.png");
    imshow("src", src);

    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    imshow("gray", gray);

	//二值化
	Mat thr;
    threshold(gray, thr, 100, 255, THRESH_BINARY);
    imshow("threshold", thr);

    // 腐蚀
    Mat eroded;
    Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
    erode(thr, eroded, element);

    // 膨胀
    Mat dilated;
    dilate(eroded, dilated, element);
    imshow("dilated", dilated);

    // Apply Canny edge detection
    Mat edges;
    Canny(dilated, edges, 50, 150);
    imshow("canny", edges);

    // Perform Hough transform to find lines
    std::vector<Vec4i> lines;
    HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);

    // Draw lines on output image
    Mat dst = src.clone();
    for (size_t i = 0; i < lines.size(); i++) 
        Vec4i vline = lines[i];
        line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
    

    imshow("dst", dst);

    waitKey(0);

优化后的效果如下:

从Canny中明显可以看到环境纹理少了很多。

3.1.2、过滤方向

在上图中,可以看到还有一些横向的纹理影响了效果,我们可以通过直线的方向来做进一步过滤。
在车的视角下,车道是朝中间斜的,两边车道成八字型,如图所示。

也就是说,车道的线在图像上倾斜角度不会小,所以我们可以在得出最终结果时,添加一个过滤条件:倾斜角度小于20度的直线不满足条件。
修改代码如下:

.....
    // Perform Hough transform to find lines
    std::vector<Vec4i> lines;
    HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);

    // Draw lines on output image
    Mat dst = src.clone();
    for (size_t i = 0; i < lines.size(); i++) 
        Vec4i vline = lines[i];
		/* 过滤倾斜45度及以下的斜线 */
        float tanVal = (float)(vline[3] - vline[1]) / (vline[2] - vline[0]);
        if (abs(tanVal) < tan(CV_PI / 18)) continue;

        line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
    
.......

效果:

3.1.3、截选区域

在识别车道时,因为车道是在车的脚下,需要识别的图像只有相机拍下的下半截,所以这里还可以加多一层优化:把上半截图像砍掉,只处理下半截图像。
修改代码:

int main() 
    Mat src = imread("road.png");
    Rect vaildRect(0, src.rows / 2, src.cols, src.rows / 2);
    Mat src = src(vaildRect);
    imshow("src", src);
......

效果:

3.2、测试其它图片

3.2.1、代码

经过前面的优化后,得到如下代码:

int main()     
	Mat src = imread("road.png");
    Rect vaildRect(0, src.rows / 2, src.cols, src.rows / 2);
    src = src(vaildRect);
    imshow("src", src);

    Mat gray;
    cvtColor(src, gray, COLOR_BGR2GRAY);
    imshow("gray", gray);

	Mat thr;
    threshold(gray, thr, 150, 255, THRESH_BINARY);
    imshow("threshold", thr);

    // 腐蚀
    Mat eroded;
    Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
    erode(thr, eroded, element);

    // 膨胀
    Mat dilated;
    dilate(eroded, dilated, element);
    imshow("dilated", dilated);

    // Apply Canny edge detection
    Mat edges;
    Canny(dilated, edges, 50, 150);
    imshow("canny", edges);

    // Perform Hough transform to find lines
    std::vector<Vec4i> lines;
    HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);

    // Draw lines on output image
    Mat dst = src.clone();
    for (size_t i = 0; i < lines.size(); i++) 
        Vec4i vline = lines[i];
        float tanVal = (float)(vline[3] - vline[1]) / (vline[2] - vline[0]);
        if (abs(tanVal) < tan(CV_PI / 18)) 
            continue;
        

        line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
    

    imshow("dst", dst);

    waitKey(0);

下面用这份代码测试其它例子。

3.2.2、图片1

因为拍照时的亮度不一,所以需要根据亮度来调整二值化时的阀值,此例用的是
threshold(gray, thr, 170, 255, THRESH_BINARY);

3.2.3、图片2

3.2.4、图片3

threshold(gray, gray, 150, 255, THRESH_BINARY);

OpenCV探索之路:霍夫变换

我们如何在图像中快速识别出其中的圆和直线?一个非常有效的方法就是霍夫变换,它是图像中识别各种几何形状的基本算法之一。

霍夫线变换

霍夫线变换是一种在图像中寻找直线的方法。OpenCV中支持三种霍夫线变换,分别是标准霍夫线变换、多尺度霍夫线变换、累计概率霍夫线变换。

在OpenCV中可以调用函数HoughLines来调用标准霍夫线变换和多尺度霍夫线变换。HoughLinesP函数用于调用累积概率霍夫线变换。

我们都知道,二维坐标轴上表示一条直线的方程式y = a*x + b,我们想求出一条直线就得想方设法求出其中的a和b的值。如果用极坐标来表示就是

theta就是直线与水平线所成的角度,而rho就是圆的半径(也可以理解为原点到直线的距离),同样地,这两个参数也是表征一条直线的重要参数,确定他们俩了,也就确定一条直线了。正如下图所示。

在OpenCV里,我们只需调用HoughLines就是可以得到表征一条直线的这两个参数值!

HoughLines用法

#include <iostream>
#include <opencv2\\opencv.hpp>
#include <opencv2\\imgproc\\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
    Mat srcImage = imread("4.jpg");
    imshow("Src Pic", srcImage);

    Mat midImage, dstImage;
    //边缘检测
    Canny(srcImage, midImage, 50, 200, 3);
    //灰度化
    cvtColor(midImage, dstImage, CV_GRAY2BGR);
    // 定义矢量结构存放检测出来的直线
    vector<Vec2f> lines;
    //通过这个函数,我们就可以得到检测出来的直线集合了
    HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
    //这里注意第五个参数,表示阈值,阈值越大,表明检测的越精准,速度越快,得到的直线越少(得到的直线都是很有把握的直线)
    //这里得到的lines是包含rho和theta的,而不包括直线上的点,所以下面需要根据得到的rho和theta来建立一条直线

    //依次画出每条线段
    for (size_t i = 0; i < lines.size(); i++)
    {
        float rho = lines[i][0]; //就是圆的半径r
        float theta = lines[i][1]; //就是直线的角度
        Point pt1, pt2;
        double a = cos(theta), b = sin(theta);
        double x0 = a*rho, y0 = b*rho;
        pt1.x = cvRound(x0 + 1000 * (-b));
        pt1.y = cvRound(y0 + 1000*(a));
        pt2.x = cvRound(x0 - 1000*(-b));
        pt2.y = cvRound(y0 - 1000 * (a));

        line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA); //Scalar函数用于调节线段颜色,就是你想检测到的线段显示的是什么颜色

        imshow("边缘检测后的图", midImage);
        imshow("最终效果图", dstImage);
    }
    waitKey();
    return 0;
}

原图

阈值我设为250,看看直线检测的效果。你会发现,怎么图中一些很明显的的直线都没检测出来啊?原因是,我们阈值写的有点高了,只有那些有足够的把握认为是直线的直线才可能检测出来。

然后我把阈值改为150,直线检测效果就变成这样子了。显然多了很多直线,这是我们把我们的要求降低了,把那些“可能是直线”的直线都当做是直线了。所以,阈值的选择很重要,就看你是要精确查找还是模糊查找了。

后来我又加了一句打印进去,想看看角度的单位是什么

    cout << "line " << i << ": " << "rho:" << rho << " theta:" << theta << endl;

可以看到,角度theta用的单位不是我们所说的度数(70度、80度),而是数学上的π/2,π/3。

要想转为我们所说的度数,自己写个转换吧

float angle = theta / CV_PI * 180;

可以看出,转换后的角度范围就是我们想要的度数!值得注意的是,rho表示离坐标原点(就是图片左上角的点)的距离,theta是直线的旋转角度(0度表示垂直线,90度表示水平线)。

HoughLinesP用法

此函数在HoughLines的基础上在末尾加了一个代表Probabilistic(概率)的P,表明使用的是累计概率变换。

#include <iostream>
#include <opencv2\\opencv.hpp>
#include <opencv2\\imgproc\\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
    Mat srcImage = imread("2.jpg");
    imshow("Src Pic", srcImage);

    Mat midImage, dstImage;

    Canny(srcImage, midImage, 50, 200, 3);
    cvtColor(midImage, dstImage, CV_GRAY2BGR);

    vector<Vec4i> lines;
    //与HoughLines不同的是,HoughLinesP得到lines的是含有直线上点的坐标的,所以下面进行划线时就不再需要自己求出两个点来确定唯一的直线了
    HoughLinesP(midImage, lines, 1, CV_PI / 180, 80, 50, 10);//注意第五个参数,为阈值

    //依次画出每条线段
    for (size_t i = 0; i < lines.size(); i++)
    {
        Vec4i l = lines[i];

        line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186, 88, 255), 1, LINE_AA); //Scalar函数用于调节线段颜色

        imshow("边缘检测后的图", midImage);
        imshow("最终效果图", dstImage);
    }
    waitKey();
    return 0;
}

貌似效果还不错。

霍夫圆变换

刚刚的霍夫变换是检测直线的,如果我们想检测圆形,那该怎么办?那就用霍夫圆变换!用法也大同小异。

#include <iostream>
#include <opencv2\\opencv.hpp>
#include <opencv2\\imgproc\\imgproc.hpp>

using namespace cv;
using namespace std;

int main()
{
    Mat srcImage = imread("test5.jpg");  
    Mat midImage, dstImage;//临时变量和目标图的定义  

    imshow("【原始图】", srcImage);

    //【3】转为灰度图,进行图像平滑  
    cvtColor(srcImage, midImage, CV_BGR2GRAY);//转化边缘检测后的图为灰度图  
    GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);

    //【4】进行霍夫圆变换  
    vector<Vec3f> circles;
    HoughCircles(midImage, circles, CV_HOUGH_GRADIENT, 1.5, 10, 200, 150, 0, 0); //注意第七的参数为阈值,可以自行调整,值越大,检测的圆更精准

    //【5】依次在图中绘制出圆  
    for (size_t i = 0; i < circles.size(); i++)
    {
        Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
        //绘制圆心  
        circle(srcImage, center, 3, Scalar(0, 255, 0), -1, 8, 0);
        //绘制圆轮廓  
        circle(srcImage, center, radius, Scalar(155, 50, 255), 3, 8, 0);
    }

    //【6】显示效果图    
    imshow("【效果图】", srcImage);

    waitKey(0);

    return 0;
}

可以看到,有一些圆没有检测出来,同时还有一些不是圆形的确有误以为是圆形了,说明阈值选择不是很妥当。

另外提一点,霍夫圆变换的检测速度真的慢,显然进行圆检测的计算量还真不少!

以上是关于opencv识别车道线(霍夫线变换)的主要内容,如果未能解决你的问题,请参考以下文章

无人驾驶的图像识别之车道线检测

OpenCV探索之路:霍夫变换

即使图像在 Python 中的 OpenCV 中包含多行,霍夫线变换也只能识别一行

无人驾驶图像识别-车道线检测

20opencv入门霍夫变换:霍夫线变换,霍夫圆变换合辑

opencv入门项目——车道线检测