利用概率霍夫变换,进行车道线的简单检测
1、首先编写一个头文件(也可以在源文件中写一个类,一样的)
#ifndef LANE_H //头文件的写法格式 if not define
#define LANE_H //_LANE_H_ 为头文件Lane.h的大写,防止被重复引用(见CSDN)
//这里其实不用加ifndef,因为只有一个自定义的头文件
class Lane { //头文件中写一个类,其实不用头文件也行,预处理会自动copy过去
private:bool left_flag = true; //这里要设置为true,否则后头警告未初始化内存bool right_flag = true;double right_m, left_m; //定义在private中,定义在regression函数中会报错,未初始化内存public:cv::Mat noise(cv::Mat input_img);cv::Mat edgedetector(cv::Mat noise_img);cv::Mat ROI(cv::Mat edge_img);std::vector<cv::Vec4i> Hough(cv::Mat ROI_img);std::vector<std::vector<cv::Vec4i>> possibleline(std::vector<cv::Vec4i> lines, cv::Mat ROI_img);//筛选左右车道线的可能线std::vector<cv::Point> regression(std::vector<std::vector<cv::Vec4i>> out, cv::Mat ROI_img);void draw(cv::Mat input_img, std::vector<cv::Point> output);
};
#endif
2、opencv源文件
#include<opencv2/opencv.hpp>
#include<vector>
#include "Lane.h"
cv::Mat Lane::noise(cv::Mat input_img) {cv::Mat output_img;cv::GaussianBlur(input_img, output_img, cv::Size(3, 3), 0, 0);/*参数详解:InputArray src---- - 源图像OutputArray dst---- - 目标图像Size ksize----高斯内核大小,其中ksize.width和ksize.height可以不同,但是必须为正数和奇数,也可为零,均有sigma计算而来。double sigmaX----表示高斯函数在X方向的标准偏差double sigmaY----表示高斯函数在Y方向的标准偏差若sigma为零,就将它设为sigmaX, 如果两者均为零,就由ksize.width和ksize.height计算出来。int borderType---- - 用于推断图像外部像素的某种边界模式。默认值 BORDER_DEFAULT */std::cout << "noise function" << std::endl;return output_img;
}
cv::Mat Lane::edgedetector(cv::Mat noise_img) {//灰度化-二值化-边缘检测算子卷积cv::Mat output_img; //定义输出图片cv::Matx33f kernel(-1,0,1,-2,0,2,-1,0,1); //Matx<>为模板类,Matx33f为模板的实例//Matx为固定矩阵类,不用于大型矩阵,可用于滤波中的kernel等cv::cvtColor(noise_img, output_img,cv::COLOR_RGB2GRAY);cv::threshold(output_img,output_img,120,256,cv::THRESH_BINARY);//filter2D(src,dst,src.depth(),kernel);//其中src与dst是Mat类型变量,src.depth表示图深度,有32,24,8等cv::filter2D(output_img, output_img, output_img.depth(), kernel);std::cout << "edgedetector function" << std::endl;return output_img;
}
cv::Mat Lane::ROI(cv::Mat edge_img) {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);std::cout << "ROI function" << std::endl;//cv::imshow("roi", ROI_img);return ROI_img;
}
std::vector<cv::Vec4i> Lane::Hough(cv::Mat ROI_img) { //概率霍夫变换//std::vector<cv::Vec4i>,HoughLinesP函数存储了检测到的线条的输出矢量,//每一条线由具有四个元素的矢量(x_1,y_1, x_2, y_2) 表示,其中,(x_1, y_1)和(x_2, y_2) 是是每个检测到的线段的结束点//vector中每个元素都是Vec4i类型的//由于不知道检测出的直线条数,使用vector动态分配元素个数std::vector<cv::Vec4i> lines; //定义此vector的名字为linescv::HoughLinesP(ROI_img,lines,1,CV_PI/180,20,20,30);std::cout << "Hough function" << std::endl;return lines;
}
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 加sstd::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>(i[1]) - static_cast<double>(i[3])) / (static_cast<double>(i[0]) - static_cast<double>(i[2])+0.00001);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);std::cout << "if2" << std::endl;}}std::cout << "a: " << a << std::endl;std::cout << "line.size: " << line.size() << std::endl; //接下来对剩下的线条进行分类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]);std::cout << "slope_ass: " << slope_ass[i] << std::endl;std::cout << "ini.x: " << ini.x << std::endl;std::cout << "fin.x: " << fin.x << std::endl;std::cout << "=============== " << std::endl;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[0] = left; //造成vector越界out.push_back(left);std::cout << "01"<<out.size() << std::endl;//out[1] = right;out.push_back(right);std::cout << "possibleline function" << std::endl;std::cout << "02"<<out.size() << std::endl; return out;
}
std::vector<cv::Point> Lane::regression(std::vector<std::vector<cv::Vec4i>> out, cv::Mat ROI_img) {//将所有分类线段的初始点和最终点提取出来,并使用最小二乘法从中拟合出一条新线std::vector <cv::Point> left,right;cv::Point left_point_init, right_point_init;cv::Point left_point_fin, right_point_fin;//double right_m, left_m; //定义在private中,定义在regression函数中会警告,未初始化内存cv::Vec4f left_line, right_line; //根据编写时的提示,选择此类型,输出点集(一个是方向向量,另一个是拟合直线上的点)std::vector<cv::Point> output;std::cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" <<out.size()<< std::endl;//if (left_flag == true) {for (auto i : out[0]) {std::cout << "123" << std::endl;std::cout << i[0]<<" "<<i[1] << " " << i[2] << " " << i[3] <<std::endl;left_point_init=cv::Point (i[0], i[1]); //初始化point的两种方法cv::Point left_point_fin(i[2], i[3]); //初始化point的两种方法left.push_back(left_point_init);left.push_back(left_point_fin);}std::cout << "before fitline test" << std::endl;cv::fitLine(left,left_line,cv::DIST_L2,0,0.01,0.01); //最小二乘法拟合 cv::DIST_L2//left_line为输出点集(一个是方向向量,另一个是拟合直线上的点)left_m = left_line[1] / left_line[0]; //通过输出的方向向量计算斜率std::cout << "左侧车道线斜率为:" << left_m << std::endl;//}//if (right_flag == true) {for (auto j : out[1]) {cv::Point right_point_init(j[0], j[1]);cv::Point right_point_fin(j[2], j[3]);right.push_back(right_point_init);right.push_back(right_point_fin);}cv::fitLine(right, right_line, cv::DIST_L2, 0, 0.01, 0.01);right_m = right_line[1] / right_line[0];std::cout <<"右侧车道线斜率为:"<< right_m << std::endl;//}//根据拟合的斜率与直线上的一个点的坐标,计算直线方程,并计算最终车道线两个中点的位置int final,init; //size_t,c++11中的类型,这里可以看作intinit = ROI_img.rows;final =465; //车道线终点的row坐标//根据左右车道线上的已知点,斜率,计算init与final的col坐标double right_ini_x = ((init - static_cast<double>( right_line[3])) / right_m) + static_cast<double>(right_line[2]);//加static_cast<double>(right_m)后不报错了double right_fin_x = ((final - static_cast<double>(right_line[3])) / right_m) + static_cast<double>(right_line[2]);double left_ini_x = ((init - static_cast<double>(left_line[3])) / left_m) + static_cast<double>(left_line[2]);double left_fin_x = ((final - static_cast<double>(left_line[3])) / left_m) + static_cast<double>(left_line[2]);//output[0] = cv::Point(right_ini_x, init);//output[1] = cv::Point(right_fin_x, final);//output[2] = cv::Point(left_ini_x, init);//output[3] = cv::Point(left_fin_x, final); //vector越界,报错,应该用push_back添加output.push_back(cv::Point(right_ini_x, init));output.push_back(cv::Point(right_fin_x, final)); //这里送入vector的顺序也有讲究,必须能一笔框住整个填充的区域output.push_back(cv::Point(left_fin_x, final));output.push_back(cv::Point(left_ini_x, init));std::cout << "regression function" << std::endl;return output;
}
void Lane::draw(cv::Mat input_img, std::vector<cv::Point> output) {cv::Mat out_img;input_img.copyTo(out_img);cv::fillConvexPoly(out_img, output, cv::Scalar(0, 255, 0)); //output定义时送入vector的顺序也有讲究,必须能一笔框住整个填充的区域cv::addWeighted(out_img, 0.3, input_img, 1.0 - 0.3, 0, input_img);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);cv::namedWindow("result", cv::WINDOW_AUTOSIZE);std::cout << "draw function" << std::endl;cv::namedWindow("result", cv::WINDOW_AUTOSIZE);cv::imshow("result", input_img);cv::waitKey(0);cv::imwrite("result.jpg",input_img);
}
int main() {cv::Mat img= cv::imread("D:\\study\\project\\origin_data24\\3.jpg");//路径格式注意//std::cout << img.rows << " " << img.cols << std::endl;//cv::namedWindow("img", cv::WINDOW_AUTOSIZE);//cv::imshow("img",img);cv::Mat input_img;img.copyTo(input_img);std::vector<cv::Vec4i> lines;std::vector<std::vector<cv::Vec4i>> out;std::vector<cv::Point> output;if (img.empty()) return -1;Lane lane; //实例化类img = lane.noise(img);img = lane.edgedetector(img);img = lane.ROI(img);lines = lane.Hough(img);out=lane.possibleline(lines, img);output = lane.regression(out, img);lane.draw(input_img,output);return 0;
}
以上是处理单帧图片,在视频流处理的时候,主函数换为:
int main() {cv::VideoCapture cap("D:\\study\\baidu_lane.mp4"); //构造读取视频的对象,并构造函数赋值if (!cap.isOpened()) {throw "the video is not opened"; //抛出一个异常信息return -1;}cv::VideoWriter video_out;cv::Size size = cv::Size(cap.get(cv::CAP_PROP_FRAME_WIDTH), cap.get(cv::CAP_PROP_FRAME_HEIGHT)); //定义输出的尺寸大小,这里与读取的一样video_out.open("D:\\study\\baidu_lane_result.avi", video_out.fourcc('M', 'J', 'P', 'G'), 10, size, true);//保存的视频格式一定为avi,mp4不行貌似!!!//保存的视频格式一定为avi,mp4不行貌似!!!//保存的视频格式一定为avi,mp4不行貌似!!!cv::Mat img;std::vector<cv::Vec4i> lines;std::vector<std::vector<cv::Vec4i>> out;std::vector<cv::Point> output;Lane lane; //实例化类int num = 0; //由于方法比较简单,没考虑复杂情况,这里设定num<500,即只处理500帧图片while (cap.read(img)&& num<500) {cv::Mat input_img;img.copyTo(input_img);img = lane.noise(img);img = lane.edgedetector(img);img = lane.ROI(img);lines = lane.Hough(img);if (!lines.empty()) { //视频处理的情况比较复杂,要考虑有些情况下未检测到车道线out = lane.possibleline(lines, img);output = lane.regression(out, img);lane.draw(input_img, output);}video_out.write(input_img);num++;}cap.release(); //一定要释放!!!return 0;
}
3、报错总结
1、花括号{ }嵌套时注意对应关系
2、vector越界 报错:vector subscripts out of range
以下两种vector添加元素的方法都是正确的:
<1>、
std::vector<int> a(2); //事先声明了两个元素的内存
a[0]=1;//直接赋值就行
a[1]=2;
//a[2]=2; //越界了
<2>、
std::vector<int> a;
a.push_back(1);
a.push_back(2);
(犯的错误)下面这样前两个元素都没有被赋值:
std::vector<int> a(2);//已经声明了两个元素的内存
a.push_back(1); //添加第三个元素,并赋值为1
a.push_back(2);//添加第四个元素,并赋值为2
下面这样也是错误的(越界):未初始化内存区域直接赋值
std::vector<int> m;m[0] = 1;m[1] = 8;
可以参考这个:
vector的越界问题
3、读取文件的路径
4、类外定义函数忘记加类名
5、fillConvexPoly区域填充,定义点的vector时,顺序有讲究,必须能一笔框住整个填充的区域
4、其他
size_t的使用
auto在for循环中的使用
cv::Vec 与cv::Point
static_cast强制类型转换
5、结果图