IplImage中的widthStep大小計算及原理
一直以為IplImage結構體中的widthStep元素大小等於width*nChannels,大錯特錯!
檢視OpenCV2.1的原始碼,在src/cxcore/cxarray.cpp檔案中,找到cvInitImageHeader函式,函式中對widthStep大小賦值如下:
image->widthStep = (((image->width * image->nChannels *
(image->depth & ~IPL_DEPTH_SIGN) + 7)/8)+ align - 1) & (~(align - 1)); (1)
其中IPL_DEPTH_SIGN的定義可以在cxtypes.h中找到,定義為:#define IPL_DEPTH_SIGN 0x80000000, align的大小為CV_DEFAULT_IMAGE_ROW_ALIGN,其大小在cxmisc.h中定義為:#define CV_DEFAULT_IMAGE_ROW_ALIGN 4,depth取8位深度。
根據(1)式,已知IPL_DEPTH_SIGN、align、depth 的大小,分別手動計算如下影象的widthStep:
影象寬度 影象通道數 計算得到的widthStep
3 3 12
3 1 4
5 3 16
5 1 8
7 3 24
7 1 8
4 3 12
4 1 4
為了進一步驗證手算的正確性,我們程式設計實現輸出widthStep的大小,程式如下:
IplImage *image_33 = cvCreateImage(cvSize(3, 3), 8, 3);
IplImage *image_31 = cvCreateImage(cvSize(3, 3), 8, 1);
IplImage *image_53 = cvCreateImage(cvSize(5, 3), 8, 3);
IplImage *image_51= cvCreateImage(cvSize(5, 3), 8, 1);
IplImage *image_73 = cvCreateImage(cvSize(7, 3), 8, 3);
IplImage *image_71 = cvCreateImage(cvSize(7, 3), 8, 1);
printf("%d, %d, %d, %d, %d, %d", image_33->widthStep,image_31->widthStep,
image_53->widthStep,image_51->widthStep,image_73->widthStep,image_71->widthStep);
執行結果為:12, 4, 16, 8, 24, 8, 與手動計算結果相同。
從網上查閱資料,OpenCV分配的記憶體按4位元組對齊,這樣我們對上述計算的結果可以有個合理的解釋,如寬度為3、通道數為3的影象,每一行需要的 實際記憶體長度為3*3,為了記憶體對齊,OpenCV會在每行末尾自動補上3個位元組的記憶體,記憶體初始化都為0,所以widthStep變為了12。
widthStep大小對IplImage極為重要,在cxarray.cpp中,我們可以找到如下程式碼行:
image->imageSize = image->widthStep * image->height;
img->imageData = img->imageDataOrigin =
(char*)cvAlloc( (size_t)img->imageSize );
可見widthStep直接影響到imageData的資料長度。在操作imageData時,我們要避開對OpenCV自動補齊的記憶體進行操作,如直方圖計算等。
寫到這裡,可能有人會問,我們平常都用widthStep = width * nChannels,怎麼就沒出錯?我之前也一直在疑惑,合理的解釋是,一般在實際應用中,影象的寬度一般為128, 256, 240, 320, 356,704等,剛好這些數字都能被4整除,widthStep剛好等於width * nChannels, 所以OpenCV並沒有為這些影象分配多的記憶體,因此我們在對imageData做順序操作也沒出錯。但是,請問誰能保證影象的寬度一定會是4的倍數?