【OpenCV C++20 学习笔记】操作图片

操作图片

概述

在本专栏的第一篇文章中就介绍了一个用OpenCV处理图片的实例(《图片处理基础》),这篇文章进一步详细介绍OpenCV中处理图片的一些操作。

我这里使用的都是C++20的初始化语法,之前版本的C++可以参考下面这节中不同版本C++语法的对比。

图片的导入和保存

从图片文件中导入图片数据:

cpp 复制代码
Mat img = imread(filename);
Mat imgCpp20 { imread(filename) };	//C++20的初始化语法

如果导入的是jpg格式的图片,那么默认是3通道的图像数据。如果想要以灰度(只有黑白两色)格式导入,可以这样导入:

cpp 复制代码
Mat img = imread(filename, IMREAD_GRAYSCALE);
Mat imgCpp20 { imread(filename, IMREAD_GRAYSCALE) };	//C++20的初始化语法

要将数据保存到图片:

cpp 复制代码
imwrite(filename, img);

对导入的图片的操作

获取像素值

要获取像素的值,必须要知道图片的类型以及颜色通道数量。

关于图片数据的类型,可以参考该合集中的《基本图像容器------Mat》

如果要获取一个单通道灰度图片(即,8UC1类型)中(x, y)坐标上的像素的值,可以使用下面这条语句:

cpp 复制代码
Scalar intensity { img.at<uchar>(y, x) };

**注意这里坐标的表示是(y, x)。**因为在OpenCV中图片都是用矩阵来表示的,而矩阵一般是通过(row, col)的先行后列的模式来定位的,为了统一,OpenCV中坐标的表示也是纵坐标在前、横坐标在后。

在C++中,还可以使用Point来换回传统的坐标表示:

cpp 复制代码
Scalar intensity { img.at<uchar>(Point(x, y) };

如果是3通道的BGR格式的图片,要获取某个像素上每个通道的颜色值,可以使用以下方法:

cpp 复制代码
Vec3b intensity { img.at<Vec3b>(y, x) };
uchar blue { intensity.val[0] };
uchar green { intensity.val[1] };
uchar red { intensity.val[2] };

可以看到,储存单通道的像素值,使用的是Scalar类型;而储存3通道的像素值,使用的是Vec3b类型 ;3通道中单个通道的颜色值则是uchar类型。

获取像素值的方法也可以用来修改像素值:

cpp 复制代码
img.at<uchar>(y, x) = 128;

Point类型和图片像素

在C++中,用2D或3D的Point类型的数组也可以创建Mat对象,这种Mat矩阵只有1列,每一行对应一个Point对象;而且矩阵的数据类型应该是32FC2或者32FC3,相应的Point对象的类型也应该是Point2f或者Point3f。示例如下:

cpp 复制代码
vector<Point2f> points;
// ... 填充该数组
Mat pointsMat { Mat(points) };

这种矩阵可以从中获取Point对象:

cpp 复制代码
Point2f point { pointsMat.at<Point2f>(i, 0) };

内存管理和引用计数

如该合集的《基本图像容器------Mat》中详细描述的那样,Mat对象只储存指向矩阵数据的指针以及描述矩阵数据的一些信息,所以若干个Mat对象共享同一个矩阵数据是被允许的。下面结合一个比较复杂的例子来讨论这个问题:

cpp 复制代码
vector<Point3f> points;
// ... 填充数组
Mat pointsMat { Mat(points).reshape(1) };	//reshape函数重新设置Mat对象的通道数

上面的例子中pointsMat最终还是一个N3的矩阵,并不是N 1的矩阵。因为reshape函数不复制数据,它修改的只是Mat对象中对矩阵的描述。所以矩阵还是原来的N*3的矩阵,只不过在Mat(points)中创建的临时Mat对象将它描述成3通道的矩阵,而pointsMat将其描述成单通道的矩阵。

要想真正的复制数据,则需要用到cv::Mat::copyTo或者cv::Mat::clone函数:

cpp 复制代码
Mat img { imread("image.jpg");
Mat img1 { img.clone() };

**空Mat对象也可以作为函数的输出参数,用来储存计算结果。**这是因为OpenCV中的函数都会调用Mat::create方法来修改输出矩阵。如果输出矩阵是空的,那就为它分配所需要的内存;如果输出矩阵不是空的,而且大小和类型都刚好,那就不会进行任何更改;如果大小和类型不符合需求,就会先释放原有的内存然后重新分配新的内存。示例如下:

cpp 复制代码
Mat img{ imread("image.jpg");
Mat sobelx;
Mat kernel = (Mat_<char>(3, 3) << 0, -1, 0,
								-1, 5, -1,
								0, -1, 0);
filter2D(img, sobelx, img.depth(), kernel);	//掩码操作函数,第二个形参为输出的矩阵

一些简便操作

将灰度图片变成黑色图片:

cpp 复制代码
img = Scalar(0); 	//img为储存灰度图片数据的Mat对象

运行结果如下:

选择兴趣区(ROI):

cpp 复制代码
Rect r(10, 10, 100, 100);
Mat smallImg { img(r) };

定义在<opencv2/imgproc.hpp>模块中的cvtColor函数可以将BGR格式的图片转换成灰度图片:

cpp 复制代码
Mat Img { imread("image.jpg") };
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);

将图片从8UC1格式转换成32FC1格式:

cpp 复制代码
src.convertTo(dst, CV_32F);	//src为原矩阵,dst为转换后的矩阵

图片可视化

在开发过程中能及时看到算法处理的结果是很有帮助的。OpenCV提供了一个简便的图片可视化方法。例如,一个8U格式的图片可以这样展示:

cpp 复制代码
Mat img { imread("image.jpg") };
namedWindow("image", WINDOW_AUTOSIZE);	//可以不用,因为下面的imshow也会自动创建窗口
imshow("image", img);
waitKey();

waitKey();函数开启一个信息传输循环,等待在图片展示窗口上的按键操作,一旦有检测到按键就会停止循环,执行下面的语句。

其他格式的图片需要转换成8U格式的,才能在窗口展示,这就涉及到了类型转换

更精确的类型转换

在该合集的《矩阵上的掩码(mask)操作》中有提到过类型转换的问题。saturate_cast可以采取截断的方法避免信息的丢失,但它只是保证数据落在值域之内,没有进行对应的缩放。下面是一个更精确的类型转换的例子:

cpp 复制代码
Mat img { imread("image.jpg") };
Mat gray;
cvtColor(img, grey, COLOR_BGR2GRAY);
Mat sobelx;
Sobel(grey, sobelx, CV_32F, 1, 0);	//得到一个32F格式的sobelx对象
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal); //找到sobelx中的最小值和最大值
Mat draw;
sobelx.convertTo(draw, CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));	//转换语句
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey();

上例中的convertTo语句的最后两个参数是用来将原来的32F格式的值转换成8U格式的。
convertTo的4个参数分别是:

  • 目标矩阵 m m m,储存转换结果
  • 目标格式 r t y p e rtype rtype,转换后的格式
  • α α α值
  • β β β值
    α α α和 β β β值,则会用来进行以下运算:
    m ( x , y ) = s a t u r a t e _ c a s t < r t y p e > ( α ( ∗ t h i s ) ( x , y ) + β ) ; m(x,y) = saturate\_cast<rtype>(α(*this)(x,y)+β); m(x,y)=saturate_cast<rtype>(α(∗this)(x,y)+β);
    可以看出, α α α实际上是一个缩放系数,所以上例将255.0/(maxVal - minVal)作为 α α α。因为255是8U格式的最大值和最小值之间的差,将它除以原始矩阵中的最大值与最小值之间的差,相当于是两个值域的比值。另一方面,β则是缩放后进行偏移量。上例将-minVal * 255.0/(maxVal - minVal)作为偏移量,代表所有的原始值在缩放之后都要向最小值偏移一定的距离。
相关推荐
豆浩宇17 分钟前
Halcon OCR检测 免训练版
c++·人工智能·opencv·算法·计算机视觉·ocr
嵌入式杂谈26 分钟前
OpenCV计算机视觉:探索图片处理的多种操作
人工智能·opencv·计算机视觉
红米煮粥29 分钟前
图像处理-掩码
图像处理·opencv·计算机视觉
潮汐退涨月冷风霜1 小时前
机器学习之非监督学习(四)K-means 聚类算法
学习·算法·机器学习
GoppViper1 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理
羊小猪~~1 小时前
深度学习基础案例5--VGG16人脸识别(体验学习的痛苦与乐趣)
人工智能·python·深度学习·学习·算法·机器学习·cnn
Zhangci]1 小时前
OpenCv(一)
人工智能·opencv·计算机视觉
Charles Ray2 小时前
C++学习笔记 —— 内存分配 new
c++·笔记·学习
我要吐泡泡了哦3 小时前
GAMES104:15 游戏引擎的玩法系统基础-学习笔记
笔记·学习·游戏引擎
骑鱼过海的猫1233 小时前
【tomcat】tomcat学习笔记
笔记·学习·tomcat