IplImage 結構解讀:
typedef struct _IplImage
{
int nSize; /* IplImage大小,等於width*height */
int ID; /* 版本 (=0)*/
int nChannels; /* 大多數OPENCV函數支持1,2,3 或 4 個通道 */
int alphaChannel; /* 被OpenCV忽略 */
int depth; /* 像素的位深度: IPL_DEPTH_8U, IPL_DEPTH_8S, IPL_DEPTH_16U, IPL_DEPTH_16S, IPL_DEPTH_32S, IPL_DEPTH_32F and IPL_DEPTH_64F 可支持 */
char colorModel[4]; /* 被OpenCV忽略 */
char channelSeq[4]; /* 同上 (ditto)*/
int dataOrder; /* 0 - 交叉存取顏色通道, 1 - 分開的顏色通道. cvCreateImage只能創建交叉存取圖像 */
int origin; /* 0 - 頂—左結構,1 - 底—左結構 (Windows bitmaps 風格) */
int align; /* 圖像行排列 (4 or 8). OpenCV 忽略它,使用 widthStep 代替 */
int width; /* 圖像寬像素數 */
int height; /* 圖像高像素數*/
struct _IplROI *roi; /* 圖像感興趣區域. 當該值非空只對該區域進行處理 */
struct _IplImage *maskROI; /* 在 OpenCV中必須置NULL */
void *imageId; /* 同上*/
struct _IplTileInfo *tileInfo; /*同上*/
int imageSize; /* 圖像數據大小(在交叉存取格式下imageSize=image->height*image->widthStep),單位字節*/
char *imageData; /* 指向排列的圖像數據 */
int widthStep; /* 排列的圖像行大小,以字節爲單位 */
int BorderMode[4]; /* 邊際結束模式, 被OpenCV忽略 */
int BorderConst[4]; /* 同上 */
char *imageDataOrigin; /* 指針指向一個不同的圖像數據結構(不是必須排列的),是爲了糾正圖像內存分配準備的 */
}
IplImage;
重要結構元素說明:
depth和nChannels
depth代表顏色深度,使用的是以下定義的宏,nChannels是通道數,爲1,2,3或4。
depth的宏定義:
IPL_DEPTH_8U,無符號8bit整數(8u)
IPL_DEPTH_8S,有符號8bit整數(8s)
IPL_DEPTH_16S,有符號16bit整數(16s)
IPL_DEPTH_32S,有符號32bit整數(32s)
IPL_DEPTH_32F,32bit浮點數,單精度(32f)
IPL_DEPTH_64F,64bit浮點數,雙精度(64f)
origin和dataOrder
origin變量可以有兩個取值:IPL_ORIGIN_TL或者IPL_ORIGIN_BL,分別代表圖像座標系原點在左上角或是左下角。相應的,在計算機視覺領域,一個重要的錯誤來源就是原點位置的定義不統一。例如,圖像的來源不同,操作系統不同,視頻解碼codec不同,存儲方式不同等等,都可以造成原點位置的變化。例如,你可能認爲你正在從圖像上面的臉部附近取樣,但實際上你卻在圖像下方的裙子附近取樣。最初時,就應該檢查一下你的系統中圖像的原點位置,這可以通過在圖像上方畫個形狀等方式實現。
dataOrder的取值可以是IPL_DATA_ORDER_PIXEL或者IPL_DATA_ORDER_PLANE,這個成員變量定義了多通道圖像數據存儲時顏色數據的排列方式,如果是IPL_DATA_ORDER_PIXEL,通道顏色數據排列將會是BGRBGR...的交錯排列,如果是IPL_DATA_ORDER_PLANE,則每個通道的顏色值在一起,有幾個通道,就有幾個“顏色平面”。大多數情況下,通道顏色數據的排列是交錯的。
widthStep與CvMat中的step類似,是以字節數計算的圖像的寬度。成員變量imageData則保存了指向圖像數據區首地址的指針。
最後還有一個重要參數roi(region of interest 感興趣的區域),這個參數是IplROI結構體類型的變量。IplROI結構體包含了xOffset,yOffset,height,width,coi成員變量,其中xOffset,yOffset是x,y座標,coi代表channel of interest(感興趣的通道)。有時候,OpenCV圖像函數不是作用於整個圖像,而是作用於圖像的某一個部分。這是,我們就可以使用roi成員變量了。如果IplImage變量中設置了roi,則OpenCV函數就會使用該roi變量。如果coi被設置成非零值,則對該圖像的操作就只作用於被coi指定的通道上了。不幸的是,許多OpenCV函數忽略了coi的值。
訪問圖像中的數據
就象訪問矩陣中元素一樣,我們希望用最直接的辦法訪問圖像中的數據,例如,如果我們有一個三通道HSV圖像(HSV色彩屬性模式是根據色彩的三個基本屬性:色相H、飽和度S和明度V來確定顏色的一種方法),我們要將每個點的飽和度和明度設置成255,則我們可以使用指針來遍歷圖像,請對比一下,與矩陣的遍歷有何不同:
- void sat_sv( IplImage* img )
- for( int y=0; y<height; y++ )
- {
- uchar* ptr = (uchar*) ( img->imageData + y * img->widthStep );
- for( int x=0; x<width; x++ )
- {
- ptr[3*x+1] = 255;
- ptr[3*x+2] = 255;
- }
- }
上面只是簡單的直接計算相關行y最左邊的像素的指針ptr。從那裏爲參考,引用第x列的飽和度數據。因爲圖像是三通道的,第c通道的地址爲3*x+c。
注意一下,3*x+1,3*x+2的方法,因爲每一個點都有三個通道,所以這樣設置。另外imageData成員的類型是uchar*(即byte型),即字節指針類型,所以與CvMat的data指針類型(union)不同——CvMat的數據元素是一個聯合體,所以必須說明你想要的指針類型。不需要象CvMat那樣麻煩(還記得step/4,step/8的那種情形嗎)。CvMat的數據元素是一個聯合體,所以必須說明你想要的指針類型,imageData是一個byte型(uchar*)。我們知道,被指向的數據不一定是uchar類型,這意味着當對指針作算術運算時,你可以簡單的加上widthStep(同樣是以字節數爲度量的)而不用擔心實際的數據類型,只需在做完加法後,把你計算所得的指針轉換成你想要的數據類型。總結:黨對矩陣操作時,你必須對偏移量進行縮減,因爲數據指針可能不是byte型,而當對圖像操作時,你可以使用“看上去”那麼多的偏移量,因爲數據指針永遠都是byte型,因此在你準備使用它時,只需把整部分做類型轉換。
roi和widthStep
roi和widthStep在實際工作中有很重要的作用,在很多情況下,使用它們會提高計算機視覺代碼的執行速度。這是因爲它們允許對圖像的某一小部分進行操作,而不是對整個圖像進行運算。在OpenCV中,所有的對圖像操作的函數都支持roi,如果你想打開roi,可以使用函數cvSetImageROI(),並給函數傳遞一個矩形子窗口。而cvResetImageROI()是用於關閉roi的。
void cvSetImageROI(IplImage* image,CvRect rect);
void cvResetImageROI(IplImage* image);
注意,在程序中,一旦使用了roi做完相應的運算,就一定要用cvResetImageROI()來關閉roi,否則,其他操作執行時還會使用roi的定義。
(以下參考百度文庫《IplImage的像素的訪問》)
opencv中訪問圖像數據——假設要訪問第k通道、第i行、第j列的像素
一、間接訪問(通用,但效率低,可以訪問任意格式的圖像)
對於單通道字節型圖像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 1);
- CvScalar s;
- s = cvGet2D(img, i, j);// get the (i,j) pixel value
- printf("intensity = %f\n", s.val[0]);
- s.val[0] = 111;
- cvSet2D(img, i, j, s);// set the (i,j) pixel value
- // mul channels byte/float image
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_32F, 3);
- CvScalar s;
- s = cvGet2D(img, i, j);// get the (i,j) pixel value
- printf("B=%f, G=%f, R=%f\n", s.val[0], s.val[1], s.val[2]);
- s.val[0] = 111;
- s.val[1] = 111;
- s.val[2] = 111;
- cvSet(img, i, j, s);// set the (i,j) pixel value
對於單通道字節型圖像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 1);
- ((uchar *) (img->imageData + i*img->widthStep))[j] = 111;// 也相當於在後面加j,即(uchar *) (img->imageData + i*img->widthStep + j) = 111;
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 3);
- ((uchar *) (img->imageData + i*img->widthStep))[j*img->nChannels + 0] = 111;// B
- ((uchar *) (img->imageData + i*img->widthStep))[j*img->nChannels + 1] = 112;// G
- ((uchar *) (img->imageData + i*img->widthStep))[j*img->nChannels + 2] = 113;// R
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_32F, 3);
- ((float *) (img->imageData + i*img->widthStep))[j*img->nChannels + 0] = 111;// B
- ((float *) (img->imageData + i*img->widthStep))[j*img->nChannels + 1] = 112;// G
- ((float *) (img->imageData + i*img->widthStep))[j*img->nChannels + 2] = 113;// R
對於單通道字節型圖像:
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 1);
- int height = img->height;
- int width = img->width;
- int step = img->widthStep/sizeof(uchar);
- uchar* data =(uchar *)img->imageData;
- data[i*step + j] = 111;
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_8U, 3);
- int height = img->height;
- int width = img->width;
- int step = img->widthStep/sizeof(uchar);
- int channels = img->nChannels;
- uchar* data = (uchar *)img->imageData;
- data[i*step + j*channels + k] = 111;
- IplImage* img = cvCreateImage(cvSize(640,480), IPL_DEPTH_32F, 3);
- int height = img->height;
- int width = img->width;
- int step = img->widthStep/sizeof(float);
- int channels = img->nChannels;
- float * data = (float *)img->imageData;
- data[i*step + j*channels + k] = 111;
- void cvSetImageROI( IplImage* image, CvRect rect );
- void cvResetImageROI( IplImage* image );
- #include <cv.h>
- #include <highgui.h>
- int main()
- {
- IplImage* src = cvLoadImage("poppy.jpg", 1);// 載入圖像
- cvSetImageROI(src, cvRect(200, 100, 100, 100));// 爲圖像設置ROI區域
- cvAddS(src, cvScalar(100, 100, 100), src);// 對圖像做與運算
- cvResetImageROI(src);// 釋放ROI區域
- cvSaveImage("poppy1.jpg", src);// 保存處理後的圖像
- cvNamedWindow("Roi_add");// 創建一個窗口
- cvShowImage("Roi_add", src);// 在窗口中顯示圖像
- cvWaitKey();// 延時
- return 0;
- }
- #include <cv.h>
- #include <highgui.h>
- int main()
- {
- IplImage* src = cvLoadImage("poppy.jpg", 1);// 載入圖像
- CvRect interest_rect = cvRect(200, 100, 100, 100);
- // 創建一個和源圖像屬性相同的子圖像
- IplImage* sub_img = cvCreateImageHeader(cvSize(interest_rect.width, interest_rect.height), src->depth, src->nChannels);
- sub_img->origin = src->origin;// 設置相同的P的原點標準
- sub_img->widthStep = src->widthStep;// 這是子圖像的widthStep,這是此技術中最精妙的一步
- sub_img->imageData = src->imageData
- + interest_rect.y*src->widthStep
- + interest_rect.x*src->nChannels;// 設置圖像的數據區域
- cvAddS(sub_img, cvScalar(100, 100, 100), sub_img);// 對圖像做與運算
- cvReleaseImageHeader(&sub_img);// 釋放子圖像頭
- cvSaveImage("poppy1.jpg", src);// 保存處理後的圖像
- cvNamedWindow("Roi_add");// 創建一個窗口
- cvShowImage("Roi_add", src);// 在窗口中顯示圖像
- cvWaitKey();// 延時
- return 0;
- }
附:
首先,個人總結大寫的Cv開頭的是數據類型,小寫的cv開頭是函數(如CvMat與cvMat,CvScalar與cvScalar)
CvScalar&cvScalar:前者是一個數組,裏面有四個double型的元素。後者是其構造函數,分別將四個值賦給數組裏面的四個元素。
CvMat 定義如下:
- typedef struct CvMat
- {
- int type; /* CvMat 標識 (CV_MAT_MAGIC_VAL), 元素類型和標記 */
- int step; /* 以字節爲單位的行數據長度*/
- int* refcount; /* 數據引用計數 */
- union
- {
- uchar* ptr;
- short* s;
- int* i;
- float* fl;
- double* db;
- } data; /* data 指針 */
- #ifdef __cplusplus
- union
- {
- int rows;
- int height;
- };
- union
- {
- int cols;
- int width;
- };
- #else
- int rows; /* 行數 */
- int cols; /* 列數*/
- #endif
- } CvMat;
2/http://blog.sina.com.cn/s/blog_7275089501011xgd.html
3/http://hi.baidu.com/eilianhell/blog/item/8c3d8e551e4ad73a43a75bb4.html
cvAddS:將數組中每個元素都與一個數相加
cvAdd:一個數組對應元素與另一個數組對應元素相加