本文主要講三點:
1、ZNCC的原理;
2、ZNCC的積分圖實現方法;
3、ZNCC編程中出現的一個小問題;
1.ZNCC的基本原理
立體匹配中最常用的一個相似性測度就是ZNCC(Zero-based Normalized Cross Correlation),ZNCC的數學公式爲:
(公式1)
直接由公式1計算ZNCC存在大量冗餘計算(比如重複求取平均值)。繼續化簡,得到:
(公式2)
公式2把ZNCC的冗餘計算表現的非常直觀了,顯然,SI,SJ,SII,SJJ這四項是無需重複計算的。
那麼,如何把SI,SII,SJ,SJJ保存起來呢?答案就是積分圖。
2. ZNCC的積分圖實現
積分圖是由普通數字圖像生成的一種屬性圖像,尺寸等於原始圖像,而每個像素點保存由原始圖像左上角和當前像素點確定的矩形範圍的灰度值(也可以爲圖像的其他屬性值)總和。下圖是一個形象的表示,左圖表示原始影像,右圖表示相應的積分圖像。
積分圖的數學表達式爲:
(公式3)
離散的實現公式爲:
計算了積分圖之後,ZNCC中的SI,SII,SJ,SJJ的計算公式爲:
其中,l爲半個窗口的尺寸。
生成積分圖的代碼如下:
void CalInteg(unsigned char *buf, unsigned long *DJ, unsigned long *DJJ)
{
unsigned char *I = buf;
*DJ=*I;
*DJJ=(*I) * (*I);
for (int j=1; j<width; j++)//第一行
{
*(DJ+j)=*(DJ+j-1)+*(I+j);
*(DJJ+j)=*(DJJ+j-1)+*(I+j)* *(I+j);
}
int WH = width*height;
for (int i=width; i<WH; i+=width)//第一列
{
*(DJ+i)=*(I+i)+ *(DJ+(i-width));
*(DJJ+i)=*(I+i)* *(I+i)+ *(DJJ+(i-width));
}
for (int i=width; i<WH; i+=width)
{
for(int j_index=1+i; j_index<width+i; j_index++)
{
*(DJ+j_index)=*(I+j_index)+ *(DJ+j_index-width)+*(DJ+j_index-1)-*(DJ+j_index-width-1);
*(DJJ+j_index)=*(I+j_index)* *(I+j_index)+ *(DJJ+j_index-width)+*(DJJ+j_index-1)-*(DJJ+j_index-width-1);
}
}
}
用積分圖計算ZNCC的代碼如下:
sij = 0;
int jd = j + d + disparity_begin;
for (int m=-hwin;m<=hwin;m++)
{
for(int n=-hwin;n<=hwin;n++)
{
sij += bufl[(i+m)*width+jd+n]*bufr[(i+m)*width+j+n];
}
}
si = bufl_i_integ[(i+hwin)*width+jd+hwin]-bufl_i_integ[(i+hwin)*width+jd-hwin-1]
-bufl_i_integ[(i-hwin-1)*width+jd+hwin]+bufl_i_integ[(i-hwin-1)*width+jd-hwin-1];
sii = bufl_ii_integ[(i+hwin)*width+jd+hwin]-bufl_ii_integ[(i+hwin)*width+jd-hwin-1]
-bufl_ii_integ[(i-hwin-1)*width+jd+hwin]+bufl_ii_integ[(i-hwin-1)*width+jd-hwin-1];
sj = bufr_i_integ[(i+hwin)*width+j+hwin]-bufr_i_integ[(i+hwin)*width+j-hwin-1]
-bufr_i_integ[(i-hwin-1)*width+j+hwin]+bufr_i_integ[(i-hwin-1)*width+j-hwin-1];
sjj = bufr_ii_integ[(i+hwin)*width+j+hwin]-bufr_ii_integ[(i+hwin)*width+j-hwin-1]
-bufr_ii_integ[(i-hwin-1)*width+j+hwin]+bufr_ii_integ[(i-hwin-1)*width+j-hwin-1];
float zncc = sqrt((float)(NUM*sii-si*si)*(NUM*sjj-sj*sj));
if (zncc)
{
cost = (NUM*sij-si*sj)/zncc;
}
else
{
cost = 0;
}
3. ZNCC積分圖實現中出現的一個小問題
由於積分圖需要保存很大的數值,而且都是正數,我們很容易想到使用unsigned long來保存積分圖像,這是合理且正確的,然而用積分圖計算ZNCC時就要非常注意了,不能沿用原來的數據類型unsigned long了。比如上面的代碼中我就繼續使用了這樣的定義方法
unsigned long sij,si,sii,sj,sjj;
結果最後的cost出現很多遠遠大於1的情況,我以爲是積分圖代碼有問題導致的計算錯誤,找了很久沒有找到錯誤所在。然後懷疑公式2是不是沒有歸一化,經過推導也沒有問題。最後,發現原來是NUM*sij-si*sj爲負導致的!
所以,因此在採用積分圖計算ZNCC時要注意用有符號的數據類型,因爲ZNCC有可能取負值!