实践教程 | OpenCV中直线拟合方法解密

极市平台

共 3978字,需浏览 8分钟

 ·

2021-08-15 17:26

↑ 点击蓝字 关注极市平台

者丨gloomyfish
来源丨OpenCV学堂
编辑丨极市平台

极市导读

 

如题,本文将解密OpenCV中直线拟合方法:通过OpenCV的距离变换,骨架提取,然后再直线拟合,使用DIST_L1得到的结果。 >>加入极市CV技术交流群,走在计算机视觉的最前沿

直线拟合原理

给出多个点,然后根据这些点拟合出一条直线,这个最常见的算法是多约束方程的最小二乘拟合,如下图所示:

但是当这些点当中有一个或者几个离群点(outlier)时候,最小二乘拟合出来的直线就直接翻车成这样了:

原因是最小二乘无法在估算拟合的时候剔除或者降低离群点的影响,于是一个聪明的家伙出现了,提出了基于权重的最小二乘拟合估算方法,这样就避免了翻车。

根据高斯分布,离群点权重应该尽可能的小,这样就可以降低它的影响,OpenCV中的直线拟合就是就权重最小二乘完成的,在生成权重时候OpenCV支持几种不同的距离计算方法,分别如下:

  • DIST_L2
  • DIST_L1
  • DIST_L12
  • DIST_FAIR
  • DIST_WELSCH
  • DIST_HUBER

其中DIST_L2是最原始的最小二乘,最容易翻车的一种拟合方式,虽然速度快点。然后用基于权重的最小二乘估算拟合结果如下:

函数与实现源码分析

OpenCV中直线拟合函数支持上述六种距离计算方式,函数与参数解释如下:

void cv::fitLine(
InputArray points,
OutputArray line,
int distType,
double param,
double reps,
double aeps
)
  • points是输入点集合

  • line是输出的拟合参数,支持2D与3D

  • distType是选择距离计算方式

  • param 是某些距离计算时生成权重需要的参数

  • reps 是前后两次原点到直线的距离差值,可以看成拟合精度高低

  • aeps是前后两次角度差值,表示的是拟合精度

六种权重的计算更新实现如下:

static void weightL1( float *d, int count, float *w )
{
int i;


for( i = 0; i < count; i++ )
{
double t = fabs( (double) d[i] );
w[i] = (float)(1. / MAX(t, eps));
}
}


static void weightL12( float *d, int count, float *w )
{
int i;


for( i = 0; i < count; i++ )
{
w[i] = 1.0f / (float) std::sqrt( 1 + (double) (d[i] * d[i] * 0.5) );
}
}




static void weightHuber( float *d, int count, float *w, float _c )
{
int i;
const float c = _c <= 0 ? 1.345f : _c;


for( i = 0; i < count; i++ )
{
if( d[i] < c )
w[i] = 1.0f;
else
w[i] = c/d[i];
}
}




static void weightFair( float *d, int count, float *w, float _c )
{
int i;
const float c = _c == 0 ? 1 / 1.3998f : 1 / _c;


for( i = 0; i < count; i++ )
{
w[i] = 1 / (1 + d[i] * c);
}
}


static void weightWelsch( float *d, int count, float *w, float _c )
{
int i;
const float c = _c == 0 ? 1 / 2.9846f : 1 / _c;


for( i = 0; i < count; i++ )
{
w[i] = (float) std::exp( -d[i] * d[i] * c * c );
}
}

拟合计算的代码实现:

static void fitLine2D_wods( const Point2f* points, int count, float *weights, float *line )
{
CV_Assert(count > 0);
double x = 0, y = 0, x2 = 0, y2 = 0, xy = 0, w = 0;
double dx2, dy2, dxy;
int i;
float t;

// Calculating the average of x and y...
if( weights == 0 )
{
for( i = 0; i < count; i += 1 )
{
x += points[i].x;
y += points[i].y;
x2 += points[i].x * points[i].x;
y2 += points[i].y * points[i].y;
xy += points[i].x * points[i].y;
}
w = (float) count;
}
else
{
for( i = 0; i < count; i += 1 )
{
x += weights[i] * points[i].x;
y += weights[i] * points[i].y;
x2 += weights[i] * points[i].x * points[i].x;
y2 += weights[i] * points[i].y * points[i].y;
xy += weights[i] * points[i].x * points[i].y;
w += weights[i];
}
}

x /= w;
y /= w;
x2 /= w;
y2 /= w;
xy /= w;

dx2 = x2 - x * x;
dy2 = y2 - y * y;
dxy = xy - x * y;

t = (float) atan2( 2 * dxy, dx2 - dy2 ) / 2;
line[0] = (float) cos( t );
line[1] = (float) sin( t );

line[2] = (float) x;
line[3] = (float) y;
}

案例:直线拟合

有如下的原图:

通过OpenCV的距离变换,骨架提取,然后再直线拟合,使用DIST_L1得到的结果如下:

OpenCV-C++/Python视频教程30课时,请看B站:

https://www.bilibili.com/video/BV1hM4y1M7vQ (python版本)
https://www.bilibili.com/video/BV1i54y1m7tw (C++版本)


如果觉得有用,就请分享到朋友圈吧!

△点击卡片关注极市平台,获取最新CV干货

公众号后台回复“小目标检测”获取2021年小目标检测综述PDF~

极市干货
深度学习环境搭建:如何配置一台深度学习工作站?
实操教程:OpenVINO2021.4+YOLOX目标检测模型测试部署为什么你的显卡利用率总是0%?
算法技巧(trick):图像分类算法优化技巧21个深度学习调参的实用技巧



CV技术社群邀请函 #

△长按添加极市小助手
添加极市小助手微信(ID : cvmart4)

备注:姓名-学校/公司-研究方向-城市(如:小极-北大-目标检测-深圳)


即可申请加入极市目标检测/图像分割/工业检测/人脸/医学影像/3D/SLAM/自动驾驶/超分辨率/姿态估计/ReID/GAN/图像增强/OCR/视频理解等技术交流群


每月大咖直播分享、真实项目需求对接、求职内推、算法竞赛、干货资讯汇总、与 10000+来自港科大、北大、清华、中科院、CMU、腾讯、百度等名校名企视觉开发者互动交流~



觉得有用麻烦给个在看啦~  
浏览 141
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报