返回:OpenCV系列文章目录(持续更新中......)
上一篇:OpenCV4.9矩阵上的掩码操作(28)
下一篇:使用 OpenCV 添加(混合)两个图像(30)
输入/输出
C++
图像
从文件加载图像
Mat img = imread(filename);
如果您读取jpg文件,则默认情况下会创建一个 3 通道图像。如果需要灰度图像,请使用:
Mat img = imread(filename, IMREAD_GRAYSCALE);
注意
文件的格式由其内容(前几个字节)决定。要将图像保存到文件:
imwrite(filename, img);
Java
从文件加载图像
Mat img = Imgcodecs.imread(filename);
如果您读取jpg文件,则默认情况下会创建一个 3 通道图像。如果需要灰度图像,请使用:
Mat img = Imgcodecs.imread(filename, Imgcodecs.IMREAD_GRAYSCALE);
注意
文件的格式由其内容(前几个字节)决定。要将图像保存到文件:
Imgcodecs.imwrite(filename, img);
Python
从文件加载图像
img = cv.imread(filename)
如果您读取jpg文件,则默认情况下会创建一个 3 通道图像。如果需要灰度图像,请使用:
img = cv.imread(filename, cv.IMREAD_GRAYSCALE)
注意
文件的格式由其内容(前几个字节)决定。要将图像保存到文件:
cv.imwrite(filename, img)
Note
Format of the file is determined by its extension.
Use cv::imdecode and cv::imencode to read and write an image from/to memory rather than a file.
图像的基本操作
访问像素强度值
为了获得像素强度值,您必须知道图像的类型和通道数。以下是单通道灰度图像(类型 8UC1)和像素坐标 x 和 y 的示例:
C++:
Scalar intensity = img.at<uchar>(y, x);
Java:
byte[] imgData = new byte[(int) (img.total() * img.channels())];
img.get(0, 0, imgData);
byte intensity = imgData[y * img.cols() + x];
Python
_intensity = img[y,x]
仅限 C++ 版本:intensity.val[0] 包含从 0 到 255 的值。请注意 x 和 y 的顺序。由于在 OpenCV 中,图像由与矩阵相同的结构表示,因此我们对这两种情况都使用相同的约定 - 从 0 开始的行索引(或 y 坐标)排在前面,从 0 开始的列索引(或 x 坐标)紧随其后。或者,您可以使用以下表示法(仅限 C++):
Scalar intensity = img.at<uchar>(Point(x, y));
现在让我们考虑一个具有 BGR 颜色排序的 3 通道图像(imread 返回的默认格式):
C++ code
Vec3b intensity = img.at<Vec3b>(y, x);
uchar blue = intensity.val[0];
uchar green = intensity.val[1];
uchar red = intensity.val[2];
Python Python
_blue = img[y,x,0]
_green = img[y,x,1]
_red = img[y,x,2]
您可以对浮点图像使用相同的方法(例如,您可以通过在 3 通道图像上运行 Sobel 来获取此类图像)(仅限 C++):
Vec3f intensity = img.at<Vec3f>(y, x);
float blue = intensity.val[0];
float green = intensity.val[1];
float red = intensity.val[2];
可以使用相同的方法来更改像素强度:
img.at<uchar>(y, x) = 128;
Java:
byte[] imgData = new byte[(int) (img.total() * img.channels())];
imgData[y * img.cols() + x] = (byte) 128;
img.put(0, 0, imgData);
Python:
img[y,x] = 128
OpenCV 中有一些函数,尤其是来自 calib3d 模块的函数,例如 cv::p rojectPoints,它们以 Mat 的形式采用 2D 或 3D 点数组。 矩阵应该只包含一列,每行对应一个点,矩阵类型应该相应为 32FC2 或 32FC3。这样的矩阵可以很容易地从(仅限 C++ )构造:std::vector
vector<Point2f> points;
//... fill the array
Mat pointsMat = Mat(points);
可以使用相同的方法(仅限 C++)访问此矩阵中的点:Mat::at
Point2f point = pointsMat.at<Point2f>(i, 0);
内存管理和引用计数
Mat 是一种保留矩阵/图像特征(行和列数、数据类型等)和指向数据的指针的结构。因此,没有什么能阻止我们拥有多个对应于相同数据的 Mat 实例。Mat 保留一个引用计数,用于指示在 Mat 的特定实例被销毁时是否必须解除分配数据。下面是在不复制数据的情况下创建两个矩阵的示例(仅限 C++):
std::vector<Point3f> points;
// .. fill the array
Mat pointsMat = Mat(points).reshape(1);
结果,我们得到一个具有 3 列的 32FC1 矩阵,而不是具有 1 列的 32FC3 矩阵。 pointsMat使用来自点的数据,并且在销毁时不会释放内存。但是,在这种特定情况下,开发人员必须确保points的生存期长于pointsMat如果我们需要复制数据,则可以使用例如, cv::Mat::copyTo or cv::Mat::clone:
img = cv.imread('image.jpg')
_img1 = np.copy(img)
可以为每个功能提供一个空的输出垫。每个实现都为目标矩阵调用 Mat::create。如果矩阵为空,此方法为矩阵分配数据。如果它不是空的,并且具有正确的大小和类型,则该方法不执行任何操作。但是,如果大小或类型与输入参数不同,则数据将解除分配(并丢失)并分配新数据。例如:
Mat img = imread("image.jpg");
Mat sobelx;
Sobel(img, sobelx, CV_32F, 1, 0);
原始操作
在矩阵上定义了许多方便的运算符。例如,以下是我们如何从现有的灰度图像制作黑色图像img
img = Scalar(0);
选择感兴趣的区域:
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
从彩色到灰度的转换:
Mat img = imread("image.jpg"); // loading a 8UC3 image
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
将映像类型从 8UC1 更改为 32FC1:
src.convertTo(dst, CV_32F);
可视化图像
在开发过程中查看算法的中间结果非常有用。OpenCV 提供了一种可视化图像的便捷方法。可以使用以下方式显示 8U 图像:
Mat img = imread("image.jpg");
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", img);
waitKey();
对 waitKey() 的调用会启动一个消息传递循环,该循环在“image”窗口中等待击键。32F 镜像需要转换为 8U 类型。例如:
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
Mat sobelx;
Sobel(grey, sobelx, CV_32F, 1, 0);
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //find minimum and maximum intensities
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey();
注意
这里 cv::namedWindow 不是必需的,因为它后面紧跟着 cv::imshow。不过,它可用于更改窗口属性或使用 cv::createTrackbar
参考文献:
1、《Operations with images》