[学习opencv3] 阅读第四章

=======================================================================
  
阅读过程中要力求有自己的见解,but,在没有自己见解的情况下就先引用书上的内容,在反复应用中建立自己的见解。

=======================================================================

独立获取数组元素

直接访问通过模板函数 at<>()来实现

这个函数有很多种变体,对不同维度的数组有不同的参数要求。这个函数的工作方式是先将at<>()特化到矩阵所包含的数据类型,然后使用你所想要的数据的行和列的位置访问该元素。
例子:

cv::Mat m = cv::Mat::eye( 10, 10, 32FC1 );
printf(
"Element (3,3) is %f\n",
m.at<float>(3,3)
);

多通道数组的操作与单通道数组类似:

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

注意,当你想要对多维数组指定一个类似于at<>()的模板函数时,最好的方式是使用cv::Vec对象(使用预先创建的别名或模板类型)。

cv::DataType<>

当需要从(编译时)的表示生成另一个(运行时)表示时使用

cv::Mat m = cv::Mat::eye( 10, 10, cv::DataType<cv::Complexf>::type );
printf(
"Element (3,3) is %f + i%f\n",
m.at<cv::Complexf>(3,3).re,
m.at<cv::Complexf>(3,3).im,
);

对于二维数组,通过C风格的指针来指定某一行。cv::Mat::Ptr<>()

cv::Mat类型的数组种,数组是按行连续组织的,因此不可以通过cv::Mat::Ptr<>()函数访问一个指定的列

例,一个M*N的二维数组。

[[.......],[.......],......,[.......]]

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

有两种方式可以获得一个指向矩阵mtx的数据区域的指针。一种是使用ptr<>()成员函数,另一种是直接使用数据指针data,然后使用成员数组step来计算地址,后者更接近于我们在C语言中所作的操作。但是一般来说,由于at<>()和ptr<>()以及迭代器的存在,这种做法已经不再推荐了。但是不得不说,直接计算地址始终是最有效率的做法,尤其是要处理多于二维的数组时。

注意,C风格指针存取方式下,如果想要访问一个数组中的所有东西,可能需要一次性迭代一行,因为这些行在数组中可能是连续的,但也有可能不连续。成员函数isContinuous()将告诉你数组是否被连续打包。如果是连续的,你就可以通过获取第一行第一个元素的指针,然后在整个数组中遍历,仿佛他是一个巨大的一维数组。

另一种序列存储的方式是使用cv::Mat内嵌的迭代器机制。这种机制在某种程度上是基于STL容器所提供的机制。基础想法是OpenCV提供的一对迭代器模板,一个用于只读(const)数组的和一个用于非只读的(ono-const)数组的 上述迭代器分别被命名为cv::MatConstIterator<>和cv::MatIterator<>。cv::Mat的成员函数begin()和end()会返回这种类型的对象 因为迭代器具有足够的智能来处理连续的内存区域和非连续的内存区域,所以这种用法非常方便,不管哪一种维度的数组中都非常有效。

int sz[3] = { 4, 4, 4 };
cv::Mat m( 3, sz, CV_32FC3 ); // A three-dimensional(3通道) array of size 4-by-4-by-4
cv::randu( m, -1.0f, 1.0f ); // fill with random numbers from -1.0 to 1.0 #从-1.0与1.0之间选取值进行随机填充
float max = 0.0f; // minimum possible value of L2 norm L2范数的最小可能值
cv::MatConstIterator<cv::Vec3f> it = m.begin(); //创建迭代器,指针指向数组的第一行第一个值
while( it != m.end() ) {    //开始迭代,到达数组末尾后退出迭代
	len2 = (*it)[0]*(*it)[0]+(*it)[1]*(*it)[1]+(*it)[2]*(*it)[2]; //分别获取每个通道中的值,进行求和。[0][1][2]对应通道1、2、3
	if( len2 > max ) max = len2;
	it++;
}

数组迭代器NAryMatIterator

另一种形式的迭代器,尽管不像cv::MatIterator<>那样将不连续内存区域打包以同时处理多个数组的迭代。这个迭代器称为NAryMatIterator,它只要求被迭代的数组有相同的几何结构(维度以及每一个维度的范围)。

该迭代器不会返回一个用于迭代的单独元素,而通过返回一堆数组来进行N-ary迭代器操作,这些返回的数组也称为“面”(plane)。一个面表示输入数组有连续内存的部分(一般来说是一位或者二位的片段)。这也是如何处理非连续内存的方法:可以一个接一个处理其中连续的内存片段,对于每一个面,你既可以通过数组操作进行处理,也可以通过自己的方式进行正常的迭代(在这里,“正常的”表示不需要检查该区域内存是否连续)。
面所包含的内容全都是从被迭代的多维数组的内容中分离出来的。

cv::RNG 随机数生成器

https://blog.youkuaiyun.com/yang_xian521/article/details/6931385

调用cv::RNG.fill() 使用随机数填充指定对象

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 );    //3通道数组,3个数组大小都为5
cv::RNG rng;
rng.fill( n_mat, cv::RNG::UNIFORM, 0.f, 1.f );//使用5*5*5个0,1之间的数进行填充
const cv::Mat* arrays[] = { &n_mat, 0 };
cv::Mat my_planes[1];

//初始化cv::NAryMatIterator迭代器对象,需要做两件事,
//首先,需要只想包含指向我们想要迭代的cv::Mat的C风格指针数组,这个数组必须以0或者NULL终止。
//然后,我们需要另一个可以作为面的参考的C风格的cv::Mat数组,从而可以通过它们进行迭代(在这里,数组长度为1)
cv::NAryMatIterator it( arrays, my_planes );

// On each iteration, it.planes[i] will be the current plane of the
// i-th array from 'arrays'.在每次迭代中,it.planes[i]将是“数组”中i-th数组的当前平面。
//
float s = 0.f; // Total sum over all planes 所有平面的和
int n = 0; // Total number of planes    面的总数
for (int p = 0; p < it.nplanes; p++, ++it) {
	s += cv::sum(it.planes[0])[0];
	n++;
}

在下面的示例中,名为arrays的C风格数组被每个输入序列赋予一个指针以及my_planes所提供的两个矩阵。当在上面进行迭代时,对于每一步来说,planes[0]包含来自n_mat0的一个面,planes[1]包含来自n_mat1的一个面。将两个面相加并添加到累加器中。

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::RNG rng; //实例化一个随机数生成器对象
rng.fill( n_mat0, cv::RNG::UNIFORM, 0.f, 1.f );
rng.fill( n_mat1, cv::RNG::UNIFORM, 0.f, 1.f );

const cv::Mat* arrays[] = { &n_mat0, &n_mat1, 0 };//用于初始化cv::NAryMatIterator迭代器对象
cv::Mat my_planes[2];
cv::NAryMatIterator it( arrays, my_planes );

float s = 0.f; // Total sum over all planes in both arrays 两个数组中所有平面的和
int n = 0; // Total number of planes 面的数量
for(int p = 0; p < it.nplanes; p++, ++it) {
	s += cv::sum(it.planes[0])[0];
	s += cv::sum(it.planes[1])[0];
	n++;
}

扩展:通过使用指针对于每个元素的加法来对两个平面求和,将结果放在第三个平面的对应位置。

/// compute dst[*] = pow(src1[*], src2[*]) //
const Mat* arrays[] = { src1, src2, dst, 0 };
float* ptrs[3];
NAryMatIterator it(arrays, (uchar**)ptrs);
for( size_t i = 0; i < it.nplanes; i++, ++it )
{
for( size_t j = 0; j < it.size; j++ )
{
	ptrs[2][j] = std::pow(ptrs[0][j], ptrs[1][j]);
}

it.nplanes 获取的是面的数量

通过测试得出。
在这里插入图片描述
在这里插入图片描述

it.size

指明每一个面的大小,这个变量显示了绵中元素的变量,所以它不会包含通道数这一要素。
如果it.planes是4,it.size将会是16.

通过块访问数组

需要将一个数组的子集作为另一个数组访问。
这个子集可能是一行或者一列,也可能是原始数组的一个子集。

有多种方法可以实现这个目的,这些方法都是cv::Mat的成员函数,并且都返回我们所请求的数组的子集。

这些方法在使用过程中,数组的数据并没有被复制到新的数组中,比如表达式m2=m.row(3),这个表达式将创建一个新的数组头,并且分配它的data指针、step数组以及其他一些东西,这样它将可以访问m中的第三行数据。如果修改了m2中得到的数据,也会修改处于m中的数据。

copyTo()方法可以真正的拷贝数据到一个新的副本中。

m.diag()返回数组中指向矩阵m的对角元素,调用m.diag()的时候,需要输入一个整形参数来说明哪一个对角需要被提取,如果参数为0,那么它将会是主对角。如果输入一个正数,它将相对于主对角线向数组上半部分偏移。如果它是负的,它将相对于主对角线向下部分偏移。

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

计算结果由操作符=()放到最终的结果中。然而,很重要的区别在于,操作符=()不是对cv::Mat或cvMat(这也有可能出现)的赋值,而是更接近于cv::Mat的cv::MatExpr(自身的表达)。这个区别非常重要,因为数据始终会复制到结果(左边的)数组中去。再想想m2=m1这一操作,尽管合理,但表达的意思却完全不一样。对于后者来说,m2只是对于m1的一个引用。相比而言,m2=m1+m0表达的意思则完全不一样。因为m1+m0是矩阵表达式,这会被计算并且将结果的指针指向m2。结果将处于一个新的内存区域中。

饱和转换

在Opencv中,常常有一些操作有溢出的风险。不仅在处理无符号类型数据的时候经常发生,在其他任何情况下也可能发生。为了处理这个问题,opencv实现了一个称为“饱和转换”(saturation casting)的构造。

这意味着opencv在进行算术和其他操作的时候会自动检查是否上溢出和下溢出。在这些情况下,这个库函数会将结果值转换为相对最小或者最大的可行值。记住,这并不是C语言的通常操作,也不是一般我们需要做的。

稀疏矩阵类 cv::SparesMat

cv::SparesMat类在数组非0元素非常少的情况下使用。这种情况在线性代数中称为“稀疏矩阵”。
一个稀疏矩阵表示只存储有数据的部分,可以节约大量的内存 一般来说,许多系数对象在元素全部以稠密形式(即所有为0的元素均被显式存储)表示的时候会过于巨大。稀疏表示的坏处在于计算速度更慢(基于每个元素进行计算)。最后,很重要的一点,稀疏矩阵的计算并不是一直都慢,如果已经知道哪些操作不需要做,会节约很多计算时间。

opencv的稀疏矩阵类cv::SparseMat的函数在很多方面都类似于稠密矩阵类cv::Mat,它们的定义十分相似,支持大多数相同的操作,并且可以包含相同的数据类型。但从内部来说,它们数据组织的方式有着非常大的不同;当cv::Mat使用接近C风格的(数据按序列被打包并且地址可以通过元素的索引计算出来)数组的时候,cv::SparseMat使用哈希表来存储非0元素,这个哈希表会自动维护,所以当数组中的非0数据变得太多以至于无法高效进行查找的时候,表也会自动增长。

访问系数矩阵中的元素

cv::SparseMat::ptr()、cv::SparseMat::ref()、cv::SparseMat::value()和cv::SparseMat::ptr()

为大型数组准备的模板结构

cv::Mat已经拥有了表示基本数据类型的能力,但是它在创建的时候还是会指明其基于的类型。在这里,cv::Mat_<>这个实例模板其实是从cv::Mat集成来的一个类,并且是这个类的特化,这样就简化了接口和其他需要模板化的成员函数。

值得重申的是,使用模板cv::Mat_<>以及cv::SparseMat_<>的目的是不必在使用其成员函数的时候调用其模板形式。

使用模板类定义了一个矩阵m,就可以不进行特化就使用at()函数。
在这里插入图片描述

cv::Mat m(10, 10, CV_32FC2 );
将它传入函数
void foo((cv::Mat_<char> *)myMat);
在运行时如果有未定义的行为,会出现错误,如果使用:
cv::Mat_<Vec2f> m( 10, 10 );
在编译的时候,就会提示错误.
template <class T> void print_matrix( const cv::SparseMat_<T>* sm ) {

cv::SparseMatConstIterator_<T> it = sm->begin();
cv::SparseMatConstIterator_<T> it_end = sm->end();

for(; it != it_end; ++it) {
	const typename cv::SparseMat_<T>::Node* node = it.node();
	cout <<"( " <<node->idx[0] <<", " <<node->idx[1]
	<<" ) = " <<*it <<endl;
	}
}

void calling_function1( void ) {
	...
	cv::SparseMat_<float> sm( ndim, size );
	...
	print_matrix<float>( &sm );
}

void calling_function2( void ) {
	...
	cv::SparseMat sm( ndim, size, CV_32F );
	...
	print_matrix<float>( (cv::SparseMat_<float>*) &sm );
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值