圖形圖像處理-之-高質量的快速的圖像縮放 下篇 三次線性插值和MipMap鏈

A:對於前一篇文章中的二次線性插值、三次卷積插值算法,但它們處理縮小到0.5倍以下的
時候效果就會越來越差;這是因爲插值的時候自考慮了附近點的原因;如下圖:


           原圖          近鄰取樣 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍


                     二次線性插值 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍


                     三次卷積插值 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍

    可以看出:當縮小的比例很大的時候,插值算法的效果和近鄰取樣的效果差不多了:( ;
一種可行的解決方案就是:縮小時考慮更多的點; 但這種解決方案有很多缺點:函數編寫麻煩,
速度也許會很慢,優化也不容易做;  還有一個方案就是預先建立一個縮放好的大小不同的圖片
列表,每一張圖片都是前一張的0.5倍(這種圖片列表就是MipMap鏈),縮放的時候根據需要縮放
的比例從表中選擇一張大小接近的圖片來作爲縮放的源圖片; 該方案的優點:不需要編寫新的
底層縮放算法,直接使用前面優化好的插值算法; 缺點:需要預先建立MipMap鏈,它需要時間,
並且它的儲存需要多佔用原圖片的1/3空間(0.5^2+0.5^4+0.5^6+...=1/3);還有一個不太明顯
的小問題,就是在一張圖片的連續的比例不同的縮放中,選擇會從MipMap的一張源圖片跳到另
一張圖片,視覺效果上可能會有一個小的跳躍(我在《魔獸世界》裏經常看到這種效應:);一種
改進方案就是選擇MipMap圖片的時候,選擇出附近的兩張圖片作爲縮放的源圖片;對兩張圖片
單獨進行插值(和原來一致)輸出兩個值,然後把這兩個值線性插值爲最終結果;還有一個比較
大的缺點就是當縮放比例不均勻時(比如x軸放大y軸縮小),縮放效果也不好;
(當前很多顯卡都提供了MipMap紋理和對應的插值方案,OpenGL和DirectX都提供了操作接口)

("三次線性插值和MipMap鏈"其實比較簡單,這裏只給出關鍵代碼或算法)

B: MipMap圖片的生成:
     原圖片縮放到0.5倍(寬和高都爲原圖片的1/2),在把0.5倍的圖片縮放到0.25倍,....
   直到寬和高都爲1個像素,如果有一個長度先到1就保持1; 縮放過程中,可以可採用前面的縮放插值算法;
   如果爲了速度可以考慮這樣的方案,要求原圖片的寬和高必須是2的整數次方的數值,縮放時就可以直接將
   2x2的像素快速合併爲一個像素(如果允許原圖片寬和高爲任何值,可以考慮在合併時引入Alpha通道);

C: MipMap鏈圖片的儲存方案:


                    MipMap鏈圖片示意圖         

         可能的一種物理儲存方案(我對每張圖片加了一個邊框)            

D: 定義MipMap數據結構:
   MipMap數據結構可以定義爲一個TPicRegion數組和該數組的大小;
   (MipMap圖片的儲存參見上面的圖示)
  比如:

     #include
     typedef std::vector TMipMap;
//其中,第一個元素TMipMap[0]指向原始圖片,後面的依次爲縮小圖片;      

E: MipMap的選擇函數和偏好:
    在進行縮放時,根據目標圖片緩衝區的大小來動態的選者MipMap中的一幅圖片來作爲源圖片;這就需要一個
選擇函數;比如:

long SelectBestPicIndex(const TMipMap& mip,const long dstWidth,const long dstHeight)
{
long oldS=mip[0].width*mip[0].height;
long dstS=dstWidth*dstHeight;
if ( (dstS>=oldS) || (mip.size()==1) )
return 0;
else if (dstS<=1)
return mip.size()-1;
else
return (long)(log(oldS/dstS)*0.5+0.5);
}

選擇函數可以增加一個偏好參數:
mip選擇偏好:0.5沒有偏好,靠近0偏向選擇小圖片,靠近1偏向選擇大圖片(質量好一些)

float public_mip_bias=0.5; //[0..1] 
long SelectBestPicIndex(const TMipMap& mip,const long dstWidth,const long dstHeight)
{
long oldS=mip[0].width*mip[0].height;
long dstS=dstWidth*dstHeight;
if ( (dstS>=oldS) || (mip.size()==1) )
return 0;
else if (dstS<=1)
return mip.size()-1;
else
return (long)(log(oldS/dstS)*0.5+public_mip_bias);
}

F:利用MipMap後的縮放效果:


                 MipMap+近鄰取樣 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍
               (利用MipMap做一次近鄰取樣)


              MipMap+二次線性插值 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍
              (利用MipMap做一次二次線性插值)


              MipMap+三次卷積插值 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍
              (利用MipMap做一次三次卷積插值)

G: 在MipMap的兩張圖片之間插值:
  選擇MipMap的時候,同時可以選擇相鄰的兩張MipMap圖片;分別進行插值算法後得到兩個顏色結果;
對兩個MipMap圖片產生的評價值可以作爲這兩個顏色的插值權重,得到最終的顏色插值結果;優點是
縮放效果好,避免跳躍;缺點是速度慢:)  

選擇和權重函數的一個可能實現:

struct TMipWeight {
long  BigMip;
long  SmallMip;
float BigMipWeight;//[0..1]
};
TMipWeight SelectBestPicIndexEx(const TMipMap& mip,const long dstWidth,const long dstHeight)
{
long oldS=mip[0].width*mip[0].height;
long dstS=dstWidth*dstHeight;
    TMipWeight result;
if ( (dstS>=oldS) || (mip.size()==1) )
    {
        result.BigMip=0;
        result.SmallMip=0;
        result.BigMipWeight=1.0;
    }
else if (dstS<=1)
    {
        result.BigMip=mip.size()-1;
        result.SmallMip=mip.size()-1;
        result.BigMipWeight=1.0;
    }
else
    {
float bestIndex=log(oldS/dstS)*0.5+0.5; //or + public_mip_bias
        result.BigMip=(long)bestIndex;
if (bestIndex==mip.size()-1)
        {
            result.SmallMip=mip.size()-1;
            result.BigMipWeight=1.0;
        }
else
        {
            result.SmallMip=result.BigMip+1;
            result.BigMipWeight=1.0-(bestIndex-result.BigMip);
        }
    }
return result;
}

H:MipMap間插值效果:


              MipMap+兩次近鄰取樣 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍
              (利用MipMap做兩次近鄰取樣輸出兩個值,然後線性插值爲最終結果)


三次線性插值 縮放到0.4倍     縮放到0.2倍     縮放到0.1倍
             (三次線性插值:利用MipMap做兩次二次線性插值輸出兩個值,然後線性插值爲最終結果)  

           MipMap+兩次三次卷積插值 縮放到0.4倍    縮放到0.2倍     縮放到0.1倍
         (利用MipMap做兩次三次卷積插值輸出兩個值,然後線性插值爲最終結果)

(圖像縮放系列終於寫完了,計劃中寫圖像任意角度的高質量的快速旋轉、Alpha圖片混合等,盡請期待:)

(ps: 思考中的一個圖片壓縮方法:利用MipMap來壓縮圖像數據;輸入一張圖片,然後生成MipMap鏈,保存相鄰之間圖片的差(數值差可能很小,很容易找好的算法壓縮得很小)和最頂的一張圖片(一個點);  解壓的時候依次求和就得到原圖片了;  該算法爲無損壓縮,適合於人物風景等過渡比較多的圖片的壓縮,不太適合線條類等相鄰間顏色變化劇烈的圖片;)

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