OpenCV常用的图像和矩阵操作总结.docx
《OpenCV常用的图像和矩阵操作总结.docx》由会员分享,可在线阅读,更多相关《OpenCV常用的图像和矩阵操作总结.docx(19页珍藏版)》请在冰豆网上搜索。
![OpenCV常用的图像和矩阵操作总结.docx](https://file1.bdocx.com/fileroot1/2022-11/23/4c7bb2f9-e3df-474f-a1b8-355f98d2fa29/4c7bb2f9-e3df-474f-a1b8-355f98d2fa291.gif)
OpenCV常用的图像和矩阵操作总结
OpenCv常用图像和矩阵操作
目录
学习资料2
书籍2
网站:
2
本地安装目录2
图像IplImage3
StructureIplImage3
图像的常用操作4
图像载入函数4
窗口定义函数4
图像显示函数4
图像保存函数5
图像销毁函数5
存取图像像素5
矩阵CvMat9
StructCvMat9
矩阵的创建和初始化10
释放矩阵10
复制矩阵:
11
存取矩阵元素11
简单的方法11
麻烦的方法12
恰当的方法15
cvmGet()和cvmSet()的局限16
矩阵/向量数学操作17
矩阵-矩阵操作:
17
按元素的矩阵操作:
18
向量乘积:
18
单矩阵操作:
18
非齐次线性系统求解:
18
特征值分析(针对对称矩阵):
19
奇异值分解SVD:
19
其他20
Shell函数显示图片20
IplImage到cvMat的转换20
学习资料
书籍
LearningOpenCV(影印版)
作者:
GaryBradski,AdrianKaehler
出版社:
东南大学出版社
学习OpenCV(中文版)
作者:
GaryBradski,AdrianKaehler
译者:
于仕琪刘瑞祯
出版社:
清华大学出版社
OpenCV中文教程
作者:
刘瑞祯于仕琪
网站:
本地安装目录
在安装目录OpenCV1.0\docs下有各种学习资料
只用在本地安装目录下面就可以查询到大部分需要的信息,当然也可以直接XX,google
图像IplImage
StructureIplImage
OpenCv中图像的结构体为IplImage,位于头文件cxcore.h中,IplImage结构体的定义如下:
/////////////////////////////////////////////////////////////////////////////
typedefstruct_IplImage
{
intnSize;/*IplImage大小*/
intID;/*版本(=0)*/
intnChannels;/*大多数OPENCV函数支持1,2,3或4个通道*/
intalphaChannel;/*被OpenCV忽略*/
intdepth;/*像素的位深度,主要有以下支持格式:
IPL_DEPTH_8U,IPL_DEPTH_8S,IPL_DEPTH_16U,IPL_DEPTH_16S,IPL_DEPTH_32S,
IPL_DEPTH_32F和IPL_DEPTH_64F*/
charcolorModel[4];/*被OpenCV忽略*/
charchannelSeq[4];/*同上*/
intdataOrder;/*0-交叉存取颜色通道,1-分开的颜色通道.
只有cvCreateImage可以创建交叉存取图像*/
intorigin;/*图像原点位置:
0表示顶-左结构,1表示底-左结构*/
intalign;/*图像行排列方式(4or8),在OpenCV被忽略,使用widthStep代替*/
intwidth;/*图像宽像素数*/
intheight;/*图像高像素数*/
struct_IplROI*roi;/*图像感兴趣区域,当该值非空时,
只对该区域进行处理*/
struct_IplImage*maskROI;/*在OpenCV中必须为NULL*/
void*imageId;/*同上*/
struct_IplTileInfo*tileInfo;/*同上*/
intimageSize;/*图像数据大小(在交叉存取格式下ImageSize=image->height*image->widthStep),单位字节*/
char*imageData;/*指向排列的图像数据*/
intwidthStep;/*排列的图像行大小,以字节为单位*/
intBorderMode[4];/*边际结束模式,在OpenCV被忽略*/
intBorderConst[4];/*同上*/
char*imageDataOrigin;/*指针指向一个不同的图像数据结构(不是必须排列的),是为了纠正图像内存分配准备的*/
}IplImage;}
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代表channelofinterest(感兴趣的通道)。
ROI的思想是:
一旦设定ROI,通常作用于整幅图像的函数便会只对ROI所表示的子图像进行操作。
如果IplImage变量中设置了ROI,则所有的OpenCV函数就会使用该ROI变量。
如果COI被设置成非0值,则对该图像的操作就只作用于被指定的通道上了。
不幸的是,许多OpenCV函数都忽略参数COI。
图像的常用操作
图像载入函数
函数cvLoadImage载入指定图像文件,并返回指向该文件的IplImage指针。
函数支持bmp、jpg、png、tiff等格式的图像。
其函数原型如下:
IplImage*cvLoadImage(constchar*filename,intiscolor);
其中,filename是待载入图像的名称,包括图像的扩展名;iscolor是一个辅助参数项,可选正数、零和负数三种值,正数表示作为三通道图像载入,零表示该图像作为单通道图像,负数表示载入图像的通道数由图像文件自身决定。
窗口定义函数
函数cvNamedWindow定义一个窗口,用于显示图像。
其函数原型如下:
intcvNamedWindow(constchar*name,unsignedlongflags);
其中,name是窗口名,flags是窗口属性指标值,可以选择CV_WINDOW_AUTOSIZE和0两种值。
CV_WINDOW_AUTOSIZE表示窗口尺寸与图像原始尺寸相同,0表示以固定的窗口尺寸显示图像。
图像显示函数
函数cvShowImage是在指定的窗口中显示图像,其函数原型如下:
voidcvShowImage(constchar*name,constCvArr*image);
其中,name是窗口名称,image是图像类型指针,一般是IplImage指针。
图像保存函数
函数cvSaveImage以指定的文件名保存IplImage类型的指针变量,其函数原型如下:
intcvSaveImage(constchar*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{
sprintf(f,”result/imgTmp%d.jpg”,i);
cvSaveImage(f,img);
}
借用sprintf函数即可以完成依次命名的功能。
图像销毁函数
函数cvReleaseImage销毁已定义的IplImage指针变量,释放占用内存空间。
其函数原型如下:
voidcvReleaseImage(IplImage**image);
其中,image为已定义的IplImage指针。
存取图像像素
包括获取像素值和对像素值赋值
●直接获取
假设图像为IplImage*img,图像的depth=IPL_DEPTH_8U(每个像素用8bits表示),获取像素坐标(x,y)的操作为
1.灰度图像(单通道img->nChannels=1)
对像素赋值:
((char*)(img->imageData+y*imge->widthStep))[x]=255;
获取像素值:
intgrayValue=((char*)(img->imageData+y*imge->widthStep))[x];
2.彩色图像(单通道img->nChannels=1)
对像素赋值:
((char*)(img->imageData+y*imge->widthStep))[3*x]=255;
((char*)(img->imageData+y*imge->widthStep))[3*x+1]=255;
((char*)(img->imageData+y*imge->widthStep))[3*x+2]=255;
获取像素值:
ucharb=((char*)(img->imageData+y*imge->widthStep))[x];
ucharg=((char*)(img->imageData+y*imge->widthStep))[x];
ucharr=((char*)(img->imageData+y*imge->widthStep))[x];
注意:
注意(char*)这个指针的强制转换是针对img->imageData+y*imge->widthStep的,也就是针对图像的行指针进行的转换,注意括弧的范围。
当image->depth为其他值时,则可能每个像素的数据类型需要进行(int*),(float*),(double*)等转换。
参数widthStep是相邻行的同列点之间的字节数。
仅凭变量width是不能计算这个值的,因为为了处理过程更高效每行都会用固定的字节数来对齐;因此在第i行末和第i+1行开始处可能会有些冗于字节。
一次在进行行切换时,一定要widthStep来进行内存的偏移,而不是用depth*width.
一般的情况下,假设有N-通道,类型为T的图像:
I(x,y)c~((T*)(img->imageData+img->widthStep*y))[x*N+c]//注意x.,y的位置
例子:
void saturate_sv( IplImage* img )
{
for( int y=0; yheight; y++ )
{
uchar* ptr = (uchar*) ( Img->imageData + y * img->widthStep );
for( int x=0; xwidth; x++ )
{
ptr[3*x] = 255;
ptr[3*x+1] = 255;
ptr[3*x+2] = 255;
}
}
}
在以上程序中,我们用指针ptr指向第y行的起始位置。
接着,我们从指针中析出饱和度和高度在x维的值。
因为这是一个三通道图像,所以C通道在x行的位置是3*x+c。
该使用方法是受限的
ucharb,g,r;
b=img->imageData[img->widthStep*row+col*3]
g=img->imageData[img->widthStep*row+col*3+1];
r=img->imageData[img->widthStep*row+col*3+2];
由于imageData指针始终是char*类型的,因此该方法只适用于8bits/pixel的图像表示,其他的图像类型则需要进行指针转换。
●宏
可以使用宏CV_IMAGE_ELEM(image_header,elemtype,y,x_Nc)
I(x,y)c~CV_IMAGE_ELEM(img,T,y,x*N+c),其中c为通道的序号,如彩色图像c=0,1,2
不过使用该宏是也要小心数据类型elemtype的问题。
也有针对各种图像(包括4通道图像)和矩阵的函数(cvGet2D,cvSet2D),但是它们非常慢。
注:
该宏在每次调用时,都会重新计算指针的位置。
这意味着,先查找数据区中第0个元素的位置,然后,根据参数中的行和列,计算所需要的元素的地址偏移量,然后将地址偏移量与第0个元素的地址相加,获得所需要的元素的地址。
所以,以上的方式虽然很容易使用,但是却不是获得元素的最好方式。
特别是当你要顺序遍历整个图像中所有元素时,这种每次对地址的重复计算就更加显得不合理。
●cvGet2D()和cvSet2D()
可以通过cvGet2D()和cvSet2D()两个函数加上一个CvScalar结构体做到获取图像的像素点。
OpenCV中,CvScalar结构为:
typedefstructCvScalar
{
double val[4];
}
CvScalar;
4个double型变量,算法处理时不至于被强制类型转换而降低精度了。
再来看读写函数的定义:
cvGet2D 获得某个点的值,idx0=hight行值,idx1=width列值。
CVAPI(CvScalar) cvGet2D( const CvArr*arr, int idx0, int idx1);
cvSet2D 给某个点赋值。
CVAPI(void) cvSet2D( CvArr*arr, int idx0, int idx1, CvScalar value);
有上可见,cvGet2D的返回类型和cvSet2D中value的类型都是CvScalar,这样定义一个CvScalar变量再调用函数就OK了。
OpenCV中像素点读写例子:
int main(int argc, char **argv)
{
IplImage *img= cvLoadImage(argv[1],1);
CvScalar pixel;
for (int i=0;iheight;++i)
{
for (int j=0;jwidth;++j)
{
//获得像素的RGB值并显示,注意内存中存储顺序是BGR
pixel= cvGet2D(img,i,j);
printf("B=%f,G=%f,R=%f\t",pixel.val[0],pixel.val[1],pixel.val[2]);
//修改各点的值
pixel.val[0]=0;
pixel.val[1]=0;
pixel.val[2]=0;
cvSet2D(img,i,j,pixel);
}
}
cvNamedWindow("image",1);
cvShowImage("image",img);
cvWaitKey(0);
cvDestroyWindow("image");
cvReleaseImage(&img);
return 0;
}
矩阵CvMat
StructCvMat
CvMat的结构体定义为:
typedefstructCvMat
{
inttype;/*CvMatsignature(CV_MAT_MAGIC_VAL),elementtypeandflags*/
intstep;/*fullrowlengthinbytes*/
int*refcount;/*underlyingdatareferencecounter*/
union
{
uchar*ptr;
short*s;
int*i;
float*fl;
double*db;
}data;/*datapointers*/
#ifdef__cplusplus
union
{
introws;
intheight;
};
union
{
intcols;
intwidth;
};
#else
introws;/*numberofrows*/
intcols;/*numberofcolumns*/
#endif
}CvMat;
step是每一行数据的长度,以字节来表示
data是存放矩阵数据的联合体,如果矩阵pMat是float类型的,那么获取矩阵数据指针的方式为pMat->data.fl,如果是整型的pMat->data.i
行数和列数在c和c++中定义略有不同,但是rows和cols是通用的两个变量。
矩阵的创建和初始化
矩阵有多种创建方法。
最常见的方法是用cvCreateMat(),它由多个原函数组成,如cvCreateMatHeader()和cvCreateData()。
cvCreateMatHeader()函数创建CvMat结构,不为数据分配内存,而cvCreateData()函数只负责数据的内存分配。
有时,只需要函数cvCreateMatHeader(),因为已因其他理由分配了存储空间,或因为还不准备分配存储空间。
第三种方法是用函数cvCloneMat(CvMat*),它依据一个现有矩阵创建一个新的矩阵。
当这个矩阵不再需要时,可以调用函数cvReleaseMat(CvMat*)释放它。
●分配矩阵空间
CvMat*cvCreateMat(introws,intcols,inttype);
type:
矩阵元素类型.格式为CV_(S|U|F)C.
例如:
CV_8UC1表示8位无符号单通道矩阵,CV_32SC2表示32位有符号双通道矩阵.
例:
CvMat*M=cvCreateMat(4,4,CV_32FC1);
●逐点赋值式初始化
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);
●使用现有数组初始化
doublea[]={1,2,3,4,
5,6,7,8,
9,10,11,12};
CvMatmat=cvMat(3,4,CV_64FC1,a);//64FC1fordouble
//不需要cvReleaseMat,因为数据内存分配是由double定义的数组进行的。
释放矩阵
CvMat*M=cvCreateMat(4,4,CV_32FC1);
cvReleaseMat(&M);
复制矩阵:
CvMat*M1=cvCreateMat(4,4,CV_32FC1);
CvMat*M2;
M2=cvCloneMat(M1);
存取矩阵元素
假设需要存取一个2维浮点矩阵的第(i,j)个元素.
简单的方法
从矩阵中得到一个元素的最简单的方法是利用宏CV_MAT_ELEM()。
这个宏(参见例3-4)传入矩阵、待提取的元素的类型、行和列数4个参数,返回提取出的元素的值。
例3-4:
利用CV_MAT_ELEM()宏存取矩阵
1.CvMat* mat = cvCreateMat( 5, 5, CV_32FC1 );
2.float element_3_2 = CV_MAT_ELEM( *mat, float, 3, 2 );
更