【OpenCV】【計算機圖形學】DIBR: Depth Image Based Rendering/ 3D image warping 中的實現細節

T_Y_P_E發表於2022-03-13

最近在學習DIBR並嘗試實現。感覺網上相關資料比較少,大多還是爬蟲,決定自己寫一個。

DIBR就是depth image based rendering問題。輸入一個視角下的影像和深度圖,要求你輸出另外一個虛擬視角下的影像(當然兩個視角的內外參矩陣都有辦法通過已知資訊求得)。

總共分三步:內參提取 和 外參提取 ,以及DIBR的主過程。這裡按照網上其他部落格的順序,先介紹內參提取。看的過程中注意座標系的定義。由於是第一次接觸,這裡我採用的座標系可能和常規的座標系不太一樣。

開始之前先介紹一些定義(基於我自己實現演算法的時候所使用的資料集):
\((u,v,1)^T\)為一個點的畫素座標。
\((x,y,1)^T\)為一個點在影像平面上以物理距離衡量的座標。
\((X,Y,Z)\)為一個點在相機座標系下的座標。
\((X_w,Y_w,Z_w)\)為一個點在世界座標系下的座標。
\(f_x、f_y\)分別表示X軸的焦距和Y軸的焦距(非理想情況下橫向和縱向的焦距是不一樣的)。
\(d_x、d_y\)分別表示X軸(對應於影像的U軸)和Y軸(對應於影像的V軸)每個畫素的物理尺寸(一個畫素實際有多長)。
\(p_u、p_v\)表示相機的主點(principal point,就是相機主軸與成像平面的交點)在影像中的位置(以畫素為單位)。
畫素縱橫比:\(pixel\ aspect\ ratio\)。表示畫素的實際尺寸的高與寬之比。
主點:\(principal\ point\)。是一個二維向量,表示相機主光軸與成像平面的交點。這裡以畫素為單位在影像上進行刻畫。
相機位置\((position_x,position_y,position_z)\)
焦距\(focal\ length\)
相機姿態(朝向)\(camera\ orientation\)。為一個三維向量,通常可用尤拉角、軸角、四元數等方式表示。這裡採用的是軸角表示法。

後面的公式可以對照著這個表來看。

內參提取

內參提取就是求從相機座標系到最終畫素影像的變換。

在看具體的矩陣之前,先看一下怎麼推的。

根據透視模型(這裡就不贅述了)有:
透視投影

透視投影

那麼就用\(\frac{y}{f}=\frac{Y}{Z}\)\(y=\frac{fY}{Z}\)。對於x同理。

那麼影像平面上的座標(以物理長度衡量)就是\((x,y,1)=(\frac{fX}{Z},\frac{fY}{Z},1)\)。這裡採用齊次座標。表示為\((fX,fY,Z)\)

再對映到以畫素為單位的座標系下:
畫素對映
就是\(u=\frac{x}{d_x}+p_u\),v同理。那麼就有\((u,v)^T=(\frac{x}{d_x}+p_u,\frac{y}{d_y}+p_v,1)\)。同樣使用齊次座標有:\((Zu,Zv,Z)=(\frac{fX}{d_x},\frac{fY}{d_y},Z)\)。後面的推理都是在齊次座標下進行的。

接下來寫出矩陣乘法的形式:

定義\(K'=\left[ \begin{matrix} f_x & 0 & 0 \\ 0 & f_y & 0 \\ 0 & 0 & 1 \end{matrix} \right]\)用於從相機座標系轉移到影像平面上的座標系(以圖片的物理尺度進行衡量,還不是畫素尺度,不妨設為V座標系)

定義\(C=\left[ \begin{matrix} \frac{1}{d_x} & 0 & p_u \\ 0 & \frac{1}{d_y} & p_v \\ 0 & 0 & 1 \end{matrix} \right]\)為從V座標系對映到影像座標系(以畫素為尺度)

定義\(K=C*K'=\left[ \begin{matrix} \frac{f_x}{d_x} & 0 & p_u \\ 0 & \frac{f_y}{d_y} & p_v \\ 0 & 0 & 1 \end{matrix} \right]\)。 這就是我們的內參矩陣。即

其中,\(\frac{f_x}{d_x}、\frac{f_y}{d_y}\)可由定義的\(par=pixel\ aspect\ ratio\)(畫素縱橫比)得到。我手上的資料集給出的焦距(focal length)是以畫素為單位的,應該預設是給出\(focal\ length=\frac{f_x}{d_x}\)。那麼\(\frac{f_y}{d_y}=\frac{f_x}{d_x}\x par\)。也就不需要我們去計算\(d_x、d_y\)了,畢竟jpg格式的解壓不是人人都會的,opencv貌似也無法直接讀出影像的解析度資訊(至少我沒有查到)。

外參提取

外參提取就是求從世界座標系到相機座標系的變換。

這裡把變換拆分為旋轉變換和平移向量。

先來看看平移變換,這個很好寫:

相機位置是\((p_x,p_y,p_z)\)(變數名有點重複,見諒見諒),那麼就有平移向量:
\(C=(-p_x,-p_y,-p_z)^T\)\((X_w,Y_w,Z_w)^T+C\)就能夠實現平移了。
(這裡論文給的程式碼是正號,感覺不對,讀者可以在評論區說服我為什麼是正號)

接下來看旋轉變換
這裡的原理還沒怎麼搞懂,後面再來補上,先把做法說了。

輸入給的是軸角表示(axis-angle),文章給出的實現是,先轉化為四元數,再用四元數進行旋轉。(我的評價是,不如直接用尤拉角,這好複雜...)

首先將axis和angle轉化為四元數。設axis=\((a,b,c)^T\)。得到的四元數是\(q=(a,b,c,w)^T\)。不妨設axis是單位向量。
那麼有\(q=(sin(\frac{angle}{2})axis,cos(\frac{angle}{2}))^T\)
下面用四元數得到旋轉矩陣。不妨設q已經經過了單位化。
那麼有:

最後就有\((X,Y,Z)^T=R((X_w,Y_w,Z_w)^T+C)\)

DIBR的核心步驟

有了兩個視角下分別的內參和外參之後怎麼做呢?這其實是最簡單的一步。

\(Z(u,v,1)^T=KR((X_w,Y_w,Z_w)^T+C)\)
這對於兩個視角下都是成立的:
\(Z_1p_1=K_1R_1(P+C_1)\)
\(Z_2p_2=K_2R_2(P+C_2)\)
用第一個式子把P求出來:
\(P=(K_1R_1)^{-1}Z_1p_1-C_1\)
再帶到第二個式子裡,最後把\(Z_2p_2\)弄成p2=(u,v,1)的形式就可以了。最後一步就是把p1對應的畫素直接賦給p2即可。
注意:這裡的Z_1,Z_2指的就是深度,所以才需要深度作為輸入。這樣看來,也能通過DIBR得到輸出影像的深度資訊。

這裡寫的時間不長,寫的比較粗糙,如果有問題敬請指出。有時間我會回來把四元數的推導補上的。

最後把求內外參的程式碼奉上:

void Calc_Intrinsic_Matrix()
{
	actual_height=1.0*height*len_per_pixel;
	actual_width=1.0*width*len_per_pixel;

	intr_K=Mat::zeros(3,3,CV_64FC1);
	intr_K.at<double>(0,0)=focal_length;
	intr_K.at<double>(1,1)=focal_length*pixel_aspect_ratio;
	intr_K.at<double>(0,2)=1.0*width-principal_point[0]-1.0;
	intr_K.at<double>(1,2)=1.0*height-principal_point[1]-1.0;
	intr_K.at<double>(2,2)=1;

	intr_K=intr_K;

}

void Calc_Extrinsic_Matrix()
{
	extr_R=Mat::eye(3,3,CV_64FC1);
	//transform axis-angle to quaternion, and calculate transform matrix
	Mat axis=Mat::zeros(3,1,CV_64FC1);
	double angle=0;
	for(int i=0;i<3;i++)
	        angle+=orientation[i]*orientation[i];
	angle=sqrt(angle);
	for(int i=0;i<3;i++)
		axis.at<double>(i,0)=orientation[i]/angle;
	Mat qt=quaternion_about_axis(-angle,axis);
	extr_R=quaternion_matrix(qt);

	extr_C=Mat::zeros(3,1,CV_64FC1);
	extr_C.at<double>(0,0)=position[0];
	extr_C.at<double>(1,0)=position[1];
	extr_C.at<double>(2,0)=position[2];

}

參考:https://blog.csdn.net/u010922186/article/details/40683129

相關文章