Opencv IplImage 和 Mat 使用介绍
创始人
2024-04-29 18:10:58
0

1. IPIImage 使用介绍

IplImage是OpenCV中CxCore部分基础的数据结构,用来表示图像,其中Ipl是Intel Image Processing Library的简写。以下是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]; /* 同上 */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;

1. 1 元素访问

对我们来说比较重要的两个元素是:char *imageData以及widthStep。imageData存放图像像素数据,而widStep类似CvMat中的step,表示以字节为单位的行数据长度

一个m*n的单通道字节型图像,其imageData排列如下:
在这里插入图片描述
如果我们要遍历图像中的元素,只需:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
uchar* tmp;
for(int i=0;iheight;i++)for(int j=0;jwidth;j++)*tmp=((uchar *)(img->imageData + i*img->widthStep))[j];

这种直接访问的方法速度快,但容易出错,我们可以通过定义指针来访问。即:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
ucha* data=(uchar *)img->imageData;
int step = img->widthStep/sizeof(uchar);
uchar* tmp;
for(int i=0;iheight;i++)for(int j=0;jwidth;j++)*tmp=data[i*step+j];

而多通道(三通道)字节图像中,imageData排列如下:
在这里插入图片描述

//IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,3);
uchar* data=(uchar *)img->imageData;
int step = img->widthStep/sizeof(uchar);
int channels = img->nChannels;
uchar *b,*g,*r;
for(int i=0;iheight;i++)for(int j=0;jwidth;j++){*b=data[i*step+j*chanels+0];*g=data[i*step+j*chanels+1];*r=data[i*step+j*chanels+2];}

如果要修改某像素值,则直接赋值。

1.2 使用cvGet2D()函数访问:

cvGet*D系列函数可以用来返回特定位置的数组元素(一般使用cvGet2D),原型如下:

CvScalar cvGet1D( const CvArr* arr, int idx0 );
CvScalar cvGet2D( const CvArr* arr, int idx0, int idx1 );
CvScalar cvGet3D( const CvArr* arr, int idx0, int idx1, int idx2 );
CvScalar cvGetND( const CvArr* arr, int* idx );

idx0,idx1,idx2分别用来指示元素数组下标,即cvGet2D返回(idx0,idx1)处元素的值。因此,单通道图像像素访问方式如下:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_8U,1);
double tmp;
for(int i=0;iheight;i++)for(int j=0;jwidth;j++)tmp=cvGet2D(img,i,j).val[0];

多通道字节型/浮点型图像:

IplImage* img=cvCreateImage(cvSize(640,480),IPL_DEPTH_32F,3);
double tmpb,tmpg,bmpr;
for(int i=0;iheight;i++)for(int j=0;jwidth;j++){tmpb=cvGet2D(img,i,j).val[0];tmpg=cvGet2D(img,i,j).val[1];tmpr=cvGet2D(img,i,j).val[2];}

如果是修改元素的值,可用cvSet*D(一般是cvSet2D)函数:

void cvSet1D( CvArr* arr, int idx0, CvScalar value );
void cvSet2D( CvArr* arr, int idx0, int idx1, CvScalar value );
void cvSet3D( CvArr* arr, int idx0, int idx1, int idx2, CvScalar value );
void cvSetND( CvArr* arr, int* idx, CvScalar value );

这种方法对于任何图像的访问方式是一样的,比较简单,但效率较低,不推荐使用。

2. Mat 使用介绍

2.1 Mat的介绍

Mat其实就是matrix(矩阵)的缩写,在opencv中,我们用Mat类的对象存储图像。
在opencv中,Mat类分为两个部分。

  • header部分

图像有很多属性。如:大小,宽和高,数据类型,通道数。这些数据存储在矩阵头中

  • data部分:放置像素点实际值

Mat和Matlab里的数组格式有点像,但一般是二维向量,如果是灰度图,一般存放 类型;如果是RGB彩色图,存放 类型。

单通道灰度图数据存放格式
在这里插入图片描述
多通道的图像中,每列并列存放通道数量的子列,如RGB三通道彩色图:
在这里插入图片描述
注意通道的顺序反转了:BGR。通常情况内存足够大的话图像的每一行是连续存放的,也就是在内存上图像的所有数据存放成一行,这中情况在访问时可以提供很大方便。可以用 isContinuous()函数来判断图像数组是否为连续的。

2.2 Mat的常见属性及类型

Mat的常见属性

在这里插入图片描述
属性部分存储了一系列的矩阵属性:行数、列数、通道数、数据类型、矩阵数据的大小等,以及指向了矩阵数据的指针等。

  • cols :矩阵列数
  • rows:矩阵行数
  • channels:通道数
  • type:数据类型
  • total:矩阵总元素数
  • data:指向矩阵数据块的指针

Mat 数据类型type

表示了矩阵中元素的类型以及矩阵的通道个数,它是一系列的预定义的变量,其命名规则为CV_(位数)+(数据类型)+(通道数)。具体的有以下值:
在这里插入图片描述
这里U(unsigned integer)表示的是无符号整数,S(signed integer),F(float)是浮点数。

例如:CV_16UC2: 表示的是元素类型是一个16位的无符号整数,通道为2. C1,C2,C3,C4则表示通道是1,2,3,4

type一般是在创建Mat对象时设定,如果要取得Mat的元素类型,则无需使用type,使用下面的depth

depth

矩阵中元素的一个通道的数据类型,这个值和type是相关的。例如 type为 CV_16SC2,一个2通道的16位的有符号整数。那么,depth则是CV_16S。depth也是一系列的预定义值:

将type的预定义值去掉通道信息就是depth值:CV_8U CV_8S CV_16U CV_16S CV_32S CV_32F CV_64F

elemSize

矩阵一个元素占用的字节数,例如:type是CV_16SC3,那么elemSize = 3 * 16 / 8 = 6 bytes

elemSize1

矩阵元素一个通道占用的字节数,例如:type是CV_16CS3,那么elemSize1 = 16 / 8 = 2 bytes = elemSize / channels

2.3 Mat的操作

2.3.1 Mat的创建

>  1. Mat ()
>  2. Mat (int rows, int cols, int type)
>  3. Mat (Size size, int type)
>  4. Mat (int rows, int cols, int type, const Scalar &s)
>  5. Mat (Size size, int type, const Scalar &s)
>  6. Mat (int ndims, const int *sizes, int type) 
>  7. Mat (int ndims, const int *sizes, int type, const Scalar &s)
    1. 使用无参数构造函数,创建Mat对象
Mat image = Mat();
image.create(4, 4, CV_8UC3);//创建一个4x4大小的像素块,每个像素都是三通道每个通道的位数都是8位
    1. 使用带行、列、类型这个三个参数的构造函数创建Mat对象
Mat m = Mat(4, 4, CV_8UC3); //创建一个4x4大小的像素块,每个像素都是三通道每个通道的位数都是8位
  • 3.使用行、列、类型、Scalar向量四个参数的构造函数创建Mat对象
Mat m = Mat(4, 4, CV_8UC3, Scalar(0, 255, 255)); 
//创建一个4x4大小的像素块,每个像素都是三通道每个通道的位数都是8位,指定三通道颜色值向量Scalar(0, 255, 255)

同样表示创建一个4x4的像素块,唯一的区别是颜色不是默认值,而是我们指定的三通道颜色值向量Scalar(0, 255, 255)。其中Scalar向量数目永远是等于通道数目。其他赋值情况也一样

  • 4.使用大小、类型个参数的构造函数创建Mat对象。
Mat m = Mat(Size(4, 4), CV_8UC3); //创建一个4x4大小的像素块,每个像素都是三通道每个通道的位数都是8位
  • 5.使用大小、类型、Scalar向量三个参数的构造函数创建Mat对象
Mat m = Mat(Size(4, 4), CV_8UC3, Scalar(255, 0, 0)); //创建一个4x4大小的像素块,每个像素都是三通道每个通道的位数都是8位
    1. 使用zeros(),eye(), ones()创建对象
Mat img = zeros(100,100,CV_8UC3); // 全0矩阵
Mat img1 = eye(100,100,CV_8UC3); // 对角为1的对角矩阵
Mat img2 = ones(100,100,CV_8UC3); // 全1矩阵
    1. 使用逗号数组创建对象
Mat img = (Mat_(2,2) << 0,1,1,0); // 按行填充
Mat A = (Mat_(4, 4) <<0.5, 0.4, 0.6, 1,0.2, 0.3, 0.1, 2,0.7, 0.8, 0.9, 3,0, 0, 0, 1);

Mat对象创建,常用的是创建空白图像。如下演示了三种

Mat src = imread("……");
Mat m4 = Mat::zeros(src.size(),src.type())Mat m5 = Mat::zeros(Size(512,512),CV_8UC3);Mat m6 = Mat::ones(Size(512,512),CV_8UC3);Mat kernel = (Mat_(3,3)<<0,-1,0,-1,5,-1,0,-1,0); 位数,只有灰度

当对图像直接赋值,如果为多通道时:

Mat m3 = Mat::zeros(Size(8, 8), CV_8UC3);
m3 = 145;

结果如下:
在这里插入图片描述
只有第一个通道的值被赋值了

正确的写法如下:

Mat m3 = Mat::zeros(Size(8, 8), CV_8UC3);
m3 = Scalar(129,39,0);

在这里插入图片描述

2.3.2 像素读写

uchar:灰度图像像素,单个值
Vec3b:彩色图像像素,3个值

单个像素的访问:pixel = image.at(row, col);

c++中像素遍历和读写:

    1. 数组遍历
void pixel_visit_Demo(Mat& image) {int h = image.rows;int w = image.cols;int dims = image.channels();for (int row = 0; row < h; row++) {for (int col = 0; col < w; col++) {if (dims == 1) {//灰度图像int pv = image.at(row, col);image.at(row, col) = 255 - pv;}if (dims == 3) {//彩色图像Vec3b bgr = image.at(row, col);image.at(row, col)[0] = 255 - bgr[0];image.at(row, col)[1] = 255 - bgr[1];image.at(row, col)[2] = 255 - bgr[2];}}}
}
    1. 指针方式遍历(比for快一点)
void pixel_visit_Demo(Mat& image) {int h = image.rows;int w = image.cols;int dims = image.channels();for (int row = 0; row < h; row++) {uchar* curren_row = image.ptr(row);for (int col = 0; col < w; col++) {if (dims == 1) {//灰度图像int pv = *curren_row;*curren_row++ = 255 - pv;}if (dims == 3) {//彩色图像*curren_row++ = 255 - *curren_row;  // b*curren_row++ = 255 - *curren_row;  // g*curren_row++ = 255 - *curren_row;  / r}}}
}

对于彩色图,对单个的像素点后移通道数量次数即可,如上++了三次。

2.3.3 图像的拷贝

再开始将拷贝之前,先给大家分享一下浅拷贝和深拷贝

浅拷贝:拷贝对象和被拷贝对象都指向同一个内存空间,修改任何一个对象的数据都会影响另外一个;
举个例子:小明和小红在沙漠中共用一个水瓶喝水,任何一个人喝了水,另外一个人都会剩下更少的水。

深拷贝:拷贝对象和被拷贝对象指向不同的内容空间,修改数据时互不影响。
举个例子:小明和小红各有一个水瓶,各自喝各自的水对对方不影响。

深拷贝和浅拷贝都各有优缺点:
在这里插入图片描述

  • 1 拷贝构造函数进行拷贝

这种拷贝方式属于浅拷贝,下面代码中的img和img2都指向相同的内存空间,修改img或者img2,另外一个中的变量也会跟着变化。

Mat img = imread("test.jpg", CV_LOAD_IMAGE_COLOR);
Mat img2(img); // 拷贝构造函数

2 赋值运算符进行拷贝
这种拷贝方式属于浅拷贝,下面代码中的img和img2都指向相同的内存空间,修改img或者img2,另外一个中的变量也会跟着变化。

Mat img = imread("test.jpg", CV_LOAD_IMAGE_COLOR);
Mat img2 = img; // 赋值运算符

3 使用Rect截取拷贝
这种拷贝方式属于浅拷贝,下面代码中的img2指向的内存空间为img的子内存空间。

Mat img = imread("test.jpg", CV_LOAD_IMAGE_COLOR);
Mat img2(img, Rect(200,200,300,300));

4 使用clone()函数拷贝
这种拷贝方式属于深拷贝,img和img2分别指向不同的内存空间,修改img或img2 的数据,不影响另一个变量。

Mat img = imread("test.jpg", CV_LOAD_IMAGE_COLOR);
Mat img2 = img.clone();

5 使用copyTo()函数拷贝
这种拷贝方式属于深拷贝,img和img2分别指向不同的内存空间,修改img或img2 的数据,不影响另一个变量。

Mat img = imread("test.jpg", CV_LOAD_IMAGE_COLOR);
Mat img2;
img.copyTo(img2);

2.4 Mat 矩阵运算

    Mat imageadd = image1 + image2;//imshow("加法", imageadd);Mat imageadd1;Mat imageadd2;add(image1, image2, imageadd1);add(image1, 2, imageadd2);//函数重载//imshow("加法", imageadd);Mat imagesub = image1 - image2;//运算符重载//imshow("减法", imagesub);Mat imageAbsdiff;absdiff(image1, image2, imageAbsdiff);//imshow("减法绝对值", imageAbsdiff);Mat imagesub1;subtract(image1, image2, imagesub1);//imshow("减法", imagesub);Mat imageweighted;addWeighted(image1, 0.5, image2, 0.2, 50, imageweighted);//imshow("加权", imageweighted);Mat imagemultiply;multiply(image1, image2, imagemultiply, 1.0, CV_32FC1);//imshow("点乘", imagemultiply);Mat imagedivide;divide(image1, image2, imagedivide, 1.0, -1);//imshow("点除", imagedivide);//非Mat image2not;Mat mask = Mat::zeros(image2.size(), CV_8UC1);mask(Rect(200, 100, 200, 200)) = 255;bitwise_not(image2, image2not, mask);//imshow("非", image2not);//或Mat image2or;bitwise_or(image2, imagesub, image2or, mask);//imshow("或", image2or);//异或Mat image2xor;bitwise_xor(image2, imageadd2, image2xor, mask);imshow("异或", image2xor);//与Mat image2and;bitwise_and(image2, imagesub, image2and, mask);//imshow("与", image2and);

2.5 Mat 转换

2.5.1 Mat与二维指针相互转换

对于一个Mat,有时需要将其转为二维指针传递

int** mat2ptrarray(Mat& pic)
{int** data;data = new int* [pic.rows];for (int i = 0; i < pic.rows; i++){data[i] = new int[pic.cols];for (int j = 0; j < pic.cols; j++){data[i][j] = pic.at(i, j);}}return data;
}

释放指针指向的内存空间

for (int i = 0; i < pic.rows; i++){delete[] data[i];}delete[] data;

将二维指针的数据再拼回到Mat中(这个是按单通道的例子)

Mat ImgData(int** pImgdata, int width, int height)
{Mat Img;Img.create(height, width, CV_8U);//这里按照uchar类型for (int i = 0; i < height; i++)   //行数--高度{for (int j = 0; j < width; j++)        //列数 -- 宽度{Img.at(i, j) = pImgdata[i][j];}}return Img;
}

2.5.2 Mat与一维数组相互转换

    //---------------------数组和Mat------------------------int height = image.rows;int width = image.cols;//转16位一维数组uint8_t* array1 = new uint8_t[height * width * 3];for (int i = 0; i < height; i++){for (int j = 0; j < width; j++){for (int k = 0; k < 3; k++){array1[i * width * 3 + j * 3 + k] = image.at(i, j)[k];}		}}Mat image3(height, width, CV_8UC3, array1);

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
客厅放八骏马摆件可以吗(家里摆... 今天给各位分享客厅放八骏马摆件可以吗的知识,其中也会对家里摆八骏马摆件好吗进行解释,如果能碰巧解决你...
苏州离哪个飞机场近(苏州离哪个... 本篇文章极速百科小编给大家谈谈苏州离哪个飞机场近,以及苏州离哪个飞机场近点对应的知识点,希望对各位有...