vlambda博客
学习文章列表

Opencv系列1.4--图像和大型数据类型

大型数组类型中最为重要的是,cv::Mat类,是整个Opencv C++库的核心;可用于表示任意维度的稠密数组,大多数图像都是采用稠密数组形式,即数组每一项都存储有数值;此外,还有一种cv::SparseMat存储稀疏矩阵,即仅存储非零项,可节约存储空间,例如直方图;
cv::Mat中数据以一种类似n维光栅扫描的方式存储,即一维数组按照顺序排列;二维数组中,数据组织为行结构,一行紧跟一行;三维数组中,每个平面均按照行接行填充,然后一个平面接一个平面;
每个矩阵都包含:
flags:表明数组内容;
dims:表明维度;
rows:行数;
cols:列数;
data:   指向数组存储位置的指针;
refcount:参考数量;使cv::Mat表现项智能指针;
创建数组
下表列出cv::Mat基本构造器,除了默认构造器外,基本可分为三种:一种是用行数和列数创建二维数组,一种是用cv::Size对象创造二维数组,一种是创建n维数组,要求指定维数和每个维度中的维数

Opencv系列1.4--图像和大型数据类型

//用cv::Mat直接实例化,此时数组无大小无数据类型 cv::Mat m;
//可用create()分配数据,行数3,列数10,数据类型三通道32位单精度,表示二维对象 m.create(3,10,CV_8UC1); //数组类型决定其元素类型,有效的数组类型需确定元素种类和通道数 //所有类型已通过头文件预定义CV_{8U,16S,16U,32F,32S,64F}C{1,2,3} //例如CV_32FC3,表示32为单精度三通道数组;
//设定第一通道的值1.0,第二通道0.0,第三通道1.0 m.setTo(cv::Scalar(1.0f,0.0f,1.0f));
//等同于cv::Mat m(3,10,CV_32FC3,cv::Scalar(1.0f,0.0f,1.0f));

通过从另一个cv::Mat复制数据构造,通过行列感兴趣范围构建,或通过cv::Rect指定感兴趣范围构建;

Opencv系列1.4--图像和大型数据类型

   //cv::Mat(const Mat& mat)   //复制构造数组 const cv::Mat img=cv::imread("a.jpg"); cv::imshow("Original Img",img); cv::waitKey(0);   cv::Mat img_copy(img);  //复制构造 cv::imshow("copy img",img_copy);   cv::waitKey(0);  
  //cv::Mat(const Mat& mat, const cv::Range rows, cv::Range cols);  //获取数组的子数组
const cv::Mat img=cv::imread("a.jpg"); cv::imshow("Original Img",img); cv::waitKey(0);
   //利用cv::Range构造数组 int height=img.cols; int width = img.rows; std::cout<<"cols"<<height<<"rows"<<width;
const cv::Range rows=cv::Range(100,800); const cv::Range cols=cv::Range(100,800); cv::Mat img_sub(img, cols, rows); cv::imshow("Sub Img",img_sub); cv::waitKey(0);    //利用Rect构造数组 cv::Rect rec1=cv::Rect(400,400,600,600); cv::Mat img_rect(img,rec1);; cv::imshow("Rect Img",img_rect); cv::waitKey(0);
静态成员构造器,全是零,全是1,对角阵;

Opencv系列1.4--图像和大型数据类型

 cv::Mat m(cv::Mat::zeros(5,5,CV_8UC1)); std::cout<<"Matrix zeros:"<<std::endl<<m<<std::endl;
cv::Mat m1(cv::Mat::ones(4,6,CV_8UC1)); std::cout<<"Matrix ones:"<<std::endl<<m1<<std::endl;
cv::Mat m2(cv::Mat::eye(4,6,CV_8UC1)); std::cout<<"Matrix eye:"<<std::endl<<m2<<std::endl;

访问数组

访问单个元素的两个主要方法是通过位置或遍历;

直接访问的方法是根据成员函数,at<数据类型>(位置)

 cv::Mat m2(cv::Mat::eye(4,6,CV_32FC1)); std::cout<<"Matrix eye:"<<std::endl<<m2<<std::endl;
std::cout<<m2.at<float>(3,3);    //at<数据类型>(位置)

对于多通道数组,方法是类似的:多通道时建议使用cv::Vec<>

 cv::Mat img=cv::imread("a.jpg",1); std::cout<<"img channels: "<<img.channels()<<std::endl; std::cout<<"value is: "<<img.at<cv::Vec3b>(10,10)[0]; std::cout<<"value is: "<<img.at<cv::Vec3b>(10,10)[1]; std::cout<<"value is: "<<img.at<cv::Vec3b>(10,10)[2];

Opencv系列1.4--图像和大型数据类型

通过指针函数ptr<>()进行访问,速度最快:

为访问二维数组,我们可以提取一个指向数组具体行的指针,ptr<>()就是cv::Mat中的模板函数。ptr<>()用一个类型名进行实例化,整数参数表示你想访问并且指针指向的行数,函数返回一个指针,指向所建数组中元素类型,也就是说,如果数组类型是CV_32FC3,那么返回值是类型float*。因此,若给定一个三通道矩阵mtx,元素类型float,构造mtx.ptr<Vec3f>(3)返回一个指针,指向矩阵mtx第三行中第一个元素的第一个通道,这是访问数组最快的方式。

如果可以判断数组是以连续行打包,则可以入一维数组那样逐行连续访问,isContinuous()函数判断。
通过迭代进行顺序访问:
Opencv提供迭代模板,分为针对const和非const,cv::MatIterator<>和cv::MatConstIterator<>。cv::Mat的方法begin()和end()可返回这种类型的对象。这种迭代方法非常方便,因为迭代器非常智能,足以处理连续打包和非连续打包情况和任意维度的数组。
每个迭代器必须被宣告和指定属于哪个数组的迭代器对象,下面是简单例子,用于计算三维数组三通道元素中最长的元素:
 int sz[3]={4,4,4}; cv::Mat m(3,sz,CV_32FC3); cv::randu(m,-1.0f,1.0f); float max=0.0f; cv::MatConstIterator<cv::Vec3f> it=m.begin();
while (it!=m.end()){ len2=(*it)[0]*(*it)[0]+(*it)[1]*(*it)[1]+(*it)[2]*(*it)[2]; if(len2>max)max=len2; it++; }
按块访问数组元素

需要注意的是,当我调用这些函数时,数组中的数据并未复制到新数组。

Opencv系列1.4--图像和大型数据类型


矩阵表达:代数和cv::Mat

Opencv系列1.4--图像和大型数据类型

Opencv系列1.4--图像和大型数据类型

溢出转换

Opencv中,进行操作时可能出现溢出等情况,尤其是无符号类型数据进行减法时,因此构建cv::saturate_cast<>()。例如:

uchar& Vxy = m0.at<uchar>(y,x);

Vxy = cv::saturate_cast<uchar>((Vxy-128)*2+128);

在该例子中,我们首先分配一个引用Vxy直向一个8位数组m0中的元素。当我们从这个数组减去128,然后乘以2,在增加128时,C算法规则会对Vxy-128分配32位有符号整数,然后进行乘法和加法。如果Vxy的原始值是10,那么最后结果是-108,将不满足Vxy 8位无符号数据类型,因此必须使用cv::saturate_casts<uchar>(),当低于无符号数据下限时,变为0。

其他数组可做的事情

Opencv系列1.4--图像和大型数据类型

稀疏数组:cv::SparseMat

通常用于0项远多于非零项的数组,一般出现在线性代数的稀疏矩阵或者直方图。稀疏数组仅存储那些存在的数据,所以节省空间,实际中稀疏对象太大很难以稠密数组表示;稀疏表示方法的缺点是,计算太慢。

cv::SparseMat类似于cv::Mat,它采用哈希表方式存储数据。

访问稀疏数组元素

稀疏数组和稠密数组最大的区别在于,如何访问元素。

稀疏数组提供四种访问方法:cv::SparseMat::ptr(),cv::SparseMat::ref(),cv::SparseMat::value(),cv::SparseMat::find();


稀疏数组独有函数

大型数组类型的模板结构

cv::SparseMat_<>和cv::Mat_<>,通过模板类则不需要使用他们成员的模板;

例如,定义矩阵cv::Mat m(10,10,CV_32FC2);则访问每个元素都需指定矩阵的类型,m.at<Vec2f>(i0,i1)=cv::Vec2f(x,y);

如果用模板类定义m,则不需要具体指明类型使用at(),

cv::Mat_<Vec2f>  m(10,10);

m.at(i0,i1)=cv::Vec2f(x,y);

这种模板结构简化了代码;

以上两种方法,效率上相同,但是更建议第二种方法;