OpenCV 学习(直线拟合) 比眉伴天荒 2022-05-30 10:39 770阅读 0赞 # # Hough 变换可以提取图像中的直线。但是提取的直线的精度不高。而很多场合下,我们需要精确的估计直线的参数,这时就需要进行直线拟合。 直线拟合的方法很多,比如一元线性回归就是一种最简单的直线拟合方法。但是这种方法不适合用于提取图像中的直线。因为这种算法假设每个数据点的X 坐标是准确的,Y 坐标是带有高斯噪声的。可实际上,图像中的每个数据点的XY 坐标都是带有噪声的。 下面就来讲讲适用于提取图像中直线的直线拟合算法。 一个点 (xi,yi) (xi,yi) 到直线的距离用 ri ri 来表示。 所谓直线拟合,就是找到一条直线,使得: ∑ρ(ri) ∑ρ(ri) 最小。 ρ(r) ρ(r) 是距离函数。ρ(r) ρ(r) 函数取不同的形式,对应不同的直线拟合方法。OpenCV 中支持 6 种不同的ρ(r) ρ(r) 函数形式。分别是: CV\_DIST\_L2 ρ(r)=r22 ρ(r)=r22 这种方法是以距离平方和为拟合判据。也就是常见的最小二乘拟合算法,运行速度也最快。但是这个算法也有个很大的问题,就是当干扰点离直线较远时,一个干扰点就可能将整条拟合直线拉偏了。简单的说就是对干扰点的鲁棒性不够。所以后来又提出了其他的函数。 CV\_DIST\_L1 ρ(r)=r ρ(r)=r CV\_DIST\_L12 ρ(r)=2(1\+r22−−−−−−√−1) ρ(r)=2(1+r22−1) CV\_DIST\_FAIR ρ(r)=C2(rC−log(1\+rC)) ρ(r)=C2(rC−log(1+rC)) 其中 C = 1.3998 CV\_DIST\_WELSCH ρ(r)=C22(1−exp(−(rC)2)) ρ(r)=C22(1−exp(−(rC)2)) 其中 C = 2.9846 CV\_DIST\_HUBER ρ(r)=\{ r22C(r−C2)ifr<C,otherwise. ρ(r)=\{r22if r<C,C(r−C2)otherwise. 其中 C = 1.345 后面这 5 种函数我知道第一种,其他的不知道是怎么来的。OpenCV 的帮助文档给出了一个链接:[M-estimator][] 但是这个页面也被墙了。 下面来说说 OpenCV 提供的直线拟合函数。函数原型如下: void fitLine( InputArray points, OutputArray line, int distType, double param, double reps, double aeps ); 1 2 3 4 5 6 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。 计算出的直线信息存放在 line 中,为 cv::Vec4f 类型。line\[0\]、line\[1\] 存放的是直线的方向向量。line\[2\]、line\[3\] 存放的是直线上一个点的坐标。 如果直线用 y=kx\+b y=kx+b 来表示,那么 k = line\[1\]/line\[0\],b = line\[3\] - k \* line\[2\]。 如果直线用 ρ=xcosθ\+ysinθ ρ=xcosθ+ysinθ 来表示, 那么 θ=arctank\+π2 θ=arctank+π2 下面是个测试图像: ![这里写图片描述][20151206123630815] 图像中有一条直线和一些干扰图案。 下面的代码可以从图像中提取出需要的坐标点。 std::vector<cv::Point> getPoints(cv::Mat &image, int value) { int nl = image.rows; // number of lines int nc = image.cols * image.channels(); std::vector<cv::Point> points; for (int j = 0; j < nl; j++) { uchar* data = image.ptr<uchar>(j); for (int i = 0; i < nc; i++) { if(data[i] == value) { points.push_back(cv::Point(i, j)); } } } return points; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 下面的代码可以在图中画一条直线。 void drawLine(cv::Mat &image, double theta, double rho, cv::Scalar color) { if (theta < PI/4. || theta > 3.*PI/4.)// ~vertical line { cv::Point pt1(rho/cos(theta), 0); cv::Point pt2((rho - image.rows * sin(theta))/cos(theta), image.rows); cv::line( image, pt1, pt2, cv::Scalar(255), 1); } else { cv::Point pt1(0, rho/sin(theta)); cv::Point pt2(image.cols, (rho - image.cols * cos(theta))/sin(theta)); cv::line(image, pt1, pt2, color, 1); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 下面的代码是程序的主体。 int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); cv::Mat image = imread("c:\\line_test.png", cv::IMREAD_GRAYSCALE); std::vector<cv::Point> points = getPoints(image, 0); cv::Vec4f line; cv::fitLine(points, line, CV_DIST_HUBER , 0, 0.01, 0.01); double cos_theta = line[0]; double sin_theta = line[1]; double x0 = line[2], y0 = line[3]; double phi = atan2(sin_theta, cos_theta) + PI / 2.0; double rho = y0 * cos_theta - x0 * sin_theta; std::cout << "phi = " << phi / PI * 180 << std::endl; std::cout << "rho = " << rho << std::endl; drawLine(image, phi, rho, cv::Scalar(0)); double k = sin_theta / cos_theta; double b = y0 - k * x0; double x = 0; double y = k * x + b; std::cout << k << std::endl; std::cout << b << std::endl; //cv::line(image, Point(x0,y0), Point(x,y), cv::Scalar(255), 1); imshow("", image); return a.exec(); } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 ![这里写图片描述][20151206124331868] 如果直线拟合类型选择 CV\_DIST\_L2。那么效果就没这么好了。代码不贴了,就贴个结果。 ![这里写图片描述][20151206124458455] 原文链接:http://blog.csdn.net/liyuanbhu/article/details/50193947 [M-estimator]: https://en.wikipedia.org/wiki/M-estimator [20151206123630815]: /images/20220530/d1e36b6820cf4575a672f2015064af15.png [20151206124331868]: /images/20220530/7e215b01e8f0453d99e7ee87a04955c4.png [20151206124458455]: /images/20220530/1aeb35f52c2348ca8e83864fe53d7edc.png
还没有评论,来说两句吧...