笔者之前零零碎碎地用过一些opencv,但是一直没有足够详尽的了解,正巧刷到了opencv的tutorial,仅以此记录。
网址:link
Mat - 基本的图像容器
一张普通的图片,如果想让计算机也能读得懂的话,我们需要把他转换成矩阵格式,也就是上图中的数字部分。使用Opencv的主要重点就是操作和处理这些数据。那么第一步就是如何存储和处理图片。
在opencv中使用mat来存储图像,且不需要手动分配其内存或在不需要时释放它。大多数OpenCV函数将自动分配其输出数据。如果您传递一个已经存在的Mat对象,它已经为矩阵分配了所需的空间,那么它将被重用。换句话说,我们在任何时候都只使用执行任务所需的内存。
为了避免对较大图像的不必要复制,opencv使用了一个引用计数系统,让每个mat对象都有自己的头,但是一个矩阵可以在两个mat对象之间共享,通过让它们的矩阵指针指向相同的地址。此外,复制操作符只复制头文件和指向大矩阵的指针,而不复制数据本身。
Mat A, C; // creates just the header parts
A = imread(argv[1], IMREAD_COLOR); // here we'll know the method used (allocate matrix)
Mat B(A); // Use the copy constructor
C = A; // Assignment operator
上面的所有的对象都指向了同一个单一的数据矩阵,并且其中任意一个发生了改变都会影响其他的。实际上不同的对象仅会对同一个根本数据提供不同的入口。然而,它们的标题部分是不同的。真正有趣的部分是,您可以创建仅引用完整数据的一部分的头。例如,要在图像中创建感兴趣的区域(ROI),只需创建带有新边界的新标题:
Mat D (A, Rect(10, 10, 100, 100) ); // using a rectangle
Mat E = A(Range::all(), Range(1,3)); // using row and column boundaries
如果矩阵属于不同的Mat对象,最后一个使用该矩阵的对象负责清理该矩阵。如果有复制图片的需求,那么可以使用cv::Mat::clone() 和cv::Mat::copyTo()功能。
Mat F = A.clone();
Mat G;
A.copyTo(G);
对F和G的修改不会对A造成任何影响。以下是一些总结的tips:
- 输出的图片分瓶是自动完成的。
- 无需担心内存管理。
- 赋值操作和复制构造只复制了头部。
- 一个图片的根本矩阵可以使用clone和copyto功能进行复制。
存储方法
我们可以使用颜色空间和数据类型来存储像素的值。颜色空间指的是我们如何组合颜色组件来编码给定的颜色。最简单的一种是灰度,我们可以使用的颜色是黑色和白色。这些组合使我们能够创造出许多灰色阴影。
彩色方案可以被分成三到四个基础颜色组合(三原色)。最流行的组合是RGB,主要是因为我们的眼睛就是以此为基础来组成颜色的。此外,透明度alpha(A)也会被添加进来。(0代表完全透明,255代表完全不透明)。
但是不同的颜色系统的优势各不相同:
- RGB是最常见的,因为我们的眼睛使用类似的东西,但请记住,OpenCV标准显示系统使用BGR色彩空间(红色和蓝色通道交换位置)来组合颜色。
- HSV和HLS将颜色分解成它们的色调、饱和度和值/亮度组件,这是我们描述颜色的一种更自然的方式。例如,您可以忽略最后一个组件,使您的算法对输入图像的光照条件不那么敏感。
- 流行的JPEG图像格式使用YCrCb。
- CIE Lab*是一个感知上一致的色彩空间,如果你需要测量一种给定颜色到另一种颜色的距离,它会派上用场。
创造一个Mat对象
以下是创建一个Mat对象的方法:
- 默认构造
Mat M(2,2, CV_8UC3, Scalar(0,0,255));
cout << "M = " << endl << " " << M << endl << endl;
对于二维多通道图像,我们首先需要定义他们的大小,行数和列数。然后需要明确用来存储这些元素的数据类型和每个矩阵点的通道数。
CV_[The number of bits per item][Signed or Unsigned][Type Prefix]C[The channel number]
举个例子,CV_8UC3代表我们使用unsigned char类型,8bit长且每个像素都有三个char类型来组成三个通道。通道最多只有四个。标量是一个四元素短向量。指定它,您可以用自定义值初始化所有矩阵点。如果需要更多类型,可以使用上面的宏创建类型,在括号中设置通道号,如下所示。
- 使用c/c++队列并通过构造器进行初始化。
int sz[3] = {2,2,2};
Mat L(3,sz, CV_8UC(1), Scalar::all(0));
上面的例子展示了如何创建一个二维以上的矩阵。指定它的维度,然后传递一个包含每个维度大小的指针,其余部分保持不变。
- cv::Mat::create功能
M.create(4,4, CV_8UC(2));
cout << "M = "<< endl << " " << M << endl << endl;
你无法通过这个构造器来初始化这个矩阵,尽在新的大小不符合原来的大小时,才会重新分配数据内存。
- MATLAB style 初始化: cv::Mat::zeros , cv::Mat::ones , cv::Mat::eye
Mat E = Mat::eye(4, 4, CV_64F);
cout << "E = " << endl << " " << E << endl << endl;
Mat O = Mat::ones(2, 2, CV_32F);
cout << "O = " << endl << " " << O << endl << endl;
Mat Z = Mat::zeros(3,3, CV_8UC1);
cout << "Z = " << endl << " " << Z << endl << endl;
- 对于小的矩阵,你可以使用逗号来区分初始化器
Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
cout << "C = " << endl << " " << C << endl << endl;
C = (Mat_<double>({0, -1, 0, -1, 5, -1, 0, -1, 0})).reshape(3);
cout << "C = " << endl << " " << C << endl << endl;
- 为一个已经存在的Mat对象创造一个新的头部并使用 cv::Mat::clone or cv::Mat::copyTo
Mat RowClone = C.row(1).clone();
cout << "RowClone = " << endl << " " << RowClone << endl << endl;
note:
你可以使用**cv::randu()**功能来随机填充一个矩阵,但是需要给出一个最小和最大值的限制。
Mat R = Mat(3, 2, CV_8UC3);
randu(R, Scalar::all(0), Scalar::all(255));
输出格式
原文写得挺好的,直接贴上来。
其他的常见输出
总结
本文主要介绍了一下如何在opencv中进行存储,以及相应的一些操作,如复制,构造等。
在笔者的实际使用过程中,需要格外注意opencv的操作耗时问题,尤其是cv::imread操作。