FROM:
http://www.cnblogs.com/summerRQ/articles/2406109.html
http://blog.sina.com.cn/s/blog_74a459380101obhm.html
一
OpenCV中常见的与图像操作有关的数据容器有Mat,cvMat和IplImage,这三种类型都可以代表和显示图像,但是,Mat类型侧重于计算,数学性较高,openCV对Mat类型的计算也进行了优化。而CvMat和IplImage类型更侧重于“图像”,opencv对其中的图像操作(缩放、单通道提取、图像阈值操作等)进行了优化。在opencv2.0之前,opencv是完全用C实现的,但是,IplImage类型与CvMat类型的关系类似于面向对象中的继承关系。实际上,CvMat之上还有一个更抽象的基类----CvArr,这在源代码中会常见。
1. IplImage
opencv中的图像信息头,该结构体定义:

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 and IPL_DEPTH_64F 可支持 */ char colorModel[4]; /* 被OpenCV忽略 */ char channelSeq[4]; /* 被OpenCV忽略 */ 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;
dataOrder中的两个取值:交叉存取颜色通道是颜色数据排列将会是BGRBGR...的交错排列。分开的颜色通道是有几个颜色通道就分几个颜色平面存储。roi是IplROI结构体,该结构体包含了xOffset,yOffset,height,width,coi成员变量,其中xOffset,yOffset是x,y坐标,coi代表channel of interest(感兴趣的通道),非0的时候才有效。访问图像中的数据元素,分间接存储和直接存储,当图像元素为浮点型时,(uchar *) 改为 (float *):

/*间接存取*/ IplImage* img=cvLoadImage("lena.jpg", 1); CvScalar s; /*sizeof(s) == img->nChannels*/ s=cvGet2D(img,i,j); /*get the (i,j) pixel value*/ cvSet2D(img,i,j,s); /*set the (i,j) pixel value*/ /*宏操作*/ IplImage* img; //malloc memory by cvLoadImage or cvCreateImage for(int row = 0; row < img->height; row++) { for (int col = 0; col < img->width; col++) { b = CV_IMAGE_ELEM(img, UCHAR, row, col * img->nChannels + 0); g = CV_IMAGE_ELEM(img, UCHAR, row, col * img->nChannels + 1); r = CV_IMAGE_ELEM(img, UCHAR, row, col * img->nChannels + 2); } } /*直接存取*/ IplImage* img; //malloc memory by cvLoadImage or cvCreateImage uchar b, g, r; // 3 channels for(int row = 0; row < img->height; row++) { for (int col = 0; col < img->width; col++) { b = ((uchar *)(img->imageData + row * img->widthStep))[col * img->nChannels + 0]; g = ((uchar *)(img->imageData + row * img->widthStep))[col * img->nChannels + 1]; r = ((uchar *)(img->imageData + row * img->widthStep))[col * img->nChannels + 2]; } }
初始化使用IplImage *,是一个指向结构体IplImage的指针:

IplImage * cvLoadImage(const char * filename, int iscolor CV_DEFAULT(CV_LOAD_IMAGE_COLOR)); //load images from specified image IplImage * cvCreateImage(CvSize size, int depth, int channels); //allocate memory
2.CvMat
首先,我们需要知道,第一,在OpenCV中没有向量(vector)结构。任何时候需要向量,都只需要一个列矩阵(如果需要一个转置或者共轭向量,则需要一个行矩阵)。第二,OpenCV矩阵的概念与我们在线性代数课上学习的概念相比,更抽象,尤其是矩阵的元素,并非只能取简单的数值类型,可以是多通道的值。CvMat 的结构:

typedef struct CvMat { int type; int step; /*用字节表示行数据长度*/ int* refcount; /*内部访问*/ union { uchar* ptr; short* s; int* i; float* fl; double* db; } data; /*数据指针*/ union { int rows; int height; }; union { int cols; int width; }; } CvMat; /*矩阵结构头*/
创建CvMat数据:

CvMat * cvCreateMat(int rows, int cols, int type); /*创建矩阵头并分配内存*/ CV_INLine CvMat cvMat((int rows, int cols, int type, void* data CV_DEFAULT); /*用已有数据data初始化矩阵*/ CvMat * cvInitMatHeader(CvMat * mat, int rows, int cols, int type, void * data CV_DEFAULT(NULL), int step CV_DEFAULT(CV_AUTOSTEP)); /*(用已有数据data创建矩阵头)*/
对矩阵数据进行访问:

/*间接访问*/ /*访问CV_32F1和CV_64FC1*/ cvmSet( CvMat* mat, int row, int col, double value); cvmGet( const CvMat* mat, int row, int col ); /*访问多通道或者其他数据类型: scalar的大小为图像的通道值*/ CvScalar cvGet2D(const CvArr * arr, int idx0, int idx1); //CvArr只作为函数的形参void cvSet2D(CvArr* arr, int idx0, int idx1, CvScalar value);
/*直接访问: 取决于数组的数据类型*/ /*CV_32FC1*/ CvMat * cvmat = cvCreateMat(4, 4, CV_32FC1); cvmat->data.fl[row * cvmat->cols + col] = (float)3.0; /*CV_64FC1*/ CvMat * cvmat = cvCreateMat(4, 4, CV_64FC1); cvmat->data.db[row * cvmat->cols + col] = 3.0;
/*一般对于单通道*/ CvMat * cvmat = cvCreateMat(4, 4, CV_64FC1); CV_MAT_ELEM(*cvmat, double, row, col) = 3.0; /*double是根据数组的数据类型传入,这个宏不能处理多通道*/
/*一般对于多通道*/ if (CV_MAT_DEPTH(cvmat->type) == CV_32F) CV_MAT_ELEM_CN(*cvmat, float, row, col * CV_MAT_CN(cvmat->type) + ch) = (float)3.0; // ch为通道值 if (CV_MAT_DEPTH(cvmat->type) == CV_64F) CV_MAT_ELEM_CN(*cvmat, double, row, col * CV_MAT_CN(cvmat->type) + ch) = 3.0; // ch为通道值
/*多通道数组*/ /*3通道*/ for (int row = 0; row < cvmat->rows; row++) { p = cvmat ->data.fl + row * (cvmat->step / 4); for (int col = 0; col < cvmat->cols; col++) { *p = (float) row + col; *(p+1) = (float)row + col + 1; *(p+2) = (float)row + col + 2; p += 3; } } /*2通道*/ CvMat * vector = cvCreateMat(1,3, CV_32SC2);CV_MAT_ELEM(*vector, CvPoint, 0, 0) = cvPoint(100,100); /*4通道*/ CvMat * vector = cvCreateMat(1,3, CV_64FC4);CV_MAT_ELEM(*vector, CvScalar, 0, 0) = CvScalar(0, 0, 0, 0);
复制矩阵操作:

/*复制矩阵*/ CvMat* M1 = cvCreateMat(4,4,CV_32FC1); CvMat* M2; M2=cvCloneMat(M1);
3.Mat
Mat是opencv2.0推出的处理图像的新的数据结构,现在越来越有趋势取代之前的cvMat和lplImage,相比之下Mat最大的好处就是能够更加方便的进行内存管理,不再需要程序员手动管理内存的释放。opencv2.3中提到Mat是一个多维的密集数据数组,可以用来处理向量和矩阵、图像、直方图等等常见的多维数据。

class CV_EXPORTS Mat { public: /*..很多方法..*/ /*............*/ int flags;(Note :目前还不知道flags做什么用的) int dims; /*数据的维数*/ int rows,cols; /*行和列的数量;数组超过2维时为(-1,-1)*/ uchar *data; /*指向数据*/ int * refcount; /*指针的引用计数器; 阵列指向用户分配的数据时,指针为 NULL /* 其他成员 */ ... };
从以上结构体可以看出Mat也是一个矩阵头,默认不分配内存,只是指向一块内存(注意读写保护)。初始化使用create函数或者Mat构造函数,以下整理自opencv2.3.1 Manual:

Mat(nrows, ncols, type, fillValue]); M.create(nrows, ncols, type);
例子: Mat M(7,7,CV_32FC2,Scalar(1,3)); /*创建复数矩阵1+3j*/ M.create(100, 60, CV_8UC(15)); /*创建15个通道的8bit的矩阵*/
/*创建100*100*100的8位数组*/ int sz[] = {100, 100, 100}; Mat bigCube(3, sz, CV_8U, Scalar:all(0));
/*现成数组*/ double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}}; Mat M = Mat(3, 3, CV_64F, m).inv();
/*图像数据*/ Mat img(Size(320,240),CV_8UC3); Mat img(height, width, CV_8UC3, pixels, step); /*const unsigned char* pixels,int width, int height, int step*/
/*使用现成图像初始化Mat*/ IplImage* img = cvLoadImage("greatwave.jpg", 1); Mat mtx(img,0); // convert IplImage* -> Mat; /*不复制数据,只创建一个数据头*/
访问Mat的数据元素:

/*对某行进行访问*/ Mat M; M.row(3) = M.row(3) + M.row(5) * 3; /*第5行扩大三倍加到第3行*/ /*对某列进行复制操作*/ Mat M1 = M.col(1); M.col(7).copyTo(M1); /*第7列复制给第1列*/ /*对某个元素的访问*/ Mat M; M.at<double>(i,j); /*double*/ M.at(uchar)(i,j); /*CV_8UC1*/ Vec3i bgr1 = M.at(Vec3b)(i,j) /*CV_8UC3*/ Vec3s bgr2 = M.at(Vec3s)(i,j) /*CV_8SC3*/ Vec3w bgr3 = M.at(Vec3w)(i,j) /*CV_16UC3*/ /*遍历整个二维数组*/ double sum = 0.0f; for(int row = 0; row < M.rows; row++) { const double * Mi = M.ptr<double>(row); for (int col = 0; col < M.cols; col++) sum += std::max(Mi[j], 0.); } /*STL iterator*/ double sum=0; MatConstIterator<double> it = M.begin<double>(), it_end = M.end<double>(); for(; it != it_end; ++it) sum += std::max(*it, 0.);
Mat可进行Matlab风格的矩阵操作,如初始化的时候可以用initializers,zeros(), ones(), eye(). 除以上内容之外,Mat还有有3个重要的方法:

Mat mat = imread(const String* filename); // 读取图像 imshow(const string frameName, InputArray mat); // 显示图像 imwrite (const string& filename, InputArray img); //储存图像
4. CvMat, Mat, IplImage之间的互相转换

IpIImage -> CvMat /*cvGetMat*/ CvMat matheader; CvMat * mat = cvGetMat(img, &matheader); /*cvConvert*/ CvMat * mat = cvCreateMat(img->height, img->width, CV_64FC3); cvConvert(img, mat)
IplImage -> Mat Mat::Mat(const IplImage* img, bool copyData=false);/*default copyData=false,与原来的IplImage共享数据,只是创建一个矩阵头*/ 例子: IplImage* iplImg = cvLoadImage("greatwave.jpg", 1); Mat mtx(iplImg); /* IplImage * -> Mat,共享数据; or : Mat mtx = iplImg;*/
Mat -> IplImage Mat M IplImage iplimage = M; /*只创建图像头,不复制数据*/
CvMat -> Mat Mat::Mat(const CvMat* m, bool copyData=false); /*类似IplImage -> Mat,可选择是否复制数据*/
Mat -> CvMat 例子(假设Mat类型的imgMat图像数据存在): CvMat cvMat = imgMat;/*Mat -> CvMat, 类似转换到IplImage,不复制数据只创建矩阵头
二
OpenCV学习之CvMat的用法详解及实例
1.初始化矩阵:
方式一、逐点赋值式:
CvMat*
cvZero(
cvmSet(
cvmSet(
cvmSet(
cvmSet(
cvReleaseMat(
方式二、连接现有数组式:
double
CvMat
//
2.IplImage <----->cvMat的转换
A.CvMat->
IplImage*
cvGetImage(matI,img);
cvSaveImage("rice1.bmp",img);
B.IplImage
IplImage*
法2:CvMat
法1:CvMat
3.IplImage <--->Mat的转换
(1)将IplImage----- > Mat类型
Mat::Mat(const IplImage* img, bool copyData=false);
默认情况下,新的Mat类型与原来的IplImage类型共享图像数据,转换只是创建一个Mat矩阵头。当将参数copyData设为true后,就会复制整个图像数据。
例:
IplImage*iplImg = cvLoadImage("greatwave.jpg", 1);
Matmtx(iplImg);
// or : Mat mtx = iplImg; 或者是:Mat
(2)将Mat类型转换-----> IplImage类型
同样只是创建图像头,而没有复制数据。
例:
IplImage ipl_img = img;
新增:这样改如何释放?
其他方法http://blog.csdn.NET/yanzi1225627/article/details/18518793
在项目中使用plImage *new_img=&src.operator IplImage(); 不用释放plImage,没有出现内存问题(2015.10.17)
CvMat cvMat(introws, int cols, int type, void*data=NULL) 创建的CvMat 也无需释放。用的数据就是data,没有新创建。
IplImage*->
BYTE*
4.CvMat<--->Mat的转换
(1)将CvMat类型转换为Mat类型
B.CvMat->Mat
与IplImage的转换类似,可以选择是否复制数据。
CvMat*m=
Mat::Mat(const
在openCV中,没有向量(vector)的数据结构。任何时候,但我们要表示向量时,用矩阵数据表示即可。
但是,CvMat类型与我们在线性代数课程上学的向量概念相比,更抽象,比如CvMat的元素数据类型并不仅限于基础数据类型,比如,下面创建一个二维数据矩阵:
这里的type可以是任意的预定义数据类型,比如RGB或者别的多通道数据。这样我们便可以在一个CvMat矩阵上表示丰富多彩的图像了。
(2)将Mat类型转换为CvMat类型
与IplImage的转换类似,不复制数据,只创建矩阵头。
例:
// 假设Mat类型的imgMat图像数据存在
CvMat cvMat = imgMat;
5.cv::Mat--->const cvArr*
cvArr * 数组的指针。就是opencv里面的一种类型。
Mat img;
const CvArr* s=(CvArr*)&img;
上面就可以了,CvArr是Mat的虚基类,所有直接强制转换就可以了
void cvResize(
6.cvArr(IplImage或者cvMat)转化为cvMat
方式一、cvGetMat方式:
int
cvMat
if(
{
}
写成函数为:
//
//
CVAPI(
{
}
7.图像直接操作
方式一:直接数组操作
uchar
for(
{
}
方式二:宏操作:
int
uchar
for(
{
}
注:CV_IMAGE_ELEM(
8.cvMat的直接操作
数组的直接操作比较郁闷,这是由于其决定于数组的数据类型。
对于CV_32FC1
CvMat*
M->data.fl[
对于CV_64FC1
CvMat*
M->data.db[
一般的,对于1通道的数组:
CvMat*
CV_MAT_ELEM(
注意double要根据数组的数据类型来传入,这个宏对多通道无能为力。
对于多通道:
看看这个宏的定义:#define
if(
if(
更优化的方法是:
int
for(
}
对于多通道的数组,以下操作是推荐的:
for(row=0;
对于两通道和四通道而言:
CvMat*
CV_MAT_ELEM(
CvMat*
CV_MAT_ELEM(
9.间接访问cvMat
cvmGet/Set是访问CV_32FC1
cvmSet(
cvmGet(
举例:打印一个数组
inline
{
}
而对于其他的,比如是多通道的后者是其他数据类型的,cvGet/Set2D是个不错的选择
CvScalar
cvSet2D(
注意:数据不能为int,因为cvGet2D得到的实质是double类型。
举例:打印一个多通道矩阵:
inline
{
}
10.修改矩阵的形状——cvReshape的操作
经实验表明矩阵操作的进行的顺序是:首先满足通道,然后满足列,最后是满足行。
注意:这和Matlab是不同的,Matlab是行、列、通道的顺序。
我们在此举例如下:
对于一通道:
//
CvMat
double
CvMat*
//11
//21
//31
mat
cvDoubleMatPrint(
//
mat
cvDoubleMatPrint(
//11
//21
//31
mat
cvDoubleMatPrint(
//
//
//
//
//
//
//
//
//
//
//
//
mat
cvDoubleMatPrint(
//11
//21
//31
mat
cvDoubleMatPrint(
//11
//23
mat
cvDoubleMatPrint(
//11
//21
//31
mat
cvDoubleMatPrint(
//
//
//
//
//
//
mat
cvDoubleMatPrint(
//11
//21
//31
//
//
//
//
//
//
//
//
对于三通道
//
CvMat
double
CvMat*
//(111,112,113)
//(211,212,213)
mat
cv3DoubleMatPrint(
//
//
mat
cvDoubleMatPrint(
//
//
mat
cvDoubleMatPrint(
//111
//122
//213
//
mat
cvDoubleMatPrint(
//111
//121
//211
//221
//
//
//
CvMat*
cvTranspose(
cvDoubleMatPrint(
//111
//112
//113
cvReleaseMat(
11.计算色彩距离
我们要计算img1,img2的每个像素的距离,用dist表示,定义如下
IplImage
IplImage
CvMat
比较笨的思路是:cvSplit->cvSub->cvMul->cvAdd
代码如下:
IplImage
IplImage
IplImage
IplImage
IplImage
IplImage
IplImage
cvSplit(
cvSplit(
cvSub(
cvMul(
cvSub(
cvMul(
cvAdd(
cvSub(
cvMul(
cvAdd(
cvReleaseImage(
cvReleaseImage(
cvReleaseImage(
cvReleaseImage(
cvReleaseImage(
cvReleaseImage(
cvReleaseImage(
比较聪明的思路是
int
int
CvMat
CvMat
CvMat
cvSub(
cvMul(
dist
cvReduce(
dist
cvReleaseMat(
#pragma
#include
#include
int
{
CvMat*
cvZero(mat);//将矩阵置0
//为矩阵元素赋值
CV_MAT_ELEM(
CV_MAT_ELEM(
CV_MAT_ELEM(
CV_MAT_ELEM(
CV_MAT_ELEM(
CV_MAT_ELEM(
CV_MAT_ELEM(
CV_MAT_ELEM(
CV_MAT_ELEM(
//获得矩阵元素(0,2)的值
float
printf("%f\n",*p);
}