Skip to content

图像操作

从文件加载一幅图像:

(从文件加载图像的代码实现见源代码。)

如果你读取的是一个 jpg 文件,默认会创建一幅 3 通道图像。如果你需要灰度图像,请使用:

(以灰度方式从文件加载图像的代码实现见源代码。)

注: 文件的格式由其内容(前几个字节)决定。要将图像保存到文件:

(保存图像的代码实现见源代码。)

注: 文件的格式由其扩展名决定。

注: 使用 cv::imdecode 和 cv::imencode 从/向内存(而非文件)读取和写入图像。

为了获取像素强度值,你必须知道图像的类型和通道数。下面是一个针对单通道灰度图像(类型 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++):

(点访问的代码实现见源代码。)

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 时使用。