• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Opencv IplImage 和 Mat 使用

武飞扬头像
@BangBang
帮助1

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;i<img->height;i  )
	for(int j=0;j<img->width;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;i<img->height;i  )
	for(int j=0;j<img->width;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;i<img->height;i  )
     for(int j=0;j<img->width;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;i<img->height;i  )
	for(int j=0;j<img->width;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;i<img->height;i  )
	for(int j=0;j<img->width;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里的数组格式有点像,但一般是二维向量,如果是灰度图,一般存放 <uchar>类型;如果是RGB彩色图,存放 <Vec3b>类型。

单通道灰度图数据存放格式
学新通
多通道的图像中,每列并列存放通道数量的子列,如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_<double>(2,2) << 0,1,1,0); // 按行填充
Mat A = (Mat_<double>(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_<char>(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<uchar>(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<uchar>(row, col);
				image.at<uchar>(row, col) = 255 - pv;
			}
			if (dims == 3) {//彩色图像
				Vec3b bgr = image.at<Vec3b>(row, col);
				image.at<Vec3b>(row, col)[0] = 255 - bgr[0];
				image.at<Vec3b>(row, col)[1] = 255 - bgr[1];
				image.at<Vec3b>(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<uchar>(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<int>(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<uchar>(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<cv::Vec3b>(i, j)[k];
			}		
		}
	}

	Mat image3(height, width, CV_8UC3, array1);
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfjjiak
系列文章
更多 icon
同类精品
更多 icon
继续加载