官方文档链接:https://docs.opencv.org/4.2.0/d5/d98/tutorial_mat_operations.html
输入 / 输出 (Input / Output)
图像 (Images)
从文件中加载图像:
cv::Mat img = cv::imread(filename);
如果读取的是一个 jpg 文件,则默认情况下会创建一个 3 通道图像。如果需要灰度图像,则使用:
cv::Mat img = cv::imread(filename, cv::IMREAD_GRAYSCALE);
注意
文件的格式由其内容(前几个字节)决定。要将图像保存到文件中,请执行如下操作:
cv::imwrite(filename, img);
注意
文件的格式由其扩展名决定。使用 cv::imdecode 和 cv::imencode 从内存(而不是文件)读取和写入图像。
图像基本操作 (Basic operations with images)
访问像素值 (Accessing pixel intensity values)
为了获得像素强度值,必须知道图像的类型和通道数。以下是单通道灰度图像(8UC1 型) 和像素坐标 x 和 y 的示例:
cv::Scalar intensity = img.at<uchar>(y, x);
C++ 版本:intensity.val[0] 存储着 0 到 255 的一个值。注意 x 和 y 的顺序。因为在 OpenCV 中,图像用和矩阵相同的结构表示,所以我们对这两种情况都使用相同的约定:首先是基于 0 的行索引(或 y 坐标),然后是基于 0 的列索引(或 x 坐标)。或者,也可以使用以下符号(仅 C++):
cv::Scalar intensity = img.at<uchar>(cv::Point(x, y);
现在考虑一下具有 BGR 颜色顺序(cv::imread 返回的默认格式) 的 3 通道图像:
cv::Vec3b intensity = img.at<cv::Vec3b>(y, x);
uchar blue = intensity.val[0];
uchar green = intensity.val[1];
uchar red = intensity.val[2];
可以用同样的方法来处理浮点图像(例如,可以通过在 3 通道图像上运行 Sobel 来获得这样的图像)(基于 C++):
cv::Vec3f intensity = img.at<cv::Vec3f>(y, x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
同样的方法可用于改变像素值:
img.at<uchar>(y, x) = 128;
例如:
完整代码:
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/imgcodecs/imgcodecs.hpp>
#include <opencv2/highgui/highgui.hpp>
int main(int argc, char** argv)
{
cv::Mat src = cv::imread(cv::samples::findFile("lena.jpg"), cv::IMREAD_COLOR);
for (int i = 100; i <= 200; ++i)
for (int j = 300; j <= 500; ++j)
{
src.at<cv::Vec3b>(i, j).val[0] = 250;
src.at<cv::Vec3b>(i, j).val[1] = 250;
src.at<cv::Vec3b>(i, j).val[2] = 250;
}
cv::imshow("Output", src);
cv::waitKey(0);
return 0;
}
输出图像:
OpenCV 中有一些函数,特别是 calib3d 模块中的函数,比如 cv::projectPoints,它们以 Mat 的形式获取二维或三维点的数组。矩阵应正好包含一列,每行对应一个点,矩阵类型应相应为 32FC2 或 32FC3 。这样的矩阵可以很容易地从 std::vector 构造:
std::vector<cv::Point2f> points;
// ... fill the array
cv::Mat pointsMat = cv::Mat(points);
可以使用相同的方法 cv::Mat::at 访问矩阵中的点:
cv::Point2f point = pointsMat.at<cv::Point2f>(i, 0);
内存管理和引用计数 (Memory management and reference counting)
Mat 是一种保持矩阵/图像特征(行和列数、数据类型等)和指向数据的指针的结构。因此,可以同时拥有于相同数据对应的多个 Mat 实例。Mat 保存一个引用计数,该计数指明在销毁 Mat 的特定实例时是否必须释放数据。这里是一个创建两个矩阵而不复制数据的例子:
std::vector<cv::Point3f> points;
// ... fill the array
cv::Mat pointsMat = cv::Mat(points).reshape(1);
结果得到了一个 3 列的 32FC1 矩阵,而不是 1 列的 32FC3 矩阵。pointsMat 使用来自点的数据,并且在销毁时不会释放内存。但是,在这个特定的实例中,如果我们需要复制数据,必须确保 points 的生存期比 pointsMat 的生存期长。可以使用 cv::Mat::copyTo 或 cv::Mat::clone:
cv::Mat img = cv::imread("image.jpg");
cv::Mat img1 = img.clone();
每个函数可提供一个空的输出 Mat。每个实现都为目标矩阵调用 cv::Mat::create。如果矩阵是空的,则此方法为其分配数据。如果它不是空的并且具有正确的大小和类型,则该方法不会执行任何操作。但是,如果大小或类型与输入参数不同,则会释放(并丢失)数据并重新分配数据。例如:
cv::Mat img = cv::imread("image.jpg");
cv::Mat sobelx;
cv::Sobel(img, sobelx, CV_32F, 1, 0);
输出图像
原始操作 (Primitive operations)
矩阵上定义了许多方便的算子。例如,
- 下面是如何从现有的灰度图像 img 生成黑色图像的方法:
img = cv::Scalar(0);
输出图像:
- 选择感兴趣的区域 (ROI):
cv::Rect r(10, 10, 100, 100);
cv::Mat smallImg = img(r);
输出图像:
- 将彩色图像转换为灰度图像:
cv::Mat img = cv::imread("image.jpg"); // loading a 8UC3 image
cv::Mat gray;
cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
- 将图像类型从 8UC1 转换为 32FC1:
img.convertTo(dst, CV_32F);
图像可视化 (Visualizing images)
在开发过程中,查看算法的中间结果是非常有用的。OpenCV 提供了一种可视化图像的便捷方法。一幅 8U 的图像可以使用以下方式显示:
cv::Mat img = cv::imread("image.jpg");
cv::namedWindow("image", cv::WINDOW_AUTOSIZE);
cv::imshow("image", img);
cv::waitKey();
调用 cv::waitKey() 将启动一个消息传递周期,该周期将等待 “图像” 窗口中的键击。一幅 32F 图像需要转换为 8U 类型,例如:
cv::Mat img = cv::imread("image.jpg");
cv::Mat grey;
cv::cvtColor(img, grey, cv::COLOR_BGR2GRAY);
cv::Mat sobelx;
cv::Soble(grey, sobelx, CV_32F, 1, 0);
double minVal, maxVal;
cv::minMaxLoc(sobelx, &minVal, &maxVal); // find minimum and maximum intensities
cv::Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
cv::namedWindow("image", cv::WINDOW_AUTOSIZE);
cv::imshow("image", draw);
cv::waitKey();
输出图像:
注意 (Note)
这里的 cv::namedWindow 不是必要的,因为后面紧跟着 cv::imshow。但是,它可以用于更改窗口属性,或者在使用 cv::createTrackbar 时。