OpenCV 学习(直线拟合)

Hough 变换可以提取图像中的直线。但是提取的直线的精度不高。而很多场合下,我们需要精确的估计直线的参数,这时就需要进行直线拟合。

直线拟合的方法很多,比如一元线性回归就是一种最简单的直线拟合方法。但是这种方法不适合用于提取图像中的直线。因为这种算法假设每个数据点的X 坐标是准确的,Y 坐标是带有高斯噪声的。可实际上,图像中的每个数据点的XY 坐标都是带有噪声的。

下面就来讲讲适用于提取图像中直线的直线拟合算法。

一个点  (xi,yi)  到直线的距离用  ri  来表示。

所谓直线拟合,就是找到一条直线,使得:

ρ(ri)

最小。

ρ(r)  是距离函数。 ρ(r)  函数取不同的形式,对应不同的直线拟合方法。OpenCV 中支持 6 种不同的 ρ(r)  函数形式。分别是:

CV_DIST_L2 

ρ(r)=r22

这种方法是以距离平方和为拟合判据。也就是常见的最小二乘拟合算法,运行速度也最快。但是这个算法也有个很大的问题,就是当干扰点离直线较远时,一个干扰点就可能将整条拟合直线拉偏了。简单的说就是对干扰点的鲁棒性不够。所以后来又提出了其他的函数。

CV_DIST_L1

ρ(r)=r

CV_DIST_L12

ρ(r)=2(1+r221)

CV_DIST_FAIR

ρ(r)=C2(rClog(1+rC))

其中 C = 1.3998

CV_DIST_WELSCH

ρ(r)=C22(1exp((rC)2))

其中 C = 2.9846

CV_DIST_HUBER

ρ(r)={r22C(rC2)if  r<C,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
  • 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  来表示,那么 k = line[1]/line[0],b = line[3] - k * line[2]。

如果直线用  ρ=xcosθ+ysinθ  来表示, 那么  θ=arctank+π2

下面是个测试图像: 
这里写图片描述

图像中有一条直线和一些干扰图案。

下面的代码可以从图像中提取出需要的坐标点。

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
  • 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
  • 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
  • 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

这里写图片描述

如果直线拟合类型选择 CV_DIST_L2。那么效果就没这么好了。代码不贴了,就贴个结果。 
这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
OpenCV提供了直线拟合函数fitLine,可以用于拟合多条直线。该函数的原型如下: void fitLine(InputArray points, OutputArray line, int distType, double param, double reps, double aeps); 其中,points是输入的坐标集合,line是输出的拟合直线的参数,distType是距离类型,param是距离参数,reps是拟合精度,aeps是角度精度。 要进行多条直线拟合,可以将多组坐标分别传入fitLine函数进行拟合,得到对应的直线参数。可以使用std::vector<cv::Point>来存储每组坐标,然后循环调用fitLine函数进行拟合。 以下是一个示例代码,用于拟合多条直线: std::vector<std::vector<cv::Point>> pointSets; // 存储多组坐标 std::vector<cv::Vec4f> lines; // 存储拟合直线的参数 // 循环拟合每组坐标 for (int i = 0; i < pointSets.size(); i++) { cv::Mat pointsMat(pointSets\[i\]); cv::Vec4f line; cv::fitLine(pointsMat, line, cv::DIST_L2, 0, 0.01, 0.01); lines.push_back(line); } 这样,lines中就存储了拟合得到的多条直线的参数。每个直线的参数是一个cv::Vec4f,其中前两个元素表示直线上的一个,后两个元素表示直线的方向向量。 请注意,拟合直线的精度参数reps和aeps可以根据实际需求进行调整,以获得更好的拟合效果。 #### 引用[.reference_title] - *1* *2* *3* [OpenCV 学习直线拟合)](https://blog.csdn.net/liyuanbhu/article/details/50193947)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值