认识OpenCV之Mat类(一)

一、Mat类的综述

1. Mat 类存储图像

Mat 类是 OpenCV 里面使用广泛的一个类,最重要的一个作用就是作为存储图像的数据结构。Mat类是如何存储图像的呢?图像分为彩色图像和灰度图像,不管是彩色图像还是灰度图像,都是二维的矩阵,具体的存储格式如下:

(1)灰度图像的格式:

(2)彩色图像的格式:

 虽然彩色图像由BGR三个通道,但是是存储在同一个平面内的,只不过OpenCV在这里把三列才当作一列,因此有 img.cols 等于图像的列数。

一般我们用 OpenCV 读取的灰度图像的数据类型为 uchar 类型的,而彩色图像的一个像素的数据类型为 <Vec3b> 类型,灰度图一个像素占用一个字节,而彩色图像一个像素3个字节。接下来简单介绍如何按像素读取图像。

2. Mat 按像素读取图像内容

这里主要介绍两种方法:一种非常简单,易于编程,但效率比较低;另一种效率高,但不好记。

(1)易于编程的方法

#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("1.jpg");
	resize(img, img, Size(375, 500));//resize为500*375的图像
	cvtColor(img, img, CV_RGB2GRAY);//转为灰度图
	imshow("gray_ori", img);
	for (int i = 0; i < img.rows; i++)
	{
		for (int j = 0; j < img.cols; j++)
		{
			//at<类型>(i,j)进行操作,对于灰度图
			img.at<uchar>(i, j) = i+j;
		}
	}
	imshow("gray_result", img);
	waitKey(0);
	return 0;
}

效果图:

使用 at 操作实现定位,就和操作一个普通的二位数组一样。对于彩色图像,方法也很简单,只需把 at<类型> 中的类型改为 Vec3b 即可,代码如下:


#include <opencv2\core\core.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main()
{
	Mat img = imread("1.jpg");
	resize(img, img, Size(375, 500));//resize为500*375的图像
	imshow("ori", img);
	for (int i = 0; i < img.rows; i++)
	{
		for (int j = 0; j < img.cols; j++)
		{
			//at<类型>(i,j)进行操作,对于灰度图
			img.at<Vec3b>(i, j)[0] = 255;//对于蓝色通道进行操作
			//img.at<Vec3b>(i, j)[1] = 255;//对于绿色通道进行操作
			//img.at<Vec3b>(i, j)[2] = 255;//对于红色通道进行操作
		}
	}
	imshow("result", img);
	waitKey(0);
	return 0;
}

效果图:

(2)采用指针对图像进行访问(这里直接写对彩色图像的操作):


#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("1.jpg");
	int rows = img.rows;
	int cols = img.cols * img.channels();
	if(img.isContinuous())//判断是否在内存中连续
	{
		cols = cols * rows;
		rows = 1;
	}
	imshow("ori",img);
	for(int i = 0;i<rows;i++)
	{
		//调取存储图像内存的第i行的指针
		uchar *pointer = img.ptr<uchar>(i);
 		for(int j = 0;j<cols;j += 3)
		{
			//pointer[j] = 255;//对蓝色通道进行操作
			//pointer[j+1] = 255;//对绿色通道进行操作
			pointer[j+2] = 255;//对红色通道进行操作
		}
	}
	imshow("result",img);
	waitKey();
    return 0;
}

效果图:

从代码可以看出,我们是如何操作图像的数据以及在 Mat 中的存放格式,彩色图像中的每一个像素点分成三份,每一份都是 uchar 类型,因此我们这里不需要使用 Vec3b 数据类型。把彩色图像看成一个 rows *(cols * channels) 的二维数组进行操作,其中每个元素的类型都是 uchar 类型。

这里需要注意的是 j += 3 是因为我们按照一个像素点进行操作,而一个像素点在这里又被分成三份,因此需要 j += 3,如果是灰度图像则直接 j ++ 即可。这种操作方式虽然复杂一些,但是在执行效率上高很多。

测试代码:


#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
#include <time.h>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("1.jpg");
	Mat img2;
	img.copyTo(img2);

	cout<<"图像的行数: "<<img.rows<<endl;
	cout<<"图像的列数: "<<img.cols<<endl;
	cout<<"图像通道数: "<<img.channels()<<endl;

	double time1;
	time1 = (double)getTickCount();

	int rows = img.rows;
	int cols = img.cols * img.channels();
	if(img.isContinuous())//判断是否在内存中连续
	{
		cols = cols * rows;
		rows = 1;
	}

	for(int i = 0;i<rows;i++)
	{
		//调取存储图像内存的第i行的指针
		uchar *pointer = img.ptr<uchar>(i);
 
		for(int j = 0;j<cols;j += 3)
		{
			//pointer[j] = 255;//对蓝色通道进行操作
			//pointer[j+1] = 255;//对绿色通道进行操作
			pointer[j+2] = 255;//对红色通道进行操作
		}
	}
	time1 = 1000 * ((double)getTickCount() - time1) / getTickFrequency();
	//imshow("result",img);
	cout<<"第一种方法用时: "<<time1<<endl;

	double time2 = (double)getTickCount();
	for (int i = 0; i < img2.rows; i++)
	{
		for (int j = 0; j < img2.cols; j++)
		{
			//at<类型>(i,j)进行操作,对于灰度图
			img2.at<Vec3b>(i, j)[0] = 255;//对于蓝色通道进行操作
			//img.at<Vec3b>(i, j)[1] = 255;//对于绿色通道进行操作
			//img.at<Vec3b>(i, j)[2] = 255;//对于红色通道进行操作
		}
	}
	time2 = 1000 * ((double)getTickCount() - time2)/getTickFrequency();
	cout<<"第二种方法用时: "<<time2<<endl;
	imshow("img",img);
	imshow("img2",img2);
	waitKey(0);
    return 0;
}

测试结果:

3、Mat 类中的变量参数

Mat 中包含了很多的变量,比如用的比较多的 rows,cols等。这里总结Mat类中用的比较少的几个变量和函数 step1(),step[],size, elemSize 和 elemSize1。

step1() Mat中每一维的通道数
step[i] Mat 中每一维的大小,以字节为单位
size[i] Mat中元素的个数
elemSize() 每个元素大小,以字节为单位
elemSize1() 每个元素中每个通道的大小,以字节为单位

测试代码:


#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main()
{
    Mat img = imread("1.jpg");
    cout<<"img.rows: "<<img.rows<<" img.cols: "<<img.cols<<" img.channels(): "<<img.channels()<<endl;
    cout<<"###############################################"<<endl;
    cout<<"img.step1(0): "<<img.step1(0)<<endl;
    cout<<"img.step1(1): "<<img.step1(1)<<endl;
    cout<<"img.step[0]: "<<img.step[0]<<endl;
    cout<<"img.step[1]: "<<img.step[1]<<endl;
    cout<<"img.size[0]: "<<img.size[0]<<endl;
    cout<<"img.size[1]: "<<img.size[1]<<endl;
    cout<<"img.elemSize(): "<<img.elemSize()<<endl;
    cout<<"img.elemSize1(): "<<img.elemSize1()<<endl;

    return 0;
}

输出显示:

结果分析:读取一张 1000*750 彩色图像。step1(i) 表示每一维的通道数,彩色图像存储方式是一种二维矩阵,因此只有二维,其中最后一维表示的是一个点,前面一维表示的是线。如果有第三维,那么再前面一维表示的是面。这里有一个规律,就是最后一维一定是点,其他的依次向前推即可。

img.step1(1)表示的是一个点的通道数。彩色图像的一个像素通道数为3(BGR),因此 img.step1(1) = 3,而 img.step1(0) 是一行的像素通道数,因此为 750*3 = 2250

img.step[i] 表示的每一维中的大小,以字节为单位。在彩色图像中一个像素分为三块,每一块为一个字节,因此 step[0] = 2250, step[1] = 3

size 表示的是每一维元素的大小,以元素为单位,即像素。 size[0] 表示 rows, size[1] 表示 cols

elemSize 表示的每一个元素的大小,以字节为单位。彩色图像一个元素分为三块,一块1个字节,因此为3

elemSize1 表示的是一个元素的每一个通道的大小,因此为1

猜你喜欢

转载自blog.csdn.net/JACK_YOUNG007/article/details/88907771
今日推荐