基于代数距离的椭圆拟合

问题

给定离散点集 X i = ( x i , y i ) , i = 1 , 2 , . . . N X_i=(x_i,y_i) ,i=1,2,...N Xi=(xi,yi),i=1,2,...N,我们希望找到误差最小的椭圆去拟合这些离散点。

方法

由于椭圆的形式可以给定, 自然我们将使用最小二乘法来求解椭圆。主要依据论文《Direct least squares fitting of ellipsees, Fitzgibbon, Pilu and Fischer in Fitzgibbon, A.W., Pilu, M., and Fischer R.B.,Proc. of the 13th Internation Conference on Pattern Recognition, pp 253–257, Vienna, 1996》。

椭圆拟合的难点

通常我们使用最小二乘法求解如下的最优化问题:

M i n ∑ i = 1 N f ( x i , E ) Min \sum_{i=1}^N f(x_i,E) Mini=1Nf(xi,E)
这里 f ( x i , E ) f(x_i,E) f(xi,E)表示点 x i x_i xi到E(指待拟合的椭圆)的最小距离。一般称为几何距离。但是我们很难直接给定几何距离的解析表达,因此很难求出。因此我们退而求其次,我们采用:为椭圆写下隐式方程,然后将点的坐标带入此隐式方程就得到了点到椭圆的距离。这种方法对于直线拟合、圆拟合,返回的就是到其的真实距离。而对于椭圆拟合,它返回的值是与距离有类似的属性,但不是一个真正的距离值。因此这个距离被称为代数距离
椭圆可以用下面的隐式方程表达:

a 1 x 2 + a 2 x y + a 3 y 2 + a 4 x + a 5 y + a 6 = 0 a_1x^2+a_2xy+a_3y^2+a_4x+a_5y+a_6=0 a1x2+a2xy+a3y2+a4x+a5y+a6=0

与直线类似(直线表达为: a 1 x + a 2 y + a 3 = 0 a_1x+a_2y+a_3=0 a1x+a2y+a3=0),上式的一组参数也是齐次量,即只能定义到一个比例因子。而且表达式也能表达双曲线和抛物线。而椭圆
通常要求: a 2 2 − 4 a 1 a 4 &lt; 0 a_2^2-4a_1a_4&lt;0 a224a1a4<0。最好通过令 a 2 2 − 4 a 1 a 4 = − 1 a_2^2-4a_1a_4=-1 a224a1a4=1,即可同时解决这两个问题。

请参考维基百科椭圆定义wolfram MathWorld 椭圆定义

一般的求解过程

对上面的椭圆方程改写为:

f ( a , ( x , y ) ) = D ⋅ a = 0 f(a,(x,y))=D\cdot a=0 f(a,(x,y))=Da=0

这里 D = ( x 2 , x y , y 2 , x , y , 1 ) D=(x^2, xy, y^2, x, y, 1) D=(x2,xy,y2,x,y,1), a = ( a 1 , a 2 , a 3 , a 4 , a 5 , a 6 ) a=(a_1, a_2, a_3, a_4, a_5, a_6) a=(a1,a2,a3,a4,a5,a6).
于是给定N个离散点 X i = ( x i , y i ) , i = 1... N X_i=(x_i,y_i),i=1...N Xi=(xi,yi),i=1...N,通过最小化代数距离:

M i n Δ ( a , X ) = ∑ i = 1 N ( f ( a , X i ) ) 2 Min \Delta(a,X)=\sum_{i=1}^{N}(f(a,X_i))^2 MinΔ(a,X)=i=1N(f(a,Xi))2

重写上式,即有:

M i n Δ ( a , X ) = ∑ i = 1 N a T D i T D i a = a T S a Min \Delta(a,X)=\sum_{i=1}^{N}a^TD_i^TD_ia=a^TSa MinΔ(a,X)=i=1NaTDiTDia=aTSa

这里 S = ∑ D i T D i S=\sum D_i^T D_i S=DiTDi为6x6矩阵。
另外,我们可以把各个 D i D_i Di合并起来,则有
D ^ = ( D 1 D 2 ⋮ D N ) \hat{D}=\begin{pmatrix} D_1\\ D_2\\ \vdots \\ D_N \end{pmatrix} D^=D1D2DN
因此:
S = D ^ T D ^ S=\hat{D}^T\hat{D} S=D^TD^

此外,我们还有约束条件:
a 2 2 − 4 a 1 a 4 &lt; 0 a_2^2-4a_1a_4&lt;0 a224a1a4<0,可以改写为:

a T C a = − 1 a^TCa=-1 aTCa=1

其中 C ∈ R 6 × 6 C\in R^{6\times 6} CR6×6,且大部分为0,只有 C 1 , 3 = C 3 , 1 = − 2 , C 2 , 2 = 1 C_{1,3}=C_{3,1}=-2,C_{2,2}=1 C1,3=C3,1=2,C2,2=1
因此综合上来即为,求解如下的最优化问题:

M i n     a T S a Min~~~ a^TSa Min   aTSa
s . t .     a T C a = − 1 s.t. ~~~a^TCa=-1 s.t.   aTCa=1

公式和拉格朗日乘子法

通过使用拉格朗日乘子法 ,我们可以得到

L ( a ) = a T S a − λ ( a T C a + 1 ) L(a)=a^TSa-\lambda (a^TCa+1) L(a)=aTSaλ(aTCa+1)

然后求解 ∂ a L ( a ) = 0 \partial_aL(a)=0 aL(a)=0可以求解得到:
S a = λ C a Sa=\lambda Ca Sa=λCa

上式是经典的广义特征值问题。我们可以直接求解,其中 λ \lambda λ为广义特征值,而a为广义特征向量
论文 中指出有且仅有一个负的特征值,且其对应的特征向量即为我们需要的椭圆方程的系数,即这里的a.

一般的圆锥方程到标准椭圆方程的转化。

当我们求解得到了圆锥曲线的系数,即a,我们一般需要获得椭圆的标准方程,也就是获得椭圆的如下参数:
中心 ( c x , c y ) (c_x,c_y) (cx,cy)、长短半轴 r 1 , r 2 r_1,r_2 r1,r2,旋转角度 θ \theta θ.

这里我们可以给出结果,也可以自己结合椭圆的标准形式与圆锥曲线的方程去推导.

参考文献:http://mathworld.wolfram.com/Ellipse.html.
##编程实现
下面描述matlab与c++实现。

matlab

% fitellip gives the 6 parameter vector of the algebraic circle fit
% to a(1)x^2 + a(2)xy + a(3)y^2 + a(4)x + a(5)y + a(6) = 0
% X & Y are lists of point coordinates and must be column vectors.(或者行向量)
function a = fitellip(X,Y)

   % normalize data
   mx = mean(X);
   my = mean(Y);
   sx = (max(X)-min(X))/2;
   sy = (max(Y)-min(Y))/2;
   x = (X-mx)/sx;
   y = (Y-my)/sy;
   % Force to column vectors
   x = x(:);
   y = y(:);

   % Build design matrix
   D = [ x.*x  x.*y  y.*y  x  y  ones(size(x)) ];

   % Build scatter matrix
   S = D'*D;

   % Build 6x6 constraint matrix
   C(6,6) = 0; C(1,3) = -2; C(2,2) = 1; C(3,1) = -2;

   % Solve eigensystem
   [gevec, geval] = eig(S,C);

   % Find the negative eigenvalue
   [NegR, NegC] = find(geval < 0 & ~isinf(geval));

   % Extract eigenvector corresponding to positive eigenvalue
   A = gevec(:,NegC);

   % unnormalize
   a = [
        A(1)*sy*sy,   ...
        A(2)*sx*sy,   ...
        A(3)*sx*sx,   ...
        -2*A(1)*sy*sy*mx - A(2)*sx*sy*my + A(4)*sx*sy*sy,   ...
        -A(2)*sx*sy*mx - 2*A(3)*sx*sx*my + A(5)*sx*sx*sy,   ...
        A(1)*sy*sy*mx*mx + A(2)*sx*sy*mx*my + A(3)*sx*sx*my*my   ...
                - A(4)*sx*sy*sy*mx - A(5)*sx*sx*sy*my   ...
                + A(6)*sx*sx*sy*sy   ...
       ]';


c++

我们参考了网上的版本,并修复了他的问题,最终也做出了和matlab一样的效果。其中最关键的是广义特征值的求解。我们使用了Eigenclapack库。其中Eigen易于表达矩阵,和matlab用法类似,是个强大的C++线性代数库。而CLAPACK是线性代数包Lapack面向C/c++的接口。里面包含了很丰富的线性代数算法,包括广义特征值求解接口,而且速度很快。我们希望将二者结合起来使用。

Eigen的安装

Eigen直接以源代码的方式提供给用户,因此我们从官网上下载下后,直接在工程中包含其头文件路径即可。具体可参考:http://blog.csdn.net/abcjennifer/article/details/7781936

clapack的安装

请查看官网,里面包含了详细的使用与安装步骤。

也可以使用我们已经编译了的vc2010和vc2013的库,可以点击下载

尽管clapack面向c语言,因此需要我们在包含头文件的时候,记得加上extern “C”.但是最新的版本(比如CLAPACK 3.2.1)已经为我们在头文件中加上了这些限制符,因此最新的版本可以兼容c和c++,所以直接在项目包含头文件即可。

比如像下面一样:

//Eigen
#include <Eigen/Dense>
#include <Eigen/Core>
#include <iostream>

//clapack,必须放在Eigen后面

#include <f2c.h>
#include <clapack.h>

而且应该注意Eigen与CLAPACK混合使用的时候,CLAPACK的头文件要加在Eigen的后面。否则会出错

代码

请查看Github主页:https://github.com/xiamenwcy/EllipseFitting

实验结果

在这里插入图片描述

参考文献

Eigen、LAPACK

  1. Interfacing Eigen with LAPACK
  2. Using BLAS/LAPACK from Eigen
  3. CLAPACK for Windows
  4. 如何在VC中调用CLAPACK
  5. 使用Lapack库的一些重要规则
  6. 在VS中用CLAPACK解决广义特征值问题
  7. LAPACK 3.7.0
  8. C++编程:C++中同时使用Eigen和CLAPACK
  9. CLAPACK在vc++6.0中成功调用
  10. CLAPACK动态调用
  11. Leading dimension
  12. leading dimension 2

椭圆拟合

论文作者,著名学者主页:

  • 2
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
如果你不想使用`cv2.fitEllipse`函数,你可以使用numpy的线性代数库`np.linalg`和`np.polyfit`函数来拟合椭圆。 以下是一个简单的示例代码: ```python import cv2 import numpy as np # 读取图像 img = cv2.imread('ellipse.png') # 转换为灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化处理 ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) # 查找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 获取第一个轮廓 cnt = contours[0] # 获取椭圆拟合的参数 (x, y), (MA, ma), angle = cv2.fitEllipse(cnt) # 将轮廓转换为(x,y)坐标数组 coord = cnt[:, 0, :] # 将坐标系平移至拟合椭圆的中心 x0, y0 = x, y coord[:, 0] -= x0 coord[:, 1] -= y0 # 计算椭圆的旋转角度 theta = np.deg2rad(angle) # 构造系数矩阵 D = np.vstack([coord[:, 0] ** 2, coord[:, 0] * coord[:, 1], coord[:, 1] ** 2]).T # 构造常数矩阵 B = np.ones((coord.shape[0], 1)) # 使用最小二乘法求解系数向量 C = np.linalg.lstsq(D, B, rcond=None)[0] # 将系数向量转换为参数向量 a = C[0, 0] b = C[1, 0] / 2 c = C[2, 0] d = -1 # 计算椭圆的长轴和短轴长度 delta = np.sqrt(b ** 2 - a * c) MA_new = np.sqrt(2 * (a + c + delta)) ma_new = np.sqrt(2 * (a + c - delta)) # 计算椭圆的旋转角度 if b == 0: angle_new = 0 elif a > c: angle_new = np.arctan(2 * b / (a - c)) / 2 else: angle_new = np.pi / 2 + np.arctan(2 * b / (a - c)) / 2 # 将椭圆的参数转换回原始坐标系 x_new, y_new = x0, y0 theta_new = -np.rad2deg(angle_new) MA_new, ma_new = int(MA_new), int(ma_new) # 绘制拟合得到的椭圆 cv2.ellipse(img, (int(x_new), int(y_new)), (MA_new, ma_new), theta_new, 0, 360, (0, 255, 0), 2) # 显示结果 cv2.imshow('Ellipse Fitting', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在这个示例代码中,我们首先读取了一张图像,然后将其转换为灰度图像并进行二值化处理。接下来,我们使用`cv2.findContours`函数查找图像中的轮廓,并获取第一个轮廓。然后,我们使用`cv2.fitEllipse`函数获取椭圆拟合的参数。 接着,我们将轮廓的坐标系平移至拟合椭圆的中心,并计算椭圆的旋转角度。然后,我们构造系数矩阵和常数矩阵,并使用最小二乘法求解系数向量。最后,我们将系数向量转换为椭圆的参数向量,并将其转换回原始坐标系。最后,我们使用`cv2.ellipse`函数绘制拟合得到的椭圆,并显示结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值