首先我看了这篇博客 http://blog.youkuaiyun.com/fred_yang2013/article/details/11891447 ,讲到了很多细节,但是不完全
接下来我来详细分析一下源码,并且这有助于我们理解Mat的存储方式,真的是花了很大力气看懂这代码,没有注释的代码看的就是蛋疼
首先这就是个Mat的迭代器类,那么我们有疑问了,为什么不直接开数组呢?对,其实完全可以开数组的,但是我们知道Mat有个continuous标志,这表示Mat的数组很可能不是连续存储的,但是我们可以通过公式step[0]*i+step[1]*j+....来访问Mat.data[i][j]...,但是这样我们就是一个个访问矩阵的元素,然而这样效率是不是太低,所以NAryMatIterator这个类采取以一段段连续的空间来访问矩阵(也就是超平面),具体来说我们参考官文档(M.step[i]>=M.step[i+1]*M.size[i+1]),这里为什么会出现大于的情况呢?很显然在第i+1维开始,矩阵并不连续存储了,于是我们的到启发,我们寻找最大的i,满足M.step[i]>M.step[i+1]*M.size[i+1]这个式子,那么可以肯定从i+1维开始到最后一维,都是连续的,所以这就形成了一个超平面,于是我们可以每次读取这么一段超平面。
下面看代码:
class CV_EXPORTS NAryMatIterator
{
public:
//! the default constructor
NAryMatIterator();
//! the full constructor taking arbitrary number of n-dim matrices
NAryMatIterator(const Mat** arrays, uchar** ptrs, int narrays=-1);
//! the full constructor taking arbitrary number of n-dim matrices
NAryMatIterator(const Mat** arrays, Mat* planes, int narrays=-1);
//! the separate iterator initialization method
void init(const Mat** arrays, Mat* planes, uchar** ptrs, int narrays=-1);
//! proceeds to the next plane of every iterated matrix
NAryMatIterator& operator ++();
//! proceeds to the next plane of every iterated matrix (postfix increment operator)
NAryMatIterator operator ++(int);
//! the iterated arrays
const Mat** arrays; //注意这个最好以NULL结尾,否则narrays参数要显示传入
//! the current planes
Mat* planes; //存每个Mat,其中data域与ptrs相同
//! data pointers
uchar** ptrs; //存每个Mat的当前正在访问的超平面的起始地址
//! the number of arrays
int narrays; //arrays中Mat的个数
//! the number of hyper-planes that the iterator steps through
size_t nplanes; //超平面总个数
//! the size of each segment (in elements)
size_t size; //超平面元素数
protected:
int iterdepth; //这个是最大的i,满足M.step[i]>M.step[i+1]*M.size[i+1]
size_t idx; //这个是内部使用,记录当前正在访问的超平面是第几个
};
最重要的是init函数
void NAryMatIterator::init(const Mat** _arrays, Mat* _planes, uchar** _ptrs, int _narrays)
{
CV_Assert( _arrays && (_ptrs || _planes) );
int i, j, d1=0, i0 = -1, d = -1;
//初始化
arrays = _arrays;
ptrs = _ptrs;
planes = _planes;
narrays = _narrays;
nplanes = 0;
size = 0;
if( narrays < 0 ) //该参数默认为-1
{
for( i = 0; _arrays[i] != 0; i++ )
; //统计Mat的个数,可以看到_arrays以0结束,有点类似字符串
narrays = i;
CV_Assert(narrays <= 1000);
}
iterdepth = 0;
//_arrays中的Mat的存储结构必须完全一样
for( i = 0; i < narrays; i++ )
{
CV_Assert(arrays[i] != 0);
const Mat& A = *arrays[i];
if( ptrs )
ptrs[i] = A.data;
if( !A.data )
continue;
if( i0 < 0 )
{
i0 = i; //i0记录第一个A.data!=null的Mat的id
d = A.dims; //Mat的维数
// find the first dimensionality which is different from 1;
// in any of the arrays the first "d1" step do not affect the continuity
for( d1 = 0; d1 < d; d1++ ) //d1记录最先出现大于1的维度
if( A.size[d1] > 1 )
break;
}
else //对之后的矩阵必须要与i0对应的矩阵具有完全相同结构
CV_Assert( A.size == arrays[i0]->size );
if( !A.isContinuous() )
{
//不连续储存的情况
CV_Assert( A.step[d-1] == A.elemSize() );
for( j = d-1; j > d1; j-- ) //第一次出现该不等式的j
if( A.step[j]*A.size[j] < A.step[j-1] )
break;
iterdepth = std::max(iterdepth, j); //iterdepth记录所有j的最大值
}
}
if( i0 >= 0 ) //有Mat的data不为空的情况
{
size = arrays[i0]->size[d-1]; //最后一维的size
for( j = d-1; j > iterdepth; j-- ) //从iterdepth维->d-1维组成了超平面
{
int64 total1 = (int64)size*arrays[i0]->size[j-1];
if( total1 != (int)total1 ) //totall是否爆64位的范围
break;
size = (int)total1;
}
//执行到这儿,size是超平面的元素数
iterdepth = j;
if( iterdepth == d1 )
iterdepth = 0;
nplanes = 1;
//从0维->iterdepth-1维组成了超平面个数
for( j = iterdepth-1; j >= 0; j-- )
nplanes *= arrays[i0]->size[j];
//执行到这儿,nplanes是超平面的个数
}
else
iterdepth = 0;
idx = 0;
if( !planes )
return;
for( i = 0; i < narrays; i++ )
{
CV_Assert(arrays[i] != 0);
const Mat& A = *arrays[i];
if( !A.data )
{
planes[i] = Mat();
continue;
}
//存储每个Mat的第一个超平面
planes[i] = Mat(1, (int)size, A.type(), A.data);
}
}
三个构造函数就很自然了
NAryMatIterator::NAryMatIterator()
: arrays(0), planes(0), ptrs(0), narrays(0), nplanes(0), size(0), iterdepth(0), idx(0)
{
}
NAryMatIterator::NAryMatIterator(const Mat** _arrays, Mat* _planes, int _narrays)
: arrays(0), planes(0), ptrs(0), narrays(0), nplanes(0), size(0), iterdepth(0), idx(0)
{
init(_arrays, _planes, 0, _narrays);
}
NAryMatIterator::NAryMatIterator(const Mat** _arrays, uchar** _ptrs, int _narrays)
: arrays(0), planes(0), ptrs(0), narrays(0), nplanes(0), size(0), iterdepth(0), idx(0)
{
init(_arrays, 0, _ptrs, _narrays);
}
前置自增运算
//将迭代器指向所有Mat的下一个超平面
NAryMatIterator& NAryMatIterator::operator ++()
{
if( idx >= nplanes-1 ) //当所有超平面都遍历完结束
return *this;
++idx; //遍历下一个超平面
if( iterdepth == 1 )
{
//有size[0]个超平面的情况
if( ptrs )
{
for( int i = 0; i < narrays; i++ )
{
if( !ptrs[i] )
continue;
//每次用step[0]*idx来定位第idx个超平面
ptrs[i] = arrays[i]->data + arrays[i]->step[0]*idx;
}
}
if( planes )
{
for( int i = 0; i < narrays; i++ )
{
if( !planes[i].data )
continue;
//每次用step[0]*idx来定位第idx个超平面
planes[i].data = arrays[i]->data + arrays[i]->step[0]*idx;
}
}
}
else
{
//有size[0]*size[1]*...*size[iterdepth-1]个超平面
for( int i = 0; i < narrays; i++ )
{
const Mat& A = *arrays[i];
if( !A.data )
continue;
int _idx = (int)idx;
uchar* data = A.data;
//_idx=k0*size[0]+k1*size[1]*...
//我们需要把k0,k1,...提取出来,就是下面的操作,不停的%和/
for( int j = iterdepth-1; j >= 0 && _idx > 0; j-- )
{
int szi = A.size[j], t = _idx/szi;
//(_idx - t * szi)等价于_idx%szi,表示在j维有多少个超平面
data += (_idx - t * szi)*A.step[j];
_idx = t; //等价于_idx/szi
}
if( ptrs )
ptrs[i] = data;
if( planes )
planes[i].data = data;
}
}
return *this;
}
后置自增就很简单了
NAryMatIterator NAryMatIterator::operator ++(int)
{
NAryMatIterator it = *this;
++*this;
return it;
}
有了这个东西,我们就可以很方便遍历Mat数组,以超平面的形式...