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( 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( 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(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<>()实现
例:单通道
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);
示例 | 描述 |
---|---|
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()
{// 声明一个Matconst 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);// 初始化和为0float 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()
{// 声明两个Matconst 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);// 初始化和为0float 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()
{// 声明一个Matconst 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;
}
通过块访问数组元素
示例 | 描述(浅复制) |
---|---|
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<>()会自动检测是否有上溢出或下溢出,如果存在这种情况,这个库函数会将结果值转换为相对最小或相对最大的可行值。
其他操作
示例 | 描述 |
---|---|
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 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的最后一个元素的后一个元素 |