1.原理
多边形近似将物体的轮廓转为一系列的直线段,在此使用基于弦算法得到近似轮廓的多边形。就复杂度和所用内存空间而言,该算法不是最有效的,但具有易于实现和提供精确的逼近阈值控制的优点。弦算法通过迭代细分过程减少多边形轮廓的点数。分段[AB]表示A是起点,B是离A距离最远的弦,在A和B之间的轮廓上,寻找离弦最远的点M。如果M与弦之间的欧氏距离小于近似阈值,则停止对端[AB]的迭代过程,否则继续对段[AM]和[MB]的迭代过程。
备注来两个公式:
2.算法实现
多边形近似算法原理源自Douglas-Peucker算法,使用点-边距离作为误差衡量标准。该算法从连接原始Polyline的第一个和最后一个顶点的边开始,计算所有中间顶点到边的距离,距离该边最远的顶点,如果其距离大于指定的公差,将被标记为Key并添加到简化结果中。这个过程将对当前简化中的每条边进行递归,直到原始Polyline的所有顶点与当前考察的边的距离都在允许误差范围内。
//点到直线距离
float getDist_P2L(Point pointP,Point pointA,Point pointB)
{
//求直线方程
int A = 0,B = 0, C = 0;
A = pointA.y - pointB.y;
B = pointB.x - pointB.x;
C = pointA.x * pointB.y - pointA.y * pointB.x;
float distance = 0;
distance = abs(A * pointP.x + B*pointP.y + C)/aqrt(A*A+B*B);
return distance;
}
void PolygonFitting(vector<vector<Point>&contours,vector<vector<Point>>&contours1)
{
int T = 10;
Point E;
vector<Point>ResultContours;//近似结果轮廓
for(int j = 0;j < contours[0].size() / (4 * T);j++)
{
float H = 0;
for(int i=2*T+4*T*j;i<=3*T+4*T*j;i++)
{
float Distance = getDist_P2L(contours[0][i],contours[0][0+4*T*j],contours[0][4*T+4*T*j]);
if(Distance>H)
{
H = Distance;
E = contours[0][i];
}
}
if(H>1) //参考论文此处应为H>T
{
ResultContours.push_back(E);
}
}
//vector<vector<Point>>constours1;
contours1.push_back(ResultContours);
}
3.迭代端点拟合法
//top:在步长范围内,距离最大点的序号
//sum:参与计算的点的个数
//j: 步数*步长
void func(int top,int sum,int j,vector<vector<Point>>&contours)
{
Point LeftE,RightE; //左右两侧点与直线距离最大的点坐标
float LeftH=0,RightH=0; //左右两侧点与直线距离的最大值
int MaxValueIndexLeft,MaxValueIndexRight; //左右两侧点与直线最大距离的点的索引
for(int i=0;i<=top;i++)
{
//左侧距离计算
float DistanceLeft = getDist_P2L(contours[0][i + j], contours[0][0 + j], contours[0][top + j]);
if (DistanceLeft>LeftH)
{
LeftH = DistanceLeft;
LeftE = contours[0][i + j];
MaxValueIndexLeft = i;
}
//右侧距离计算
if (sum != top && i <= (sum - top))
{
float DistanceRight = getDist_P2L(contours[0][i + j + top], contours[0][top + j], contours[0][sum + j]);
if (DistanceRight>RightH)
{
RightH = DistanceRight;
RightE = contours[0][i + j + top];
MaxValueIndexRight = i;
}
}
}
if (LeftH>T)
{
ResultContours.push_back(LeftE);
fun(MaxValueIndexLeft, top, j, contours);
}
if (RightH>T && sum != top)
{
ResultContours.push_back(RightE);
fun(MaxValueIndexRight, top, j, contours);
}
}
void IterativeEndpointFitting(vector<vector<Point>>&contours)
{
for(int j=0;j<contours[0].size()-10;j+j+step)
{
func(step,step,j contours);
}
vector<vector<Point>>contours1;
contours1.push_back(ResultContours);
}
4.opencv近似
void OpencvFitting(vector<vector<Point>>&contours)
{
//逼近多边形曲线
vector<vector<Point>>poly(contours.size());
for(size_t t=0;t<contours.size();t++)
{
approxPolyDP(contours[t],poly[t],5,true);
}
}
附上主函数main
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
Mat src, binary, src_gray;
TickMeter tm;
/***** Opencv自带方法*****/
void OpencvFitting(vector<vector<Point>>& contours);
/***** 迭代端点拟合法*****/
float T = 0.9; //距离阈值
int step = 20; //步长
vector<Point>ResultContours;
void IterativeEndpointFitting(vector<vector<Point>>& contours);
void fun(int top, int sum, int j, vector<vector<Point>>& contours);
float getDist_P2L(Point pointP, Point pointA, Point pointB);
/***** 多边形拟合法*****/
void PolygonFitting(vector<vector<Point>>& contours);
int main(int argc,char** argv )
{
//读取图片
src = imread("3.jpg");
if (src.empty()){
printf("读取图像失败\n");
return -1;
}
imshow("输入图片",src);
//二值化
cvtColor(src,src_gray,COLOR_BGR2GRAY);
threshold(src_gray,binary,0,255,THRESH_BINARY|THRESH_OTSU);
//寻找轮廓,并获取坐标点集
vector<Vec4i>hireachy;
vector<vector<Point>> contours;
findContours(binary,contours,hireachy,RETR_TREE,CHAIN_APPROX_NONE,Point());
//Opencv自带方法
tm.start();
OpencvFitting(contours);
tm.stop();
cout << "Opencv自带方法运行时间:" << tm.getTimeMilli() << endl;
//迭代端点拟合法
tm.start();
IterativeEndpointFitting(contours);
tm.stop();
cout << "迭代端点拟合法运行时间:" << tm.getTimeMilli() << endl;
//多边形拟合法
tm.start();
PolygonFitting(contours);
tm.stop();
cout << "多边形拟合法运行时间:" << tm.getTimeMilli() << endl;
waitKey(0);
return 0;
}
参考:https://blog.csdn.net/jgj123321/article/details/93718088