基于Opencv的车道线识别算法
关于基于opencv的车道线识别,网上很多教程,有些不是特别详细,这里实战了一下,顺便给大家分享。
关键步骤:
1.从视频中读取图像
cap.read(img)
2.高斯降噪
img = lane.noise(img);
3.转为灰度图并二值化
cv::cvtColor(noise_img, output_img, cv::COLOR_RGB2GRAY);
cv::threshold(output_img, output_img, 120, 256, cv::THRESH_BINARY);
4.卷积
cv::Matx33f kernel(-1, 0, 1, -2, 0, 2, -1, 0, 1);
cv::filter2D(output_img, output_img, output_img.depth(), kernel);
这里的卷积核为3X3, 为
-1,0, 1
-2,0, 2
-1, 0, 1
作用是:过滤大白区域,进行细化,这里主要是天空,我们可以做个比较上图是卷积前的,下图是卷积后的。
5.ROI,过滤掉不用的区域, 获取感兴趣的兴趣
cv::Mat ROI_img;
cv::Mat mask = cv::Mat::zeros(edge_img.size(), edge_img.type());
cv::Point pts[4] = {
cv::Point(210, 720), cv::Point(550, 450), cv::Point(717, 450), cv::Point(1280, 720)};
cv::fillConvexPoly(mask, pts, 4, cv::Scalar(255, 0, 0));
cv::bitwise_and(edge_img, mask, ROI_img);
长这样:
6.利用霍夫变换(原理网上很多教程),这里是为了再图片中找出线段
std::vector<cv::Vec4i> lines; //定义此vector的名字为lines
cv::HoughLinesP(ROI_img, lines, 1, CV_PI / 180, 20, 20, 30);
s t d : : v e c t o r < c v : : V e c 4 i > std::vector<cv::Vec4i> std::vector<cv::Vec4i>,HoughLinesP函数存储了检测到的线条的输出矢量,每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每个检测到的线段的结束点。
7.如果找到线段不为空,则对检测的线段进行过滤
按照斜率、端点与图中心的位置来进行剔除,分类(分为左车道线还是右边车道线)
std::vector<std::vector<cv::Vec4i>> Lane::possibleline(std::vector<cv::Vec4i> lines, cv::Mat ROI_img)
{
//按照斜率、端点与图中心的位置来进行剔除,分类
double slope_th = 0.3; //设定斜率阈值
double mid = ROI_img.cols / 2; //计算中点,记得cols 加s
// std::cout << "mid: " << mid << std::endl;
std::vector<std::vector<cv::Vec4i>> out;
std::vector<cv::Vec4i> left, right;
std::vector<double> slope_ass;
std::vector<cv::Vec4i> line;
cv::Point ini, fin;
int a = 0;
//首先根据斜率剔除部分线条
for (auto i : lines)
{
ini = cv::Point(i[0], i[1]);
fin = cv::Point(i[2], i[3]);
double slope = (static_cast<double>(fin.y) - static_cast<double>(ini.y)) / (static_cast<double>(fin.x) - static_cast<double>(ini.x) + 0.00001);
// std::cout << "slope:" << slope << std::endl;
if (std::abs(slope) > slope_th)
{
a++;
slope_ass.push_back(slope);
line.push_back(i);
}
}
//接下来对剩下的线条进行分类
for (size_t i = 0; i < line.size(); i++)
{
ini = cv::Point(line[i][0], line[i][1]);
fin = cv::Point(line[i][2], line[i][3]);
if (slope_ass[i] < 0 && ini.x < mid && fin.x < mid)
{
// std::cout << "左" << std::endl;
left.push_back(line[i]);
left_flag = true;
}
else if (slope_ass[i] > 0 && ini.x > mid && fin.x > mid)
{
// std::cout << "右" << std::endl;
right.push_back(line[i]);
right_flag = true;
}
}
out.push_back(left);
out.push_back(right);
return out;
}
其实好像也可以加根据长度条件进行筛选。
8.将所有分类线段的初始点和最终点提取出来,并使用最小二乘法从中拟合出一条新线
使用的函数是最小二乘法拟合 cv::DIST_L2
cv::fitLine(left, left_line, cv::DIST_L2, 0, 0.01, 0.01);
9.填充并,绘制车道线
填充效果:
然后将填充区域(绿色区域)和原图像进行融合:
cv::addWeighted(out_img, 0.3, input_img, 1.0 - 0.3, 0, input_img);
最后使用cv::line进行绘制
cv::line(input_img, output[0], output[1], cv::Scalar(0, 0, 255), 5);
cv::line(input_img, output[2], output[3], cv::Scalar(0, 0, 255), 5);
10.最终效果
这里说明一下,我是使用Cmake进行编译的C++工程。
完整工程文件:https://download.csdn.net/download/weixin_39735688/43146799