雙線性插值原理及其實現--基於OpenCV實現
這是我第一個部落格,一方面我把部落格看作筆記,記錄我在學習的過程中遇到的問題,寫部落格的過程也是加深理解的過程;另一方面也是為了與大家分享,共同進步。
雙線性插值分為兩步:1. 先做影象尺寸縮放;2. 求縮放後的影象畫素灰度值。
- 影象尺度縮放變換的公式
其中,(x, y)是原圖座標,(x’, y’)是變換後的座標,a,b分別是水平和垂直方向的縮放因子。這是前向變換,即從原圖變換到目標影象。在雙線性插值處理中,我們往往已知原圖大小和縮放因子,求得縮放後的影象灰度值。目標影象灰度值就是根據目標畫素在原圖中的位置的灰度值雙線性插值得到的。根據目標影象的畫素求得對應於原圖中的位置就是後向變換,公式如下:
根據公式,後向變換計算出來的座標一般不是整數。最鄰近插值就是把後向變換的座標取整,獲得原圖座標處的畫素值作為目標影象的畫素值。
上述變換公式都假定原圖和目標影象的座標原點在左上角,前向變換沒有任何問題,但是後向變換就存在問題了。如下圖所示,假設把5x5的影象縮小為3x3的,那麼原圖與目標影象之間的座標對應關係如下圖所示:這裡只畫出第一行!
其中,黑色的原圖,紅色的框是目標影象。變換後的影象的3個畫素平均佔據在原圖的5個畫素的前3個位置上,但是由於目標影象的3個畫素起始點選在了原圖的左上角,導致原圖最後一個畫素座標沒有以及最後一行的畫素座標都沒有使用到。為了充分利用原圖每一個畫素座標,應該保持兩個影象的幾何中心重合,並且目標影象的每個畫素之間都是等間隔的,並且都和兩邊有一定的邊距,如下圖所示。
所以,最終的後向變換公式為
- 雙線性插值
在求得影象縮放後的座標只之後,就需要對目標影象的每一個畫素計算其畫素值了。
雙線性變換其實就是在水平和垂直方向分別作一次線性變換,如下圖所示
假設目標影象P點座標為P(x, y),現在利用P點在原圖的位置的最鄰近的4個點的畫素值擬合P點的畫素值。P點座標在原圖上最鄰近的4點分別為左上、右上、左下、右下。
(1). 先對x方向插值
Q12,R2,Q22; Q11,R1,Q21三點共線,所以
(2). 再對y方向進行插值
R2,P,R1三點共線,有
其中,與P點最鄰近的4點座標分別為
Q11 = (x’, y’), Q21 = (x’+1, y’)
Q21 = (x’, y’+1), Q22 = (x’+1, y’ + 1)
其中,(x’, y’)是P點座標在原圖的整數部分。
所以,上述公式可以進一步變化為
其中,u,v分別是P點y,x座標取整後的小數部分。
至此,雙線性插值的原理部分講完了。其實這個原理也不完全是我自己寫的,我也是參考了很多網上的別人的播客,待會我會在下面把那些部落格貼上來。
下面附上我的雙線性插值程式碼實現:
這裡寫程式碼片
void Geometry_Transform::Interpolate(Mat src, Mat& mask)
{
int Rows = mask.rows;
int Cols = mask.cols;
uchar* mask_data = mask.data;
uchar* src_data = _src.data;
if (3 == src.channels())
{
for (int i = 0; i < Rows; ++i)
{
double r = (i + 0.5 ) * 1.0 / _sy - 0.5;//浮點行
for (int j = 0; j < Cols; ++j)
{
double c = (j + 0.5 ) * 1.0 / _sx - 0.5;//浮點列
//獲得四個頂點
int lRow = static_cast<int>(r);//最鄰近行,取整
int nRow = lRow + 1;
int lCol = static_cast<int>(c);//最鄰近列
int nCol = lCol + 1;
double u = r - lRow;
double v = c - lCol;
if (lRow >= _src.rows - 1 && lCol >= _src.cols - 1)//右下角
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]]);//B通道
mask_data[i * mask.step[0] + j * mask.step[1] + 1] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]] + 1);//G通道
mask_data[i * mask.step[0] + j * mask.step[1] + 2] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2]);//R通道
}
else if (lRow >= _src.rows - 1)//最後一行
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]]);
mask_data[i * mask.step[0] + j * mask.step[1] + 1] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1] + 1] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 1]);
mask_data[i * mask.step[0] + j * mask.step[1] + 2] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 2]);
}
else if (lCol >= _src.cols /** _src.channels()*/ - 1)//最後一列
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]]);
mask_data[i * mask.step[0] + j * mask.step[1] + 1] =
saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 1] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 1]);
mask_data[i * mask.step[0] + j * mask.step[1] + 2] =
saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 2]);
}
else
{
mask_data[i * mask.step[0] + j * mask.step[1]] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1]]);
mask_data[i * mask.step[0] + j * mask.step[1] + 1] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 1] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 1] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 1] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1] + 1]);
mask_data[i * mask.step[0] + j * mask.step[1] + 2] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1] + 2] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1] + 2] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1] + 2] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1] + 2]);
}
}//end inner for
}//end outer for
}//end color
else
{
for (int i = 0; i < Rows; ++i)
{
double r = i * 1.0 / _sy;//浮點行
//uchar* mask_p = mask.ptr<uchar>(i);//當前行指標
for (int j = 0; j < Cols; ++j)
{
double c = j * 1.0 / _sx;//浮點列
//獲得四個頂點
int lRow = static_cast<int>(r);//最鄰近行,取整
int nRow = lRow + 1;
int lCol = static_cast<int>(c);//最鄰近列
int nCol = lCol + 1;
double u = r - lRow;
double v = c - lCol;
if (lRow >= _src.rows - 1 && lCol >= _src.cols - 1)//右下角
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]]);
}
else if (lRow >= _src.rows - 1)//最後一行
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) *
src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]]);
}
else if (lCol >= _src.cols - 1)//最後一列
{
mask_data[i * mask.step[0] + j * mask.step[1]] =
saturate_cast<uchar>((1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]]);
}
else
{
mask_data[i * mask.step[0] + j * mask.step[1]] = saturate_cast<uchar>(
(1 - u) * (1 - v) * src_data[lRow * _src.step[0] + lCol * _src.step[1]] +
(1 - u) * v * src_data[lRow * _src.step[0] + nCol * _src.step[1]] +
u * (1 - v) * src_data[nRow * _src.step[0] + lCol * _src.step[1]] +
u * v * src_data[nRow * _src.step[0] + nCol * _src.step[1]]);
}
}//end inner for
}//end outer for
}//end else
}
這裡寫程式碼片
Mat Geometry_Transform::Up_Down(const double sx, const double sy)
{
int sw = static_cast<int>(_src.rows * sy);
int sh = static_cast<int>(_src.cols * sx);
Mat mask = Mat::zeros(sw, sh, _src.type());
_sx = sx;
_sy = sy;
if (sx == 1 && sy == 1)//不做縮放運算,直接返回原圖
return _src;
else
{
Interpolate(_src, mask);
return mask;
}
}
這裡寫程式碼片
int main()
{
const string impath = "D:/opera.png";
Mat src = imread(impath);
if (!src.data)
{
cerr << "讀取圖片失敗..." << endl;
return 0;
}
Mat gray;
cvtColor(src, gray, CV_BGR2GRAY);
const double sx = 3;
const double sy = 3;
Geometry_Transform Geo_obj(src, sx, sy);
Mat mask = Geo_obj.Up_Down();
//呼叫OpenCV自帶的縮放函式測試
Mat res;
resize(src, res, Size(0, 0), sx, sy, INTER_LINEAR);
Mat ss;
subtract(mask, rs, ss);//做差比較
return 0;
}
PS:
- 啊,我的第一個部落格終於寫完了。第一次寫部落格發現好難,文筆向來不好,不知道怎麼寫。對於數學公式插入進來不知道怎麼編輯,不會latex語法,後面我會學一點latex語法,儘量把公式編輯的漂亮一點吧。