运动控制平面坐标下对离散点的误差可控的过起点和终点的圆弧拟合算法-C语言实现

对于圆弧拟合算法是多种多样的,比较常规的是:最小二乘法圆弧拟合和双圆弧拟合。就工控领域而言,这里提出一种过起点/终点的误差可控的圆弧拟合算法。本算法基于最小二乘法圆弧拟合的基础上,实现误差可控,适用于连续顺序输出的轨迹拟合。
算法如下:
圆的标准方程:x^2+y^2+ax+by+c=0 (1)
起点坐标:(x0,y0) 终点坐标:(xn,yn)
将起点和终点坐标带入(1)式:
x0^2+y0^2+ax0+by0+c=0 (2)
xn^2+yn^2+axn+byn+c=0 (3)
(2) - (3) 可得:
(x0^2-xn^2)+(y0^2-yn^2)+a(x0-xn)+b(y0-yn)=0
当(y0-yn) != 0时,有:
b=[(x0^2-xn^2)+(y0^2-yn^2)+a(x0-yn)] / (yn-y0) (4)

( (2)*yn - (3)*y0 )可得:
(yn*x0^2-y0*xn^2)+(yn*y0^2-y0*yn^2)+a(x0*yn-xn*y0)+c(yn-y0)=0
当(yn-y0) != 0时,有:
c=[(yn*x0^2-y0*xn^2)+(yn*y0^2-y0*yn^2)+a(x0*yn-xn*y0)] / (y0-yn) (5)

令 D = [(x0^2-xn^2)+(y0^2-yn^2)]/(yn-y0)
E = (x0-xn)/(yn-y0)
F = [(yn*x0^2-y0*xn^2)+(yn*y0^2-y0*yn^2)]/(y0-yn)
G = (x0*yn-xn*y0)/(y0-yn)
则 b = D+a*E (6)
c = F+a*G (7)

令 Q(a,b,c) = ∑[(xi^2+yi^2+a*xi+b*yi+c)^2] (8)
将(6)和(7)代入(8)可得:
Q(a,b,c) = ∑[(xi^2+yi^2+a*xi+(D+a*E)*yi+(F+a*G))^2] (9)

让Q(a,b,c)对a求偏导可得:
∂ Q(a,b,c)/∂a = 2*∑[(xi^2+yi^2+a*xi+(D+a*E)yi+(F+a*G))(xi+E*yi+G)]
根据最小二乘法原理可知:∂ Q(a,b,c)/∂a = 0
即:
∑[(xi^2+yi^2+a*xi+(D+a*E)yi+(F+a*G))(xi+E*yi+G)] = 0
化简可得:
∑[(xi^2+yi^2+D*yi+F)(xi+E*yi+G)]+∑[a(xi+E*yi+G)^2] = 0 (10)

当∑[(xi+E*yi+G)^2] !=0时,由(10)式可得:
a = -∑[(xi^2+yi^2+D*yi+F)*(xi+E*yi+G)]/∑[(xi+E*yi+G)^2]
令Hi = xi+E*yi+G , J = ∑[(xi^2+yi^2+Dyi+F)*Hi]
则:a = - J/(∑Hi^2) (11)

整理后的拟合算法为:
圆弧方程:x^2+y^2+ax+by+c=0
D = [(x0^2-xn^2)+(y0^2-yn^2)]/(yn-y0)
E = (x0-xn)/(yn-y0)
F = [(yn*x0^2-y0*xn^2)+(yn*y0^2-y0*yn^2)]/(y0-yn)
G = (x0*yn-xn*y0)/(y0-yn)
Hi = xi+E*yi+G
J = ∑[(xi^2+yi^2+Dyi+F)*Hi]
a = - J/(∑Hi^2)
b = D+a*E
c = F+a*G
由上可知,当(yn-y0) != 0时,可先行算出a的值,再根据式(6)、(7)可算出b和c,最终算得拟合的圆弧方程。

当(yn-y0) = 0时,(2) - (3) 有:
(x0^2-xn^2)+(y0^2-yn^2)+a(x0-xn) = 0 (12)
在(12)中,当(x0-xn) != 0 时有:
a = [(x0^2-xn^2)+(y0^2-yn^2)]/(xn-x0) (13)
由(8)式和最小二乘法原理可得:
∂ Q(a,b,c)/∂b = 0
∂ Q(a,b,c)/∂c = 0
即:
∂ Q(a,b,c)/∂b = ∑[(xi^2+yi^2+a*xi+b*yi+c)*yi] = 0 (14)
∂ Q(a,b,c)/∂c = ∑(xi^2+yi^2+a*xi+b*yi+c) = 0 (15)
由(15)可得:
c = - [ ∑(xi^2+yi^2+a*xi+b*yi) ]/n (16)
将(16)代入(14)可求得:
当∑(yi^2 * (1/n - 1)) != 0时,有:
b = [ ∑(xi^2+yi^2+a*xi)(1-1/n)*yi ] / [ ∑(yi^2 (1/n - 1)) ] (17)

令:D = xi^2+yi^2+a*xi
E = (1-1/n)*yi
F = E*yi
则有:
b = ∑(D*E) / ∑(E*F)
C = - ∑(D+b*yi) / n

整理后的拟合算法为:
圆弧方程:x^2+y^2+ax+by+c=0
a = [(x0^2-xn^2)+(y0^2-yn^2)]/(xn-x0)
D = xi^2+yi^2+a*xi
E = (1-1/n)*yi
F = E*yi
b = ∑(D*E) / ∑(E*F)
C = - ∑(D+b*yi) / n
由上可知,当(yn-y0) = 0时,可先行算出a和b的值,再根据式(16)可算出c,最终算得拟合的圆弧方程。
如需要计算圆心坐标和半径可根据以下公式计算:
A = - a / 2
B = - b / 2
R =√(a*a+b*b-4*c) / 2
对于工控行业而言,最重要的是误差可控,故对圆弧拟合的结果进行误差统计,即:将每个离散点的坐标输出误差公式:
√| (xi - A)^2 + (yi - B)^2 - R | > max_dev
其中max_dev为最小允许拟合误差。当拟合误差超过最小允许拟合误差时,需减少一个拟合离散点,重新进行拟合运算,直至误差在允许范围内,或者离散点总数少于3个。
注意:当待拟合线段长度过长时,则该线段的中点也应代入误差公式进行误差统计,以防止误差过大。
下面为圆弧拟合算法的C语言实现:

typedef struct{
    float x;
    float y;
}COORDINATE;

typedef enum{
    LINE,
    ARC,
}FIT_TYPE;

typedef struct{
    FIT_TYPE type;//拟合结果
    float st_x;//起点坐标x
    float st_y;//起点坐标y
    float ed_x;//终点坐标x
    float ed_y;//终点坐标y
    float arc_x;//圆心坐标x
    float arc_y;//圆心坐标y
    float R;
}CIRCLE;
#define coordinate_SIZE     (20u)//缓冲器大小
#define MAX_DEV             (0.02F)//最大允许拟合误差

uint8_t cur_idx= 0;//记录当前拟合缓冲器中的索引
uint8_t first_idx = 0;//记录前拟合缓冲器中的第一段索引
uint8_t poinsum = 0;

COORDINATE coordinate[coordinate_SIZE];

static uint8_t Next_coordinate(uint8_t idx)
{//环形缓冲器向前计数
  idx++;
  if (idx == coordinate_SIZE) { idx = 0; }
    return idx;
}

static uint8_t Next_n_coordinate(uint8_t idx, uint8_t n)
{
    uint8_t i = 0;
  for(;i<n;i++){
        idx = Next_coordinate(idx);
    }
    return idx;
}

static uint8_t Prev_coordinate(uint8_t idx)
{//环形缓冲器向后计数
  if (idx == 0) { idx = coordinate_SIZE; }
  idx--;
    return idx;
}

static bool least_square_method_circle_fitting(uint8_t Idx , uint8_t num, CIRCLE *arc)
{
    uint8_t i = 0;
    uint8_t cur_idx = Idx;
    uint8_t next_idx = Next_coordinate(Idx);
    float a,b,c,D,E,F,G,Hi,J,K;
    float A,B,R;
    float x0,y0,xn,yn;
    float x0_pow,y0_pow,xn_pow,yn_pow;

    if(num<3){//因为是线段,故最少点数不少于2个
        arc->type = LINE;
        arc->st_x= coordinate[Idx].x;
        arc->st_y= coordinate[Idx].y;
        arc->ed_x= coordinate[next_idx].x;
        arc->ed_y= coordinate[next_idx].y;
        return ture;
    }
    for(i=0;i<num;i++){
        K = coordinate[next_idx].x - coordinate[cur_idx].x;
        if(K!=(0.0f)){
            K=(coordinate[next_idx].y - coordinate[cur_idx].y)/K;
            break;
        }
        else {
            cur_idx = next_idx;
            next_idx = Next_coordinate(next_idx );
        }
        if(i == (num-1)){//所有点共线,且与Y轴平行
            arc->type = LINE;
            arc->st_x= coordinate[Idx].x;
            arc->st_y= coordinate[Idx].y;
            arc->ed_x= coordinate[next_idx].x;
            arc->ed_y= coordinate[next_idx].y;
            return ture;
        }
    }
    cur_idx = Idx;
    next_idx = Next_coordinate(Idx);
    if(i==0){
        for(i=1;i<num;i++){
            cur_idx = Next_coordinate(cur_idx);
            next_idx = Next_coordinate(cur_idx);
            D = coordinate[next_idx].y - coordinate[cur_idx].y;
            D /= coordinate[next_idx].x - coordinate[cur_idx].x;
            if(K != D) break;
            if(i == (num-1)){//所有点共线,且不与Y轴平行
                arc->type = LINE;
                arc->st_x= coordinate[Idx].x;
                arc->st_y= coordinate[Idx].y;
                arc->ed_x= coordinate[next_idx].x;
                arc->ed_y= coordinate[next_idx].y;
                return ture;
            }
        }
    }
    cur_idx = Idx;
    x0 = coordinate[cur_idx].x;
    y0 = coordinate[cur_idx].y;
    xn = coordinate[Next_n_coordinate(cur_idx, num-1)].x;
    yn = coordinate[Next_n_coordinate(cur_idx, num-1)].y;
    x0_pow = x0*x0;
    y0_pow = y0*y0;
    xn_pow = xn*xn;
    yn_pow = yn*yn;
    if(y0 != yn){
        D = ((x0_pow-xn_pow)+(y0_pow-yn_pow))/(yn - y0);
        E = (x0 - xn)/(yn - y0);
        F = (((yn*x0_pow) - y0*xn_pow)+((yn*y0_pow)-(y0*yn_pow)))/(y0 - yn);
        G = ((x0*yn) - (xn*y0))/(y0 - yn);
        for(i=0;i<num;i++){
            Hi = (coordinate[cur_idx].x + E*coordinate[cur_idx].y + G);
            Hi_pow += Hi*Hi;
            J += ((coordinate[cur_idx].x*coordinate[cur_idx].x + coordinate[cur_idx].y*coordinate[cur_idx].y + D*coordinate[cur_idx].y + F)*Hi);
            cur_idx = Next_coordinate(cur_idx);
        }
        a = -1*(J/Hi_pow);
        b = D + a*E;
        c = F + a*G;
    }else{
        a = ((x0_pow-xn_pow)+(y0_pow-yn_pow))/(xn - x0);
        G = 0;
        Hi = 0;
        for(i=0;i<num;i++){
            D = pow(coordinate[cur_idx].x,2)+pow(coordinate[cur_idx].y,2)+a*coordinate[cur_idx].x;
            E = (1-num)*coordinate[cur_idx].y;
            F = E*coordinate[cur_idx].y;
            G += D*E;
            Hi+= E*f;
            cur_idx = Next_coordinate(cur_idx);
        }
        b = G/Hi;
        cur_idx = Idx;
        D = 0;
        E = 0;
        for(i=0;i<num;i++){
            D += pow(coordinate[cur_idx].x,2)+pow(coordinate[cur_idx].y,2)+a*coordinate[cur_idx].x;
            E += b*coordinate[cur_idx].y;
        }
        c = -(D+E)/num;
    }
    cur_idx = Next_n_coordinate(Idx, num-1)
    arc->type = ARC;
    arc->st_x= coordinate[Idx].x;
    arc->st_y= coordinate[Idx].y;
    arc->ed_x= coordinate[cur_idx ].x;
    arc->ed_y= coordinate[cur_idx ].y;
    arc->arc_x = a/(-2); 
    arc->arc_y = b/(-2); 
    arc->R = sqrt(a*a+b*b-4*c)/2;
    return check_deviation(Idx , num, arc);//误差检测
}

//误差检测
static bool check_deviation(uint8_t Idx , uint8_t num, CIRCLE *arc)
{
    uint8_t i;
    uint8_t cur_idx = Idx ;
    uint8_t dec_idx = Idx ;
    float dev = 0;
    float x,y;

    for(i=1; i<num; i++){
        cur_idx = Next_Arc_Fit_Buff(cur_idx );
        dev = pow(coordinate[cur_idx].x-arc->arc_x,2);
        dev += pow(coordinate[cur_idx].y-arc->arc_y,2);
        dev =sqrt(fabs(dev));
        if(dev > MAX_DEV) retrun false;
        x = (coordinate[cur_idx].x + coordinate[dec_idx ].x)/2.0;
        y = (coordinate[cur_idx].x + coordinate[dec_idx ].x)/2.0;
        dev = pow(x*x-arc->arc_x,2);
        dev += pow(y*y-arc->arc_y,2);
        dev =sqrt(fabs(dev));
        if(dev > MAX_DEV) retrun false;
        dec_idx = cur_idx ;
    }
    return true;
} 

//圆弧拟合函数
//每次输入一个坐标,当数据结束时应使isFinish = true, arc为拟合结果,sursum为剩余的拟合点数
bool Arc_Fitting(float x, float y, bool isFinish, CIRCLE *arc, uint8_t *sursum)
{
    coordinate[cur_num].x = x;
    coordinate[cur_num].y = y;
    bool res = false;
    uint8_t sum = 0;
    poinsum++;
    cur_idx= Next_coordinate(cur_num);
    if(cur_idx== first_idx || isFinish == ture){
        res = least_square_method_circle_fitting(first_idx, poinsum, arc);
        sum = poinsum;
        while(res != true && sum >= 2) {
            sum--;
            res = least_square_method_circle_fitting(first_idx, sum, arc);
        }
        first_idx = Next_n_coordinate(first_idx,sum);
        *sursum = poinsum - sum ;
        return res;
    }else return false;
}

拟合结果如下:
这里写图片描述

放大后观察1:
这里写图片描述

放大后观察2:
这里写图片描述
由上可看出拟合效果相当可观,当然仍有许多考虑不全的地方,在实际项目应用中,根据项目需求改变算法计算的复杂度,和节省所占的堆栈空间。后期将提出一种圆弧方向的判别方法,以及对双圆弧拟合进行总结,采用双圆弧拟合将更大程度的圆滑轨迹,减少设备抖动。
结语:初来乍到,仍有许多不足的地方,希望各位看官勿喷!

  • 6
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
### 回答1: 我可以提供一段MATLAB代码,它可以实现nurbs曲线对一系列离散最小二乘法拟合:% 定义一系列离散 P=[x1 y1;x2 y2;...;xn yn]; % 计算nurbs曲线的控制 n=length(P); U=zeros(1,n); for i=2:n-1 U(i)=U(i-1)+sqrt((P(i,1)-P(i-1,1))^2+(P(i,2)-P(i-1,2))^2); end U=U/U(n); C=zeros(n,2); for i=1:n C(i,1)=P(i,1); C(i,2)=P(i,2); end knots=zeros(1,n+4); for i=2:n+3 knots(i)=U(i-1); end knots(1)=0; knots(n+4)=1; % 通过控制、节值矩阵和次数构建nurbs曲线 curve = nrbmak(C,knots); % 调用nrbdeval函数进行最小二乘法拟合 ts=linspace(0,1,100); Q=nrbdeval(curve,ts); % 绘制原始数据及拟合曲线 plot(P(:,1),P(:,2),'*'); hold on; plot(Q(1,:),Q(2,:)); hold off; ### 回答2: nurbs曲线是一种可以灵活调整控制和权重的曲线模型,可以用于拟合一系列离散。下面是用MATLAB实现nurbs曲线对离散进行最小二乘法拟合的代码: ```MATLAB % 输入离散数据(假设为二维数据,x和y分别表示离散的横纵坐标) x = [x1, x2, ..., xn]; y = [y1, y2, ..., yn]; % 设置nurbs曲线的阶数(假设为3阶) order = 3; % 设置nurbs曲线的控制(可以初始化为离散数据) control_pts = [x; y]; % 设置权重矩阵(可以初始化为全1矩阵) weights = ones(1, n); % 构建最小二乘法拟合矩阵 A = zeros(n, n); b = zeros(n, 1); for i = 1:n for j = 1:n % 计算曲线的基函数值 basisij = basis_function(order, control_pts, weights, x(j)); % 构建最小二乘法系数矩阵 A(i, j) = basisij; end % 构建最小二乘法常数项 b(i) = y(i); end % 求解最小二乘法拟合曲线的控制和权重 x_fit = A \ b; control_pts_fit = control_pts; control_pts_fit(2,:) = x_fit; % 计算拟合曲线上的离散 y_fit = zeros(1, n); for i = 1:n y_fit(i) = basis_function(order, control_pts_fit, weights, x(i)); end % 绘制原始数据拟合曲线 plot(x, y, 'ro', 'MarkerSize', 5); hold on; plot(x, y_fit, 'b-', 'LineWidth', 2); xlabel('x'); ylabel('y'); legend('原始数据', '拟合曲线'); function basis = basis_function(order, control_pts, weights, x) % 计算nurbs曲线的基函数值 n = size(control_pts, 2); basis = zeros(1, n); for i = 1:n % 计算曲线的齐次坐标系值 homo = basis_function_homo(order, control_pts, weights, i, x); % 计算曲线的基函数值 basis(i) = homo * weights' / sum(weights); end end function homo = basis_function_homo(order, control_pts, weights, i, x) % 计算nurbs曲线的齐次坐标系值 n = size(control_pts, 2); homo = 0; for j = i-order:i % 处理边界情况 if j > 0 && j <= n basis_val = basis_function_val(order, control_pts, weights, j, x); homo = homo + basis_val * weights(j); end end end function val = basis_function_val(order, control_pts, weights, i, x) % 计算nurbs曲线的基函数值 n = size(control_pts, 2); if order == 1 % 一阶基函数 if x >= control_pts(i) && x < control_pts(i+1) val = 1; else val = 0; end else % 多阶基函数 val = (x - control_pts(i)) / (control_pts(i+order-1) - control_pts(i))... * basis_function_val(order-1, control_pts, weights, i, x)... + (control_pts(i+order) - x) / (control_pts(i+order) - control_pts(i+1))... * basis_function_val(order-1, control_pts, weights, i+1, x); end end ``` 这段代码实现了一个函数,其中包含了对nurbs曲线进行最小二乘法拟合的过程。输入离散数据后,首先设置nurbs曲线的阶数和控制,并初始化权重矩阵。然后根据离散数据构建最小二乘法拟合矩阵,通过求解得到拟合曲线的控制和权重。最后计算拟合曲线上的离散,并绘制原始数据拟合曲线。函数中还包含了辅助函数用于计算nurbs曲线的基函数值及相应的齐次坐标系值。 ### 回答3: 下面是一个使用MATLAB实现nurbs曲线对一系列离散进行最小二乘法拟合的代码: ```matlab % 输入离散的坐标(x和y) x = [1 2 3 4 5]; y = [1 3 4 3 1]; % 设置nurbs曲线的控制个数和次数 n = 4; % 控制个数 p = 3; % 次数 % 创建nurbs曲线参数矩阵 knots = [zeros(1,p), linspace(0,1,n-p+1), ones(1,p)]; t = linspace(knots(p+1),knots(n+1),100); % 在参数空间内生成等间距的 % 构建最小二乘问题的系数矩阵和常数向量 A = zeros(length(x), n+p+1); for i = 1:length(x) A(i,:) = NURBSBasis(i-1,p,t,knots)*NURBSBasisCoeff(i-1,p,x); % NURBSBasis(k,p,t,knots)计算第k个基函数在参数t处的值 % NURBSBasisCoeff(k,p,x)计算第k个基函数在x处的值 end b = y'; % 求解最小二乘问题得到控制的权重向量 w = A\b; % 计算nurbs曲线上的 curve = zeros(size(t)); for i = 1:length(t) basis = NURBSBasis(i-1,p,t,knots); curve(i) = sum(w'.*basis); end % 绘制原始离散拟合曲线 plot(x, y, 'ro', 'MarkerSize', 5); % 绘制离散 hold on; plot(curve, 'b', 'LineWidth', 1.5); % 绘制拟合曲线 hold off; % 定义NURBSBasis函数,用于计算nurbs曲线的基函数 function value = NURBSBasis(i, p, t, knots) if p == 0 value = double(t>=knots(i+1) & t<knots(i+2)); else value = (t-knots(i+1))/(knots(i+p+1)-knots(i+1))*NURBSBasis(i,p-1,t,knots) ... + (knots(i+p+2)-t)/(knots(i+p+2)-knots(i+2))*NURBSBasis(i+1,p-1,t,knots); end end % 定义NURBSBasisCoeff函数,用于计算nurbs曲线的基函数的系数 function value = NURBSBasisCoeff(i, p, x) if p == 0 value = double(x==i); else value = (x-i)/(i+p)*NURBSBasisCoeff(i,p-1,x) ... + (i+p+1-x)/(i+p+1-i)*NURBSBasisCoeff(i+1,p-1,x); end end ``` 该代码可以根据输入的离散坐标,使用nurbs曲线对这些离散进行最小二乘法拟合,并绘制出拟合曲线和离散的图形。输入的离散坐标保存在`x`和`y`中,其中`x`是x坐标的值,`y`是y坐标的值。 代码中的关键步骤是构建最小二乘问题的系数矩阵和常数向量。系数矩阵`A`的每一行代表了一个离散在nurbs曲线上的基函数值的系数,常数向量`b`保存了离散的y坐标。然后,通过求解最小二乘问题,可以得到控制的权重向量`w`。 最后,使用权重向量`w`和nurbs曲线的基函数,可以计算出nurbs曲线上的,并将原始离散拟合曲线绘制出来。 请注意,该代码中使用了两个自定义的函数`NURBSBasis`和`NURBSBasisCoeff`,用于计算nurbs曲线的基函数和基函数的系数。这些函数分别定义在代码的最后两个部分。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值