基於OpenCV C++ 的車道檢測
阿新 • • 發佈:2018-12-17
車道檢測感覺屬於比較適合練手的小專案,本文主要參考了部落格: 車道線檢測之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);
}