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;
}