最近学习opencv学到了霍夫线变换,霍夫线变换是一个查找图像中直线的算法,它的其中一种应用场景就是识别车道,本文以识别车道为例,介绍霍夫线的简单用法。
下面是chatGPT给出的说明:
霍夫线变换(Hough Line Transform)是一种图像处理技术,可以用于检测图像中的直线。它的基本思想是,将直线转换为参数空间,并在参数空间中寻找与图像中的边缘相对应的点,从而找到这些直线。霍夫线变换常用于计算机视觉领域,例如在车道线检测、图像拼接、人脸识别等方面应用广泛。
原理性的东西这里不讲,因为有点复杂,我看得也有点懵。
opencv实现霍夫线变换的函数是HoughLinesP,它的定义如下。
void HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength = 0, double maxLineGap = 0 );
它的参数的含义如下:
image:8位、单通道二进制源图像。
lines:输出线的矢量。每条线由一个4元素矢量表示,可以传入vector< cv::Vec4i>类型。
控制精度:
rho:累加器的距离分辨率(以像素为单位)。
theta:累加器的角度分辨率(弧度)。
过滤:
threshold:累加器阈值参数。
minLineLength:最小行长度。小于该长度的线段将被拒绝。
maxLineGap:同一条线上链接点的最大允许间隙。
因为HoughLinesP传入的图像必须是8位、单通道二进制源图像,所以在传入图像之前,需要做转灰度图-》转二进制图的操作。
opencv提供了一些转二进制图的方法,因为HoughLinesP的目的是找到直线,而直线其实也是轮廓的一部分,所以一般我们采用Canny算法来把灰度图转为二进制图。
例程:
#include
#include
#include
#include
using namespace cv;
int main() {
Mat src = imread("road.png");
imshow("src", src);
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
// Apply Canny edge detection
Mat edges;
Canny(gray, edges, 50, 150);
imshow("canny", edges);
// Perform Hough transform to find lines
std::vector<Vec4i> lines;
HoughLinesP(gray, lines, 1, CV_PI / 180, 50, 50, 10);
// Draw lines on output image
Mat dst = src.clone();
for (size_t i = 0; i < lines.size(); i++) {
Vec4i vline = lines[i];
line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
}
imshow("dst", dst);
waitKey(0);
}
首先准备一张图片,如下图所示,要识别出它的白色车道线。
我们直接使用上一节的例程,效果如下。
发现虽然车道是识别出来了,但是环境中的纹理也被误认为车道,所以要做进一步优化。
从上面的Canny图可以看到,环境中的树木形成了密密麻麻的纹理,这些就是影响效果的因素之一。
经过测试,我选用了“二值化 - 》腐蚀 - 》膨胀”的方式来完成降噪,经过优化后的代码如下:
#include
#include
#include
#include
using namespace cv;
int main() {
Mat src = imread("/road.png");
imshow("src", src);
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
//二值化
Mat thr;
threshold(gray, thr, 100, 255, THRESH_BINARY);
imshow("threshold", thr);
// 腐蚀
Mat eroded;
Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
erode(thr, eroded, element);
// 膨胀
Mat dilated;
dilate(eroded, dilated, element);
imshow("dilated", dilated);
// Apply Canny edge detection
Mat edges;
Canny(dilated, edges, 50, 150);
imshow("canny", edges);
// Perform Hough transform to find lines
std::vector<Vec4i> lines;
HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);
// Draw lines on output image
Mat dst = src.clone();
for (size_t i = 0; i < lines.size(); i++) {
Vec4i vline = lines[i];
line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
}
imshow("dst", dst);
waitKey(0);
}
优化后的效果如下:
从Canny中明显可以看到环境纹理少了很多。
在上图中,可以看到还有一些横向的纹理影响了效果,我们可以通过直线的方向来做进一步过滤。
在车的视角下,车道是朝中间斜的,两边车道成八字型,如图所示。
也就是说,车道的线在图像上倾斜角度不会小,所以我们可以在得出最终结果时,添加一个过滤条件:倾斜角度小于20度的直线不满足条件。
修改代码如下:
.....
// Perform Hough transform to find lines
std::vector<Vec4i> lines;
HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);
// Draw lines on output image
Mat dst = src.clone();
for (size_t i = 0; i < lines.size(); i++) {
Vec4i vline = lines[i];
/* 过滤倾斜45度及以下的斜线 */
float tanVal = (float)(vline[3] - vline[1]) / (vline[2] - vline[0]);
if (abs(tanVal) < tan(CV_PI / 18)) continue;
line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
}
.......
效果:
在识别车道时,因为车道是在车的脚下,需要识别的图像只有相机拍下的下半截,所以这里还可以加多一层优化:把上半截图像砍掉,只处理下半截图像。
修改代码:
int main() {
Mat src = imread("road.png");
Rect vaildRect(0, src.rows / 2, src.cols, src.rows / 2);
Mat src = src(vaildRect);
imshow("src", src);
......
经过前面的优化后,得到如下代码:
int main() {
Mat src = imread("road.png");
Rect vaildRect(0, src.rows / 2, src.cols, src.rows / 2);
src = src(vaildRect);
imshow("src", src);
Mat gray;
cvtColor(src, gray, COLOR_BGR2GRAY);
imshow("gray", gray);
Mat thr;
threshold(gray, thr, 150, 255, THRESH_BINARY);
imshow("threshold", thr);
// 腐蚀
Mat eroded;
Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
erode(thr, eroded, element);
// 膨胀
Mat dilated;
dilate(eroded, dilated, element);
imshow("dilated", dilated);
// Apply Canny edge detection
Mat edges;
Canny(dilated, edges, 50, 150);
imshow("canny", edges);
// Perform Hough transform to find lines
std::vector<Vec4i> lines;
HoughLinesP(edges, lines, 1, CV_PI / 180, 50, 50, 10);
// Draw lines on output image
Mat dst = src.clone();
for (size_t i = 0; i < lines.size(); i++) {
Vec4i vline = lines[i];
float tanVal = (float)(vline[3] - vline[1]) / (vline[2] - vline[0]);
if (abs(tanVal) < tan(CV_PI / 18)) {
continue;
}
line(dst, Point(vline[0], vline[1]), Point(vline[2], vline[3]), Scalar(0, 0, 255), 2);
}
imshow("dst", dst);
waitKey(0);
}
下面用这份代码测试其它例子。
因为拍照时的亮度不一,所以需要根据亮度来调整二值化时的阀值,此例用的是
threshold(gray, thr, 170, 255, THRESH_BINARY);
threshold(gray, gray, 150, 255, THRESH_BINARY);