很久没更新blog了,主要最近开发任务有点重,然而最近需要开发一个拟合工具箱,所以才会有这一篇blog,废话不多说,进入正题。(磨刀不误砍柴功,省的后面开发不好挨骂)
首先先整理一下参考链接:(包括最小二乘法(下一章的参考链接))
opencv的曲线拟合polyfit:
opencv多边形拟合曲线approxPolyDP()函数:
【OpenCV】多项式方程求解(PolySolver):
OpenCV曲线拟合与圆拟合:https://blog.51cto.com/gloomyfish/2128313?source=dra
【OpenCV3】直线拟合——cv::fitLine()详解:
【算法+OpenCV】基于opencv的直线和曲线拟合与绘制(最小二乘法):【算法+OpenCV】基于opencv的直线和曲线拟合与绘制(最小二乘法):
最小二乘法多项式曲线拟合原理与实现:
OpenCV—椭圆拟合fitEllipse:
OpenCV中approxPolyDP()函数:
【算法+OpenCV】基于三次Bezier原理的曲线拟合算法C++与OpenCV实现:
opencv自带函数拟合
(1)直线拟合
在图像处理中,通常会遇到根据给定的点集(比如轮廓)拟合出一条直线的情形。opencv2和opencv3中提供了一个专门用于直线拟合的函数——cv::fitLine()。
cv::fitLine()的具体调用形式如下:
void cv::fitLine(
cv::InputArray points, // 二维点的数组或vector
cv::OutputArray line, // 输出直线,Vec4f (2d)或Vec6f (3d)的vector
int distType, // 距离类型
double param, // 距离参数
double reps, // 径向的精度参数
double aeps // 角度精度参数
);
第一个参数是用于拟合直线的输入点集,可以是二维点的cv::Mat数组,也可以是二维点的STL vector。
第二个参数是输出的直线,对于二维直线而言类型为cv::Vec4f,对于三维直线类型则是cv::Vec6f,输出参数的前半部分给出的是直线的方向,而后半部分给出的是直线上的一点(即通常所说的点斜式直线)。
第三个参数是距离类型,拟合直线时,要使输入点到拟合直线的距离和最小化(即下面公式中的cost最小化),可供选的距离类型如下表所示,ri表示的是输入的点到直线的距离。
第四个参数是距离参数,跟所选的距离类型有关,值可以设置为0,cv::fitLine()函数本身会自动选择最优化的值。
第五、六两个参数用于表示拟合直线所需要的径向和角度精度,通常情况下两个值均被设定为1e-2。
//创建一个用于绘制图像的空白图
cv::Mat image = cv::Mat::zeros(480, 640, CV_8UC3);
//输入拟合点
std::vector<cv::Point> points;
points.push_back(cv::Point(48, 58));
points.push_back(cv::Point(105, 98));
points.push_back(cv::Point(155, 160));
points.push_back(cv::Point(212, 220));
points.push_back(cv::Point(248, 260));
points.push_back(cv::Point(320, 300));
points.push_back(cv::Point(350, 360));
points.push_back(cv::Point(412, 400));
//将拟合点绘制到空白图上
for (int i = 0; i < points.size(); i++)
{
cv::circle(image, points[i], 5, cv::Scalar(0, 0, 255), 2, 8, 0);
}
cv::Vec4f line_para;
cv::fitLine(points, line_para, cv::DIST_L2, 0, 1e-2, 1e-2);
std::cout << "line_para = " << line_para << std::endl;
//获取点斜式的点和斜率
cv::Point point0;
point0.x = line_para[2];
point0.y = line_para[3];
double k = line_para[1] / line_para[0];
//计算直线的端点(y = k(x - x0) + y0)
cv::Point point1, point2;
point1.x = 0;
point1.y = k * (0 - point0.x) + point0.y;
point2.x = 640;
point2.y = k * (640 - point0.x) + point0.y;
cv::line(image, point1, point2, cv::Scalar(0, 255, 0), 2, 8, 0);
cv::imshow("image", image);
cv::waitKey(0);
return;
拟合效果如下:
(2)椭圆拟合
OpenCV中提供的椭圆拟合API如下:
RotatedRect fitEllipse(InputArray points)
输入:二维点集,要求拟合的点至少为6个点。存储在std::vector<>
or Mat
处理:该函数使用的是最小二乘的方法进行拟合的。
输出:RotatedRect 类型的矩形,是拟合出椭圆的最小外接矩形。
对应的参数可参见下图:
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
int main( )
{
const char* filename = "rect-45度.bmp";
Mat src_image = imread(filename, 0);
if( src_image.empty() )
{
cout << "Couldn't open image!" << filename;
return 0;
}
imshow("原图", src_image);
//轮廓
vector<vector<Point>> contours;
//使用canny检测出边缘
Mat edge_image;
Canny(src_image,edge_image,30,70);
imshow("canny边缘",edge_image);
//边缘追踪,没有存储边缘的组织结构
findContours(edge_image, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
Mat cimage = Mat::zeros(edge_image.size(), CV_8UC3);
for(size_t i = 0; i < contours.size(); i++)
{
//拟合的点至少为6
size_t count = contours[i].size();
if( count < 6 )
continue;
//椭圆拟合
RotatedRect box = fitEllipse(contours[i]);
//如果长宽比大于30,则排除,不做拟合
if( MAX(box.size.width, box.size.height) > MIN(box.size.width, box.size.height)*30 )
continue;
//画出追踪出的轮廓
drawContours(cimage, contours, (int)i, Scalar::all(255), 1, 8);
//画出拟合的椭圆
ellipse(cimage, box, Scalar(0,0,255), 1, CV_AA);
}
imshow("拟合结果", cimage);
waitKey();
return 0;
}
(3)多边形拟合
approxPolyDP()函数是opencv中对指定的点集进行多边形逼近的函数,其逼近的精度可通过参数设置。
对应的函数为:
void approxPolyDP(InputArray curve, OutputArray approxCurve, double epsilon, bool closed);
例如:approxPolyDP(contourMat, approxCurve, 10, true);//找出轮廓的多边形拟合曲线
第一个参数 InputArray curve:输入的点集
第二个参数OutputArray approxCurve:输出的点集,当前点集是能最小包容指定点集的。画出来即是一个多边形。
第三个参数double epsilon:指定的精度,也即是原始曲线与近似曲线之间的最大距离。
第四个参数bool closed:若为true,则说明近似曲线是闭合的;反之,若为false,则断开。
该函数采用是道格拉斯-普克算法(Douglas-Peucker)来实现。该算法也以Douglas-Peucker算法和迭代终点拟合算法为名。是将曲线近似表示为一系列点,并减少点的数量的一种算法。该算法的原始类型分别由乌尔斯·拉默(Urs Ramer)于1972年以及大卫·道格拉斯(David Douglas)和托马斯·普克(Thomas Peucker)于1973年提出,并在之后的数十年中由其他学者予以完善。
经典的Douglas-Peucker算法描述如下:
(1)在曲线首尾两点A,B之间连接一条直线AB,该直线为曲线的弦;
(2)得到曲线上离该直线段距离最大的点C,计算其与AB的距离d;
(3)比较该距离与预先给定的阈值threshold的大小,如果小于threshold,则该直线段作为曲线的近似,该段曲线处理完毕。
(4)如果距离大于阈值,则用C将曲线分为两段AC和BC,并分别对两段取信进行1~3的处理。
(5)当所有曲线都处理完毕时,依次连接各个分割点形成的折线,即可以作为曲线的近似。