图像操作
从文件加载一幅图像:
(从文件加载图像的代码实现见源代码。)
如果你读取的是一个 jpg 文件,默认会创建一幅 3 通道图像。如果你需要灰度图像,请使用:
(以灰度方式从文件加载图像的代码实现见源代码。)
注: 文件的格式由其内容(前几个字节)决定。要将图像保存到文件:
(保存图像的代码实现见源代码。)
注: 文件的格式由其扩展名决定。
注: 使用
cv::imdecode和cv::imencode从/向内存(而非文件)读取和写入图像。
图像的基本操作
Section titled “图像的基本操作”访问像素强度值
Section titled “访问像素强度值”为了获取像素强度值,你必须知道图像的类型和通道数。下面是一个针对单通道灰度图像(类型 8UC1)以及像素坐标 x 和 y 的示例:
(像素访问 1 的代码实现见源代码。)
仅限 C++ 版本:intensity.val[0] 包含一个 0 到 255 之间的值。注意 x 和 y 的顺序。由于在 OpenCV 中图像由与矩阵相同的结构表示,我们在两种情况下使用相同的约定——从 0 开始的行索引(或 y 坐标)在前,从 0 开始的列索引(或 x 坐标)在后。或者,你可以使用以下记法(仅限 C++):
(像素访问 2 的代码实现见源代码。)
现在让我们考虑一幅具有 BGR 颜色排序的 3 通道图像(imread 返回的默认格式):
C++ 代码
(像素访问 3 的代码实现见源代码。)
Python
(像素访问 3 的代码实现见源代码。)
你可以对浮点图像使用相同的方法(例如,你可以通过对 3 通道图像运行 Sobel 来得到这样一幅图像)(仅限 C++):
(像素访问 4 的代码实现见源代码。)
相同的方法可用于更改像素强度:
(像素访问 5 的代码实现见源代码。)
OpenCV 中有一些函数,尤其是来自 calib3d 模块的函数,例如 cv::projectPoints,它们以 Mat 的形式接收一个 2D 或 3D 点的数组。矩阵应恰好包含一列,每一行对应一个点,矩阵类型应分别为 32FC2 或 32FC3。这样的矩阵可以很容易地从 std::vector 构造(仅限 C++):
(从点向量构造 Mat 的代码实现见源代码。)
可以使用相同的方法 Mat::at 访问此矩阵中的一个点(仅限 C++):
(点访问的代码实现见源代码。)
内存管理与引用计数
Section titled “内存管理与引用计数”Mat 是一个保存矩阵/图像特征(行数和列数、数据类型等)以及一个指向数据的指针的结构。因此,没有什么能阻止我们让多个 Mat 实例对应同一份数据。Mat 维护一个引用计数,用以告知当某个 Mat 实例被销毁时数据是否需要释放。下面是一个不复制数据而创建两个矩阵的示例(仅限 C++):
(引用计数 1 的代码实现见源代码。)
结果是,我们得到一个具有 3 列的 32FC1 矩阵,而不是具有 1 列的 32FC3 矩阵。pointsMat 使用来自 points 的数据,并且在销毁时不会释放内存。然而在这种特定情况下,开发者必须确保 points 的生命周期长于 pointsMat。如果我们需要复制数据,可以使用例如 cv::Mat::copyTo 或 cv::Mat::clone:
(引用计数 2 的代码实现见源代码。)
可以为每个函数提供一个空的输出 Mat。每个实现都会为目标矩阵调用 Mat::create。如果矩阵为空,此方法会为矩阵分配数据。如果它不为空且具有正确的尺寸和类型,此方法什么也不做。然而,如果尺寸或类型与输入参数不同,数据会被释放(并丢失),然后分配新数据。例如:
(引用计数 3 的代码实现见源代码。)
矩阵上定义了许多便利的运算符。例如,下面是我们如何从一幅现有的灰度图像 img 制作一幅黑色图像:
(将图像设为黑色的代码实现见源代码。)
选择感兴趣区域(ROI):
(选择 ROI 的代码实现见源代码。)
从彩色转换为灰度:
(BGR 转灰度的代码实现见源代码。)
将图像类型从 8UC1 改为 32FC1:
(转换为 CV_32F 的代码实现见源代码。)
在开发过程中查看算法的中间结果非常有用。OpenCV 提供了一种方便的图像可视化方式。一幅 8U 图像可以这样显示:
(imshow 1 的代码实现见源代码。)
调用 waitKey() 会启动一个消息传递循环,等待”image”窗口中的按键。一幅 32F 图像需要转换为 8U 类型。例如:
(imshow 2 的代码实现见源代码。)
注: 这里不需要
cv::namedWindow,因为它紧接着被cv::imshow调用。不过,它可以用来更改窗口属性,或在使用cv::createTrackbar时使用。