核線影像製作--雙像三維建模小軟件開發實例(六)

一、基本原理

根據相對定向或絕對定向確定了兩張相片的相對位置關係之後,可以把原始影像糾正成核線影像,即兩張相片的光軸平行,且與基線(相機頭影像中心連線)垂直,同時核線影像的行(或列)與基線也保持平行,這時兩張相片上的同名點將是行對準或列對準的,因此,尋找同名的過程將被限制到一維搜索。詳細內容可參考計算機視覺的“對極幾何”理論或攝影測量的“核線採樣”理論。

原始影像其實是物方空間點到原始像空間的投影,而核線影像其實是物方空間點到基線座標系的投影,二者的幾何關係如下圖所示,圖中IMG1和IMG2是原始影像,EP1和EP2爲相應的核線影像。

圖 1. 核線糾正示意圖(此圖其實不準確,影像旋轉時應當保持投影中心位置不變,而不是像主點位置不變)

原始影像空間轉換到物空間的關係爲

物空間轉換到核線影像空間的關係爲

而原始影像到核線影像的轉換關係則爲

反過來,核線影像到原始影像的關係則爲

二、算法實現

void EpipolarGenerator(double Bx, double By, double Bz, double *Rotationl,double *Rotationr, double psize, double f,
					   char *FilepathImgL, char *FilepathImgR, char *FilePathEpiImgL, char *FilePathEpiImgR)
{
	//讀取原始影像
	CFxImage cImg1,cImg2;
	int bandMap[] = {1,2,3};
	cImg1.Open(FilepathImgL);
	int nWidth_l = cImg1.GetWidth();
	int nHeight_l = cImg1.GetHeight();
	cImg2.Open(FilepathImgR);
	int nWidth_r = cImg2.GetWidth();
	int nHeight_r = cImg2.GetHeight();
	if (nWidth_l != nWidth_r || nHeight_l != nHeight_r)
	{
		return;
	}
	int nWidth, nHeight;
	nWidth = nWidth_l; nHeight = nHeight_l;
	unsigned char *ImgL = new unsigned char[nHeight*nWidth];
	unsigned char *ImgR = new unsigned char[nHeight*nWidth];
	memset(ImgL,0,sizeof(unsigned char)*nHeight*nWidth);
	memset(ImgR,0,sizeof(unsigned char)*nHeight*nWidth);

	cImg1.ReadInterleavingImage(0,0,nHeight,nWidth,1,bandMap,ImgL);
	cImg2.ReadInterleavingImage(0,0,nHeight,nWidth,1,bandMap,ImgR);

	cImg1.Close();
	cImg2.Close();
	//將當前座標系轉換至基線座標系
	double B  = sqrt(Bx*Bx + By*By + Bz*Bz);
	double m_T = atan2(By,Bx);
	double m_V = asin(Bz/B);

	double Rtemp[9],Rl[9],Rr[9];
	GroundToBaseAuxMatrix(m_T,m_V,Rtemp);

 	MultMatrix(Rtemp,Rotationl,Rl,3,3,3);
 	MultMatrix(Rtemp,Rotationr,Rr,3,3,3);

	int EpipolarHeight, EpipolarWidth;
	GetEpiImgSize(nWidth,nHeight,f,psize,Rl,Rr,&EpipolarWidth,&EpipolarHeight);
    unsigned char* EpiImgL = new unsigned char[EpipolarWidth*EpipolarHeight];
	unsigned char* EpiImgR = new unsigned char[EpipolarWidth*EpipolarHeight];

	//開始製作核線影像
	double u1,v1,x1,y1;
	double u2,v2,x2,y2;
	double c1,r1,c2,r2;
	int i,j;
    double xo = nWidth/2;
	double yo = nHeight/2;
	for(i=0;i<EpipolarHeight;i++)
	{
		for(j=0;j<EpipolarWidth;j++)
		{
			u1 = j*psize-EpipolarWidth*psize/2;
			v1 = -i*psize+EpipolarHeight*psize/2;
			
			x1 = -f*(Rl[0]*u1 + Rl[3]*v1 - Rl[6]*f)/(Rl[2]*u1 + Rl[5]*v1 - Rl[8]*f);
			y1 = -f*(Rl[1]*u1 + Rl[4]*v1 - Rl[7]*f)/(Rl[2]*u1 + Rl[5]*v1 - Rl[8]*f);
			
			c1 = x1/psize + xo;
			r1 = -y1/psize + yo;
			
			if((c1 < 0.0) || (r1 < 0.0) || (r1 > nHeight-1 ) || (c1 > nWidth-1))
			{
				EpiImgL[i*EpipolarWidth+j] = 0;
			}
			else// 雙線性內插
			{
				EpiImgL[i*EpipolarWidth+j] = BilinearInterpolation(ImgL,nWidth,c1,r1);
			}
			
			u2 = j*psize-EpipolarWidth/2*psize;
			v2 = v1;
			x2 = -f*(Rr[0]*u2 + Rr[3]*v2 - Rr[6]*f)/(Rr[2]*u2 + Rr[5]*v2 - Rr[8]*f);
			y2 = -f*(Rr[1]*u2 + Rr[4]*v2 - Rr[7]*f)/(Rr[2]*u2 + Rr[5]*v2 - Rr[8]*f);
			c2 = x2/psize + xo;
			r2 = -y2/psize + yo;
			if((c2 < 0.0) || (r2 < 0.0) || (r2 > nHeight-1) || (c2 > nWidth-1))
			{
				EpiImgR[i*EpipolarWidth+j] = 0;
			}
			else// 雙線性內插
			{
				EpiImgR[i*EpipolarWidth+j] = BilinearInterpolation(ImgR,nWidth,c2,r2);
			}
		}
	}
    int bandmap[] = { 1, 2, 3, 4};
	CFxImage EpiImgl,EpiImgr;
	EpiImgl.Create(FilePathEpiImgL,1,EpipolarWidth,EpipolarHeight);
	EpiImgl.WriteInterleavingImage(0,0,EpipolarHeight,EpipolarWidth,1,bandmap,EpiImgL);
	EpiImgr.Create(FilePathEpiImgR,1,EpipolarWidth,EpipolarHeight);
	EpiImgr.WriteInterleavingImage(0,0,EpipolarHeight,EpipolarWidth,1,bandmap,EpiImgR);
	EpiImgl.Close();
	EpiImgr.Close();
	delete []EpiImgL;
	delete []EpiImgR;
	delete []ImgL;
	delete []ImgR;
} 
//地面座標系到基線輔助座標系的轉換矩陣
void GroundToBaseAuxMatrix( double t, double v, double matrix[9])
{
	double sint = sin(t), cost = cos(t);
	double sinv = sin(v), cosv = cos(v);
	
	matrix[0] = cost*cosv;      matrix[1] = sint * cosv;         matrix[2] = sinv;
	matrix[3] = -sint;          matrix[4] = cost;                matrix[5] = 0;
	matrix[6] = -cost*sinv;     matrix[7] = -sint*sinv;          matrix[8] = cosv;
}
void GetEpiImgSize(int nWidth,int nHeight,double f, double psize,double *Rl, double *Rr, int *EpiWidth, int *EpiHeight)
{

	double uplx,uply,uprx,upry,downlx,downly,downrx,downry;
	double Oriuplx,Oriuply,Oriuprx,Oriupry,Oridownlx,Oridownly,Oridownrx,Oridownry;

	int xo = nWidth/2;
	int yo = nHeight/2;
	
	Oriuplx = -xo*psize;
	Oriuply = (nHeight - yo)*psize;
	Oriuprx = (nWidth - xo)*psize;
	Oriupry = (nHeight - yo)*psize;
	Oridownlx = -xo*psize;
	Oridownly = -yo*psize;
	Oridownrx = (nWidth - xo)*psize;
	Oridownry = -yo*psize;
	uplx = -f*(Rl[0]*Oriuplx + Rl[1]*Oriuply - Rl[2]*f)/(Rl[6]*Oriuplx+Rl[7]*Oriuply-Rl[8]*f);
	uply = -f*(Rl[3]*Oriuplx + Rl[4]*Oriuply - Rl[5]*f)/(Rl[6]*Oriuplx+Rl[7]*Oriuply-Rl[8]*f);
	uprx = -f*(Rl[0]*Oriuprx + Rl[1]*Oriupry - Rl[2]*f)/(Rl[6]*Oriuprx+Rl[7]*Oriupry-Rl[8]*f);
	upry = -f*(Rl[3]*Oriuprx + Rl[4]*Oriupry - Rl[5]*f)/(Rl[6]*Oriuprx+Rl[7]*Oriupry-Rl[8]*f);
	downlx = -f*(Rl[0]*Oridownlx + Rl[1]*Oridownly - Rl[2]*f)/(Rl[6]*Oridownlx+Rl[7]*Oridownly-Rl[8]*f);
	downly = -f*(Rl[3]*Oridownlx + Rl[4]*Oridownly - Rl[5]*f)/(Rl[6]*Oridownlx+Rl[7]*Oridownly-Rl[8]*f);
	downrx = -f*(Rl[0]*Oridownrx + Rl[1]*Oridownry - Rl[2]*f)/(Rl[6]*Oridownrx+Rl[7]*Oridownry-Rl[8]*f);
	downry = -f*(Rl[3]*Oridownrx + Rl[4]*Oridownry - Rl[5]*f)/(Rl[6]*Oridownrx+Rl[7]*Oridownry-Rl[8]*f);

	// 根據x、y的最大值和最小值確定核線影像的範圍
	double a[4] = {uplx,uprx,downlx,downrx};
	OnBubbleSort(a,4);// 從小到大排序
	double x_Min = a[0];// 負值
	double x_Max = a[3];// 正值
	double b[4] = {uply,upry,downly,downry};
	OnBubbleSort(b,4);
	double y_Max = b[3];// 正值
	double y_Min = b[0];// 負值
	
	int EpipolarWidth1 = x_Max>-x_Min?(2*x_Max/psize):(-2*x_Min/psize);
	int EpipolarHeight1 = y_Max>-y_Min?(2*y_Max/psize):(-2*y_Min/psize);

	uplx = -f*(Rr[0]*Oriuplx + Rr[1]*Oriuply - Rr[2]*f)/(Rr[6]*Oriuplx+Rr[7]*Oriuply-Rr[8]*f);
	uply = -f*(Rr[3]*Oriuplx + Rr[4]*Oriuply - Rr[5]*f)/(Rr[6]*Oriuplx+Rr[7]*Oriuply-Rr[8]*f);
	uprx = -f*(Rr[0]*Oriuprx + Rr[1]*Oriupry - Rr[2]*f)/(Rr[6]*Oriuprx+Rr[7]*Oriupry-Rr[8]*f);
	upry = -f*(Rr[3]*Oriuprx + Rr[4]*Oriupry - Rr[5]*f)/(Rr[6]*Oriuprx+Rr[7]*Oriupry-Rr[8]*f);
	downlx = -f*(Rr[0]*Oridownlx + Rr[1]*Oridownly - Rr[2]*f)/(Rr[6]*Oridownlx+Rr[7]*Oridownly-Rr[8]*f);
	downly = -f*(Rr[3]*Oridownlx + Rr[4]*Oridownly - Rr[5]*f)/(Rr[6]*Oridownlx+Rr[7]*Oridownly-Rr[8]*f);
	downrx = -f*(Rr[0]*Oridownrx + Rr[1]*Oridownry - Rr[2]*f)/(Rr[6]*Oridownrx+Rr[7]*Oridownry-Rr[8]*f);
	downry = -f*(Rr[3]*Oridownrx + Rr[4]*Oridownry - Rr[5]*f)/(Rr[6]*Oridownrx+Rr[7]*Oridownry-Rr[8]*f);
	// 根據x、y的最大值和最小值確定核線影像的範圍
	double c[4] = {uplx,uprx,downlx,downrx};
	OnBubbleSort(c,4);// 從小到大排序
	x_Min = c[0];// 負值
	x_Max = c[3];// 正值
	double d[4] = {uply,upry,downly,downry};
	OnBubbleSort(d,4);
	y_Max = d[3];// 正值
	y_Min = d[0];// 負值
    
	int EpipolarWidth2 = x_Max>-x_Min?(2*x_Max/psize):(-2*x_Min/psize);
	int EpipolarHeight2 = y_Max>-y_Min?(2*y_Max/psize):(-2*y_Min/psize);

	*EpiWidth = EpipolarWidth1>EpipolarWidth2?EpipolarWidth1:EpipolarWidth2;
    *EpiHeight = EpipolarHeight1>EpipolarHeight2?EpipolarHeight1:EpipolarHeight2;
}

三、實驗結果

以兩幅航空影像爲實驗數據,測試本程序。
如下圖所示爲原始航空影像,由圖可見,同名像點存在上下和左右視差。


如下圖所示,爲進過核線糾正後的影像,由圖可見,同名像點之間只存在左右視差,而不存在上下時差,即同名點的行座標是一致的。

 

四、補充說明

1. 上述核線糾正程序沒有考慮像主點偏移和影像畸變,影像畸變糾正和內定向的問題應當預先解決,這個過程也被稱爲內定向,早期攝影測量領域對膠片影像進行數字化處理時需要進行內定向,後來發展的航空數字影像一般不需要該處理,但是近年來大量應用的無人機影像通常都有不可忽略的影像畸變與像主點偏移,因此又必須進行內定向。
2. 如果影像像主點偏移很小(比如1個像素左右),畸變也很小,但是生成的核線影像仍然有較大的上下視差(比如10個以上像素),則很有可能是影像的外方位元素有誤,進一步地,可能是因爲相對定向沒有做好。
3. 完整代碼已經放到下載資源裏。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章