1)结构体IplImage
OpenCv中图像的结构体为IplImage,位于头文件cxcore.h中,IplImage结构体的定义如下:
typedef struct _IplImage
{
int nSize; /* IplImage大小 */
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 和IPL_DEPTH_64F */
char colorModel[4]; /* 被OpenCV忽略 */
char channelSeq[4]; /* 同上 */
int dataOrder; /* 0 - 交叉存取颜色通道, 1 - 分开的颜色通道.
只有cvCreateImage可以创建交叉存取图像 */
int origin; /*图像原点位置: 0表示顶-左结构,1表示底-左结构 */
int align; /* 图像行排列方式 (4or 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;
主要的成员变量有。
nChannels : 图像的通道数目,即灰度图像:nChannels= 1; RGB图像nChannels= 3
depth:每个像素值的数据类型和所占的存储空间
origin变量可以有两种取值:IPL_ORIGIN_TL 或者 IPL_ORIGIN_BL,分别设置坐标原点的位置于图像的左上角或者左下角。在计算机视觉领域,一个重要的错误来源就是原点位置的定义不统一。具体而言,图像的来源、操作系统、编解码器和存储格式等因素都可以影响图像坐标原点的选取。举例来说,你或许认为自己正在从图像上面的脸部附近取样,但实际上却在图像下方的裙子附近取样。避免此类现象发生的最好办法是在最开始的时候检查一下系统,在所操作的图像块的地方画点东西试试。
dataOrder: 多通道的数据存储方式,dataOrder=0是交叉通道存储方式,即BGRBGRBGRBGR的方式存储;dataOrder=1是采用独立通道方式存储,即RRRRRRR。。。,GGGGGGG…,BBBBBB…,一般都是BGRBGRBGR的这种交叉存储方式,cvCreateImage生成的图像也是这种存储方式。
width: 图像的宽度
height: 图像的高度
imageData: 图像的像素矩阵
widthStep: 每一行像素所占的字节数目. 参数widthStep包括相邻行的同列点之间的字节数。仅凭变量width是不能计算这个值的,因为为了处理过程更高效每行都会用固定的字节数来对齐;因此在第i行末和第i+1行开始处可能会有些冗于字节。参数imageData包含一个指向第一行图像数据的指针。如果图像中有些独立的平面(如当dataOrder = IPL_DATA_ORDER_PLANE)那么把它们作为单独的图像连续摆放,总行数为height和nChannels的乘积。但通常情况下,它们是交错的,使得行数等于高度,而且每一行都有序地包含交错的通道。
ROI-- 感兴趣的区域(ROI),实际上它是另一个IPL/IPP 结构IplROI的实例。IplROI包含xOffset,yOffset,height,width和coi成员变量,其中COI代表channel of interest(感兴趣的通道)。ROI的思想是: 一旦设定ROI,通常作用于整幅图像的函数便会只对ROI所表示的子图像进行操作。如果IplImage变量中设置了ROI,则所有的OpenCV函数就会使用该ROI变量。如果COI被设置成非0值,则对该图像的操作就只作用于被指定的通道上了 。不幸的是,许多OpenCV函数都忽略参数COI。
2)图像载入函数 cvLoadImage
函数cvLoadImage载入指定图像文件,并返回指向该文件的IplImage指针。函数支持bmp、jpg、 png、 tiff等格式的图像。其函数原型如下:
IplImage*cvLoadImage( const char* filename, int iscolor);
其中,filename是待载入图像的名称,包括图像的扩展名;iscolor是一个辅助参数项,可选正数、零和负数三种值,正数表示作为三通道图像载入,零表示该图像作为单通道图像,负数表示载入图像的通道数由图像文件自身决定。
3)窗口定义函数 cvNamedWindow
函数cvNamedWindow定义一个窗口,用于显示图像。其函数原型如下:
int cvNamedWindow( const char* name, unsignedlong flags );
其中,name是窗口名,flags是窗口属性指标值,可以选择CV_WINDOW_AUTOSIZE和0两种值。CV_WINDOW_AUTOSIZE表示窗口尺寸与图像原始尺寸相同,0表示以固定的窗口尺寸显示图像。
4)图像显示函数 cvShowImage
函数cvShowImage是在指定的窗口中显示图像,其函数原型如下:
void cvShowImage( const char* name, constCvArr* image );
其中,name是窗口名称,image是图像类型指针,一般是IplImage指针。
5)图像保存函数 cvSaveImage
函数cvSaveImage以指定的文件名保存IplImage类型的指针变量,其函数原型如下:
int cvSaveImage( const char* filename, constCvArr* image );
其中,filename是图像保存路径和名称,image是IplImage指针变量。
Trick:
如果要保存一组图像到result文件夹,图像个数为n,保存名称按照一定的序号递增,假设为imgTmp0.jpg,imgTmp1.jpg,imgTmp2.jpg,imgTmp3.jpg,…, imgTmpn.jpg,则
操作为:
char* f[30];
for(inti=0; i<n; i++)
{
sprintf(f,”result/imgTmp%d.jpg”,i);//把格式化的数据写入某个字符串缓冲区f中
cvSaveImage(f,img);
}
借用sprintf函数即可以完成依次命名的功能。
6)图像销毁函数 cvReleaseImage
函数cvReleaseImage销毁已定义的IplImage指针变量,释放占用内存空间。其函数原型如下:
void cvReleaseImage( IplImage** image );
其中,image为已定义的IplImage指针。
7)矩阵结构体 CvMat
CvMat的结构体定义为:
typedef struct CvMat
{
int type; /* CvMat signature (CV_MAT_MAGIC_VAL), element type and flags */
int step; /* full row length in bytes */
int* refcount; /* underlying data reference counter */
union
{
uchar* ptr;//数据头指针
short* s;
int* i; //int,pMat->data.i;
float* fl;//float,pMat->data.fl;
double* db;
} data; /* data pointers */
#ifdef __cplusplus
union
{
int rows;
int height;
};
union
{
int cols;
int width;
};
#else
int rows; /* number of rows */
int cols; /* number of columns */
#endif
} CvMat;
step是每一行数据的长度,以字节来表示
data是存放矩阵数据的联合体,如果矩阵pMat是float类型的,那么获取矩阵数据指针的方式为pMat->data.fl,如果是整型的 pMat->data.i
行数和列数在c和c++中定义略有不同,但是rows和cols是通用的两个变量。
cvMat:
CV_INLINE CvMat cvMat( int rows, int cols, int type, void* data CV_DEFAULT(NULL))
{
CvMat m;
assert( (unsigned)CV_MAT_DEPTH(type) <= CV_64F );
type = CV_MAT_TYPE(type);
m.type = CV_MAT_MAGIC_VAL | CV_MAT_CONT_FLAG | type;
m.cols = cols;
m.rows = rows;
m.step = m.cols*CV_ELEM_SIZE(type);
m.data.ptr = (uchar*)data;
m.refcount = NULL;
m.hdr_refcount = 0;
return m;
}
8)分配矩阵空间 cvCreateMat
CvMat* cvCreateMat(int rows, int cols, int type);
type: 矩阵元素类型. 格式为CV_<bit_depth>(S|U|F)C<number_of_channels>.
例如: CV_8UC1 表示8位无符号单通道矩阵, CV_32SC2表示32位有符号双通道矩阵.
例: CvMat* M = cvCreateMat(4,4,CV_32FC1);
9)逐点赋值方式初始化
CvMat* mat = cvCreateMat( 2, 2,CV_64FC1 );
cvZero( mat );
cvmSet( mat, 0, 0, 1 );
cvmSet( mat, 0, 1, 2 );
cvmSet( mat, 1, 0, 3 );
cvmSet( mat, 2, 2, 4 );
cvReleaseMat( &mat );
10)使用现有数组初始化
doublea[] = { 1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12 };
CvMat mat = cvMat( 3, 4, CV_64FC1, a ); // 64FC1 for double
// 不需要cvReleaseMat,因为数据内存分配是由double定义的数组进行的。
或者这样:
float vals[]={0.866000,0.555000,0.333000,0.222000};
CvMat rotmat;
cvInitMatHeader(&rotmat,2,2,CV_32FC1,vals);
11)释放矩阵
CvMat* M = cvCreateMat(4,4,CV_32FC1); cvReleaseMat(&M);//矩阵指针的地址或者 void cvReleaseMat(CvMat * * mat);//矩阵指针的指针
12)复制矩阵
CvMat* M1 = cvCreateMat(4,4,CV_32FC1);
CvMat* M2;
M2=cvCloneMat(M1);
13)存取矩阵元素
a,从矩阵中得到一个元素的最简单的方法是利用宏CV_MAT_ELEM()。
例1,利用CV_MAT_ELEM()宏存取矩阵
1. CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 );
2. float element_3_2 = CV_MAT_ELEM( *mat, float, 3, 2 );
b,还有一个与此宏类似的宏,叫CV_MAT_ELEM_PTR()。CV_MAT_ELEM_ PTR()传入矩阵、待返回元素的行和列号这3个参数,返回指向这个元素的指针。
例2:利用宏CV_MAT_ELEM_PTR()为矩阵设置一个数值
1. CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 );
2. float element_3_2 = 7.7;
3. *( (float*)CV_MAT_ELEM_PTR( *mat, 3, 2 ) ) = element_3_2;
c,例3:指针访问矩阵结构
1. uchar* cvPtr1D(
2. const CvArr* arr,
3. int idx0,
4. int* type = NULL
5. );
6.
7. uchar* cvPtr2D(
8. const CvArr* arr,
9. int idx0,
13. int idx1,
14. int* type = NULL
15. );
16.
17. uchar* cvPtr3D(
18. const CvArr* arr,
19. int idx0,
20. int idx1,
21. int idx2,
22. int* type = NULL
23. );
24. uchar* cvPtrND(
25. const CvArr* arr,
26. int* idx,
27. int* type = NULL,
28. int create_node = 1,
29. unsigned* precalc_hashval = NULL
30. );
d,如果仅仅是读取数据,可用另一个函数族cvGet*D。但是返回矩阵元素的实际值。
例4:CvMat和IPlImage元素函数
1. double cvGetReal1D( const CvArr* arr, int idx0 );
2. double cvGetReal2D( const CvArr* arr, int idx0, int idx1 );
3. double cvGetReal3D( const CvArr* arr, int idx0, int idx1, int idx2 );
4. double cvGetRealND( const CvArr* arr, int* idx );
5.
6. CvScalar cvGet1D( const CvArr* arr, int idx0 );
7. CvScalar cvGet2D( const CvArr* arr, int idx0, int idx1 );
8. CvScalar cvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 );
9. CvScalar cvGetND( const CvArr* arr, int* idx );
e,例5:为CvMat或者IplImage元素设定值的函数
1. void cvSetReal1D( CvArr* arr, int idx0, double value );
2. void cvSetReal2D( CvArr* arr, int idx0, int idx1, double value );
3. void cvSetReal3D(
4. CvArr* arr,
5. int idx0,
6. int idx1,
7. int idx2,
8. double value
9. );
10. void cvSetRealND( CvArr* arr, int* idx, double value );
11.
12. void cvSet1D( CvArr* arr, int idx0, CvScalar value );
13. void cvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value );
14. void cvSet3D(
15. CvArr* arr,
16. int idx0,
18. int idx1,
19. int idx2,
20. CvScalar value
21. );
22. void cvSetND( CvArr* arr, int* idx, CvScalar value );
为了方便,我们也可以使用cvmSet()和cvmGet(),这两个函数用于处理浮点型单通道矩阵,非常简单。
1. double cvmGet( const CvMat* mat, int row, int col )
2. void cvmSet( CvMat* mat, int row, int col, double value )
以下函数调用cvmSet():
1. cvmSet( mat, 2, 2, 0.5000 );
等同于cvSetReal2D函数调用:
1. cvSetReal2D( mat, 2, 2, 0.5000 );
f,例6:累加一个三通道矩阵中的所有元素
1. float sum( const CvMat* mat )
{
3. float s = 0.0f;
4. for(int row=0; row<mat->rows; row++ )
{ const float* ptr=(const float*)(mat->data.ptr + row * mat->step);
6. for( col=0; col<mat->cols; col++ )
{
7. s += *ptr++;
8. }
10. }
11. return( s );
12. }
g,间接存取矩阵元素
cvmSet(M,i,j,2.0); // Set M(i,j)
t = cvmGet(M,i,j); // Get M(i,j)
注意:cvmGet()和cvmSet()函数只支持CV_32FC1(float)和CV_64FC1(double)的类型
h,直接存取,使用4字节校正
CvMat* M = cvCreateMat(4,4,CV_32FC1);
int n = M->cols;
float *data = M->data.fl;//注意:是浮点型数据
data[i*n+j] = 3.0;
i,直接存取,使用任意字节
CvMat* M = cvCreateMat(4,4,CV_32FC1);
int step = M->step/sizeof(float);
float *data = M->data.fl;
(data+i*step)[j] = 3.0;
j,直接存取一个初始化的矩阵
double a[16];
CvMat Ma = cvMat(3, 4, CV_64FC1, a);
a[i*4+j] = 2.0; // Ma(i,j)=2.0;
14,矩阵/向量数学操作
a,矩阵-矩阵操作
CvMat *Ma, *Mb, *Mc;
cvAdd(Ma, Mb, Mc); // Ma+Mb -> Mc
cvSub(Ma, Mb, Mc); // Ma-Mb -> Mc
cvMatMul(Ma, Mb, Mc); // Ma*Mb -> Mc
b,矩阵元素操作
CvMat *Ma, *Mb, *Mc;
cvMul(Ma, Mb, Mc); // Ma.*Mb -> Mc
cvDiv(Ma, Mb, Mc); // Ma./Mb -> Mc
cvAddS(Ma, cvScalar(-10.0), Mc); // Ma.-10 -> Mc
c,向量乘积
double va[] = {1, 2, 3};
double vb[] = {0, 0, 1};
double vc[3];
CvMat Va=cvMat(3, 1, CV_64FC1, va);
CvMat Vb=cvMat(3, 1, CV_64FC1, vb);
CvMat Vc=cvMat(3, 1, CV_64FC1, vc);
double res=cvDotProduct(&Va,&Vb); // 点乘: Va . Vb -> res
cvCrossProduct(&Va, &Vb, &Vc); // 向量积: Va x Vb -> Vc
注意 Va, Vb, Vc 在向量积中向量元素个数须相同.
d,单矩阵操作
CvMat *Ma, *Mb;
cvTranspose(Ma, Mb); // transpose(Ma) -> Mb (不能对自身进行转置)
CvScalar t = cvTrace(Ma); // trace(Ma) -> t.val[0]
double d = cvDet(Ma); // det(Ma) -> d
cvInvert(Ma, Mb); // inv(Ma) -> Mb
e,非齐次线性系统求解
CvMat* A = cvCreateMat(3,3,CV_32FC1);
CvMat* x = cvCreateMat(3,1,CV_32FC1);
CvMat* b = cvCreateMat(3,1,CV_32FC1);
cvSolve(&A, &b, &x); // solve (Ax=b) for x
f,特征值分析(对称矩阵)
CvMat* A = cvCreateMat(3,3,CV_32FC1);
CvMat* E = cvCreateMat(3,3,CV_32FC1);
CvMat* l = cvCreateMat(3,1,CV_32FC1);
cvEigenVV(&A, &E, &l); // l = A的特征值 (降序排列)
// E = 对应的特征向量 (每行)
g,奇异值分解
CvMat* A = cvCreateMat(3,3,CV_32FC1);
CvMat* U = cvCreateMat(3,3,CV_32FC1);
CvMat* D = cvCreateMat(3,3,CV_32FC1);
CvMat* V = cvCreateMat(3,3,CV_32FC1);
cvSVD(A, D, U, V, CV_SVD_U_T|CV_SVD_V_T); // A = U D V^T
标号使得 U 和 V 返回时被转置(若没有转置标号,则有问题不成功!!!).