線性插值和雙線性插值
最近在學數字影象處理中旋轉變換的問題,發現旋轉以後圖片有一些不連續點,於是試著用雙線性插值法進行解決。下面就介紹下插值的原理:
線性插值
如果你只處理分離的資料、想知道分離點之間的某些值,需要用到某種型別的插值。這種情況如圖5-17座標所示。對某些分離的(整數) X值,你知道Y值。當X=2,你知道Y=10,X=3時Y=30。但你不知道X=2.7時的Y值。
圖1線性插值:簡單常規的例子
使用線性插值,你通過連線兩點的線段找到X=2.7對應的Y值,如圖1所示。使用線性插值,通過連線兩點的線段找到X=2.7對應的Y值。線性插值總是將X表達成0和1之間,0對應X的最小值(你知道對應的Y值,本例中為2),1對應X的最大值(本例中為3) 。本例中你想找到X=2.7時的Y值,結果是0.7,意思是“2和3至之間的70%。”
在圖1的左圖中,0%對應Y值10,100%對應Y值20,所以70%對應Y=17。這很容易看出,但右圖的情況如何?14對應0.33,因為它是13和16之間的33%。但35和46之間的33%是多少?顯然,你希望有程式碼可以為你計算結果。
首先要有程式碼找到0和1對應的值。從X值開始,首先減去最小的X值,這樣最小值變為0。然後,將最大值縮放為1,你可以通過將它除以最大值和最小值之差實現。
下面是圖1左圖的做法: 2.7→(2.7-min)/(max-min)=(2.7-2)/(3-2)=0.7
然後,進行逆運算獲取對應的Y值:首先縮放這個值(通過乘以最大值和最小值的差值),並加到最小的Y值上:
0.7* (maxY-min Y)+minY=0.7*(20-10)+10=0.7*10+10=17
這裡你採取圖1左圖簡單例子的規則,但你可以使用這個方法計算任何線性插值。看一下圖1右圖更難的例子,在這種情況中,你知道X=13對應Y=35,X=16對應Y=46,但你想知道X=14對應的Y值。所以,首先獲取0和1之間對應的值:
14→(14-minX)/(maxX-minX) =(14-13)/(16-13)=0.33
知道了對應值,就做好了獲取對應Y值的準備:
0.33* (maxY-minY)+minY=0.33*(46-35)+35=0.33*11+35=3.67+35=38.67
最後,需要進行浮點計算。圖5-17的右圖中找到X=14對應Y=38.67。事實上,幾乎所有插值計算都返回一個浮點數。
雙線性插值
對所有在這些獨立頂點之間的(X,Z)值,你不知道精確的Y值,所以需要進行插值。這次你需要獲取0和1之間的值,包含X和Z。
有了這些值,就可以分兩步計算出精確的Y值。這裡的x與z分別表示一幅影象的橫縱座標,Z值表示在該座標下的圖片的畫素值。
給定任意(X,Z)座標,你需要找到圖片上的精確Y灰度值。首先使用前面的公式找到對應的X和Z值,你需要用兩次:
int xLower = (int)xCoord; int xHigher = xLower + 1; float xRelative = (xCoord - xLower) / ((float)xHigher - (float)xLower); int zLower = (int)zCoord; int zHigher = zLower + 1; float zRelative = (zCoord - zLower) / ((float)zHigher- (float)zLower);
在地形中每個X和Z的整數值你定義了一個頂點,所以你知道精確的Y值。所以對每個X的浮點數,你要將它們轉換為整數獲取最小的X值(例如,2.7變為2)。將這個值加1獲取最大X值(2.7對應3作為最大值)。知道了邊界,很容易使用前面的公式找到對應值。Z值的求法類似。
知道了0和1之間的對應值,下一步是找到精確Y值。但是,首先需要知道minY和maxY值。這些值表示頂點中的高度。你需要知道點在哪個三角形中才能知道使用哪個頂點的高度作為Y值。
你知道點P的X和Z座標,所以你知道點周圍的四個頂點,很容易獲取它們的Y值:
float heightLxLz = heightData[xLower, zLower]; float heightLxHz = heightData[xLower, zHigher]; float heightHxLz = heightData[xHigher, zLower]; float heightHxHz = heightData[xHigher, zHigher];
LxHz表示“低X座標,高Z座標” 決定(X,Z)。
點在哪個三角形中用來繪製地形的兩個三角形。有兩個方式可以定義這兩個三角形,如圖5-18所示。繪製三角形的方式影響到P點的高度,如圖所示。
圖5-18 從四個頂點繪製兩個三角形的兩種方法
雖然四個頂點有相同的座標,但兩種情況中的點的高度並不相同,圖中你可以可出明顯的區別。
基於我即將討論的理由,更好的方式是圖5-18的右圖。
使用這個旋轉方式,很容易確定點在哪個三角形上方。兩個三角形之間的邊界由對角線給出。在右圖中,如果xRelative + zRelative 為1的話,這條線對應具有X和Z座標的點。
例如,如果這個點在四個點中央,如圖5-18所示,xRelative和zRelative都是0.5f,所以和為1,說明在對角線上。如果這個點偏向左邊一點,xRelative會小一些,和會小於1,對Z座標也是類似的情況。所以如果和小於1,(X,Z)座標位於左下角的三角形內;否則,該點在右上角的三角形內:
bool pointAboveLowerTriangle = (xRelative + zRelative < 1);
獲取精確高度
知道了對應高度,四個周圍頂點的高度和點位於哪個三角形中,你就可以計算精確高度了。
如果點在左下方的三角形中,這時pointAboveLowerTriangle為true,下面是使用雙線性插值獲取三角形任意點高度的程式碼:
finalHeight = heightLxLz; finalHeight += zRelative * (heightLxHz - heightLxLz); finalHeight += xRelative * (heightHxLz - heightLxLz);
根據前面解釋的單插值的方法,從lowestX的Y值開始。因為這是“雙”插值,你要從lowestXlowestZ的Y值開始。
在單插值中,你maxY之間新增高度差,並乘以對應的X值。在雙插值中,你乘的是 zRelative和xRelative。
換句話說,從左下頂點的高度開始,對這個高度,你添加了這個頂點和有著更高Z座標的頂點間的高度差,並乘以距離第二個頂點的Z座標的接近程度。最後一行程式碼類似:對這個高度,你添加了左下頂點和右下頂點的高度差,乘以距離右下頂點的X座標的接近程度。
如果該點在右上三角形的內部,這時pointAboveLowerTriangle為false,情況有所不同,你需要以下程式碼:
finalHeight = heightHxHz; finalHeight += (1.0f - zDifference) *(heightHxLz - heightHxHz); finalHeight += (1.0f - xDifference) * (heightLxHz - heightHxHz);
從高度開始,從右上頂點開始,遵循同樣的步驟:新增高度差,乘以對應距離。
程式碼
這個方法包含前面解釋的所有程式碼。基於任意(X,Z)座標,無論是整數還是浮點數,這個方法返回該點的精確高度。首先檢查該點是否在地形上。如果不是,返回預設的高度10。
public float GetExactHeightAt(float xCoord, float zCoord) { bool invalid = xCoord < 0; invalid |= zCoord < 0; invalid |= xCoord > heightData.GetLength(0) - 1; invalid |= zCoord > heightData.GetLength(1) - 1; if (invalid) return 10; int xLower = (int)xCoord; int xHigher = xLower + 1; float xRelative = (xCoord - xLower) / ((float)xHigher - (float)xLower); int zLower = (int)zCoord; int zHigher = zLower + 1; float zRelative = (zCoord - zLower) / ((float)zHigher - (float)zLower); float heightLxLz = heightData[xLower, zLower]; float heightLxHz = heightData[xLower, zHigher]; float heightHxLz = heightData[xHigher, zLower]; float heightHxHz = heightData[xHigher, zHigher]; bool pointAboveLowerTriangle = (xRelative + zRelative < 1); float finalHeight; if (pointAboveLowerTriangle ) { finalHeight = heightLxLz; finalHeight += zRelative * (heightLxHz - heightLxLz); finalHeight += xRelative * (heightHxLz - heightLxLz); } else { finalHeight = heightHxHz; finalHeight += (1.0f - zRelative) * (heightHxLz - heightHxHz); finalHeight += (1.0f - xRelative) * (heightLxHz - heightHxHz); } return finalHeight; }
在opencv中圖片旋轉時,影象旋轉之後,會出現許多的空白點,對這些空白點必須進行填充處理,否則畫面效果不好。稱這種操作為插值處理。
這裡給出旋轉的計算公式,(x,y)表示旋轉後圖片上一點,(x',y')表示旋轉錢圖片上的對應點,雙線性插值的思想就是對旋轉以後的圖片進行反變換,找出原圖片中對應的點,但這一點有可能同網格點值不重合,即不落在整數點座標上,因此需要通過內插的方法將非網格點的灰度值變換成網格點的灰度值,已達到比較平滑過度的效果。
我們一般認為,影象的比例縮放、旋轉變換時等,變換過程需要兩個獨立的演算法:
一個演算法完成幾何變換;
一個演算法用於灰度級插值;
數字影象處理只能對座標網格點(離散點)的值進行變換。而座標變換後產生的新座標值同網格點值往往不重合,因此需要通過內插的方法將非網格點的灰度值變換成網格點的灰度值,這種演算法稱為灰度內插。常見的灰度內插有:
1.最鄰近插值法
2.雙線性插值(一階插值)
最近鄰插值法很好理解,離誰最近就插值誰的值。而雙線性插值則採用在(x ,y)周圍四個網格點的灰度值進行內插:
但雙線性插值也有他的不足的地方:
1.計算量大,但縮放後圖像質量高,不會出 現影象不連續的情況。2.具有低通濾波器的性質,使高頻分量減弱,所以使影象的輪廓在一定程度上受損。 我們將最近鄰插值與雙線性插值做個比較: