OpenCV常用类总结
Point类
描述: 表示二维点,通常用于图像坐标。
构造函数:
Point(int x, int y)
示例:
Point pt(10, 20);
cout << "Point coordinates: (" << pt.x << ", " << pt.y << ")" << endl;
Scalar类
描述: 表示四维向量,通常用于表示颜色(BGR或RGBA)。
构造函数:
Scalar(double v0, double v1, double v2, double v3 = 0)
示例:
Scalar color(255, 0, 0); // 红色
cout << "Color: B=" << color[0] << ", G=" << color[1] << ", R=" << color[2] << endl;
Size类
描述: 表示图像的大小,包含宽度和高度。
构造函数:
Size(int width, int height)
示例:
Size imgSize(640, 480);
cout << "Image size: " << imgSize.width << "x" << imgSize.height << endl;
Rect类
描述: 表示矩形区域,包含位置和大小。
构造函数:
Rect(int x, int y, int width, int height)
示例:
Rect rect(10, 20, 100, 200);
cout << "Rectangle: (" << rect.x << ", " << rect.y << ", " << rect.width << ", " << rect.height << ")" << endl;
Range类
描述: 表示一个连续的数值范围,通常用于处理图像的ROI(感兴趣区域)。
构造函数:
Range(int start, int end)
示例:
Range range(1, 5);
cout << "Range: [" << range.start << ", " << range.end << ")" << endl;
Mat类
Mat类的创建和访问
OpenCV库中最重要的组成部分当属Mat类,几乎所有操作都是基于Mat类进行展开,我们先详细介绍一下Mat类。
首先,我们得搞清楚,Mat是一个类,他的名称是矩阵(Matrix)英文的前三个字母,其形式也几乎和线性代数中所学的矩阵类似,用来存储图像信息。又因为Mat是一个类,因此我们对Mat的创建和初始化赋值都必须借助Mat类的构造函数进行操作,可以简单理解为就是有个固定化的格式。
//Mat类的最常用构造函数
Mat::Mat(int rows, int cols, int type, const Scalar &s)
Mat类的参数介绍
rows,cols: 分别为该矩阵的行数(row)和列数(column)
type: 该矩阵存储的数据类型
s: 为对该矩阵每个数据的初始化的参数变量
我们可以通过利用图像信息与Mat矩阵的联系来进一步认识这些参数的实际意义(书接Task2).
对于Mat的每一个元素我们可以认为其是图像中的对应的一个像素点,图像的尺寸(或者我们日常说的图像分辨率)就是对应Mat的rows和cols,这也决定了我们定义的Mat对象中元素个数的总量(图像的像素点个数)。
现在,我们可以详细来康康Mat类的type类型。
简单来说,这个type主要由位数(图像的色深,一般来说位数(不知道位是什么的回去补大计)越高能让我们能够存储图像的信息越加精确和细腻),数据类型(跟你C++学的常用数据类型大差不差(不知道的回去重学C++第一课)),通道数(对应颜色模型的颜色通道数,例如RGB模型对应是3通道数)三部分组成。
至于为什么是浮点整数,作者在这里挖个坑,有兴趣的可以去任何一本计算机系统的教材的前两章找找答案看看。
我们定义好Mat类的type之后,我们可以顺带地初始化数据(其实没什么大用,但是一般都会被强迫说要养成好习惯去做这件事),也就是写这个Scalar的参数,例如GRAY模型,我们可以赋值为Scalar(255);而RGB模型,我们可以赋值为Scalar(0,255,0)(你可以抬头看看能不能看到这个颜色Ovo)。
在这里我也顺便列出常用的颜色模型供大家学习,详细的不多介绍自己查好吧。
批注:RGB的通道存储顺序虽然和我们想象当中有点不太一样,但是在我们现在用的OpenCV的版本中,我们如果对其进行赋值,修改,分离等之类的操作,其实他内部会有一种反序的格式帮我们在过程中矫正到我们想象中的样子,例如Scalar(255,0,0)代表的是红色,而不是蓝色;split(img, imgs)得到的imgs[0]实际是红色通道的数据。
相信看到这里的人应该看麻了,不如举个简单的例子形象理解一下。
//创建一个大小为3*4的单通道8位无符号整数的矩阵,每个像素都是0
Mat a(3, 4, CV_8UC1, Scalar(0))
//创建一个大小为648*328的3通道8位无符号整数的矩阵,每个像素都是0,0,255
Mat b(648, 328, CV_8UC3, Scalar(0,0,255))
//创建一个大小为521*521的3通道32位浮点整数的矩阵
Mat c(521, 521, CV_32FC3)
看到这里,我相信你应该学会了这种最基本的常见Mat类以及对其初始化赋值的方法了,现在我来介绍一些其他的赋值方法。
- 枚举法
//利用枚举法赋值示例
cv::Mat a = (cv::Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
cv::Mat b = (cv::Mat_<double>(2, 3) << 1.0, 2.1, 3.2, 4.0, 5.1, 6.2);
上面第一行代码创建了一个3×3的矩阵,矩阵中存放的是从1-9的九个整数,先将矩阵中的第一行存满,之后再存入第二行、第三行,即1、2、3存放在矩阵a的第一行,4、5、6存放在矩阵a的第二行,7,8,9存放在矩阵a的第三行。第二行代码创建了一个2×3的矩阵,其存放方式与矩阵a相同。 采用枚举法时,输入的数据个数一定要与矩阵元素个数相同,例如代码清单2-13中第一行代码只输入从1到8八个数,赋值过程会出现报错,因此本方法常用在矩阵数据比较少的情况。
- .at运算赋值
//利用for循环进行赋值
Mat image(3, 10, CV_32FC3);
int div = 4;
for(int i=0; i<image.rows; i++){
for(int j=0; j<image.cols; j++){
image.at<Vec3f>(i, j)[0] = (i + j) / div;
image.at<Vec3f>(i, j)[1] = (i + j + 1) / div;
image.at<Vec3f>(i, j)[2] = (i + j + 2) / div;
}
}
这段代码的作用是创建一个 3 行 10 列的彩色图像,并用特定的值填充每个像素的 RGB 通道。下面是对代码的逐行解释:
Mat img(3, 10, CV_32FC3);
:创建一个名为img
的矩阵,其大小为 3 行 10 列,数据类型为 32 位浮点数,且每个像素有 3 个通道(对应 RGB)。int div = 4;
:定义一个整数变量div
,其值为 4,用于后续计算像素值。for(int i=0; i<image.rows; i++){
:外层循环遍历每一行,i
表示当前行的索引。for(int j=0; j<image.cols; j++){
:内层循环遍历每一列,j
表示当前列的索引。在内层循环中,以下三行代码为当前像素(
i, j
)的 RGB 通道赋值:image.at<Vec3f>(i,j)[0] = (i + j) / div;
:将红色通道(第一个通道)设置为(i + j)
除以div
。image.at<Vec3f>(i,j)[1] = (i + j + 1) / div;
:将绿色通道(第二个通道)设置为(i + j + 1)
除以div
。image.at<Vec3f>(i,j)[2] = (i + j + 2) / div;
:将蓝色通道(第三个通道)设置为(i + j + 2)
除以div
。 最终,生成的图像将根据行和列的索引进行填充,产生一种渐变效果,RGB 通道的值随位置变化。
我们顺便介绍一下Mat类的.at运算,结构为.at<通道数据类型>(矩阵元素位置坐标)[第几个通道]
- 类似Matlab的几个函数赋值
利用类方法赋值示例
Mat a = Mat::eye(3, 3, CV_8UC1);
Mat b = (Mat_<int>(1, 3) << 1, 2, 3);
Mat c = Mat::diag(b);
Mat d = Mat::ones(3, 3, CV_8UC1);
Mat e = Mat::zeros(4, 2, CV_8UC3);
Mat::eye(3, 3, CV_8UC1)
:创建一个 3x3 的单位矩阵(对角线为 1,其余元素为 0),数据类型为 8 位无符号单通道(灰度图像)。Mat_<int>(1, 3)
:创建一个 1 行 3 列的矩阵b
,数据类型为int
。 << 1, 2, 3:用给定的值初始化矩阵
b。Mat::diag(b)
:创建一个对角矩阵c
,对角线元素来自矩阵b
,其余元素为 0。Mat::ones(3, 3, CV_8UC1)
:创建一个 3x3 的矩阵d
,所有元素都初始化为 1,数据类型为 8 位无符号单通道。Mat::zeros(4, 2, CV_8UC3)
:创建一个 4 行 2 列的矩阵e
,所有元素初始化为 0,数据类型为 8 位无符号三通道(通常用于彩色图像)`
- 利用数组进行赋值
结构: Mat(rows, cols, type, array)
利用数组赋值示例
float a[8] = { 5,6,7,8,1,2,3,4 };
Mat b = Mat(2, 2, CV_32FC2, a);
Mat c = Mat(2, 4, CV_32FC1, a);
这种赋值方式首先将需要存入到Mat类中的变量存入到一个数组中,之后通过设置Mat类矩阵的尺寸和通道数将数组变量拆分成矩阵,这种拆分方式可以自由定义矩阵的通道数,当矩阵中的元素数目大于数组中的数据时,将用-1.0737418e+08填充赋值给矩阵,如果矩阵中元素的数目小于数组中的数据时,将矩阵赋值完成后,数组中剩余数据将不再赋值。
上面代码图片解释由下图给出,左边是b矩阵,右边是c矩阵。
Mat类的运算
我们一般只进行Mat类的二元运算。
首先我们介绍Mat类的最基础的运算,这也是图像处理经常用到的一些运算。
注意:除了.dot()运算,其余运算的结果均为Mat类型,而.dot()运算最终返回的结果是一个double类型!
下面我们看一串的例子进行具体化理解。
Mat a = (Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
Mat b = (Mat_<int>(3, 3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
Mat c = (Mat_<double>(3, 3) << 1.0, 2.1, 3.2, 4.0, 5.1, 6.2, 2, 2, 2);
Mat d = (Mat_<double>(3, 3) << 1.0, 2.1, 3.2, 4.0, 5.1, 6.2, 2, 2, 2);
Mat e, f, g, h, i, j, k;
e = a + b;
f = c - d;
g = 2 * d;
h = d / 2.0;
i = a – 1;
j = c * d;
k = a.mul(b);
double m = a.dot(b);
输出结果如下:
a = [1, 2, 3;
4, 5, 6;
7, 8, 9]
b = [1, 2, 3;
4, 5, 6;
7, 8, 9]
c = [1, 2.1, 3.2;
4.3, 5.4, 6.5;
7.6, 8.699999999999999, 9.800000000000001]
d = [1, 2.1, 3.2;
4.3, 5.4, 6.5;
7.6, 8.699999999999999, 9.800000000000001]
e = [2, 4, 6;
8, 10, 12;
14, 16, 18]
f = [0, 0, 0;
0, 0, 0;
0, 0, 0]
g = [2, 4.2, 6.4;
8.6, 10.8, 13;
15.2, 17.4, 19.6]
h = [0.5, 1.05, 1.6;
2.15, 2.7, 3.25;
3.8, 4.35, 4.9]
i = [0, 1, 2;
3, 4, 5;
6, 7, 8]
j = [34.35, 41.28, 48.21000000000001;
76.92, 94.73999999999999, 112.56;
119.49, 148.2, 176.91]
k = [1, 4, 9;
16, 25, 36;
49, 64, 81]
m = 285.000000000000000
篇幅有限,我在这里不多介绍Mat类的其他基础运算了,例如还有矩阵幂运算,求逆运算之类的,这些等以后用到再说吧,挖个小坑。接下来,我来介绍一种比较神奇的运算——位运算(其实也没多神奇)。
对于位运算不太了解的或者只知道操作本身不知道一些特殊性质的可以看看这两篇总结: 位运算(&、|、^、~、>>、 | 菜鸟教程 (runoob.com) 位运算全面总结,关于位运算看这篇就够了-CSDN博客
这系列的运算也许以我们目前的水平来看没什么作用,也许到最后调车了也确实对大部分人来说没什么作用,但我还是建议大家学一下,因为这系列的操作也许会在你想象不到的地方派上用场。
函数原型:
- bitwise_not(src1, dst, mask(可省略))
- bitwise_and(src1, src2, dst, mask(可省略))
- bitwise_or(src1, src2, dst, mask(可省略))
- bitwise_xor(src1, src2, dst, mask(可省略))
其中src1,src2均为输入(进行运算)的Mat;而dst是输出(运算结果)的Mat;mask是掩膜(马赛克),这个参数可以省略,但一般使用时不省略,因为我们对图片处理时往往选取我们感兴趣的部分进行处理。
看个例子...
#include <opencv2/opencv.hpp>
using namespace cv;
#define mat Mat
int main() {
// 读取图像
mat src1 = imread("image1.jpg");
mat src2 = imread("image2.jpg");
// 位运算
mat dst_not, dst_and, dst_or, dst_xor;
bitwise_not(src1, dst_not);
bitwise_and(src1, src2, dst_and);
bitwise_or(src1, src2, dst_or);
bitwise_xor(src1, src2, dst_xor);
// 显示结果
imshow("Original Image 1", src1);
imshow("Original Image 2", src2);
imshow("Not Image 1", dst_not);
imshow("AND Image 1 2", dst_and);
imshow("OR Image 1 2", dst_or);
imshow("XOR Image 1 2", dst_xor);
waitKey(0);
return 0;
}
本作者比较懒,只是随手写了下代码,并没有贴图演示,建议大家可以用我代码(不建议学习我的习惯和码风)和自己私藏的一些图片去试试效果,不建议使用颜色过于鲜艳的图像去尝试。
Mat类的其他成员函数
Mat类的其他成员函数我贴心地给大家做了一个分类,以便大家进行记忆。其大致可以分为不想记忆类,让你知道类,有点操作类这三类。
不想记忆类
图像读取函数imread
//imread函数原型
imread(const String &filename, int flags = IMREAD_COLOR)
其中filename是需要读取图像的文件名称,flags是读取图像形式的标志,默认为IMREAD_COLOR
这里只列举最常用的三种,其他等有需要时自行查询,下面来看下代码示例
Mat img1 = imread("image.jpg");
Mat img2 = imread("image.jpg", 0);
我们可以用empty()来判断图像是否读取成功,其用法和STL的用法一致
图像窗口函数namedWindow
//namedWindow函数原型
namedWindow(const String &winname, int flags = WINDOW_AUTOSIZE)
其中winname是窗口名称,flags是窗口属性设置标志,默认为WINDOW_AUTOSIZE
这里只列举最常用的三种,其他等有需要时自行查询,下面来看下代码示例
namedWindow("example1");
namedWindow("example2", WINDOW_NORMAL);
namedWindow("example3", 1);
图像显示函数imshow
//imshow函数原型
imshow(const String &winname, InputArray mat)
其中winname是要显示图像窗口的名字,mat是要显示的图像矩阵
下面来看下代码示例
imshow("example2", img1);
视频读取函数VideoCapture
//VideoCapture函数原型
//第一种,一般用于读取视频文件
VideoCapture(const String &filename, int apiPreference = CAP_ANY)
//第二种,一般用于调用本地摄像头
VideoCapture(int index, int apiPreference = CAP_ANY)
示例一
VideoCapture video("cup.avi"); // 读取视频文件
if (!cap.isOpened()) {
cout << "Error" << endl;
return -1;
}
Mat frame;
while (1) {
cap >> frame; // 捕获一帧
if (frame.empty()) break; // 检查帧是否为空
imshow("Video", frame); // 显示帧
waitKey(1000 / video.get(CAP_PROP_FPS));
}
waitkey(0);
示例二
VideoCapture cap(0); // 从默认摄像头捕获
if (!cap.isOpened()) {
cout << "Error" << endl;
return -1;
}
Mat frame;
while (1) {
cap >> frame; // 捕获一帧
if (frame.empty()) break; // 检查帧是否为空
imshow("Camera Feed", frame); // 显示帧
if (waitKey(30) >= 0) break; // 按任意键退出
}
cap.release();
destroyAllWindows();
等待按键函数waitkey
//wairKey函数原型
waitKey(int delay = 0);
delay: 等待时间(毫秒)。如果设置为0,函数将无限等待直到按下任意键;如果设置为正数,它会等待指定的时间(以毫秒为单位),之后继续执行程序。
其具有返回值,返回按下的键的 ASCII 码。如果在等待时间内没有按下任何键,返回-1。
用法示例:
while (1) {
Mat frame;
cap >> frame; // 捕获一帧
if (frame.empty()) break; // 检查帧是否为空
imshow("Camera Feed", frame); // 显示帧
int key = waitKey(30); // 等待30毫秒
if (key == 27) { // ESC键的ASCII码是27
break; // 按ESC键退出循环
}
}
让你知道类
我们使用Mat类进行记录图像信息,但是当我们想获取我们记录过的图像的信息又应该如何获取呢?
别急,我们其实可以直接访问Mat类矩阵的属性来获取(至于为什么可以直接访问其信息我就不多解释了,留个坑自己去看Mat类的内部,看不懂的重学C++)
示例
cout << img.cols() << endl;
cout << img.rows() << endl;
cout << img.total() << endl;
cout << img.channels() << endl;
其实,我们不仅可以对图像矩阵进行这样的操作,更进一步地,我们还能对视频也进行类似的操作
VideoCapture类变量同时也提供了可以查看视频属性的get()函数,通过输入指定的标志来获取视频属性
示例
cout << video.get(CAP_PROP_FRAME_WIDTH) << endl;
cout << video.get(CAP_PROP_FRAME_HEIGHT) << endl;
cout << video.get(CAP_PROP_FPS) << endl;
cout << video.get(CAP_PROP_FRAME_COUNT) << endl;
有点操作类
绘制图形的几个函数
绘制线段函数line
//line函数原型
line(img, pt1, pt2, color, thickness, lineType);
其中img
是要绘制的图像; pt1
, pt2
是线段的两个端点; color
是线段颜色(Scalar
类型); thickness
是线段厚度; lineType
是线型(例如:LINE_AA
)(可省略)。
绘制矩形函数retangle
//rectangle函数原型
rectangle(img, rect, color, thickness);
其中rect
是矩形区域(Rect
类型);其他参数同上。
绘制圆形函数circle
//circle函数原型
circle(img, center, radius, color, thickness);
其中center
: 圆心位置(Point
类型);radius
: 半径;其他参数同上。
示例
Mat img = Mat::zeros(400, 400, CV_8UC3); // 创建黑色背景
// 绘制线段
line(img, Point(50, 50), Point(300, 50), Scalar(255, 0, 0), 2);
// 绘制矩形
rectangle(img, Rect(50, 70, 200, 100), Scalar(0, 255, 0), 2);
// 绘制圆
circle(img, Point(200, 250), 50, Scalar(0, 0, 255), 2);
imshow("Drawings", img);
cvtColor(),split(),merge()应该都会吧,我懒得介绍了
后记
我感觉我没什么好说的,整了有好几个小时了,就这样吧,下次再也不郑这玩意了,其实我觉得自己理解并记忆或者有用于记忆的媒介就好了,不必大费周章去证这可爱玩意。对了,编者也是一个新来的,有错的很正常,自己看着知道就行。