用C++实现最小二乘法,最小区域法,最小包容圆拟合圆的算法和在Qt中的运行结果

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Science_Tec/article/details/102764642

用C++实现最小二乘法,最小区域法,最小包容圆拟合圆的算法和在Qt中的运行结果

我们工科生经常要处理数据,大家在处理数据的过程中遇到圆拟合的问题,一般会用到最小包容圆,最小外接圆,最小二乘拟合圆,最小区域法(遗传算法,粒子群算法),这里为了方便大家参考,提供C++实现最小二乘法,最小包容圆法,最小区域法(粒子群算法)的算法。话不多说,先上在Qt中的运行结果:

如图红色的点是随机生成的,图一中随机点在以原点为圆心,半径为10的圆内随机生成。其余两个图中点的范围在以原点为圆心,半径为9.5和10.5的同心圆范围内。黑色的线为拟合的圆。可以看到最小区域法的拟合比较精确。

下面附上三个算法代码的源文件和头文件(Qt中的代码,和C++稍微有一点不同,主要是为了可视化。大家可以根据需要自行修改。)

#ifndef LEASTSQUAREMETHODFITTINGCIRCLE_H
#define LEASTSQUAREMETHODFITTINGCIRCLE_H

#include <QList>

typedef struct pointInfo
{
    double x;
    double y;
    double radius;
}pointInfo;

pointInfo LSFit(QList<pointInfo> &Point);   //最小二乘圆拟合函数

#endif // LEASTSQUAREMETHODFITTINGCIRCLE_H
#include "leastsquaremethodfittingcircle.h"

/* 最小二乘法圆拟合函数 */
pointInfo LSFit(QList<pointInfo> &Point)
{

    pointInfo tempPoint;        //定义临时变量,存储最小二乘圆心坐标

    double X1=0,Y1=0,X2=0,Y2=0,X3=0,Y3=0;
    double X1Y1=0,X1Y2=0,X2Y1=0;
    int N=Point.size();

    for(int i=0;i<Point.size();i++)
    {
        double x=Point.at(i).x;
        double y=Point.at(i).y;
        double x2=x*x,y2=y*y;
        X1+=x;
        Y1+=y;
        X2+=x2;
        Y2+=y2;
        X3+=x*x2;
        Y3+=y*y2;
        X1Y1+=x*y;
        X1Y2+=x*y2;
        X2Y1+=x2*y;
    }

    double a=0,b=0,c=0;
    double C1=0,D1=0,E1=0,G1=0,H1=0;

    C1=N*X2-X1*X1;
    D1=N*X1Y1-X1*Y1;
    E1=N*X3+N*X1Y2-(X2+Y2)*X1;
    G1=N*Y2-Y1*Y1;
    H1=N*X2Y1+N*Y3-(X2+Y2)*Y1;

    a=(H1*D1-E1*G1)/(C1*G1-D1*D1);
    b=(H1*C1-E1*D1)/(D1*D1-G1*C1);
    c=-(a*X1+b*Y1+X2+Y2)/N;

    tempPoint.x=a/(-2);
    tempPoint.y=b/(-2);
    tempPoint.radius=(sqrt(a*a+b*b-4*c))/2;

    return tempPoint;
}

最小包容圆

#ifndef MINIMALENCLOSINGCIRCLE_H
#define MINIMALENCLOSINGCIRCLE_H

#include <QList>
#include <algorithm>
#include <qmath.h>
#include <random>

struct Point
{
    double x;
    double y;

    Point subtract(const Point &p) const;

    double distance(const Point &p) const;

    double cross(const Point &p) const;// Signed area / determinant thing
};

struct Circle
{
public:
    static const Circle INVALID;
    Point c;
    double r;

    bool contains(const Point &p) const;

private:
    static const double MULTIPLICATIVE_EPSILON;
};

 Circle MakeSmallestEnclosingCircle(QList<Point> &points);

 Circle MakeSmallestEnclosingCircleOnePoint(QList<Point> &points, int end, Point &p);

 Circle MakeSmallestEnclosingCircleTwoPoints(QList<Point> &points, int end, Point &p, Point &q);

 Circle MakeDiameter(const Point &a, const Point &b);

 Circle MakeCircumcircle(const Point &a, const Point &b, const Point &c);

#endif // MINIMALENCLOSINGCIRCLE_H
#include "minimalenclosingcircle.h"

/* 求取坐标差值函数 */
Point Point::subtract(const Point &p) const
{
    return Point{x - p.x, y - p.y};      //q点与P点横纵坐标的差值
}

/* 求取两点间距离函数 */
double Point::distance(const Point &p) const
{
    //hypot求斜边的长度,返回两点的距离值
    return hypot(x - p.x,y-p.y);         //求p点的坐标与圆c的圆心的距离
}

/* 求向量的叉乘 */
double Point::cross(const Point &p) const
{
    return x * p.y - y * p.x;
}

/*定义INVALID*/
const Circle Circle::INVALID{Point{0, 0}, -1};

/* 定义MULTIPLICATIVE_EPSILON */
const double Circle::MULTIPLICATIVE_EPSILON = 1 + 1e-14;

bool Circle::contains(const Point &p) const
{
    return c.distance(p) <= r * MULTIPLICATIVE_EPSILON; //r为圆c的半径,如果距离点p到圆c的距离小于半径(也就是说,点p在圆c内)返回真
}

/*初始化圆没有边界,圆为坐标(0,0),半径-1*/
Circle MakeSmallestEnclosingCircle(QList<Point> &points)
{
    QList<Point> shuffled=points;
    Circle c(Circle::INVALID);           //初始化为坐标(0,0)  半径为-1的圆   显然不存在{Point{0, 0}, -1};

    for (int i = 0; i < shuffled.size(); i++)
    {
        Point p = shuffled.at(i);
        if (c.r < 0 || !c.contains(p))   //如果半径小于0或者点p不在圆c内,执行下面的语句。不在圆内就有了第一个点
        {
            c = MakeSmallestEnclosingCircleOnePoint(shuffled, i + 1, p);
        }
    }
    return c;
}

/*知道了圆的一个点,要确定第二个点*/
Circle MakeSmallestEnclosingCircleOnePoint(QList<Point> &points, int end, Point &p)
{
    Circle c{p, 0};
    for (int i = 0; i < end; i++)
    {
        Point q = points.at(i);
        if (!c.contains(q))            //p不在圆c内就执行以下语句,有了第二个点
        {
            if (c.r == 0)
                c = MakeDiameter(p, q);//第一次的r等于0.以p,q两点为直径当做圆
            else
            {
                c = MakeSmallestEnclosingCircleTwoPoints(points, i + 1, p, q);
            }
        }
    }
    return c;
}

/* 知道了圆的两个点 */
Circle MakeSmallestEnclosingCircleTwoPoints(QList<Point> &points, int end,  Point &p,  Point &q)
{
    Circle circ = MakeDiameter(p, q);
    Circle left = Circle::INVALID;
    Circle right = Circle::INVALID;

    //有任意一点不在以p和q为圆的内部
    Point pq = q.subtract(p);
    for (int i = 0; i < end; i++)
    {
        const Point &r = points.at(i);
        if (circ.contains(r))//如果以圆p,q为直径的圆 包括点r  跳出这次循环
        {
            continue;
        }

        //找一个外接圆,并在左边或右边分类
        double cross = pq.cross(r.subtract(p));//可以用向量的方式判断第三点在q的左边还是右边
        Circle c = MakeCircumcircle(p, q, r);
        if (c.r < 0)
        {
            continue;
        }
        else if (cross > 0 && (left.r < 0 || pq.cross(c.c.subtract(p)) > pq.cross(left.c.subtract(p))))//点r在q的左边
        {
            left = c;
        }
        else if (cross < 0 && (right.r < 0 || pq.cross(c.c.subtract(p)) < pq.cross(right.c.subtract(p))))//点r在q的右边
        {
            right = c;
        }
    }

    //选择返回哪个圆
    if (left.r < 0 && right.r < 0)
        return circ;
    else if (left.r < 0)
        return right;
    else if (right.r < 0)
        return left;
    else
        return left.r <= right.r ? left : right;//返回半径较小的那个圆
}

/*两点为直径画圆*/
Circle MakeDiameter(const Point &a, const Point &b)
{
    Point c{(a.x + b.x) / 2, (a.y + b.y) / 2};//求 a,b两点连线的中点,当做圆心
    return Circle{c, qMax(c.distance(a),c.distance(b))};//返回较大值
}

/*三点确定一个圆*/
Circle MakeCircumcircle(const Point &a, const Point &b, const Point &c)
{
    //数学上的外接圆算法
    double ox= (qMin(qMin(a.x,b.x),c.x)+qMax(qMin(a.x,b.x),c.x))/2;//求一条连线的中点
    double oy= (qMin(qMin(a.y,b.y),c.y)+qMax(qMin(a.y,b.y),c.y))/2;

    double ax = a.x - ox, ay = a.y - oy;
    double bx = b.x - ox, by = b.y - oy;
    double cx = c.x - ox, cy = c.y - oy;
    double d = (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) * 2;
    if (d == 0)
        return Circle::INVALID;
    double x = ((ax * ax + ay * ay) * (by - cy) + (bx * bx + by * by) * (cy - ay) + (cx * cx + cy * cy) * (ay - by)) / d;
    double y = ((ax * ax + ay * ay) * (cx - bx) + (bx * bx + by * by) * (ax - cx) + (cx * cx + cy * cy) * (bx - ax)) / d;
    Point p{ox + x, oy + y};
    double r=qMax(qMax(p.distance(a),p.distance(b)),p.distance(c));
    return Circle{p, r};
}

当然最后一个比较复杂,代码质量不太好,答主还在调试优化。有兴趣可以关注答主微信公众号,不定时分享一些关于Qt知识。也可以一起讨论。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/Science_Tec/article/details/102764642