霍夫变换
在图像处理和计算机视觉领域中,我们对于直线检测最常用的就是霍夫变换。霍夫变换是从黑白图像中检测直线和线段。OpenCV支持三种不同的霍夫变换。
-
标准霍夫变换 (SHT)
-
多尺度霍夫变换(MSHT)
-
累积概率霍夫变换(PPHT)
在opencv中,我们可以用HoughLines函数来调用标准霍夫变换和多尺度霍夫变换。而HoughLinesP函数用于调用累积概率霍夫变换,累计霍夫变换的执行效率很高,所以相较于另外两个函数,我们更倾向于使用HoughLiensP函数进行直线检测。检测原理这里就不再多提,下面是我利用绘图,随手画的一条直线,我们开始进行测试。
Mat srcimg = imread("test.bmp");
Mat temp, gray;
Canny(srcimg, temp, 0 ,255, 3);
cvtColor(temp, gray, COLOR_GRAY2BGR);
vector<Vec4i>lines;
HoughLinesP(temp, lines, 1, CV_PI / 180, 25, 20.0, 0);
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
line(srcimg, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186, 88, 255), 1, CV_AA);
}
imshow("dst", srcimg);
imwrite("L1.bmp", srcimg);
waitKey();
大家可以看到,图片上的线段看似是一个完整并笔直的线段。但仔细观察发现可能是线段过粗的原因,其被划分为四节小线段。如果线段较为复杂其检测出来的线段也会更多。这时我们虽然利用霍夫变换检测出来了线段,却无法求其两侧端点及线段长度。
线段拟合
在更多的场合下霍夫变换的精度不高,所测量出来的参数并不能满足我们的需求。这时,我们需要利用线段拟合来解决问题。
线段拟合的方法很多,这里我们这接用一元线性回归。
下面来看一下OpenCV 提供的直线拟合函数。函数原型如下:
void fitLine( InputArray points, OutputArray line, int distType, double param, double reps, double aeps );
distType 指定拟合函数的类型,可以取 CV_DIST_L2、CV_DIST_L1、CV_DIST_L12、CV_DIST_FAIR、CV_DIST_WELSCH、CV_DIST_HUBER。
param 就是 CV_DIST_FAIR、CV_DIST_WELSCH、CV_DIST_HUBER 公式中的C。如果取 0,则程序自动选取合适的值。
reps 表示直线到原点距离的精度,建议取 0.01。
aeps 表示直线角度的精度,建议取 0.01。
下来开始基于上面那张测试图进行线段拟合。
Point MaxP, MinP, TempP;
Mat src_img, roi_img, dst_img;
int temp_x1; int temp_x2; int temp_y1; int temp_y2;
int initx1, inity1,initx2,inity2;
vector<cv::Point> fit1;
src_img = imread("132.bmp", IMREAD_GRAYSCALE);
roi_img = src_img;
//阈值化图像
Mat binary_img, morhp_img;
threshold(roi_img, binary_img, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);
imshow("二值化结果", binary_img);
//进行开运算操作
Mat kernel = getStructuringElement(MORPH_RECT, Size(30, 1), Point(-1, -1));
//定义一个宽20、高1的一个矩形结构元
kernel = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
//定义一个宽3、高3的一个矩形结构元
//作膨胀操作,图像中的直线增强
dilate(binary_img, binary_img, kernel);
imshow("膨胀操作结果", binary_img);
morphologyEx(binary_img, morhp_img, MORPH_OPEN, kernel, Point(-1, -1));
//作开运算,只保留图像中的直线
imshow("开运算操作结果", morhp_img);
// 进行膨胀操作
// 霍夫直线检测
vector<Vec4i> lines;
HoughLinesP(morhp_img, lines, 1, CV_PI / 180, 25,50, 0);
Mat result_img = roi_img.clone();
cvtColor(result_img, result_img, COLOR_GRAY2BGR);
//灰度图转为彩色图
for (size_t t = 0; t < lines.size(); ++t)
{
Vec4i ln = lines[t];
//line(result_img, Point(ln[0], ln[1]), Point(ln[2], ln[3]), Scalar(0, 0, 255), 2, 8, 0);
int x1 = Point(ln[0], ln[1]).x;
int y1 = Point(ln[0], ln[1]).y;
int x2 = Point(ln[2], ln[3]).x;
int y2 = Point(ln[2], ln[3]).y;
temp_x1 = x1; temp_x2 = x2; temp_y1 = y1; temp_y2 = y2;
fit1.push_back(cv::Point(x1, y1));
fit1.push_back(cv::Point(x2, y2));
if (t > 0)
{//根据情况进行点过滤,将合适的点导入到点集中
if ((temp_x1-x1<10)&&(temp_x2-x2<10)&&(temp_y2-y2<10)&&(temp_y1-y1<10))
{
fit1.push_back(cv::Point(x1, y1));
fit1.push_back(cv::Point(x2, y2));
}
}
}
//进行线段拟合
cv::Vec4f line_para1;
cv::fitLine(fit1, line_para1, cv::DIST_L2, 0, 1e-2, 1e-2);
for (int k = 0; k < fit1.size(); k++)
{
MaxP = fit1[0]; MinP = fit1[0];
cv::circle(result_img, fit1[k], 5, cv::Scalar(0, 0, 255), 2, 8, 0);
for (int j = 0; j < fit1.size() - 1; j++)
{
if (fit1[j + 1].x > MaxP.x)
{
MaxP = fit1[j + 1];
}
if (fit1[j + 1].x < MinP.x)
{
MinP = fit1[j + 1];
}
}
}
//printf("x1 = %d,y1 = %d\n", MaxP.x, MaxP.y);
//printf("x2 = %d,y2 = %d\n", MinP.x, MinP.y);
//获取点斜式的点和斜率
cv::Point point0;
point0.x = line_para1[2];
point0.y = line_para1[3];
double k = line_para1[1] / line_para1[0];
// 计算直线的端点(y = k(x - x0) + y0)
cv::Point point1, point2;
point1.x =MinP.x;
point1.y = k * (MinP.x - point0.x) + point0.y;
point2.x = MaxP.x;
point2.y = k * (MaxP.x - point0.x) + point0.y;
printf("x1 = %d,y1 = %d\n", point1.x ,point1.y);
printf("x2 = %d,y2 = %d\n", point2.x ,point2.y);
//计算线段长度
cv::line(result_img, point1, point2, cv::Scalar(0, 255, 0), 2, 8, 0);
double Distance = sqrt((point1.x - point2.x) * (point1.x - point2.x) + (point1.y - point2.y) * (point1.y - point2.y));
cout << "Length For Line" << Distance << endl;
imshow("dst", result_img);
imwrite("Ldst.bmp", result_img);
waitKey();
就此,我们到得了我们想要的参数。(线段的两端点及线段长度)其实也有很多其他方法可以使用,例如线段细化,不过其精度有待考究。
如有不足,还请指正。转载请标明出处。希望能对各位有所帮助。