1、前言
背景:获取传感器数据后需要保存成图片,有时需要对里面的元素进行操作,因为是自己开发,不能直接得到图片,所以研究了一下Mat赋值的几种方式。建议首先要理解预定义结构的类型CV_<bit_depth>(S|U|F)C<number_of_channels>,再进行下面的阅读,这部分不在这里细说。
2、Mat简介
Mat这种类,可以理解为动态数组,不需要人为释放内存。它由两个数据部分组成:矩阵头(包含矩阵尺寸、存储方式、存储地址等信息)和一个指向存储所有像素值的矩阵(根据所存储方法的不同矩阵可以是不同的维数)的指针。在opencv中,矩阵赋值(拷贝),比如cv::Mat a = b 或者 cv::Mat a(b),是将a的指针指向b的地址,通过浅拷贝的方式,让矩阵指针指向同一地址而实现的。如果想深拷贝的话,可以用函数clone()或者copyTo()来实现。
3、遍历Mat赋值方式
方式一
采用指针的方式,效率最高,一开始理解有点困难,习惯就好了,具体见代码中的解释。
#include <stdio.h>
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(){
int height = 5;
int width = 5;
//初始化赋值
cv::Mat image = cv::Mat::zeros(height, width, CV_16UC2);
//CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
for (int row = 0; row < height; row++)
{
//指向矩阵当前行的指针
Vec<ushort, 2> *data_Ptr = image.ptr<Vec<ushort, 2>> (row);
//可以将data_Ptr看出一个数组,data_Ptr[col]代表了第几个元素
//每个元素的数据类型是<ushort,2>,包含了2个无符号整型的元素
for (int col = 0; col < width; col++)
{
data_Ptr[col][0] = 5;
data_Ptr[col][1] = 2;
}
}
cout << image << endl;
return 0;
}
方式二
使用成员函数.at的方式,直接定位到第几行第几列,然后根据定义的数据类型,给元素赋值。这种方法最容易理解,但是,他的效率是最低的。
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
//直接给第几行第几列赋值
image.at<Vec<ushort, 2>>(row,col)[0] = 5;
image.at<Vec<ushort, 2>>(row,col)[1] = 2;
}
}
方式三
使用迭代器来进行遍历赋值。可以用cv::MatIterator_< cv::Vec3b>it或者cv::Mat_< cv::Vec3b>::iterator it。中间的类型取决于Mat定义的类型。这种方式,可以理解为将整个矩阵拉成一个向量,每个向量里面的元素就是申明Mat变量时定义的元素的类型。比如定义的是2通道的ushort型,那么向量里面的存储的每个元素就是2通道的ushort型,这理解的解释是我个人的类比,不是很严谨。这种方式迭代赋值效率一般,在这三种方法中排第二。
//迭代起始位置
cv::Mat_<Vec<ushort, 2>>::iterator it = image2.begin<Vec<ushort, 2>>();
//迭代终止位置
cv::Mat_<Vec<ushort, 2>>::iterator end = image2.end<Vec<ushort, 2>>();
for (; it != end; ++it)
{
(*it)[0] = 5;
(*it)[1] = 2;
}
4、测试
将三种方法测试了一下,代码如下,比较简单。
#include <stdio.h>
#include <chrono>
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(){
int height = 5;
int width = 5;
cv::Mat image = cv::Mat::zeros(height, width, CV_16UC2);
cv::Mat image1 = cv::Mat::zeros(height, width, CV_16UC2);
cv::Mat image2 = cv::Mat::zeros(height, width, CV_16UC2);
chrono::steady_clock::time_point t1 = chrono::steady_clock::now();
for (int row = 0; row < height; row++)
{
//指向矩阵当前行的指针
Vec<ushort, 2> *data_Ptr = image.ptr<Vec<ushort, 2>> (row);
//可以将data_Ptr看出一个数组,data_Ptr[col]代表了第几个元素
//每个元素的数据类型是<ushort,2>,包含了2个无符号整型的元素
for (int col = 0; col < width; col++)
{
data_Ptr[col][0] = 5;
data_Ptr[col][1] = 2;
}
}
chrono::steady_clock::time_point t2 = chrono::steady_clock::now();
chrono::duration<double> time_used = chrono::duration_cast<chrono::duration<double>>(t2-t1);
cout << "The first method cost time is "<<time_used.count() << " seconds."<< endl;
for (int row = 0; row < height; row++)
{
for (int col = 0; col < width; col++)
{
image1.at<Vec<ushort, 2>>(row,col)[0] = 5;
image1.at<Vec<ushort, 2>>(row,col)[1] = 2;
}
}
chrono::steady_clock::time_point t3 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t3-t2);
cout << "The second method cost time is "<<time_used.count() << " seconds."<< endl;
//迭代起始位置
cv::Mat_<Vec<ushort, 2>>::iterator it = image2.begin<Vec<ushort, 2>>();
//迭代终止位置
cv::Mat_<Vec<ushort, 2>>::iterator end = image2.end<Vec<ushort, 2>>();
for (; it != end; ++it)
{
(*it)[0] = 5;
(*it)[1] = 2;
}
chrono::steady_clock::time_point t4 = chrono::steady_clock::now();
time_used = chrono::duration_cast<chrono::duration<double>>(t4-t3);
cout << "The third method cost time is "<<time_used.count() << " seconds."<< endl;
cout << image << endl;
cout << image1 << endl;
cout << image2 << endl;
return 0;
}
例子中的这个矩阵比较小,一般图片矩阵都很大的,这时候运算速度就很关键了。建议以后采用第一种方式。
5、参考文献
【1】Mat类详解 之 元素的获取与赋值
【2】Mat-基本图像容器