圖形影象處理-之-高質量的快速的影象縮放 上篇 近鄰取樣插值和其速度優化
轉自:http://blog.csdn.net/housisong/article/details/1404896
圖形影象處理-之-高質量的快速的影象縮放 上篇 近鄰取樣插值和其速度優化
HouSisong@GMail.com 2006.11.22
(2009.03.07 可以到這裡下載縮放演算法的完整的可以編譯的專案原始碼: http://blog.csdn.net/housisong/archive/2009/03/07/3967270.aspx )
( 2007.06.06 更新測試資料,編譯器由vc6改為vc2005,CPU由賽揚2G改為AMD64x2 4200+(2.1G) )
(2007.01.02更新)
tag:影象縮放,速度優化,定點數優化,近鄰取樣插值,二次線性插值,三次線性插值,
MipMap鏈,三次卷積插值,MMX,SSE,SSE2,CPU快取優化
摘要:首先給出一個基本的影象縮放演算法,然後一步一步的優化其速度和縮放質量;
高質量的快速的影象縮放 全文 分為:
上篇 近鄰取樣插值和其速度優化
中篇 二次線性插值和三次卷積插值
下篇 三次線性插值和MipMap鏈
補充 使用SSE2優化
正文:
為了便於討論,這裡只處理32bit的ARGB顏色;
程式碼使用C++;涉及到彙編優化的時候假定為x86平臺;使用的編譯器為vc2005;
為了程式碼的可讀性,沒有加入異常處理程式碼;
測試使用的CPU為AMD64x2 4200+(2.37G) 和 Intel Core2 4400(2.00G);
速度測試說明:
只測試記憶體資料到記憶體資料的縮放
測試圖片都是800*600縮放到1024*768; fps表示每秒鐘的幀數,值越大表示函式越快
////////////////////////////////////////////////////////////////////////////////
//Windows GDI相關函式參考速度:
//==============================================================================
// BitBlt 544.7 fps //is copy 800*600 to 800*600
// BitBlt 331.6 fps //is copy 1024*1024 to 1024*1024
// StretchBlt 232.7 fps //is zoom 800*600 to 1024*1024
////////////////////////////////////////////////////////////////////////////////
A: 首先定義影象資料結構:
#define asm __asm
typedef unsigned char TUInt8; // [0..255]
struct TARGB32 //32 bit color
{
TUInt8 B,G,R,A; // A is alpha
};
struct TPicRegion //一塊顏色資料區的描述,便於引數傳遞
{
TARGB32* pdata; //顏色資料首地址
long byte_width; //一行資料的物理寬度(位元組寬度);
//abs(byte_width)有可能大於等於width*sizeof(TARGB32);
long width; //畫素寬度
long height; //畫素高度
};
//那麼訪問一個點的函式可以寫為:
inline TARGB32& Pixels(const TPicRegion& pic,const long x,const long y)
{
return ( (TARGB32*)((TUInt8*)pic.pdata+pic.byte_width*y) )[x];
}
B: 縮放原理和公式圖示:
縮放後圖片 原圖片
(寬DW,高DH) (寬SW,高SH)
(Sx-0)/(SW-0)=(Dx-0)/(DW-0) (Sy-0)/(SH-0)=(Dy-0)/(DH-0)
=> Sx=Dx*SW/DW Sy=Dy*SH/DH
C: 縮放演算法的一個參考實現
//給出一個最簡單的縮放函式(插值方式為近鄰取樣,而且我“盡力”把它寫得慢一些了:D)
//Src.PColorData指向源資料區,Dst.PColorData指向目的資料區
//函式將大小為Src.Width*Src.Height的圖片縮放到Dst.Width*Dst.Height的區域中
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
for (long x=0;x<Dst.width;++x)
{
for (long y=0;y<Dst.height;++y)
{
long srcx=(x*Src.width/Dst.width);
long srcy=(y*Src.height/Dst.height);
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicZoom0 19.4 fps
////////////////////////////////////////////////////////////////////////////////
D: 優化PicZoom0函式
a.PicZoom0函式並沒有按照顏色資料在記憶體中的排列順序讀寫(內部迴圈遞增y行
索引),將造成CPU快取預讀失敗和記憶體顛簸導致巨大的效能損失,(很多硬體都有這種特性,
包括快取、記憶體、視訊記憶體、硬碟等,優化順序訪問,隨機訪問時會造成巨大的效能損失)
所以先交換x,y迴圈的順序:
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
for (long y=0;y<Dst.height;++y)
{
for (long x=0;x<Dst.width;++x)
{
long srcx=(x*Src.width/Dst.width);
long srcy=(y*Src.height/Dst.height);
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicZoom1 30.1 fps
////////////////////////////////////////////////////////////////////////////////
b.“(x*Src.Width/Dst.Width)”表示式中有一個除法運算,它屬於很慢的操作(比一般
的加減運算慢幾十倍!),使用定點數的方法來優化它;
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
//函式能夠處理的最大圖片尺寸65536*65536
unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width+1; //16.16格式定點數
unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height+1; //16.16格式定點數
{
for (unsigned long x=0;x<Dst.width;++x)
{
unsigned long srcx=(x*xrIntFloat_16)>>16;
unsigned long srcy=(y*yrIntFloat_16)>>16;
Pixels(Dst,x,y)=Pixels(Src,srcx,srcy);
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicZoom2 185.8 fps
////////////////////////////////////////////////////////////////////////////////
c. 在x的迴圈中y一直不變,那麼可以提前計算與y相關的值; 1.可以發現srcy的值和x變數無關,可以提前到x軸迴圈之前;2.展開Pixels函式,優化與y相關的指標計算;
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width+1;
unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height+1;
TARGB32* pDstLine=Dst.pdata;
unsigned long srcy_16=0;
for (unsigned long y=0;y<Dst.height;++y)
{
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));
unsigned long srcx_16=0;
for (unsigned long x=0;x<dst_width;++x)
{
pDstLine[x]=pSrcLine[srcx_16>>16];
srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicZoom3 414.4 fps
////////////////////////////////////////////////////////////////////////////////
d.定點數優化使函式能夠處理的最大圖片尺寸和縮放結果(肉眼不可察覺的誤差)受到了一
定的影響,這裡給出一個使用浮點運算的版本,可以在有這種需求的場合使用:
{
//注意: 該函式需要FPU支援
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
double xrFloat=1.000000001/((double)Dst.width/Src.width);
double yrFloat=1.000000001/((double)Dst.height/Src.height);
unsigned short RC_Old;
unsigned short RC_Edit;
asm //設定FPU的取整方式 為了直接使用fist浮點指令
{
FNSTCW RC_Old // 儲存協處理器控制字,用來恢復
FNSTCW RC_Edit // 儲存協處理器控制字,用來修改
FWAIT
OR RC_Edit, 0x0F00 // 改為 RC=11 使FPU向零取整
FLDCW RC_Edit // 載入協處理器控制字,RC場已經修改
}
unsigned long dst_width=Dst.width;
TARGB32* pDstLine=Dst.pdata;
double srcy=0;
for (unsigned long y=0;y<Dst.height;++y)
{
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*((long)srcy)));
/**//*
double srcx=0;
for (unsigned long x=0;x<dst_width;++x)
{
pDstLine[x]=pSrcLine[(unsigned long)srcx];//因為預設的浮點取整是一個很慢
//的操作! 所以才使用了直接操作FPU的內聯彙編程式碼。
srcx+=xrFloat;
}*/
asm fld xrFloat //st0==xrFloat
asm fldz //st0==0 st1==xrFloat
unsigned long srcx=0;
for (long x=0;x<dst_width;++x)
{
asm fist dword ptr srcx //srcx=(long)st0
pDstLine[x]=pSrcLine[srcx];
asm fadd st,st(1) //st0+=st1 st1==xrFloat
}
asm fstp st
asm fstp st
srcy+=yrFloat;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
asm //恢復FPU的取整方式
{
FWAIT
FLDCW RC_Old
}
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicZoom3_float 286.2 fps
////////////////////////////////////////////////////////////////////////////////
e.注意到這樣一個事實:每一行的縮放比例是固定的;那麼可以預先建立一個縮放對映表格
來處理縮放對映演算法(PicZoom3_Table和PicZoom3_float的實現等價);
{
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsigned long dst_width=Dst.width;
unsigned long* SrcX_Table = new unsigned long[dst_width];
for (unsigned long x=0;x<dst_width;++x)//生成表 SrcX_Table
{
SrcX_Table[x]=(x*Src.width/Dst.width);
}
TARGB32* pDstLine=Dst.pdata;
for (unsigned long y=0;y<Dst.height;++y)
{
unsigned long srcy=(y*Src.height/Dst.height);
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*srcy));
for (unsigned long x=0;x<dst_width;++x)
pDstLine[x]=pSrcLine[SrcX_Table[x]];
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
delete [] SrcX_Table;
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicZoom3_Table 390.1 fps
////////////////////////////////////////////////////////////////////////////////
f.為了加快縮放,可以採用根據縮放比例動態生成函式的方式來得到更快的縮放函式;這
有點像編譯器的工作原理;要實現它需要的工作量比較大(或比較晦澀)就不再實現了;
(動態生成是一種不錯的思路,但個人覺得對於縮放,實現它的必要性不大)
g.現代CPU中,在讀取資料和寫入資料時,都有自動的快取機制;很容易知道,演算法中生
成的資料不會很快再次使用,所以不需要寫入快取的幫助;在SSE指令集中增加了movntq
等指令來完成這個功能;
(嘗試過利用CPU顯式prefetcht0、prefetchnta預讀指令或直接的mov讀取指令等速度反
而略有下降:( 但預讀在copy演算法中速度優化效果很明顯 )
{
//警告: 函式需要CPU支援MMX和movntq指令
if ( (0==Dst.width)||(0==Dst.height)
||(0==Src.width)||(0==Src.height)) return;
unsigned long xrIntFloat_16=(Src.width<<16)/Dst.width+1;
unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height+1;
unsigned long dst_width=Dst.width;
TARGB32* pDstLine=Dst.pdata;
unsigned long srcy_16=0;
for (unsigned long y=0;y<Dst.height;++y)
{
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));
asm
{
push ebp
mov esi,pSrcLine
mov edi,pDstLine
mov edx,xrIntFloat_16
mov ecx,dst_width
xor ebp,ebp //srcx_16=0
and ecx, (not 3) //迴圈4次展開
TEST ECX,ECX //nop
jle EndWriteLoop
lea edi,[edi+ecx*4]
neg ecx
//todo: 預讀
WriteLoop:
mov eax,ebp
shr eax,16 //srcx_16>>16
lea ebx,[ebp+edx]
movd mm0,[esi+eax*4]
shr ebx,16 //srcx_16>>16
PUNPCKlDQ mm0,[esi+ebx*4]
lea ebp,[ebp+edx*2]
// movntq qword ptr [edi+ecx*4], mm0 //不使用快取的寫入指令
asm _emit 0x0F asm _emit 0xE7 asm _emit 0x04 asm _emit 0x8F
mov eax,ebp
shr eax,16 //srcx_16>>16
lea ebx,[ebp+edx]
movd mm1,[esi+eax*4]
shr ebx,16 //srcx_16>>16
PUNPCKlDQ mm1,[esi+ebx*4]
lea ebp,[ebp+edx*2]
// movntq qword ptr [edi+ecx*4+8], mm1 //不使用快取的寫入指令
asm _emit 0x0F asm _emit 0xE7 asm _emit 0x4C asm _emit 0x8F asm _emit 0x08
add ecx, 4
jnz WriteLoop
//sfence //重新整理寫入
asm _emit 0x0F asm _emit 0xAE asm _emit 0xF8
emms
EndWriteLoop:
mov ebx,ebp
pop ebp
//處理邊界 迴圈次數為0,1,2,3;(這個迴圈可以展開,做一個跳轉表,略)
mov ecx,dst_width
and ecx,3
TEST ECX,ECX
jle EndLineZoom
lea edi,[edi+ecx*4]
neg ecx
StartBorder:
mov eax,ebx
shr eax,16 //srcx_16>>16
mov eax,[esi+eax*4]
mov [edi+ecx*4],eax
add ebx,edx
inc ECX
JNZ StartBorder
EndLineZoom:
}
//
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
}
//讀者可以相互對照來閱讀程式碼
//要編譯PicZoom3_SSE_mmh,需要#include <mmintrin.h> #include <xmmintrin.h>
//並且需要編譯器支援
//函式PicZoom3_SSE_mmh速度為 593.7 fps
{
//警告: 函式需要CPU支援MMX和movntq指令
||(0==Src.width)||(0==Src.height)) return;
unsigned long yrIntFloat_16=(Src.height<<16)/Dst.height+1;
TARGB32* pDstLine=Dst.pdata;
unsigned long srcy_16=0;
unsigned long for4count=dst_width/4*4;
for (unsigned long y=0;y<Dst.height;++y)
{
TARGB32* pSrcLine=((TARGB32*)((TUInt8*)Src.pdata+Src.byte_width*(srcy_16>>16)));
unsigned long srcx_16=0;
unsigned long x;
for (x=0;x<for4count;x+=4)//迴圈4次展開
{
__m64 m0=_m_from_int(*(int*)(&pSrcLine[srcx_16>>16]));
srcx_16+=xrIntFloat_16;
m0=_m_punpckldq(m0, _m_from_int(*(int*)(&pSrcLine[srcx_16>>16])) );
srcx_16+=xrIntFloat_16;
__m64 m1=_m_from_int(*(int*)(&pSrcLine[srcx_16>>16]));
srcx_16+=xrIntFloat_16;
m1=_m_punpckldq(m1, _m_from_int(*(int*)(&pSrcLine[srcx_16>>16])) );
srcx_16+=xrIntFloat_16;
_mm_stream_pi((__m64 *)&pDstLine[x],m0); //不使用快取的寫入指令
_mm_stream_pi((__m64 *)&pDstLine[x+2],m1); //不使用快取的寫入指令
}
for (x=for4count;x<dst_width;++x)//處理邊界
{
pDstLine[x]=pSrcLine[srcx_16>>16];
srcx_16+=xrIntFloat_16;
}
srcy_16+=yrIntFloat_16;
((TUInt8*&)pDstLine)+=Dst.byte_width;
}
_m_empty();
}
////////////////////////////////////////////////////////////////////////////////
//速度測試:
//==============================================================================
// PicZoom3_SSE 711.7 fps
////////////////////////////////////////////////////////////////////////////////
E: 縮放效果圖:
原圖 放大圖(x軸放大8倍,y軸放大12倍)
原圖 縮小圖(縮小到0.66倍) 放大圖(放大到1.6倍)
F: 把測試成績放在一起:
////////////////////////////////////////////////////////////////////////////////
//CPU: AMD64x2 4200+(2.1G) zoom 800*600 to 1024*768
//==============================================================================
// BitBlt 544.7 fps //is copy 800*600 to 800*600
// BitBlt 331.6 fps //is copy 1024*1024 to 1024*1024
// StretchBlt 232.7 fps //is zoom 800*600 to 1024*1024
//
// PicZoom0 19.4 fps
// PicZoom1 30.1 fps
// PicZoom2 185.8 fps
// PicZoom3 414.4 fps
// PicZoom3_float 286.2 fps
// PicZoom3_Table 390.1 fps
// PicZoom3_SSE 711.7 fps
////////////////////////////////////////////////////////////////////////////////
補充Intel Core2 4400上的測試成績:
////////////////////////////////////////////////////////////////////////////////
//CPU: Intel Core2 4400(2.00G) zoom 800*600 to 1024*768
//==============================================================================
// PicZoom0 15.0 fps
// PicZoom1 63.9 fps
// PicZoom2 231.2 fps
// PicZoom3 460.5 fps
// PicZoom3_float 422.5 fps
// PicZoom3_Table 457.6 fps
// PicZoom3_SSE 1099.7 fps
////////////////////////////////////////////////////////////////////////////////
相關文章
- [Python影象處理] 六.影象縮放、影象旋轉、影象翻轉與影象平移Python
- [work] 影象縮放——雙線性插值演算法演算法
- [Python影象處理] 七.影象閾值化處理及演算法對比Python演算法
- 極端影象壓縮的生成對抗網路,可生成低位元速率的高質量影象
- 影象處理之影象增強
- 影像重取樣演算法之最鄰近插值演算法演算法
- [Python影象處理] 八.影象腐蝕與影象膨脹Python
- [Python影象處理] 十.形態學之影象頂帽運算和黑帽運算Python
- [Python影象處理] 三.獲取影象屬性、興趣ROI區域及通道處理Python
- 騰訊優圖提出SRN-DeblurNet:高效高質量去除複雜影象模糊
- 影象中的畫素處理
- [Python影象處理] 九.形態學之影象開運算、閉運算、梯度運算Python梯度
- [Python影象處理] 五.影象融合、加法運算及影象型別轉換Python型別
- 數字影象處理DIP
- 影象處理的濾鏡演算法演算法
- 黑洞圖片的背後,是影象處理技術的成熟!
- [Python影象處理] 一.影象處理基礎知識及OpenCV入門函式PythonOpenCV函式
- 譯文:影象優化(上)優化
- 影象二值化(python+opencv)PythonOpenCV
- 影象的卷積和池化操作卷積
- 數字影象處理-第一節
- 影象處理庫GPUImage簡單使用GPUUI
- 【筆記】基於Python的數字影象處理筆記Python
- [Python影象處理] 四.影象平滑之均值濾波、方框濾波、高斯濾波及中值濾波Python
- SwiftUI圖片處理(縮放、拼圖)SwiftUI
- 影象處理入門:目標檢測和影象檢索綜述
- 實戰 | 用Python做影象處理(一)Python
- [Python影象處理] 二.OpenCV+Numpy庫讀取與修改畫素PythonOpenCV
- OpenCV之影象直方圖均衡化OpenCV直方圖
- Luminar 4 for MacOS影象後期處理軟體Mac
- matlab中將RGB影象轉化為灰度影象Matlab
- Python基礎教程:Day15-影象和辦公文件處理Python
- [Python影象處理] 十一.灰度直方圖概念及OpenCV繪製直方圖Python直方圖OpenCV
- WebComponent+WebGl的實時影象處理彈幕播放器Web播放器
- 【查蟲日誌】快速判斷一副灰度影象中是否只有黑色和白色值(即是否為二值影象)過程中bool變數的是是非非。變數
- 圖鴨科技獲CVPR 2018影象壓縮挑戰賽單項冠軍,技術解讀端到端影象壓縮框架框架
- unity中取樣深度圖的結果處理Unity
- CVPR 2019 | 全新缺失影象資料插補框架—CollaGAN框架
- 影象處理1--傅立葉變換(Fourier Transform )ORM