1. 程式人生 > >雙線性插值的實現

雙線性插值的實現

影象在進行仿射變換後由於變換後的某些畫素點在原影象中並不存在,因此需採用灰度內插為新位置賦灰度值,常用的內插方法包括:最近鄰內插法、雙線性內插法以及雙三次內插技術,綜合插值效果及效率,雙線性內插法得到了更廣泛的應用。

雙線性插值的演算法實現是這樣的:首先對源影象與目標影象做對比,比如源影象大小為1100*1100,目標影象大小為500*500,則二者之間的寬度比和高度比均可得到,為2.2,也就是說目標影象中的(1,1)對映到源影象中是(2.2,2.2),而實際影象中的畫素點位置都是整數,沒有浮點數,因此我們可以使用與此數值接近的四個點通過插值得到目標影象中(1,1)位置的畫素值,這四個點是:Q11(2,2),Q21(2,3),Q12(3,2),Q22(3,3)。從通用性考慮,Q11的橫縱座標分別為x1,y1,同理Q21的橫縱座標分別為x1,y2,Q12的橫縱座標分別為x2,y1,Q22的橫縱座標分別為x2,y2。而目標影象所對映到源影象中的為(2.2,2.2)即為(x,y),這樣通過四個點對映到(x,y)即可得到目標影象(1,1)處的畫素值,首先在x方向進行兩次線性插值計算:

f(x,y1) = (x2-x)/(x2-x1) * f(x1,y1) + (x-x1)/(x2-x1) *f(x2,y1)

f(x,y2)=  (x2-x)/(x2-x1) * f(x1,y2) + (x-x1)/(x2-x1)*f(x2,y2)

然後在y方向進行一次插值計算:

f(x,y) = (y2-y)/(y2-y1)*f(x,y1) + (y-y2)/(y2-y1)*f(x,y2)

由於x2-x1 = 1, y2-y1 = 1,最終可得

f(x,y) = (x2-x)(y2-y)f(x1,y1) + (x-x1)(y-y1)f(x2,y2)  + (x-x1)(y2-y)f(x2,y1) + (x2-x)(y-y1)f(x1,y2)

令x-x1=u,y-y1 =v,則上式又可轉化為:

f(x,y) = (1-u)(1-v)f(x1,y1) + uvf(x2,y2) + u(1-v)f(x2,y1) + (1-u)vf(x1,y2)

式中x,y為目標影象中的畫素點位置,(x1,y1),(x1,y2),(x2,y1),(x2,y2)為對應的源影象中的四個畫素點位置,那麼如何得到這四個點的座標值呢?若按原始座標變換關係,有:

srcX = dstX * (srcWidth/dstWidth)

srcY = dstY* (srcHeight/dstHeight)

正如我們舉的例子中,對應於目標影象(1,1),可得到其對應源影象的畫素位置:

x = 1 *(1100/500) = 2.2 

y = 1 * (1100/500) =2.2

為了使目標影象和源影象中心對齊,上述公式改寫為:

srcX = (dstX +0.5) * (srcWidth/dstWidth) - 0.5

srcY = (dstY+0.5)* (srcHeight/dstHeight) - 0.5

另外為了提高運算速度,建議將浮點運算轉換成整數運算,opencv中選擇的放大倍數是2048,這可通過左移11位得到。最終得到的結果則需右移22位(u,v的乘積)。

基於此,得到雙線性插值的演算法如下:

Mat matDst1,matSrc;
	matSrc = cv::imread("opencv.jpg",IMREAD_COLOR);
	int channel = matSrc.channels();
	matDst1 = Mat(1000,1000,CV_8UC3);
	float scale_x = matSrc.cols/1000.0,scale_y = matSrc.rows/1000.0;

	uchar* dataDst = matDst1.data; //目標影象
    int stepDst = matDst1.step;
    uchar* dataSrc = matSrc.data;//源影象
    int stepSrc = matSrc.step; //一行的寬度
    int iWidthSrc = matSrc.cols; 
    int iHieghtSrc = matSrc.rows;  

    for (int j = 0; j < matDst1.rows; ++j) //高度方向
    {
        float fy = (float)((j + 0.5) * scale_y - 0.5);
        int sy = cvFloor(fy);
        fy -= sy; //u = fy
        sy = std::min(sy, iHieghtSrc - 2);//邊界限制
        sy = std::max(0, sy);

        short cbufy[2];
        cbufy[0] = cv::saturate_cast<short>((1.f - fy) * 2048);// 1-u 擴大2048倍,即左移11位
        cbufy[1] = 2048 - cbufy[0];

        for (int i = 0; i < matDst1.cols; ++i)//寬度方向
        {
            float fx = (float)((i + 0.5) * scale_x - 0.5);
            int sx = cvFloor(fx);
            fx -= sx;
			//以下兩個if與上面的sy的兩條語句(min和max)是一樣的
            if (sx < 0) {
                fx = 0, sx = 0;
            }
            if (sx >= iWidthSrc - 1) {
                fx = 0, sx = iWidthSrc - 2;
            }

            short cbufx[2];
            cbufx[0] = cv::saturate_cast<short>((1.f - fx) * 2048);
            cbufx[1] = 2048 - cbufx[0];

            for (int k = 0; k < matSrc.channels(); ++k)
            {
                *(dataDst+ j*stepDst + 3*i + k) = (*(dataSrc + sy*stepSrc + 3*sx + k) * cbufx[0] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*sx + k) * cbufx[0] * cbufy[1] + 
                    *(dataSrc + sy*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[0] + 
                    *(dataSrc + (sy+1)*stepSrc + 3*(sx+1) + k) * cbufx[1] * cbufy[1]) >> 22;
            }
        }
    }
	
    cv::imwrite("linear_1.jpg", matDst1);

上述程式碼通過編譯,執行成功後將生成的新影象儲存為jpg圖片。