七.opencv(七):Mat_类
Mat_类是对 Mat 类的一个包装,其定义如下:
template<typename _Tp>
class Mat_ : public Mat
{
public:
//只定义了几个方法
//没有定义新的属性
};
这是一个非常轻量级的包装,既然已经有 Mat 类,为何还要定义一个 Mat_? 下面我们看这段代码:
Mat M(600, 800, CV_8UC1);
for( int i = 0; i < M.rows; ++i)
{
uchar * p = M.ptr<uchar>(i);
for( int j = 0; j < M.cols; ++j )
{
double d1 = (double) ((i+j)%255);
M.at<uchar>(i,j) = d1;
double d2 = M.at<double>(i,j);//此行有错
}
}
在读取矩阵元素时,以及获取矩阵某行的地址时,需要指定数据类型。这样 首先需要不停地写“<uchar>”,让人感觉很繁琐,在繁琐和烦躁中容易犯错,如上面代码中的错误,用 at()获取矩阵元素时错误的使用了 double 类型。这种错误 不是语法错误,因此在编译时编译器不会提醒。在程序运行时,at()函数获取到 的不是期望的(i,j)位置处的元素,数据已经越界,但是运行时也未必会报错。这样 的错误使得你的程序忽而看上去正常,忽而弹出“段错误”,特别是在代码规模很大时,难以查错。
如果使用 Mat_类,那么就可以在变量声明时确定元素的类型,访问元素时 不再需要指定元素类型,即使得代码简洁,又减少了出错的可能性。
#include<iostream>
#include<math.h>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[]){
Mat M(600, 800, CV_8UC1);
// 用指针输入pixel
for (int i = 0; i < M.rows; i++){
uchar* p = M.ptr<uchar>(i); //M.ptr是指向第i行开头DIY的指针
for (int j = 0; j < M.cols; j++){
p[j] = (i + j) % 255;
}
}
// 通过at()函数输入pixels
for (int i = 0; i < M.rows; i++){
uchar* p = M.ptr<uchar>(i); //M.ptr是指向第i行开头DIY的指针
for (int j = 0; j < M.cols; j++){
// 用at()读写像素,需要指定类型,很麻烦,很容易出错
M.at<uchar>(i, j) = ((i + j) % 255);
}
}
// Mat_ 类管理
// create 一个Mat_类对象,将原来Mat类对象的 M 强制类型转换成Mat_<uchar>&,
// 这样就可以完成关联、赋值
Mat_<uchar> M1 = (Mat_<uchar>&) M;
for (int i = 0; i < M1.rows; i++){
// 不需指定元素类型,语句简洁
uchar* p = M1.ptr(i);
for (int j = 0; j < M1.cols; j++){
double d1 = (double)((i + j) % 255);
// MATLAB风格
M1(i, j) = d1;
double d2 = M1(i, j);
}
}
imshow("name", M1);
waitKey();
return 0;
}
八.opencv(八):Mat类 内存管理
使用 Mat 类,内存管理变得简单,不再像使用 IplImage 那样需要自己申请 和释放内存。虽然不了解 Mat 的内存管理机制,也无碍于 Mat 类的使用,但是 如果清楚了解 Mat 的内存管理,会更清楚一些函数到底操作了哪些数据。
Mat 是一个类,由两个数据部分组成:矩阵头(包含矩阵尺寸,存储方法, 存储地址等信息)和一个指向存储所有像素值的矩阵的指针,如图 3.9 所示。矩阵头的尺寸是常数值,但矩阵本身的尺寸会依图像的不同而不同,通常比矩阵头 的尺寸大数个数量级。复制矩阵数据往往花费较多时间,因此除非有必要,不要 复制大的矩阵。
为了解决矩阵数据的传递,OpenCV 使用了引用计数机制。其思路是让每个 Mat 对象有自己的矩阵头信息,但 多个 Mat 对象可以共享同一个矩阵数据。让矩 阵指针指向同一地址而实现这一目的。很多函数以及很多操作(如函数参数传值) 只复制矩阵头信息,而不复制矩阵数据。
前面提到过,有很多中方法创建 Mat 类。如果 Mat 类自己申请数据空间, 那么该类会多申请 4 个字节,多出的 4 个字节存储数据被引用的次数。引用次数 存储于数据空间的后面,refcount 指向这个位置,如图 3.9 所示。当计数等于 0 时,则释放该空间。
关于多个矩阵对象共享同一矩阵数据,我们可以看这个例子:
Mat A(100,100, CV_8UC1);
Mat B = A;
Mat C = A(Rect(50,50,30,30));
上面代码中有三个 Mat 对象,分别是 A, B 和 C。这三者共有同一矩阵数据, 其示意图如图 3.10 所示
九.opencv(九):关于Mat的更多细节
马上学完了,加油了。
从前面的例程中,可以看到 Mat 类重载了<<操作符,可以方便得使用流操作来输出矩阵的内容。默认情况下输出的格式是类似 Matlab 中矩阵的输出格式。除了默认格式,Mat 也支持其他的输出格式。
code:
Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));
// 默认格式输出的代码如下:
cout << "R (default) = " << endl << R << endl << endl;
之前一直想,如果有RGB三个通道,Mat会如何进行显示。现在发现了问题的答案。(全部显示的代码放在文末)
1.默认格式
Default print:
[ 91, 2, 79, 179, 52, 205;
236, 8, 181, 239, 26, 248;
207, 218, 45, 183, 158, 101]
2.Python 格式
Python print:
[[[ 91, 2, 79], [179, 52, 205]],
[[236, 8, 181], [239, 26, 248]],
[[207, 218, 45], [183, 158, 101]]]
3.Numpy 格式
Numpy print:
array([[[ 91, 2, 79], [179, 52, 205]],
[[236, 8, 181], [239, 26, 248]],
[[207, 218, 45], [183, 158, 101]]], dtype='uint8')
#include<iostream>
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
int main(int argc, char* argv[]){
Mat R(3, 2, CV_8UC3);
// 这个Scalar::all最多支持4-d标量赋值,pixels经常拿他赋值
/*randu
Generates a single uniformly-distributed random number or an array of random numbers.
Non-template variant of the function fills the matrix dst with uniformly-distributed
random numbers from the specified range:
@ param dst output array of random numbers; the array must be pre-allocated.
@ param low, inclusive lower boundary of the generated random numbers.
@ param high, exclusive upper boundary of the generated random numbers.
@sa RNG, randn, theRNG*/
randu(R, Scalar::all(0), Scalar::all(255));
cout << "Default print:" << endl << R << endl;
//版本问题,可能是你的是format(R,"python")
cout << "Python print:" << endl << format(R, Formatter::FMT_PYTHON) << endl;
cout << "Numpy print:" << endl << format(R, Formatter::FMT_NUMPY) << endl;
return 0;
}