1. 程式人生 > >基於OpenCV C++ 的車道檢測

基於OpenCV C++ 的車道檢測

車道檢測感覺屬於比較適合練手的小專案,本文主要參考了部落格: 車道線檢測之lanelines-detection, 博主的解釋已經非常詳細了,也給出了Github上的python程式碼,感興趣的可以看看。考慮到實際應用中多用C++開發,同時也想更熟悉一下C++裡的opencv函式,所以將其重寫成C++,開始感覺比較容易,實際轉換中還是有一些坑的。。

印象最深的就是python中的cv2.fillPoly和 C++ 中的cv::fillPoly不對應,折騰了半天看了API文件才發現二者不一樣,在C++中應該對應的是cv::fillConvexPoly。

以下是程式碼

一些申明:

typedef std::vector<cv::Vec4i> linesType;
const double PI = 3.1415926535;
bool show_steps = false;

// m: 斜率, b: 截距, norm: 線長
struct hough_pts {
	double m, b, norm;
	hough_pts(double m, double b, double norm) :
		m(m), b(b), norm(norm) {};
};

獲取ROI(region of interest):

void getROI(cv::Mat img, std::vector<cv::Point2i> vertices, cv::Mat& masked) {
	cv::Mat mask = cv::Mat::zeros(img.size(), img.type());
	if (img.channels() == 1) {
		cv::fillConvexPoly(mask, vertices, cv::Scalar(255));
	}
	else if(img.channels() == 3){
		cv::fillConvexPoly(mask, vertices, cv::Scalar(255,255,255));
	}
	int a = 10;
	cv::bitwise_and(img, img, masked, mask);
}

求Hough直線檢測出的線的均值(左右車道根據直線斜率判斷)

void averageLines(cv::Mat, linesType lines, double y_min, double y_max, linesType& output) {
	std::vector<hough_pts> left_lane, right_lane;
	for (int i = 0; i < lines.size(); ++i) {
		cv::Point2f pt1 = cv::Point2f(lines[i][0], lines[i][1]);
		cv::Point2f pt2 = cv::Point2f(lines[i][2], lines[i][3]);
		double m = (pt2.y - pt1.y) / (pt2.x - pt1.x);
		double b = -m * pt1.x + pt1.y;
		double norm = sqrt((pt2.x - pt1.x)*(pt2.x - pt1.x) + (pt2.y - pt1.y)*(pt2.y - pt1.y));
		if (m < 0) { // left lane
			left_lane.push_back(hough_pts(m, b, norm));
		}
		if (m > 0) { // right lane
			right_lane.push_back(hough_pts(m, b, norm));
		}
	}

	double b_avg_left = 0.0, m_avg_left = 0.0, xmin_left, xmax_left;
	for (int i = 0; i < left_lane.size(); ++i) {
		b_avg_left += left_lane[i].b;
		m_avg_left += left_lane[i].m;
	}
	b_avg_left /= left_lane.size();
	m_avg_left /= left_lane.size();
	xmin_left = int((y_min - b_avg_left) / m_avg_left);
	xmax_left = int((y_max - b_avg_left) / m_avg_left);
	// left lane
	output.push_back(cv::Vec4i(xmin_left, y_min, xmax_left, y_max));

	double b_avg_right = 0.0, m_avg_right = 0.0, xmin_right, xmax_right;
	for (int i = 0; i < right_lane.size(); ++i) {
		b_avg_right += right_lane[i].b;
		m_avg_right += right_lane[i].m;
	}
	b_avg_right /= right_lane.size();
	m_avg_right /= right_lane.size();
	xmin_right = int((y_min - b_avg_right) / m_avg_right);
	xmax_right = int((y_max - b_avg_right) / m_avg_right);
	// right lane
	output.push_back(cv::Vec4i(xmin_right, y_min, xmax_right, y_max));

	// left lane: output[0]
	// right lane: output[1]
}

角度濾波(去噪):

void bypassAngleFilter(linesType lines, double low_thres, double high_thres, linesType& output) {
	for (int i = 0; i < lines.size(); ++i) {
		double x1 = lines[i][0], y1 = lines[i][1];
		double x2 = lines[i][2], y2 = lines[i][3];
		double angle = abs(atan((y2 - y1) / (x2 - x1)) * 180 / PI);
		if (angle > low_thres && angle < high_thres) {
			output.push_back(cv::Vec4i(x1, y1, x2, y2));
		}
	}
}

黃色車道顏色增強:

void yellowEnhance(cv::Mat img_rgb, cv::Mat& result) {
	cv::Mat img_hsv;
	cv::cvtColor(img_rgb, img_hsv, cv::COLOR_RGB2HSV);
	cv::Scalar lower_yellow = cv::Scalar(40, 100, 20);
	cv::Scalar upper_yellow = cv::Scalar(100, 255, 255);
	cv::Mat mask;
	// inRange 之內為 255, 否則為 0
	cv::inRange(img_hsv, lower_yellow, upper_yellow, mask);
	cv::Mat gray;
	cv::cvtColor(img_rgb, gray, cv::COLOR_BGR2GRAY);
	cv::addWeighted(mask, 1.0, gray, 1.0, 1.0, result);
}

檢測與判定:

void pipeline(cv::Mat img, std::vector<cv::Point>vertices, double low_thres, double high_thres, cv::Mat& img_all_lines) {
	cv::Mat gray;
	yellowEnhance(img, gray);
	cv::Mat gray_blur;
	cv::GaussianBlur(gray, gray_blur, cv::Size(3, 3), 0, 0);
	cv::Mat element = cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
	cv::morphologyEx(gray_blur, gray_blur, cv::MORPH_DILATE, element, cv::Point(-1, -1), 3);
	cv::Mat edges;
	cv::Canny(gray_blur, edges, 50, 180);
	cv::Mat masked;
	getROI(edges, vertices, masked);
	std::vector<cv::Vec4i>lines;
	cv::HoughLinesP(masked, lines, 1, CV_PI / 180, 26, 5, 50);
	
	cv::Mat hlines_img = cv::Mat::zeros(img.size(), CV_8UC3);
	drawLines(hlines_img, lines, cv::Scalar(255, 0, 0));
	
	linesType filtered_lines;
	bypassAngleFilter(lines, low_thres, high_thres, filtered_lines);
	cv::Mat avg_img = cv::Mat::zeros(img.size(), CV_8UC3);
	linesType avg_hlines;
	averageLines(img, filtered_lines, int(img.rows * 0.65), img.rows, avg_hlines);
	drawLines(avg_img, avg_hlines, cv::Scalar(255, 0, 0));

	if (show_steps) {
		cv::imshow("original", img);
		cv::imshow("gray", gray);
		cv::imshow("gray_blur", gray_blur);
		cv::imshow("edges", edges);
		cv::imshow("masked image", masked);
		cv::imshow("Hough Lines image", hlines_img);
		cv::waitKey(0);
	}
	cv::Mat colorMask = cv::Mat::zeros(img.size(), CV_8UC3);
	std::vector<cv::Point2i> lineVertices; // 注意順序
	lineVertices.push_back(cv::Point2i(avg_hlines[0][0], avg_hlines[0][1]));
	lineVertices.push_back(cv::Point2i(avg_hlines[0][2], avg_hlines[0][3]));
	lineVertices.push_back(cv::Point2i(avg_hlines[1][2], avg_hlines[1][3]));
	lineVertices.push_back(cv::Point2i(avg_hlines[1][0], avg_hlines[1][1]));

	cv::fillConvexPoly(colorMask, lineVertices, cv::Scalar(0, 255, 0));

	cv::addWeighted(avg_img, 1.0, img, 1, 0.0, img_all_lines);
	cv::addWeighted(img_all_lines, 1.0, colorMask, 0.6, 0.0, img_all_lines);
}

在原圖中標記:

void processImg(cv::Mat img, cv::Mat& result, int h, int w) {
	cv::Size size = cv::Size(h, w);
	std::vector<cv::Point2i> vertices;
	vertices.push_back(cv::Point2i(200, size.width - 80));
	vertices.push_back(cv::Point2i(490, int(size.width*0.65)));
	vertices.push_back(cv::Point2i(820, int(size.width*0.65)));
	vertices.push_back(cv::Point2i(size.height - 150, size.width - 80));
	vertices.push_back(cv::Point2i(100, size.width - 80));
	double low_thres = 30.0;
	double high_thres = 80.0;
	pipeline(img, vertices, low_thres, high_thres, result);
}