by chuckGao 2009
最近在研究對視頻流放大播放時進行圖形縮放的bilinear和bicubic演算法。這裡給出最近一個項目中對映像進行2倍放大的演算法。而對於演算法的最佳化,可以考慮用建立縮放映射表的方法。
bilinear演算法:
int bilinear_scale
(long newx,
long newy,
unsigned long oldBytesperline,
unsigned long newBytesperline,
int format,
unsigned char* newImage,
Image image)
{
unsigned char* ptr;
if (newx==0 || newy==0){
return SIZE_ERROR;
}
if (image.width==newx && image.height==newy){
newImage = image.bits;
return NOT_TRANS;
}
float xScale, yScale, fX, fY;
xScale = (float)0.5;
yScale = (float)0.5;
long ifX, ifY, ifX1, ifY1, xmax, ymax;
float ir1, ir2, ig1, ig2, ib1, ib2, dx, dy;
unsigned char r,g,b;
scale_RGBQUAD rgb1, rgb2, rgb3, rgb4;
xmax = image.width-1;
ymax = image.width-1;
for(long y=0; y<newy; y++){ /*演算法最佳化by chuckGao:如果映像height>width,修改使程式減少for跨層計算*/
fY = y * yScale;
ifY = (int)fY;
ifY1 = min(ymax, ifY+1);
dy = fY - ifY;
for(long x=0; x<newx; x++){
fX = x * xScale;
ifX = (int)fX;
ifX1 = min(xmax, ifX+1);
dx = fX - ifX;
// Interpolate using the four nearest pixels in the source
unsigned char* ptr;
ptr = image.bits + ifY*oldBytesperline + ifX*format;
rgb1.rgbRed = *ptr++;
rgb1.rgbGreen= *ptr++;
rgb1.rgbBlue =*ptr;
ptr = image.bits + ifY*oldBytesperline + ifX1*format;
rgb2.rgbRed = *ptr++;
rgb2.rgbGreen= *ptr++;
rgb2.rgbBlue =*ptr;
ptr = image.bits + ifY1*oldBytesperline + ifX*format;
rgb3.rgbRed = *ptr++;
rgb3.rgbGreen= *ptr++;
rgb3.rgbBlue =*ptr;
ptr = image.bits + ifY1*oldBytesperline + ifX1*format;
rgb4.rgbRed = *ptr++;
rgb4.rgbGreen= *ptr++;
rgb4.rgbBlue =*ptr;
// Interplate in x direction:
ir1 = rgb1.rgbRed + (rgb3.rgbRed - rgb1.rgbRed) * dy;
ig1 = rgb1.rgbGreen + (rgb3.rgbGreen - rgb1.rgbGreen) * dy;
ib1 = rgb1.rgbBlue + (rgb3.rgbBlue - rgb1.rgbBlue) * dy;
ir2 = rgb2.rgbRed + (rgb4.rgbRed - rgb2.rgbRed) * dy;
ig2 = rgb2.rgbGreen + (rgb4.rgbGreen - rgb2.rgbGreen) * dy;
ib2 = rgb2.rgbBlue + (rgb4.rgbBlue - rgb2.rgbBlue) * dy;
// Interpolate in y:
r = (unsigned char)(ir1 + (ir2-ir1) * dx);
g = (unsigned char)(ig1 + (ig2-ig1) * dx);
b = (unsigned char)(ib1 + (ib2-ib1) * dx);
// Set output
ptr = newImage + y*newBytesperline + x*format;
*ptr++ = (unsigned char)r;
*ptr++ = (unsigned char)g;
*ptr = (unsigned char)b;
}
}
return 0;
}
bicubic演算法:
int bicubic_scale
(long newx,
long newy,
unsigned long oldBytesperline,
unsigned long newBytesperline,
int format,
unsigned char* newImage,
Image image)
{
unsigned char* ptr;
if (newx==0 || newy==0){
return SIZE_ERROR;
}
if (image.width==newx && image.height==newy){
return NOT_TRANS;
}
float xScale, yScale, fX, fY;
xScale = (float)0.5;
yScale = (float)0.5;
// bicubic interpolation by chuckGao
float f_x, f_y, a, b, rr, gg, bb, r1, r2;
int i_x, i_y, xx, yy;
scale_RGBQUAD rgb;
for(long y=0; y<newy; y++){
f_y = (float) y * yScale - 0.5f;
i_y = (int) floor(f_y);
a = f_y - (float)floor(f_y);
for(long x=0; x<newx; x++){
f_x = (float) x * xScale - 0.5f;
i_x = (int) floor(f_x);
b = f_x - (float)floor(f_x);
rr = gg = bb = 0.0f;
for(int m=-1; m<3; m++) {
r1 = kernelBSpline((float) m - a);
yy = i_y+m;
if (yy<0) yy=0;
if (yy>=image.height) yy = image.height-1;
for(int n=-1; n<3; n++) {
r2 = r1 * kernelBSpline(b - (float)n);
xx = i_x+n;
if (xx<0) xx=0;
if (xx>=image.width){
xx=image.width-1;
}
ptr = image.bits + yy*oldBytesperline + xx*format;
rgb.rgbRed = *ptr++;
rgb.rgbGreen= *ptr++;
rgb.rgbBlue = *ptr;
rr += rgb.rgbRed * r2;
gg += rgb.rgbGreen * r2;
bb += rgb.rgbBlue * r2;
}
}
ptr = newImage + y*newBytesperline + x*format;
*ptr++ = (unsigned char)rr;
*ptr++ = (unsigned char)gg;
*ptr = (unsigned char)bb;
}
}
return 0;
}
float kernelBSpline(const float x)
{
if (x>2.0f) return 0.0f;
// thanks to Kristian Kratzenstein
float a, b, c, d;
float xm1 = x - 1.0f; // Was calculatet anyway cause the "if((x-1.0f) < 0)"
float xp1 = x + 1.0f;
float xp2 = x + 2.0f;
if ((xp2) <= 0.0f) a = 0.0f; else a = xp2*xp2*xp2; // Only float, not float -> double -> float
if ((xp1) <= 0.0f) b = 0.0f; else b = xp1*xp1*xp1;
if (x <= 0) c = 0.0f; else c = x*x*x;
if ((xm1) <= 0.0f) d = 0.0f; else d = xm1*xm1*xm1;
return (0.16666666666666666667f * (a - (4.0f * b) + (6.0f * c) - (4.0f * d)));
}
在windows上用QT寫了一個opencv的視頻採集程式,今天測試的結果為:
1.對於640x480放大到1280x960,採用bicubic演算法,fps = 1 or 2,採用bilinear演算法,fps為6,而不進行縮放的映像播放fps為12
2.預計改換顯示大小為320x240,放大到640x480時fps有所增強
3.這裡的fps值只是近似得到,並不絕對,只能進行相對比較
(9月22日補充)
對於映像縮放演算法的最佳化,在很大程度上是對其運算速度進行c語言級甚至是彙編級的最佳化。首先要做c語言級的最佳化,一般採用兩種方法:
1.運算流程的最佳化,盡量減少跨層計算
2.以空間換時間,犧牲記憶體空間以加快計算速度
在基本清楚最佳化原則的前提下,也要對如下概念有清楚的認識:
1.演算法中涉及到的int、long、short(包括unsigned)以及float、double等數實值型別的掌握。要知道,對於映像縮放後精度的控制,必須清楚的知道計算時這些類型資料的情況,比如在對float類型資料(假設變數名為rgb)賦值時:
float rgb = 0.5 和float rgb = 0.5f在精度上就有所差異,前者自動轉換類型為double型,64位元據,而後者為float 32位
2.在32-bit電腦中,long和int的size一般是相等的,即佔4bytes,32位。但計算時,特別在malloc時,還是應謹慎使用sizeof(long)來確定其大小
3.拋開演算法本身不談,計算中資料類型為unsigned char的運算肯定比float的快,但精確度肯定不如後者。在實際編程中的取捨可以根據映像顯示效果和速度來決定