OpenCV2-Mat类、图像加载与保存

  • 1.Mat类介绍
  • 2.数据类型与取值范围
  • 3.Mat类构造与赋值
  • 4.Mat矩阵运算
  • 5.Mat属性与元素的遍历
  • 方法1 pt<>
  • 方法2 迭代器方法
  • 方法3 at<>
  • 方法4 data成员
  • 6.图像的读取、显示、保存



1.Mat类介绍

Mat类分为矩阵头和指向存储数据的矩阵指针两部分。

矩阵头:包含矩阵的尺寸、存储方法、地址和引用计数等,矩阵头的大小是一个常数。

在OpenCV中复制和传递图像时,只是复制了矩阵头和指向存储数据的指针

Mat a; // 矩阵头
a = imread("lena.jpg"); // 矩阵指针指向像素数据
Mat b = a; // 复制矩阵头和数据指针

C++中使用引用计数管理矩阵数据。

查看Mat类继承关系图:声明一个存放指定类型的Mat变量

Mat A = Mat_<double>(3, 3);

2.数据类型与取值范围

CV_8U - 8-bit unsigned integers ( 0..255 )
CV_8S - 8-bit signed integers ( -128..127 )
CV_16U - 16-bit unsigned integers ( 0..65535 )
CV_16S - 16-bit signed integers ( -32768..32767 )
CV_32S - 32-bit signed integers ( -2147483648..2147483647 )
CV_32F - 32-bit floating-point numbers ( -FLT_MAX..FLT_MAX, INF, NAN )
CV_64F - 64-bit floating-point numbers ( -DBL_MAX..DBL_MAX, INF, NAN )

仅有数据类型还是不够的,还需要定义图像数据的通道数(Channel)。C1、C2、C3、C4分别表示单通道、双通道、3通道、4通道。

由于每一种数据类型都存在多个通道的情况,所以将数据类型与通道数结合便得到了OpenCV中对图像数据类型的完整定义。例如CV_8UC1 表示8位单通道数据表示8位灰度图。

创建一个声明通道数和数据类型的Mat类:

Mat a(640, 480, CV_8UC3); // 3通道用于存放彩色图像
Mat a(3, 3, CV_8UC1); // 单通道用于存放灰度图像
Mat a(3, 3, CV_8U); // 单通道矩阵,C1标识可忽略

3.Mat类构造与赋值

// 1.默认构造函数
Mat::Mat();

// 2.根据矩阵尺寸和类型构造
Mat::Mat(int rows, int cols, int type);

// 3.用Size结构构造
Mat::Mat(Size size(), int type); 
Mat a(Size(480, 640), CV_8UC1); // 640x480单通道矩阵

注意使用Size结构时,行和列顺序相反。

// 4.拷贝构造 浅拷贝
Mat::Mat(const Mat& m);

// 5.利用已有的矩阵的子内容构造 浅拷贝 共享数据
Mat::Mat(const Mat& m, const Range& rowRange, const Range& colRange=Range::all());
Mat b(a, Range(2,5), Range(2,5)); // 2-5行,2-5列拷贝,
Mat c(a, Range(2,5)); // 2-5行,所有列拷贝

赋值:

// 1.构造时赋值
Mat::Mat(int rows, int cols, int type, const Scalar& s);
// 将每个元素要赋值的变量放到Scalar结构中,如Scalar(0,0,255),将会给每个像素的3个通道分别赋值为0、0、255
Mat a(2,2,CV_8UC3,Scalar(0,0,255)); // 3通道矩阵,每个像素都是0、0、255
Mat b(2,2,CV_8UC2,Scalar(0,255));   // 2通道矩阵,每个像素都是0、255
Mat c(2,2,CV_8UC1,Scalar(255));     // 单通道矩阵,每个像素都是255
// 2.枚举赋值 数组赋值
Mat a = (Mat_<int>(3,3) << 1,2,3,4,5,6,7,8,9);
Mat b = Mat(2,2,CV_32FC2, a);
// a[0][0]:1,2
// a[0][1]:3,4
// a[1][0]:5,6
// a[1][1]:7,8

// 3.循环赋值
Mat c = Mat_<int>(3,3);
for(int i = 0; i < c.rows; ++i)
{
    for(int j = 0; j < c.cols; ++j)
    {
        c.at<int>(i, j) = i+j;
    }
}

Mat类中提供了可以快速赋值的方法,可以初始化指定的矩阵。例如生成单位矩阵、对角矩阵、所有元素都为0或者1的矩阵等。

// 3.类方法赋值
Mat a = Mat::eye(3,3,CV_8UC1); // 单位矩阵,如果不是方阵,则在主对角位置放1
Mat b = (Mat_<int>(1,3) << 1,2,3);
Mat c = Mat::diag(b); // 对角矩阵,参数必须是向量,用来存放对角元素的值
Mat d = Mat::ones(3,3,CV_8UC1); // 全为1的矩阵,参数含义同eye
Mat e = Mat::zeros(4,2,CV_8UC3); // 全为0的矩阵,参数含义同eye

4.Mat矩阵运算

1.两个Mat类变量进行加减运算,必须保证数据类型相同,

2.Mat类变量与常数进行乘除时,结果的数据类型保留Mat类变量的数据类型,Mat类变量的每一个元素都要与常数乘除。

3.两个矩阵的内积和对应位的乘法:

Mat a = (Mat_<int>(3,3) << 1,2,3,4,5,6,7,8,9);
Mat b = (Mat_<int>(3,3) << 1,2,3,4,5,6,7,8,9);
Mat j = a*b; // 不可以,必须是float、double类型
int k = a.dot(b);
Mat m = a.mul(b);

*矩阵乘积运算:数据类型必须是CV_32FC1、CV_32FC2、CV_64FC1、CV_64FC2

dot点积:把矩阵看成一维进行点积运算,结果永远是double类型。

mul对应位相乘。可以是任意类型。注意乘积结果的溢出。

5.Mat属性与元素的遍历

Mat类常用属性:

属性

作用

cols

矩阵的列数

rows

矩阵的行数

step

以字节为单位的矩阵的有效宽度

elemSize()

每个元素的字节数

total()

矩阵中元素的个数

channels()

矩阵的通道数

Mat a(3, 4, CV_32FC3);

cout << a.rows << endl;		  // 3
cout << a.cols << endl;		  // 4
cout << a.step << endl;		  // 48
cout << a.elemSize() << endl; // 12
cout << a.total() << endl;    // 12
cout << a.channels() << endl; // 3

共3x4个元素,每个元素的类型是float类型,由于是3通道,所以每个元素有3个float类型,所以每个元素占据4x3个字节数,也即是elemSize()返回12。每行有4列,也即4个元素,所以step为4x12共48字节。(step/cols可以求出每个元素所占据的字节数,再与channels()属性结合,可以知道每个通道的字节数。)

// 矩阵元素的类型 CV_16SC3
int type() const;

// return true if Mat::total() is 0 or if Mat::data is NULL
bool empty() const;

方法1 pt<>

通过.ptr<>函数得到一行的指针,并用[]操作符访问某一列的像素值

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>

using namespace cv;
using namespace std;

void travel_1(Mat& image, int div = 64)
{
	int nr = image.rows;
	int nc = image.cols * image.channels(); // 每行总元素数,也即遍历的列数
	for (int row = 0; row < nr; ++row)
	{
		// 通过ptr<>函数得到一行的指针
		uchar* data = image.ptr<uchar>(row);

		for (int col = 0; col < nc; ++col)
		{
			// 使用[]操作符访问某一列的像素值
			data[col] = data[col] / div * div + div / 2;
            // *data++ = *data / div * div + div / 2;
		}
	}
}

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat lena_c = imread("lena_c.bmp", IMREAD_UNCHANGED);
	imshow("lena_c", lena_c);
	travel_1(lena_c);
	imshow("lena_c changed", lena_c);

	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

方法2 迭代器方法

void travel_2(Mat& image, int div = 64)
{
	// get iterators
	cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();
	cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
	for (; it != itend; ++it)
	{
		(*it)[0] = (*it)[0] / div * div + div / 2; // B channel
		(*it)[1] = (*it)[1] / div * div + div / 2; // G channel
		(*it)[2] = (*it)[2] / div * div + div / 2; // R channel
	}
}

方法3 at<>

void travel_3(Mat& image, int div = 64)
{
	int nr = image.rows;
	int nc = image.cols;

	for (int row = 0; row < nr; row++)
	{
		for (int col = 0; col < nc; col++)
		{
			image.at<cv::Vec3b>(row, col)[0] = image.at<cv::Vec3b>(row, col)[0] / div * div + div / 2; // B channel
			image.at<cv::Vec3b>(row, col)[1] = image.at<cv::Vec3b>(row, col)[1] / div * div + div / 2; // G channel
			image.at<cv::Vec3b>(row, col)[2] = image.at<cv::Vec3b>(row, col)[2] / div * div + div / 2; // R channel
		}
	}
}

方法4 data成员

void travel_4(Mat& image, int div = 64)
{
	int nr = image.rows;
	int nc = image.cols;
	for (int row = 0; row < nr; row++)
	{
		for (int col = 0; col < nc; col++)
		{
			image.data[row * nc * 3 + col * 3 + 0] = image.data[row * nc * 3 + col * 3 + 0] / div * div + div / 2; // B channel
			image.data[row * nc * 3 + col * 3 + 1] = image.data[row * nc * 3 + col * 3 + 1] / div * div + div / 2; // G channel
			image.data[row * nc * 3 + col * 3 + 2] = image.data[row * nc * 3 + col * 3 + 2] / div * div + div / 2; // R channel
		}
	}
}

// 简单优化
void travel_4(Mat& image, int div = 64)
{
	int nr = image.rows;
	int nc = image.cols;
	for (int row = 0; row < nr; row++)
	{
		for (int col = 0; col < nc; col++)
		{
			int idx = (row * nc + col) * image.channels();

			image.data[idx + 0] = image.data[idx + 0] / div * div + div / 2; // B channel
			image.data[idx + 1] = image.data[idx + 1] / div * div + div / 2; // G channel
			image.data[idx + 2] = image.data[idx + 2] / div * div + div / 2; // R channel
		}
	}
}

6.图像的读取、显示、保存

读取显示:

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/core/utils/logger.hpp>

using namespace cv;
using namespace std;

int main()
{
	cout << "OpenCV Version: " << CV_VERSION << endl;
	utils::logging::setLogLevel(utils::logging::LOG_LEVEL_SILENT);

	Mat lena_c = imread("lena_c.bmp", IMREAD_UNCHANGED);

	string winname = "lena_c";
	// 创建窗口 显示
	namedWindow(winname);
	imshow(winname, lena_c);

	destroyAllWindows();
	int k = waitKey(0); // Wait for a keystroke in the window
	return 0;
}

imwrite:第三个参数设置保存图片格式属性标志

void AlphaMat(Mat &mat)
{
	CV_Assert(mat.channels() == 4);
	for (int i = 0; i < mat.rows; ++i)
		{
			for (int j = 0; j < mat.cols; ++j)
			{
				Vec4b& bgra = mat.at<Vec4b>(i, j);
				bgra[0] = UCHAR_MAX;  // 蓝色通道
				bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols) * UCHAR_MAX);  // 绿色通道
				bgra[2] = saturate_cast<uchar>((float(mat.rows - i)) / ((float)mat.rows) * UCHAR_MAX);  // 红色通道
				bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2]));  // Alpha通道
			}
		}
}
int main(int agrc, char** agrv)
{
	// Create mat with alpha channel
	Mat mat(480, 640, CV_8UC4);
	AlphaMat(mat);
	vector<int> compression_params;
	compression_params.push_back(IMWRITE_PNG_COMPRESSION);  //PNG格式图像压缩标志
	compression_params.push_back(9);  //设置最高压缩质量		
	bool result = imwrite("alpha.png", mat, compression_params);
	if (!result)
	{
		cout << "保存成PNG格式图像失败" << endl;
		return -1;
	}
	cout << "保存成功" << endl;
	return 0;
}