問題
給定離散點集Xi=(xi,yi),i=1,2,...NX_i=(x_i,y_i) ,i=1,2,...NXi?=(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》。
橢圓擬合的難點
通常我們使用最小二乘法求解如下的最優化問題:
Min∑i=1Nf(xi,E)Min \sum_{i=1}^N f(x_i,E)Mini=1∑N?f(xi?,E)
這里f(xi,E)f(x_i,E)f(xi?,E)表示點xix_ixi?到E(指待擬合的橢圓)的最小距離。一般稱為幾何距離。但是我們很難直接給定幾何距離的解析表達,因此很難求出。因此我們退而求其次,我們采用:為橢圓寫下隱式方程,然后將點的坐標帶入此隱式方程就得到了點到橢圓的距離。這種方法對于直線擬合、圓擬合,返回的就是到其的真實距離。而對于橢圓擬合,它返回的值是與距離有類似的屬性,但不是一個真正的距離值。因此這個距離被稱為代數距離。
橢圓可以用下面的隱式方程表達:
a1x2+a2xy+a3y2+a4x+a5y+a6=0a_1x^2+a_2xy+a_3y^2+a_4x+a_5y+a_6=0a1?x2+a2?xy+a3?y2+a4?x+a5?y+a6?=0
與直線類似(直線表達為:a1x+a2y+a3=0a_1x+a_2y+a_3=0a1?x+a2?y+a3?=0),上式的一組參數也是齊次量,即只能定義到一個比例因子。而且表達式也能表達雙曲線和拋物線。而橢圓
通常要求:a22?4a1a4<0a_2^2-4a_1a_4<0a22??4a1?a4?<0。最好通過令a22?4a1a4=?1a_2^2-4a_1a_4=-1a22??4a1?a4?=?1,即可同時解決這兩個問題。
請參考維基百科橢圓定義和wolfram MathWorld 橢圓定義
一般的求解過程
對上面的橢圓方程改寫為:
f(a,(x,y))=D?a=0f(a,(x,y))=D\cdot a=0f(a,(x,y))=D?a=0
這里D=(x2,xy,y2,x,y,1)D=(x^2, xy, y^2, x, y, 1)D=(x2,xy,y2,x,y,1),a=(a1,a2,a3,a4,a5,a6)a=(a_1, a_2, a_3, a_4, a_5, a_6)a=(a1?,a2?,a3?,a4?,a5?,a6?).
于是給定N個離散點Xi=(xi,yi),i=1...NX_i=(x_i,y_i),i=1...NXi?=(xi?,yi?),i=1...N,通過最小化代數距離:
MinΔ(a,X)=∑i=1N(f(a,Xi))2Min \Delta(a,X)=\sum_{i=1}^{N}(f(a,X_i))^2MinΔ(a,X)=i=1∑N?(f(a,Xi?))2
重寫上式,即有:
MinΔ(a,X)=∑i=1NaTDiTDia=aTSaMin \Delta(a,X)=\sum_{i=1}^{N}a^TD_i^TD_ia=a^TSaMinΔ(a,X)=i=1∑N?aTDiT?Di?a=aTSa
這里S=∑DiTDiS=\sum D_i^T D_iS=∑DiT?Di?為6x6矩陣。
另外,我們可以把各個DiD_iDi?合并起來,則有
D^=(D1D2?DN)\hat{D}=\begin{pmatrix} D_1\\ D_2\\ \vdots \\ D_N \end{pmatrix}D^=??????D1?D2??DN????????
因此:
S=D^TD^S=\hat{D}^T\hat{D}S=D^TD^
此外,我們還有約束條件:
a22?4a1a4<0a_2^2-4a_1a_4<0a22??4a1?a4?<0,可以改寫為:
aTCa=?1a^TCa=-1aTCa=?1
其中C∈R6×6C\in R^{6\times 6}C∈R6×6,且大部分為0,只有C1,3=C3,1=?2,C2,2=1C_{1,3}=C_{3,1}=-2,C_{2,2}=1C1,3?=C3,1?=?2,C2,2?=1。
因此綜合上來即為,求解如下的最優化問題:
MinaTSaMin~~~ a^TSa Min???aTSa
s.t.aTCa=?1s.t. ~~~a^TCa=-1s.t.???aTCa=?1
公式和拉格朗日乘子法
通過使用拉格朗日乘子法 ,我們可以得到
L(a)=aTSa?λ(aTCa+1)L(a)=a^TSa-\lambda (a^TCa+1)L(a)=aTSa?λ(aTCa+1)
然后求解?aL(a)=0\partial_aL(a)=0?a?L(a)=0可以求解得到:
Sa=λCaSa=\lambda CaSa=λCa
上式是經典的廣義特征值問題。我們可以直接求解,其中λ\lambdaλ為廣義特征值,而a為廣義特征向量
論文 中指出有且僅有一個負的特征值,且其對應的特征向量即為我們需要的橢圓方程的系數,即這里的a.
一般的圓錐方程到標準橢圓方程的轉化。
當我們求解得到了圓錐曲線的系數,即a,我們一般需要獲得橢圓的標準方程,也就是獲得橢圓的如下參數:
中心(cx,cy)(c_x,c_y)(cx?,cy?)、長短半軸r1,r2r_1,r_2r1?,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 datamx = 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 vectorsx = x(:);y = y(:);% Build design matrixD = [ x.*x x.*y y.*y x y ones(size(x)) ];% Build scatter matrixS = D'*D;% Build 6x6 constraint matrixC(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 eigenvalueA = gevec(:,NegC);% unnormalizea = [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一樣的效果。其中最關鍵的是廣義特征值的求解。我們使用了Eigen與clapack庫。其中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
- Interfacing Eigen with LAPACK
- Using BLAS/LAPACK from Eigen
- CLAPACK for Windows
- 如何在VC中調用CLAPACK
- 使用Lapack庫的一些重要規則
- 在VS中用CLAPACK解決廣義特征值問題
- LAPACK 3.7.0
- C++編程:C++中同時使用Eigen和CLAPACK
- CLAPACK在vc++6.0中成功調用
- CLAPACK動態調用
- Leading dimension
- leading dimension 2
橢圓擬合
- Fitting an Ellipse to a Set of Data Points(python實現)
- 二次曲線Quadratic Curve
- fitellipse.cpp(opencv)
- Creating Bounding rotated boxes and ellipses for contours
- OpenCV’s fitEllipse() sometimes returns completely wrong ellipses
- Fitting an Ellipse to a Set of Data Points
- Direct Least Square Fitting of Ellipses (論文)
- best-fitting-line-circle-and-ellipse
- opencv中的橢圓擬合
- Python and C implementations of an ellipse fitting algorithm in double and fixed point precision.
- C++ code for circle fitting algorithms
- OriginDelight/EllipseFit (c++源碼)
論文作者,著名學者主頁:
- Bob Fisher: http://homepages.inf.ed.ac.uk/rbf/
- Fitzgibbon, Pilu, Fisher: Direct Least Squares Fitting of Ellipses:http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/FITZGIBBON/ELLIPSE/