在opencv早期的版本中,图像通过一个叫做IplImage的结构(structure)存储在内存中。由于C语言对程序员高度的信任,因此它需要手动地对内存进行管理,比如内存的分配和回收,这在大型程序设计中是比较麻烦的。幸运地是,C++可以很好地帮助程序员管理内存,因此opencv2.0后就引入了C++接口。但是C++也有缺点,比如说目前大部分的嵌入式系统只支持C语言,在这些平台上开发opencv程序的话用C++就不是很好。
cv::Mat是一个C++类,包含两部分:1)Matrix header,包括矩阵的size、存储方式、矩阵的存储地址等信息;2)指向Marix的指针ji。由于图像处理算法通常都是计算密集型算法,出于程序速度上的考虑,opencv的设计应尽可能地避免拷贝大图像,为了解决这个问题,opencv使用了引用计数机制(reference counter system)【python也使用了这个机制,参考之前的博客】。简单来说,灭个Mat对象都有自己的header,在进行copy运算时,只有headers和指向矩阵的指针会被拷贝,而矩阵本身不会被拷贝,举个栗子:
int main() {
cv::Mat srcImg(2, 2, CV_8UC3, cv::Scalar(0, 0, 255));
cv::Mat dstImg(srcImg);
cv::Mat C = srcImg;
std::cout << srcImg << std::endl;
std::cout << dstImg << std::endl;
dstImg.ptr(0)[0] = 255;
dstImg.ptr(0)[1] = 255;
dstImg.ptr(0)[2] = 0;
std::cout << "After modified dstImg:" << std::endl;
std::cout << srcImg << std::endl;
std::cout << dstImg << std::endl;
std::cout << C << std::endl;
return 0;
}
上面的三个Mat对象srcImg,dstImg,C最终都只想同一个数据矩阵,虽然它们的headers是不同的。对它们其中的任意一个进行修改都会影响另外两个对象。上面程序的运行结果如图:
当然,如果想拷贝矩阵本身也是有办法的,opencv提供了两个方法:clone()和copyTo():
Mat F = A.clone();
Mat G;
A.copyTo(G);
最后总结一下:
1)opencv函数中输出图像的内存是自动分配的;
2)赋值运算和拷贝构造函数只是拷贝了header,我们可以把这种拷贝理解为一种浅拷贝;
3)如果想进行深拷贝,即拷贝矩阵本身的数据,可以采用clone()或copyTo()函数。
对1和2的理解可以很重要,这可以解释下面这个程序:
int main() {
cv::String path = "E:/Data Sets/ORIGINAL/data_road_fisheye/training/image/";
std::vector filenames;
cv::glob(path, filenames);
cv::Mat srcImg(2, 2, CV_8UC3, cv::Scalar(0, 0, 255));
cv::Mat dstImg(srcImg);
cv::Mat C = srcImg;
std::cout << srcImg << std::endl;
std::cout << dstImg << std::endl;
std::cout << C << std::endl;
dstImg.ptr(0)[0] = 255;
dstImg.ptr(0)[1] = 255;
dstImg.ptr(0)[2] = 0;
std::cout << "After modified dstImg:" << std::endl;
std::cout << srcImg << std::endl;
std::cout << dstImg << std::endl;
std::cout << C << std::endl;
cv::cvtColor(C, C, CV_RGB2GRAY);
std::cout << "After call cv::cvtColor(C,C,CV_RGB2GRAY)" << std::endl;
std::cout << srcImg << std::endl;
std::cout << dstImg << std::endl;
std::cout << C << std::endl;
return 0;
}
其运行结果为:
关于如何创建一个Mat对象,最好的办法就是看mat.hpp,因为实在有太多了...,这里在介绍一下opencv里面的一下data
type,比如说CV_8UC3,CV_32FC3,CV_32F是什么意思:
CV_[the number of bits per item][signed or unsigned][Type prefix]C[The channel number]
最后是一个大头部分:介绍如何遍历cv::Mat。
Q1:图像在Mat中是如何存储的呢?
通常我们有足够多的内存,使得上面这个矩阵可以一行接着一行地连续存储,具体是不是呢,可以用isContinous()函数来判断。因此最高效的遍历方法还是采用指针(还有迭代器方法):
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{
// accept only char type matrices
CV_Assert(I.depth() != sizeof(uchar));
int channels = I.channels();
int nRows = I.rows * channels;
int nCols = I.cols;
if (I.isContinuous())
{
nCols *= nRows;
nRows = 1;
}
int i,j;
uchar* p;
for( i = 0; i < nRows; ++i)
{
p = I.ptr(i) ;
for ( j = 0; j < nCols; ++j)
{
p[j] = table[p[j]];
}
}
return I;
}
MatObject.ptr(rowIndex)[columIndex] = a number