OpenCV的数据结构——大型数组类型

OpenCV中的数据类型可分为三类,而前两类(基础数据类型和辅助对象)在前面已进行详细讲述,下面将对最后一种——大型数组类型进行讲解。

大型数据类型中最重要的当属cv::Mat,这可谓是OpenCV的核心,所有主要函数几乎都和其相关。cv::Mat类用于表示任意维度的稠密数组。所谓“稠密”表示该数组的所有部分都有一个值存储,即使这个值为0。而和其相对的就是稀疏数组cv::SparseMat,稀疏数组中只有非0的数值会被存储,在数组存在很多0的时候,稀疏数组将非常节约内存;但是在数组比较稠密的时候,稀疏数组反而会浪费大量的内存。

cv::Mat类N维稠密矩阵

实际上cv::Mat由一个头部和一个数据块组成,头部包含了数组的相关信息(大小,数据类型,通道数等),数据块包含了图像中所有像素的值。头部还一个指向数据块的指针和一个引用计数器,即data属性和refcount属性。

创建一个数组

不拷贝数据的操作称为“浅拷贝”,只是复制了头部;如果拷贝了数据的操作叫“深拷贝”,这种操作会创建空间并拷贝对方的数据。

type的形式为CV_{8U, 16S, 16U, 32S, 32F, 64F}C{1, 2, 3},其中8,16,32,64指的是位数,U,S,F表示字符、整型和浮点型,1,2,3表示通道数。

cv::Mat的构造函数(非复制构造)
构造 描述
cv::Mat; 默认构造函数
cv::Mat( int rows, int cols, int type ); 指定类型的二维数组
cv::Mat(int rows, int cols, int type,const Scalar& s); 指定类型的二维数组,并指定初始化值
cv::Mat(int rows, int cols, int type,void* data, size_t step=AUTO_STEP); 指定类型的二维数组,并指定预先存储的数据
cv::Mat( cv::Size sz, int type ); 指定类型的二维数组(大小由sz指定)
cv::Mat(cv::Size sz,int type, const Scalar& s); 指定类型的二维数组,并指定初始化值(大小由sz指定)
cv::Mat(cv::Size sz, int type,void* data, size_t step=AUTO_STEP); 指定类型的二维数组,并指定预先存储的数据(大小由sz指定)
cv::Mat(int ndims, const int* sizes,int type); 指定类型的多维数组
cv::Mat(int ndims, const int* sizes,int type, const Scalar& s); 指定类型的多维数组,并指定初始化值
cv::Mat(int ndims, const int* sizes,int type, void* data,size_t step=AUTO_STEP); 指定类型的多维数组,并指定预先存储的数据

注:cv::Size传入参数是先cols后rows的。

cv::Mat从其他cv::Mat复制数据的构造函数
构造 描述
cv::Mat( const Mat& mat ); 复制构造函数
cv::Mat(const Mat& mat,const cv::Range& rows,const cv::Range& cols); 只从指定的行列中复制数据的复制构造函数(只对二维有效)
cv::Mat(const Mat& mat,const cv::Rect& roi); 只从感兴趣的区域中复制数据的复制构造函数(只对二维有效)
cv::Mat(const Mat& mat,const cv::Range* ranges); 服务于n维数组的,从泛化的感兴趣的区域中复制数据的复制构造函数
cv::Mat( const cv::MatExpr& expr ); 从其他矩阵的线性代数表述中生成新矩阵的复制构造函数
cv::Mat模板构造函数和构造cv::Mat的静态方法
构造 描述
cv::Mat(const cv::Vec& vec,bool copyData=true);

构造一个如同cv::Vec所指定的数据类型为T、大小为n的一维数组

cv::Mat(const cv::Matx& vec,bool copyData=true); 构造一个如同cv::Vec所指定的数据类型为T、大小为 m × n的二维数组
cv::Mat(const std::vector& vec,bool copyData=true); 构造STL的vector所指定的数据类型为T、大小为vector元素数的一维数组
cv::Mat::zeros( rows, cols, type ); 构造一个大小为rows × cols、数据类型为type指定类型的、值全为0的矩阵
cv::Mat::ones( rows, cols, type ); 构造一个大小为rows × cols、数据类型为type指定类型的、值全为1的矩阵
cv::Mat::eye( rows, cols, type ); 构造一个大小为rows × cols、数据类型为type指定类型的单位矩阵

注:使用cv::Mat::ones和cv::Mat::eye时,如果要求创建的是一个多维数组,就只有第一通道会被设置为1,其余通道保持为0。

独立获取数组元素

OpenCV有几种不同的访问数组的方法。

① 通过模板函数at<>()实现

例:单通道

扫描二维码关注公众号,回复: 2866359 查看本文章
cv::Mat m = cv::Mat::eye( 10, 10, CV_32FC1 );
printf("element (3, 3) is %f\n", m.at<float>(3, 3));

 例:多通道(最好使用cv::Vec<>对象)

cv::Mat m = cv::Mat::eye( 10, 10, CV_32FC2 );
printf("element (3, 3) is (%f, %f)\n", m.at<cv::Vec2f>(3, 3)[0], m.at<cv::Vec2f>(3, 3)[1]);

例:复数

cv::Mat m = cv::Mat::eye( 10, 10, cv::DataType<cv::Complexf>::type );
printf("element (3, 3) is %f + %f\n", m.at<cv::Complexf>(3, 3).re, m.at<cv::Complexf>(3, 3).im);
at<>访问器函数的变体
示例 描述
M.at<int>( i ) 整型数组M中的元素 i
M.at<float>( i, j ) 浮点型数组M中的元素( i, j )
M.at<int>( pt ) 整型数组M中处于( pt.x, pt.y )的元素
M.at<float>( i, j, k ) 三维浮点型数组M中处于( i, j, k )位置的元素
M.at<uchar>( idx ) 无符号字符数组M中位于idx[]所索引的n维位置的元素

② 通过模板函数ptr<>()实现

ptr<>()函数接收一个整型参数来指示希望指针指向的行,并返回一个和矩阵原始数据类型相同的数据指针。例如:给定一个类型为float三通道的矩阵mtx,结构体mtx.ptr<Vec3f>(3)将会返回mtx的第三行指向第一个元素第一个(浮点)通道的指针。这通常是访问数组最快的一种方式。

③ 使用cv::Mat内嵌的迭代机制

OpenCV提供了一对迭代器模板,cv::MatConstIterator<>用于只读数组,cv::MatIterator<>用于非只读数组。所有的迭代器都必须在数组建立的时候声明并指定一个对象的类型,这样可以调用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++;
}

数组迭代器NAryMatIterator

cv::NAryMatIterator只要求被迭代的数组有相同的几何结构,结果将返回一堆数组来进行N-ary迭代器操作。这些数组也被称为“面”(plane),一个面表示输入数组有连续内存的部分。

示例一:按面进行多维数组相加

#include "stdafx.h"
#include <iostream>
#include <opencv2/opencv.hpp>


int main()
{
	// 声明一个Mat
	const int n_mat_size = 5;
	const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
	cv::Mat n_mat(3, n_mat_sz, CV_32FC1);

	// 为Mat随机填充0.0-1.0之间的数
	cv::RNG rng;
	rng.fill(n_mat, cv::RNG::UNIFORM, 0.f, 1.f);

	// 生成包含指向所有我们想要迭代的Mat的指针的C风格的指针数组,这个数组必须以0或NULL终止
	const cv::Mat* arrays[] = { &n_mat, 0 };
	// 作为面的参考的C风格的Mat数组,通过其进行迭代,这里数组长度为1.
	cv::Mat my_planes[1];
	// 创建N-ary迭代器
	cv::NAryMatIterator it(arrays, my_planes);

	// 初始化和为0
	float s = 0.f;
	// 统计面数
	int n = 0;
	for (int p = 0; p < it.nplanes; p++, ++it)
	{
		// it.planes中保存着每个输入数组当前平面的头
		s += cv::sum(it.planes[0])[0];
		n++;
	}
	// 输出结果
	std::cout << s << "\n" << n << std::endl;
    return 0;
}
// 运行结果:62.6319 和 1

示例二:使用N-ary将两个数组相加

#include "stdafx.h"
#include <iostream>
#include <opencv2/opencv.hpp>


int main()
{
	// 声明两个Mat
	const int n_mat_size = 5;
	const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
	cv::Mat n_mat0(3, n_mat_sz, CV_32FC1);
	cv::Mat n_mat1(3, n_mat_sz, CV_32FC1);

	// 为Mat随机填充0.0-1.0之间的数
	cv::RNG rng;
	rng.fill(n_mat0, cv::RNG::UNIFORM, 0.f, 1.f);
	rng.fill(n_mat1, cv::RNG::UNIFORM, 0.f, 1.f);

	// 生成包含指向所有我们想要迭代的Mat的指针的C风格的指针数组,这个数组必须以0或NULL终止
	const cv::Mat* arrays[] = { &n_mat0, &n_mat1, 0 };
	// 作为面的参考的C风格的Mat数组,通过其进行迭代,这里数组长度为1.
	cv::Mat my_planes[2];
	// 创建N-ary迭代器
	cv::NAryMatIterator it(arrays, my_planes);

	// 初始化和为0
	float s = 0.f;
	// 统计面数
	int n = 0;
	for (int p = 0; p < it.nplanes; p++, ++it)
	{
		// it.planes中保存着每个输入数组当前平面的头
		s += cv::sum(it.planes[0])[0];
		s += cv::sum(it.planes[1])[0];
		n++;
	}
	// 输出结果
	std::cout << s << "\n" << n << std::endl;
	return 0;
}
// 运行结果:126.34和1

示例三:两个面求和并将结果放入第三个平面的对应位置

#include "stdafx.h"
#include <iostream>
#include <opencv2/opencv.hpp>


int main()
{
	// 声明一个Mat
	const int n_mat_size = 5;
	const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
	cv::Mat n_mat0(3, n_mat_sz, CV_32FC1);
	cv::Mat n_mat1(3, n_mat_sz, CV_32FC1);
	cv::Mat dst(3, n_mat_sz, CV_32FC1);

	// 为Mat随机填充0.0-1.0之间的数
	cv::RNG rng;
	rng.fill(n_mat0, cv::RNG::UNIFORM, 0.f, 1.f);
	rng.fill(n_mat1, cv::RNG::UNIFORM, 0.f, 1.f);

	// 生成包含指向所有我们想要迭代的Mat的指针的C风格的指针数组,这个数组必须以0或NULL终止
	const cv::Mat* arrays[] = { &n_mat0, &n_mat1, &dst, 0 };
	float* ptrs[3];
	// 创建N-ary迭代器
	cv::NAryMatIterator it(arrays, (uchar**)ptrs);

	for (size_t i = 0; i < it.nplanes; i++, ++it) {
		for (size_t j = 0; j < it.nplanes; j++) {
			ptrs[3][j] = std::pow(ptrs[0][j], ptrs[1][j]);
		}
	}
	return 0;
}

通过块访问数组元素

cv::Mat区块访问
示例 描述(浅复制)
m.row( i ); m中第 i 行数组
m.col( j ); m中第 j 列数组
m.rowRange( i0, i1 ); m中第 i0 行到第 i1-1 行所构成的数组
m.rowRange( cv::Range( i0, i1 ) ); m中第 i0 行到第 i1-1 行所构成的数组
m.colRange( j0, j1 ); m中第 j0 列到第 j1-1 列所构成的数组
m.colRange( cv::Range( j0, j1 ) ); m中第 j0 列到第 j1-1 列所构成的数组
m.diag( d ); m中偏移量为d的主对角线所组成的数组
m( cv::Range(i0,i1), cv::Range(j0,j1) ); m中从点( i0, j0 )到点 ( i1-1,  j1-1 ) 所包含数据构成的数组
m( cv::Rect(i0,i1,w,h) ); m中从点( i0, j0 )到点 (i0+w-1, j0+h-1) 所包含数据构成的数组
m( ranges ); m中依据 ranges[0] 到 ranges[ndim-1] 所索引区域构成的数组

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

矩阵表达式可用的运算操作
示例 描述
m0 + m1, m0 – m1; 矩阵的加法和减法
m0 + s; m0 – s; s + m0; s – m1; 矩阵和单个元素相加减
-m0; 矩阵取负
s * m0; m0 * s; 通过单个数进行缩放
m0.mul( m1 ); m0/m1; 按元素将m0和m1相乘或相除
m0 * m1; m0和m1做矩阵相乘
m0.inv( method ); 对m0求逆(默认使用DECONP_LU)
m0.t(); 对m0求转置
m0>m1; m0>=m1; m0==m1; m0<=m1; m0 按元素进行比较,返回元素只有0和255的uchar类型矩阵

m0&m1; m0|m1; m0^m1; ~m0; m0&s;

s&m0; m0|s; s|m0; m0^s; s^m0;

矩阵和矩阵之间或矩阵和单个元素之间按位进行逻辑操作

min(m0,m1); max(m0,m1); min(m0,s);

min(s,m0); max(m0,s); max(s,m0);

矩阵和矩阵之间或矩阵和单个元素之间按元素取最大值和最小值
cv::abs( m0 ); 对m0按元素取绝对值
m0.cross( m1 ); m0.dot( m1 ); 向量叉乘和点乘操作(叉乘只适用于3×1矩阵)

cv::Mat::eye( Nr, Nc, type );

cv::Mat::zeros( Nr, Nc, type );

cv::Mat::ones( Nr, Nc, type );

用于返回规定类型的 Nr × Nc 矩阵的静态方法

饱和转换

cv::saturate_cast<>()会自动检测是否有上溢出或下溢出,如果存在这种情况,这个库函数会将结果值转换为相对最小或相对最大的可行值。

其他操作

cv::Mat类的更多函数成员
示例 描述
m1 = m0.clone(); 从m0进行完全复制,该复制将复制所有的数据元素
m0.copyTo( m1 ); 将m0复制给m1,若有必要,将给m1重分配内存空间
m0.copyTo( m1, mask ); 和m0.copyTo( m1 )一样,但只复制mask所指示的区域
m0.convertTo(m1, type, scale, offset); 转换m0中元素的类型并且在尺度变换(默认为1)和增加偏置(默认为0)之后赋值给m1
m0.assignTo( m1, type ); 只在内部使用(集成在convertTo中)
m0.setTo( s, mask ); 设置m0所有元素为s,如果存在mask,那么只对mask指示区域进行操作
m0.reshape( chan, rows ); 改变二维数组的有效形状,chan和rows变量可能为0,表示不改变
m0.push_back( s ); 在末尾增加一个m×1大小的数组
m0.push_back( m1 ); 向m×n大小的矩阵m0增加k行并将m1复制到这些行中,m1大小必须是k×n
m0.pop_back( n ); 从矩阵移除n行(默认为1)
m0.locateROI( size, offset ); 将m0的全尺寸写入cv::size变量size,如果m0只是一个大矩阵的一小块区域,还会写入一个Point类型的offset
m0.adjustROI( t, b, l, r ); 通过上、下、左、右四个值调整ROI范围
m0.total(); 计算数组序列的元素的数目(不包括通道)
m0.isContinuous(); 如果m0的行之间没有空隙,则返回true
m0.elemSize(); 返回m0的位长度(比如三通道浮点型矩阵返回12)
m0.elemSize1(); 返回m0最基本元素的位长度(比如三通道浮点型矩阵返回4)
m0.type(); 返回m0元素的类型(比如CV_32FC3)
m0.depth(); 返回m0通道中的元素类型(比如CV_32F)
m0.channels(); 返回m0的通道数目
m0.size(); 以cv::Size返回m0的大小
m0.empty(); 如果数组没有元素,将返回true

稀疏数据类cv::SparseMat

cv::SparseMat类在数组非0元素非常少的情况下使用,只存储有数据的部分,可以节约大量的内存。稀疏表示的缺点是计算速度更慢(基于每个元素),但从整体来看,会节约很多计算时间。

访问稀疏数组中的元素

① cv::SparseMat::ptr()

最简单的变体为:uchar* cv::SparseMat::ptr( int i0, bool createMissing, size_t* hashval = 0)

这个特定的版本用于访问一维数组。第一个参数i0是请求元素的索引;第二个参数createMissing表示该元素不存在时是否创建;最后一个参数hashval 是哈希key,默认为NULL/0,需先计算得到哈希key。返回值是一个指向无符号字符型的指针(uchar*),一般需要再次转换为正确的类型。

cv::SparseMat::ptr()的变体还允许使用两个或三个索引,也有第一个参数是一个整型数组指针的版本,它要求这个索引的元素数量和被访问的数组的维度一样多。

补充:cv::SparseMat的数据存储格式为哈希表。查询在哈希表中的对象需要两步:一是计算哈希key(根据索引进行计算),二是在key所指向的列表(很短,理想状态下只有一个元素)中进行查找。所以基础运算时间主要耗费在查找和计算哈希key,若key已计算出来,耗费的时间就会大大减少。

② cv::SparseMat::ref()

模板函数cv::SparseMat::ref()用于返回一个指向数组中特定元素的引用,索引参数和hashval 参数和cv::SparseMat::ptr()类似。因为这是一个模板函数,所以必须指定对象的类别。

③ cv::SparseMat::value()

模板函数cv::SparseMat::value()返回的是一个值,而不是一个引用,所以这个方法也叫“只读方法”。

④ cv::SparseMat::find()

cv::SparseMat::find()返回一个请求对象的只读指针,指针类型由模板指定,所以不需要再次转换。

⑤ 迭代器

迭代器
  只读 非只读
模板化 cv::SparseMatConstIterator_<> cv::SparseMatIterator_<>
非模板化 cv::SparseMatConstIterator<> cv::SparseMatIterator<>

示例:打印一个稀疏矩阵中的所有非0元素

#include "stdafx.h"
#include <opencv2/opencv.hpp>


int main()
{
	// 创建一个10 × 10的稀疏矩阵
	int size[] = { 10, 10 };
	cv::SparseMat sm( 2, size, CV_32F );

	// 给矩阵填充值
	for (int i=0; i < 10; i++)
	{
		int idx[2];
		// 生成0-327670范围的数
		idx[0] = size[0] * rand();
		idx[1] = size[1] * rand();

		// 添加元素或修改元素
		sm.ref<float>(idx) += 1.0f;
	}

	// 创建迭代器
	cv::SparseMatConstIterator_<float> it = sm.begin<float>();
	cv::SparseMatConstIterator_<float> it_end = sm.end<float>();

	for (; it != it_end; ++it) {
		// 返回一个指向被迭代器索引指向的稀疏矩阵的实际数据区域
		const cv::SparseMat::Node* node = it.node();
		printf(" (%3d, %3d) %f\n", node->idx[0], node->idx[1], *it);
	}
    return 0;
}

运行结果:

 (99610,   4910)   1.000000
 (232810,  168270) 1.000000
 (114780,  293580) 1.000000
 (410,     184670) 1.000000
 (29950,   119420) 1.000000
 (191690,  157240) 1.000000
 (57050,   281450) 1.000000
 (269620,  244640) 1.000000
 (63340,   265000) 1.000000
 (48270,   54360)  1.000000

稀疏数组中的特有函数

cv::SparseMat中额外的类成员函数
示例 描述
cv::SparseMat sm; 跳过初始化,创建一个稀疏矩阵
cv::SparseMat sm( 3, sz, CV_32F ); 创建一个由sz指定维度大小的三维稀疏浮点型矩阵
cv::SparseMat sm( sm0 ); 从系数矩阵sm0复制一个系数矩阵
cv::SparseMat( m0, try1d ); 从已有的稠密矩阵m0创建一个稀疏矩阵。若try1d为true,那么将m0转换成一维稀疏矩阵
cv::SparseMat( &old_sparse_mat ); 从一个2.1版本之前的C风格稀疏矩阵CVSparseMat创建一个新的稀疏矩阵
CvSparseMat* old_sm =(cv::SparseMat*) sm; 转换操作将创建一个2.1版本之前的C风格稀疏矩阵CVSparseMat对象并且所有的数据都会被复制到新的对象中,最后返回对象的指针
size_t n = sm.nzcount(); 返回sm中非0元素数量

size_t h = sm.hash( i0 );

size_t h = sm.hash( i0, i1 );

size_t h = sm.hash( i0, i1, i2 );

size_t h = sm.hash( idx );

返回一维稀疏矩阵中索引 i0 所指向数据的哈希值;

返回二维稀疏矩阵中索引 i0,i1 所指向数据的哈希值;

返回三维稀疏矩阵中索引 i0,i1,i2 所指向数据的哈希值;

返回多维稀疏矩阵中索引 idx 所指向数据的哈希值;

sm.ref( i0 ) = f0;

sm.ref( i0, i1 ) = f0;

sm.ref( i0, i1, i2 ) = f0;

sm.ref( idx ) = f0;

设置一维稀疏矩阵中索引 i0的所指向元素的值为 f0;

设置二维稀疏矩阵中索引 i0, i1的所指向元素的值为 f0;

设置三维稀疏矩阵中索引 i0,i1,i2 的所指向元素的值为 f0;

设置多维稀疏矩阵中索引 idx 的所指向元素的值为 f0;

f0 = sm.value( i0 );

f0 = sm.value( i0, i1 );

f0 = sm.value( i0, i1, i2 );

f0 = sm.value( idx );

将一维稀疏矩阵中索引 i0 的所指向元素的值赋给 f0;

将二维稀疏矩阵中索引 i0, i1的所指向元素的值赋给 f0;

将三维稀疏矩阵中索引 i0,i1,i2 的所指向元素的值赋给 f0;

将多维稀疏矩阵中索引 idx 的所指向元素的值赋给 f0;

p0 = sm.find( i0 );

p0 = sm.find( i0, i1 );

p0 = sm.find( i0, i1, i2 );

p0 = sm.find( idx );

将一维稀疏矩阵中索引 i0 的所指向元素赋给 f0;

将二维稀疏矩阵中索引 i0, i1的所指向元素赋给 f0;

将三维稀疏矩阵中索引 i0,i1,i2 的所指向元素赋给 f0;

将多维稀疏矩阵中索引 idx 的所指向元素赋给 f0;

sm.erase( i0, i1, &hashval );

sm.erase( i0, i1, i2, &hashval );

sm.erase( idx, &hashval );

移除二维稀疏矩阵中索引为 i0, i1 的元素;

移除三维稀疏矩阵中索引为 i0, i1, i2 的元素;

移除多维稀疏矩阵中索引为数组 idx 的元素;

cv::SparseMatIterator it= sm.begin(); 创建一个浮点型稀疏矩阵迭代器it并指向其第一个元素
cv::SparseMatIterator it_end= sm.end(); 创建一个无符号字符型稀疏矩阵迭代器it_end并将其初始化指向数组sm的最后一个元素的后一个元素

猜你喜欢

转载自blog.csdn.net/godadream/article/details/81703712