OpenCV(三) 之 基本數據結構 CvMat和 IplImage
OpenCv中基本的數據類型
類型 | 參數 | 表示 |
---|---|---|
CvPoint | int x,y | 像素點 |
CvPoint2D32f | float x,y | 平面點 |
CvPoint3D32f | float x,y,z | 空間點 |
CvSize | int width,height | 圖像大小 |
CvSize2D32f | float x, y | 區域大小 |
CvSize3D32f | float x,y,z | 立方體大小 |
CvRect | int x,y,width,height | 矩形區域 |
CvScalar | double val[4] | RGBA 值 |
類型的構造函數僅是類型名首字母小寫,比如cvPoint(int x,int y),除了CvScalar
CvScalar類型是可以用來存放4個double類型的數組,最多四個不一定要四個
- typedef struct CvScalar
- {
- double val[4];
- };
其賦值函數有四種
- 1. CvScalar cvScalar(double v0,double v1,double v2, double v3);
- 2. CvScalar cvRealScalar(double v0);
- //第一個數值賦值,剩余三個為0
- 3. CvScalar cvScalarAll(double v);
- //所有的四個值都相同
- 4. CV_RGB
- #define CV_RGB(r,g,b) cvScalar(b,r,g,0);
CvMat類型
- typedef struct CvMat {
- int type;
- int step;
- int* refcount; // for internal use only
- union {
- uchar* ptr;
- short* s;
- int* i;
- float* fl;
- double* db;
- } data;
- union {
- int rows;
- int height;
- };
- union {
- int cols;
- int width;
- };
- } CvMat;
其構造函數
- CvMat* cvCreateMat(int rows,int cols,int type);
- CvMat* cvCreateMatHeader(int rows,int cols,int type);
- CvMat* cvInitMatHeader(CvMat *mat,int rows,int cols,int type, void* data=NULL, int step = CV_AUTOSTEP)
- CvMat* cvCloneMat(CvMat*)
-
- //內存釋放
- void cvReleaseMat(CvMat** mat)
使用數組創建CvMat
- float vals[4]=[1,2,3,4];
- CvMat* mat;
- cvInitMatHeader(mat,2,2,CV_32FC1,vals);
CvMat屬性值的獲取
- cvGetElemType(const CvArr* arr);//返回數據類型
- cvGetDims(const CvArr* arr,int* sizes=NULL);//返回CvMat的維數,相當於張量的階數, int* 可以用來存儲每一維對應的長度
- cvGetDimSize(const CvArr* arr,int index);//返回第index維度上的長度
CvMat數據值的讀取
- float sum( const CvMat* mat )
- {
- float s = 0.0f;
- for(int row=0; row<mat->rows; row++ )
- {
- const float* ptr = (const float*)(mat->data.ptr + row * mat->step);
- for( col=0; col<mat->cols; col++ )
- {
- s += *ptr++;
- }
- }
- return( s );
- }
這里要注意的是
要強制指定數據類型,因為CvMat定義中數據類型是union。
使用step參數是每一行所占的字節數,並不等於cols*sizeof(bytes),因為含有優化的地址對齊問題。
CvMat矩陣數據存儲順序是先行再列,3D數據存儲順序是每個位置的channels,然后按行、列排列。
下圖時一組空間點存到不同的CvMat的存儲結果

IplImage
- typedef struct _IplImage{
- int nSize;
- int ID;
- int nChannels; //**type
- int alphaChannel;
- int depth; //**type
- int dataOrder; //數組存儲結構,是按照像素存IPL_DATA_ORDER_PIXEL,還是先按照通道存IPL_DATA_ORDER_PLANE
- int origin; //圖像坐標原點位於左上角 IPL_ORIGIN_TL,還是左下角IPL_ORIGIN_BL
- int align;
- int width; //**columns
- int height; //**rows
- char colorModel[4];
- char channelSeq[4];
- struct _IplROI* roi; //region of interest
- struct _IplImage* maskROI;
- void* imageId;
- struct _IplTileInfo* tileInfo;
- int imageSize;
- char* imageData; //** data
- int widthStep; //** step
- int BorderMode[4];
- int BorderConst[4];
- char* imageDataOrigin;
- }
其中注釋**部分對應着CvMat的成員變量,CvMat的type成員被拆分成了兩個屬性:depth, nChannels表示像素值深度和圖像的通道數
depth的取值有
- IPL_DEPTH_8U; IPL_DEPTH_8S; IPL_DEPTH_16S; IPL_DEPTH_32S; IPL_DEPTH_32F; IPL_DEPTH_64F;
看字面就知道什么意思了。
IplROI包含的屬性:
- int xOffset, yOffset; //x,y 方向的偏置
- int height, width; //感興趣區域的大小
- int COI; //Channel of Interest,感興趣的通道
一旦ROI設置了,那么對圖像的操作將盡在ROI內操作。
IplImage數據的訪問示例
對於HSV圖像,希望將S,V通道值設置為255:
- void saturate_sv(IplImage* img)
- {
- for(int y=0;y<img->height;y++)
- {
- uchar* ptr=(uchar*)(img->imageData+y*img->widthStep);
- for(int x=0;x<img->width;i++)
- {
- ptr[3*x+1]=255;
- ptr[3*x+2]=255;
- }
- }
- }
這里奇怪為什么默認img->dataOrder=IPL_DATA_ORDER_PIXEL。這是因為
We say that dataOrder may be either IPL_DATA_ORDER_PIXEL of IPL_DATA_ORDER_PLANE, but in fact only IPL_DATA_ORDER_PIXEL is supported by OpenCV. Both values are generally supported by IPL/IPP, but OpenCV always uses interleaved images.
Note: 比較CvMat和IplImage數據區的操作,發現IplImage->imageData並沒有進行類型轉換,全部都是byte字節,而CvMat則需要轉換成所存儲數據的類型,在指針運算時需要特別注意,尤其是IplImage和CvMat進行計算時。
ROI的使用:
- void cvSetImageROI(IplImage* image, CvRect rect); //設置rect區域為感興趣區域
- void cvResetImageROI(IplImage* image); //取消圖像的感興趣區域
- #include "highgui.h"
- #include "cv.h"
- int main(int argc, char** argv)
- {
- // 圖像路徑、ROI的x,y偏移量, 長和寬、像素值增加量
- IplImage* src;
- if (argc == 7 && ((src = cvLoadImage(argv[1])) != 0))
- {
- int xOffset = atoi(argv[2]); //x偏移
- int yOffset = atoi(argv[3]); //y偏移
- int width = atoi(argv[4]); //ROI寬
- int height = atoi(argv[5]); //ROI高
- int add = atoi(argv[6]); //每個像素值增加量
-
- cvSetImageROI(src, cvRect(xOffset, yOffset, width, height));
- cvAddS(src, cvScalarAll(add), src);
- cvResetImageROI(src);
- cvNamedWindow("src");
- cvShowImage("src", src);
- cvWaitKey(0);
- cvReleaseImage(&src);
- cvDestroyWindow("src");
-
- }
-
- }

上述過程還存在另一種直接地址操作的方法
- #include "highgui.h"
- #include "cv.h"
- int main(int argc, char** argv)
- {
- // 圖像路徑、ROI的x,y偏移量, 長和寬、像素值增加量
- IplImage* src;
- if (argc == 7 && ((src = cvLoadImage(argv[1])) != 0))
- {
- int xOffset = atoi(argv[2]); //x偏移
- int yOffset = atoi(argv[3]); //y偏移
- int width = atoi(argv[4]); //ROI寬
- int height = atoi(argv[5]); //ROI高
- int add = atoi(argv[6]); //每個像素值增加量
-
- IplImage * sub_img = cvCreateImageHeader(cvSize(width, height), src->depth, src->nChannels);
- sub_img->widthStep = src->widthStep;
- sub_img->imageData = src->imageData + yOffset*src->widthStep + xOffset*src->nChannels;
-
- cvAddS(sub_img, cvScalarAll(add), sub_img);
- cvReleaseImageHeader(&sub_img);
-
- cvResetImageROI(src);
- cvNamedWindow("src");
- cvShowImage("src", src);
- cvWaitKey(0);
- cvReleaseImage(&src);
- cvDestroyWindow("src");
-
- }
-
- }
這里很容易理解,就是將ROI截取出來作為一個新的圖像,需要注意的時sub_img並沒有新分配地址,數據區指向的仍然是src的數據區,此時圖像sub_img的數據區並不是連續的,所以將src->widthStep賦值給sub_img-》widthStep保證讀取正確的地址數據。