OpenCV4 快速入门 学习笔记分享【第二章】
本人正在学习 OpenCV 计算机视觉相关知识,此篇为个人学习笔记,参考书籍为冯振等人的《OpenCV4 快速入门》。主要记录 OpenCV 在 Linux 环境下的编译、运行实操流程与相关知识点总结。
笔记内容均为亲自实操验证,过程中踩过不少编译配置、运行参数的坑,整理出来一方面用于自己复盘巩固,另一方面也希望能帮助到刚入门 OpenCV、在编译运行示例程序时遇到问题的小伙伴。文中内容如有疏漏或错误之处,欢迎各位大佬评论区指正交流,共同进步~
!!层级标题编号与书中编号一致,以方便查阅对应书籍。
第二章 数据载入、显示与保存
2.1 图像读取容器
2.1.1 Mat类介绍
定义:用来存矩阵类型的数据信息,包括向量、矩阵、灰度或色彩图像等数据。
分类:矩阵头(包括矩阵尺寸/存储方法/地址和引用次数等)、矩阵指针(指向存储数据)
特点:矩阵头是常数,不会随矩阵尺寸的变化而变化。
使用:可以先创建矩阵头,再赋值数据
创建Mat类代码:
cv::Mat a; #创建名为a的矩阵头
a = cv::imread("test.jpg") #向a中赋值图像数据,矩阵指针指向像素数据
cv::Mat b=a; #复制矩阵头,并命名为b
为了学习方便好找,这里创建专门学习文件夹
操作步骤:
mkdir opencv_learn # 创建文件夹
cd opencv_learn # 进入文件夹
gedit mat_demo.cpp #创建文件并打开
Mat类可存储数据类型包括:double,float,uchar,unsigned char,自定义模块等
声明一个指定类型的Mat类:
cv::Mat A = Mat_<double>(3,3); #创建一个3*3的矩阵用于存放double类型数据
C1,C2,C3,C4表示单通道、双通道、3通道、4通道(灰度图像数据是单通道,彩色数据是3通道或4通道) ,,,,单通道图像是一个二维矩阵,多通道矩阵每个元素坐标都是多个数据

CV_8UC1:表示8位单通道数据,表示8位灰度图
CV_8UC3:表示8位3通道数据,表示8位彩色图
CV_8UC(n):表示多通道矩阵,n<=512
创建声明通道数和数据类型的Mat类型:
cv::Mat a(640,480,CV_8UC3) //创建一个640*480的3通道矩阵用于存放彩色图像
cv::Mat a(3,3,CV_8UC1) //创建一个3*3的8位无符号整数单通道矩阵
cv::Mat a(3,3,CV_8U) //同第二行
2.1.2 Mat类构造与赋值
一、构造
- 默认构造函数方式:不需要任何参数,后面变量赋值会自动判断矩阵的类型和大小,实现灵活的存储
cv::Mat::Mat();
- 根据输入矩阵尺寸和类型构造(好处:清晰直观。相机的内参矩阵/物体的旋转矩阵)
rows表示矩阵行数,cols表示矩阵列数,type表示存储的数据类型
cv::Mat::Mat(int rows,
int cols,
int type
)
- 用Size()构造Mat类
size:二维数组变量尺寸,通过Size(cols,rows)进行赋值
cv::Mat::Mat(Size size(),
int type
)
示例:
cv::Mat a(Size(480,640), CV_8UC1); //构造行640,列480的单通道矩阵
cv::Mat b(Size(480,640), CV_32FC3); //构造行640,列480的3通道矩阵
- 利用已有构造函数构造
注意:只复制了已有函数的矩阵头,矩阵指针指向的是同一地址
cv::Mat::Mat( const Mat & m);
示例:需要构造的矩阵类型比已有矩阵小,并且存储的是已有矩阵的子内容
m表示已经构建完成的Mat类矩阵数据
rowRange表示已有矩阵中需要截取的行数范围,不输入任何值时,表示所有行都被截取
cv::Mat::Mat(const Mat & m,
const Range & rowRange,
const Range & colRange = Range::all()
)
示例:在原图中截图使用,在原Mat类截取子Mat类
cv::Mat b(a, Range(2,5), Range(2,5)); //从a中截取部分数据构造b
cv::Mat c(a, Range(2,5)); //默认最后一个参数构造c
二、赋值(创建完后,变量里没有数据,需要进行赋值)
- 构造时赋值
cv::Mat::Mat(int rows,
int cols,
int type,
const Scalar & s
)
示例:Scalar结构中的变量的个数要与通道数对应
cv::Mat a(2,2,CV_8UC3, cv::Scalar(0,0,255)); //创建一个3通道矩阵,每个像素都是0,0,255
cv::Mat a(2,2,CV_8UC2, cv::Scalar(0,255)); //创建一个双通道矩阵,每个像素都是0,255
cv::Mat a(2,2,CV_8UC1, cv::Scalar(255)); //创建一个单通道矩阵,每个像素都是255
- 枚举法赋值:将所有元素一一列举,并用数据流形式赋值给Mat类。
示例:第一行:第一行存放1、2、3;第二行存放4、5、6;第三行存放7、8、9.
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);
- 循环法赋值:对每一个元素进行赋值,但可以不在声明变量时赋值
cv::Mat c = cv::Mat_<int>(3,3); //定义一个3*3矩阵
for (int i = 0; i< c.rows; i++) //矩阵行数循环
{
for (int j = 0; j<c.cols; j++)
{
c.at<int>(i,j) = i+j;
}
}
- 类方法赋值:在Mat类里,提供了可以快速赋值的方法,可以初始化指定的矩阵。例如:生成单位矩阵/对角矩阵/全1矩阵/全0矩阵

- 数组赋值
float a[8] = { 5,6,7,8,1,2,3,4 };
cv::Mat b = cv::Mat(2,2,CV_32FC2, a);
cv::Mat c = cv::Mat(2,4,CV_32FC1, a);
将Mat类中的变量存入一个数组中,之后设置类矩阵的尺寸和通道数将数组变量拆分成矩阵
2.1.3 Mat类支持的运算
在加减运算中,要保证两个矩阵的数据类型是相同的
cv::Mat a = (cv::Mat_<int>(3,3) <<1,2,3,4,5,6,7,8,9);
cv::Mat b = (cv::Mat_<int>(3,3) <<1,2,3,4,5,6,7,8,9);
cv::Mat c = (cv::Mat_<double>(3,3) <<1.0,2.1,3.2,4.0,5.1,6.2,2,2,2);
cv::Mat d = (cv::Mat_<double>(3,3) <<1.0,2.1,3.2,4.0,5.1,6.2,2,2,2);
cv::Mat e,f,g,h,i;
e=a+b;
f=c-d;
g=2*a;
h=d/2.0;
i=a-1;
cv::Mat j,m;
double k;
j=c*d;
k=a.dot(b); //两个矩阵的内积
m=a.mul(b); //两个矩阵的乘积,注意数据溢出问题
图像处理中,常用的数据类型是CV_8U,范围是0-255
2.1.4 Mat类元素的读取
先存储第一个元素的每个通道数据,再存储第二个元素的每个通道数据

- at方法读取Mat类矩阵中的元素
需要at后+数据类型,,,当数据类型是uchar,要强制转换成int类型进行输出,否则输出不为整数。
//读取单通道矩阵元素
cv::Mat a = (cv::Mat_<uchar>(3,3) <<1,2,3,4,5,6,7,8,9);
int value = (int)a.at<uchar>(0,0);
三通道矩阵表示方法:(6种),,,,,,2通道把“3”变“2”
cv::Vec3b、cv::Vec3s、cv::Vec3w、cv::Vec3d、cv::Vec3f、cv::Vec3i
b是uchar ,s是short, w是ushort, d是double,f是float, i是int
//at方法读取Mat类多通道矩阵元素
cv::Mat b(3,4, CV_8UC3,cv::Scalar(0,0,1));
cv::Vec3b vc3 = b.at<cv::Vec3b>(0,0);
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];
- 通过指针ptr读取
找到每一行元素的起始地址位置,读取矩阵每一行不同位置的元素时将指针在起始位置向后移动即可。
cv::Mat b(3,4, CV_8UC3,cv::Scalar(0,0,1));
for (int i = 0; i<b.rows; i++)
{
uchar*ptr = b.ptr<uchar>(i);
for (int j = 0;j<b.cols*b.channels(); j++)
{
cout << (int)ptr[j] <<endl;
}
}
- 通过迭代器访问
cv::MatIterator_<uchar> it = a.begin<uchar>();
cv::MatIterator_<uchar> it_end = a.end<uchar>();
for (int i = 0; it !=it_end; it ++)
{
cout << (int)(*it) << " ";
if ((++i% a.cols) ==0)
{
cout <<endl;
}
}
- 通过矩阵元素的地址访问
直接给出行、列和通道数进行读取
(int)(*(b.data + b.step[0] *row + b.step[1] *col +channel));
2.2 图像的读取与显示
2.2.1 图像读取函数imread
imread原型:
用于读取指定的图像并返回给一个Mat类变量,可通过data属性返回值为0或者empty()返回值为1,说明图像读取失败。
filename:需要读取的图像文件名称,有图像地址、名称和文件拓展名
flags:读取图像形式的标志,如彩色图像按照灰度图读取
cv::Mat cv::imread(const String & filename,
int flags-IMREAD_COLOR
)
标志参数不冲突下,可声明多个,中间用“|”隔开

2.2.2 图像窗口函数namedWindow
一般会自动生成,但如果添加内存条,此时就需要创建图像窗口
namedWindow()函数原型:
winname:窗口名称,用作窗口的标识符
flags:窗口属性设置标志 ,设置窗口大小、图像是否铺满窗口等
两个关闭窗口的函数cv::destoryWindow() 『用于关闭指定名称窗口』和cv :: destoryAllWindows() 『关闭所有窗口』 但不!!需要
void cv::namedWindow(const String & winname,
int flags = WINDOW_AUTOSIZE
)
一般用“打钩”的这几个组合,用“|”隔开

2.2.3图像显示函数imshow:在指定窗口中显示图像
函数原型:
winname:显示图像窗口的名字
mat:显示图像的矩阵
InputArray:用作输入参数标识
规则:如果没有创建窗口,则创建一个窗口,显示原始图像的大小;
如果创建,就会缩放图像适应窗口属性:
如果图像是8位无符号类型,原图显示;
如果是16位无符号类型或者32位整数类型,会将像素处以256,将范围由[0,255*256]映射到【0,255】
如果是32位或64位浮点类型,将像素乘以255,将范围由[0,1]映射到[0,255]
void cv::imshow(const String & winname,
InputArray mat
)
2.3 视频加载与摄像头调用
2.3.1 视频数据读取VideoCapture()
读取视频文件ViedoCapture类构造函数:读取和处理视频文件的视频流
filename:读取的视频文件
apiPreference:数据读取时设置的属性,如编码格式/是否调用OpenNI等
是否读取成功显示:isOpened()函数,成功返回true,失败返回false
是否读取完毕:empty()函数
查看视频属性:get()函数
cv::VideoCapture :: VideoCapture(); //默认构造函数
cv::VideoCapture :: VideoCapture(const String & filename,
int apiPreference =CAP_ANY
)

示例:读取视频文件
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
system("color F0"); //更改输出界面颜色
VideoCapture video("cup.mp4");
if (video.isOpened())
{
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);
}
else
{
cout << "视频文件名称是否正确" << endl;
return -1;
}
while (1)
{
Mat frame;
video >> frame;
if (frame.empty())
{
break;
}
imshow("video", frame);
waitKey(1000 / video.get(CAP_PROP_FPS));
}
waitKey();
return 0;
}
2.3.2 摄像头的直接调用
第一个参数是打开摄像头设备的ID,
cv :: VideoCapture :: VidEoCapture(int index,
int apiPreference = CAP_ANY
)
2.4 数据保存
2.4.1 图像保存imwrite()函数
imwrite()函数原型:
img:将要保存的Mat类矩阵变量
params:保存图片格式属性设置标志
如果保存成功返回true,失败返回false
通道对应保存格式:16位无符号------PNG/JPEG/TIFF
32位浮点---------PFM/TIFF/OpenEXR/Radiance HDR
4通道-------PNG
注意:第三个参数一般不写,当保存的数据比较特殊(16位深度数据),则需要设置第三个参数,包含第三个参数可选的标志和作用
bool cv::imwrite(const String& filename,
InputArray img,
Const std::vector<int>& params = std::vector<int>()
)
示例:保存图像
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
void AlphaMat(Mat &mat)
{
CV_Assert(mat.channels() == 4);
for (int i = 0; i < mat.rows; ++i)
{
for (int j = 0; j < mat.cols; ++j)
{
Vec4b& bgra = mat.at<Vec4b>(i, j);
bgra[0] = UCHAR_MAX; // 蓝色通道
bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols) * UCHAR_MAX); // 绿色
bgra[2] = saturate_cast<uchar>((float(mat.rows - i)) / ((float)mat.rows) * UCHAR_MAX); // 红色
bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2])); // Alpha通道
}
}
}
int main(int agrc, char** agrv)
{
// Create mat with alpha channel
Mat mat(480, 640, CV_8UC4);
AlphaMat(mat);
vector<int> compression_params;
compression_params.push_back(IMWRITE_PNG_COMPRESSION); //PNG格式图像压缩标志
compression_params.push_back(9); //设置最高压缩质量
bool result = imwrite("alpha.png", mat, compression_params);
if (!result)
{
cout << "保存成图像格式失败" << endl;
return -1;
}
cout << "保存成功" << endl;
return 0;
}
2.4.2 视频的保存
视频构造函数:
fourcc:压缩帧的4字符编解码器代码,,,赋值“-1”,会自动搜索合适的编解码器
fps:保存视频的帧率,实现原倍速还是2倍速等
frameSize:视频帧的尺寸,需要与图像尺寸一样
isColor:保存视频是否为彩色视频,默认为彩色
同样可以使用isOpened函数和get函数
cv:: VideoWriter();
cv:: VideoWriter()::VideoWriter(const String& filename,
int fourcc,
double fps,
Size frameSize,
bool is Color=true
)
示例:
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img;
VideoCapture video(0); //使用某个摄像头
//读取视频
//VideoCapture video;
//video.open("cup.mp4");
if (!video.isOpened()) // 判断是否调用成功
{
cout << "打开摄像头失败,请确实摄像头是否安装成功";
return -1;
}
video >> img; //获取图像
//检测是否成功获取图像
if (img.empty()) //判断有没有读取图像成功
{
cout << "没有获取到图像" << endl;
return -1;
}
bool isColor = (img.type() == CV_8UC3); //判断相机(视频)类型是否为彩色
VideoWriter writer;
int codec = VideoWriter::fourcc('M', 'J', 'P', 'G'); // 选择编码格式
//OpenCV 4.0版本设置编码格式
//int codec = CV_FOURCC('M', 'J', 'P', 'G');
double fps = 25.0; //设置视频帧率
string filename = "live.avi"; //保存的视频文件名称
writer.open(filename, codec, fps, img.size(), isColor); //创建保存视频文件的视频流
if (!writer.isOpened()) //判断视频流是否创建成功
{
cout << "打开视频文件失败,请确实是否为合法输入" << endl;
return -1;
}
while (1)
{
//检测是否执行完毕
if (!video.read(img)) //判断能都继续从摄像头或者视频文件中读出一帧图像
{
cout << "摄像头断开连接或者视频读取完成" << endl;
break;
}
writer.write(img); //把图像写入视频流
//writer << img;
imshow("Live", img); //显示图像
char c = waitKey(50);
if (c == 27) //按ESC案件退出视频保存
{
break;
}
}
// 退出程序时刻自动关闭视频流
//video.release();
//writer.release();
return 0;
}
2.4.3 保存和读取xml和ymal文件:FileStorage类
- XML是元标记语言,使用者可以根据自身需求定义自己的标记
例:表示age值为24: <age>24</age>
表示color含有red的100和blue的150数据:<color><red>100</red><blue>150</blue></color>
- ymal文件是以数据为中心的语言“变量:数值”来表示
encoding:编码格式,支持UTF-8 XML
可以使用isOpened()和open()函数和write()函数『将不同类型的变量名称写入文件』
例:表示age值为24:file<<"age"<<24 ,,,,,表示隶属关系用“{}”
cv::FileStorage::FileStorage(const String & filename,
int flags,
const String & encoding = String()
)

2.5 总结

至此,第二章就结束了,望各位读者“一键三连”,再次感谢读到这里的各位读者。有任何问题欢迎评论区留言或者私信。
更多推荐
所有评论(0)