c++ 车道线检测

利用概率霍夫变换,进行车道线的简单检测
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的名字为lines
	cv::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 加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>(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中的类型,这里可以看作int
	init = 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、结果图
在这里插入图片描述

  • 14
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值