橢圓擬合方法

最近工作中遇到了有關橢圓擬合的問題,把在這一過程中踩坑和最後使用的方法進行了總結。
參考鏈接:https://github.com/seisgo/EllipseFit
https://github.com/xiamenwcy/EllipseFitting

opencv3.2的方法

首先使用的是opencv的方法,該方法有時候會失效。

//待擬合的點
vector<Point> vpfinalfit = dangerzone;

//把點的數量限制到20個
int interval = floor(vpfinalfit.size() / 20.0);
if (interval > 1)
{
	vector<Point> vpsample;
	for (int i = 0; i < vpfinalfit.size(); i += interval)
	{
		vpsample.push_back(vpfinalfit[i]);
	}
	vpfinalfit = vpsample;
}

//把座標輸出的文件中,在matlab中擬合
ofstream fout3;
fout3.open("data3.txt");
for (unsigned long i = 0; i < vpfinalfit.size(); i++)
{
	fout3 << vpfinalfit[i].x << " " << vpfinalfit[i].y << endl;
}
fout3.close();

//使用橢圓擬合的方法
RotatedRect rr;
if (vpfinalfit.size() > 5)
{
	rr = fitEllipse(vpfinalfit);
	ellipse(src, rr, Scalar(0, 0, 0), 4);
}

擬合較好的結果
在這裏插入圖片描述
擬合不對的結果
在這裏插入圖片描述

matlab的方法(BFisher方法)

參考鏈接 http://homepages.inf.ed.ac.uk/rbf/CVonline/LOCAL_COPIES/FITZGIBBON/ELLIPSE/
參考文獻:Andrew W. Fitzgibbon, Maurizio Pilu, and Robert B. Fisher Direct least-squares fitting of ellipses, IEEE Transactions on Pattern Analysis and Machine Intelligence, 21(5), 476–480, May 1999

同樣的點,在matlab中進行擬合,則可以實現較好的效果。

x = load('data3.mat');
p0=[0.005 0.005 0.005 0.005 0.005 0.005];
warning off
F=@(p,x)p(1)*x(:,1).^2+p(2)*x(:,1).*x(:,2)+p(3)*x(:,2).^2+p(4)*x(:,1)+p(5)*x(:,2)+p(6);
% ???????????
p=nlinfit(x,zeros(size(x,1),1),F,p0);

A=p(1)/p(6);
B=p(2)/p(6);
C=p(3)/p(6);
D=p(4)/p(6);
E=p(5)/p(6);

X_center = (B*E-2*C*D)/(4*A*C - B^2);
Y_center = (B*D-2*A*E)/(4*A*C - B^2);
fprintf(' X_center=%g, Y_center=%g\n',X_center,Y_center);

a= 2*sqrt((2*A*(X_center^2)+2*C*(Y_center^2)+2*B*X_center*Y_center-2)/(A+C+sqrt(((A-C)^2+B^2))));
b= 2*sqrt((2*A*(X_center^2)+2*C*(Y_center^2)+2*B*X_center*Y_center-2)/(A+C-sqrt(((A-C)^2+B^2))));
 
 
q=0.5 * atan(B/(A-C));
fprintf(' q=%g\n',q);
 
 
fprintf(' a=%g, b=%g\n',a,b);
plot(x(:,1),x(:,2),'ro');
 
 
hold on;
xmin=min(x(:,1));
xmax=max(x(:,1));
ymin=min(x(:,2));
ymax=max(x(:,2));
% 作圖
 
ezplot(@(x,y)F(p,[x,y]),[xmin,xmax,ymin,ymax]);
title('曲線擬合');
legend('採樣點','擬合結果')

在這裏插入圖片描述

c++版本(BFisher方法)

該方法也有c++的版本,但是lapack的配置比較複雜。
DirectEllipseFit擬合的類定義,具體的代碼可以去CSDN下載,稍後會上傳,命名分別爲myEllipse.h和myEllipse.cpp

template <typename T>
class DirectEllipseFit
{
public:
	DirectEllipseFit(const vector<T>& xData, const vector<T>& yData);
	Ellipse doEllipseFit();

private:
	T getMeanValue(const vector<T>& data);
	T getMaxValue(const vector<T>& data);
	T getMinValue(const vector<T>& data);
	T getScaleValue(const vector<T>& data);
	vector<T> symmetricNormalize(const vector<T>& data);
	//Make sure xData and yData are of same size
	vector<T> dotMultiply(const vector<T>& xData, const vector<T>& yData);
	
	//Get n*6 design matrix D, make sure xData and yData are of same size
	vector<vector<T> > getDesignMatrix(const vector<T>& xData,const vector<T>& yData);
	//Get 6*6 constraint matrix C
	vector<vector<T> > getConstraintMatrix();
	//Get 6*6 scatter matrix S from design matrix
	vector<vector<T> > getScatterMatrix(const vector<vector<T> >& dMtrx);
	//Transpose matrix
	vector<vector<T> > transposeMatrix(const vector<vector<T> >& mtrx);
	//Do matrix multiplication, mtrx1: j*l; mtrx2: l*i; return: j*i
	vector<vector<T> > doMtrxMul(const vector<vector<T> >& mtrx1,
		const vector<vector<T> >& mtrx2);

	/**
	 * @brief solveGeneralEigens:   Solve generalized eigensystem
	 * @note        For real eiginsystem solving.
	 * @param sMtrx:    6*6 square matrix in this application
	 * @param cMtrx:    6*6 square matrix in this application
	 * @param eigVV:    eigenvalues and eigenvectors, 6*7 matrix
	 * @return  success or failure status
	 */
	bool solveGeneralEigens(const vector<vector<T> >& sMtrx,
		const vector<vector<T> >& cMtrx,
		vector<vector<T> >& eigVV);
	//Convert matrix expression from nested QVector to 1-order array
	double* mtrx2array(const vector<vector<T> >& mtrx);

	/**
	 * @brief calcEllipsePara:  calculate ellipse parameter form eigen information
	 * @param eigVV:    eigenvalues and eigenvectors
	 * @return ellipse parameter
	 */
	Ellipse calcEllipsePara(const vector<vector<T> >& eigVV);

private:
	vector<T> m_xData, m_yData;
};

更簡單的方法

如果你喜歡用最新版的軟件,在opencv4.0以上的版本中,橢圓擬合函數增加了新的方法,其中有一個就是使用的上述方法,所有可以直接調用opencv4.0以上版本的庫。

RotatedRect rr;
if (vpfinalfit.size() > 5)
{
	rr = fitEllipseAMS(vpfinalfit);
	//rr = fitEllipse(vpfinalfit);
	ellipse(src, rr, Scalar(0, 255, 255), 1);  //畫橢圓
}

但是不能盲目升級,就是因爲這個原因,筆者把原來開發的Android程序jni使用的opencv版本從3.2升級到了4.0,導致隊友原來開發的視覺算法編譯通不過來,不過沒有什麼,從opencv3到opencv4大部分是是簡單的變量命名替換,改起來還是比較輕鬆。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章