opencv C艹:读取视频文件,保存图像,视频文件,读取保存XML YAML文件

《opencv4快速入门》

认识认识模块

D:\opencv\build\include\opencv2 路径下

  • calib3d 主要包含相机标定,立体视觉的功能:物体姿势估计,三维重建,摄像头标定
  • core,包含库的基本结构和操作,比如数据结构,绘图函数,数组操作等函数
  • dnn,深度学习模块,包含构建网络,加载序列化的模型,但是不支持训练,只能推理
  • features2d,处理图像特征点,特征检测,描述匹配之类的
  • flann,这个模块是高维的近似近邻快速搜索算法库 主要包含快速近似近邻搜索与聚类等。
  • gapi,加速图像处理的框架,没有特定的算法
  • highgui,创建操作显示图像的窗口,处理鼠标事件和键盘命令,提供交互式可视化的界面
  • imgcodecs 图片的保存和读取
  • imgproc,图像处理模型,滤波,几何变换,直方图,特征检测目标检测等。
  • ml,机器学习模块,统计分类,回归,聚类等
  • objdetect,目标检测慕课,Haar特征等
  • photo,摄影模型用来图片修复,去噪
  • stitching,图像拼接,包含特征点的寻找和匹配,估计选择,自动校准,接缝等(啊这)
  • video ,视频分析模型,用于运动估计,背景分离,对象跟踪等
  • videoio ,视频输入,输出模块。

Mat

Mat类用于存储矩阵数据,可以自动管理内存,解决内存释放的问题。用来保存矩阵类型的数据,包括向量,矩阵,图像数据等。分为矩阵头和指向数据的指针两部分,矩阵头包括矩阵的尺寸,存储方法,地址,引用次数等。大小是个常数,opencv中复制和传递图像,就只是复制了矩阵头和指向数据的地址。 创建Mat时可以先创建矩阵头再赋值。

using namespace cv;
Mat a;  // 名为a的矩阵头
a = imread("3.jpg");  // 向a中赋值图像数据,矩阵指针指向像素数据
Mat b=a;   // 复制矩阵头,命名b,虽然有各自的矩阵头,但数据指针指向的时一个数据

Mat c = Mat_<double>(3,3)  // 创建3*3 的矩阵存放double类型的数据。
// 同样的有 flaot,uchar,char 和模板,CV_64F

通过其中一个矩阵头改变数据,另一个矩阵头指向的数据同样变化。
但是当a 变量删除时,b不会指向一个i空数据,只有两个变量都没了才会删除数据。
这就是矩阵头的引用次数标记了引用矩阵数据的次数, 只有值为0才会释放数据。

为了避免在不同环境下因变量位数长度不同而造成程序执行问题,OpenCV 根据数值变量存储位数长度定义了数据类型,后加一个C表示通道
在这里插入图片描述

Mat a(640,640,CV_8UC3); // 创建一个640*640 8位三通道的矩阵
Mat a(3,3,CV_8U); // 单通道,C1可以省略

Mat的构造方法:

Mat ()

Mat (int rows, int cols, int type)  

Mat (Size size, int type)  size:二维数组尺寸 Size(cols,rows),注意行列顺序
  						Mat a(Size(480,640), CV_8UC1); 行640,列480
  						
Mat (int rows, int cols, int type, const Scalar &s)  构造的同时赋值(相同的值)
               Mat a(2,2,CV_8UC3, Scalar(0,0,255)); 创建三通道矩阵,通道值位0,0,255。
               
Mat (Size size, int type, const Scalar &s)

Mat (int ndims, const int *sizes, int type)

Mat (const std::vector< int > &sizes, int type)

Mat (int ndims, const int *sizes, int type, const Scalar &s)

Mat (const std::vector< int > &sizes, int type, const Scalar &s)

Mat (const Mat &m) 利用已有矩阵构建,只是复制了矩阵头,这辆指向同一数据,m=a.clone() 复制数据

Mat (int rows, int cols, int type, void *data, size_t step=AUTO_STEP)

Mat (Size size, int type, void *data, size_t step=AUTO_STEP)

Mat (int ndims, const int *sizes, int type, void *data, const size_t *steps=0)

Mat (const std::vector< int > &sizes, int type, void *data, const size_t *steps=0)

Mat (const Mat &m, const Range &rowRange, const Range &colRange=Range::all())
			再已有矩阵中截取一个范围,rowRange是一个Range变量 Range(2,5) 截取2至5行,colRange 截取列,默认全截
			这个截取的矩阵仍然与原Mat 共享数据,改一个另一个也会改变
			
Mat (const Mat &m, const Rect &roi)

Mat (const Mat &m, const Range *ranges)

Mat (const Mat &m, const std::vector< Range > &ranges)

Mat a = (Mat_<int>(3, 3) << 1 , 2 , 3 , 4 , 5, 6 , 7 , 8 , 9) 枚举赋值,用数据流赋值
Mat c=Mat_<int>(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 a = Mat::eye(3,3, CV_8UC1);   // 单位矩阵
Mat b = (Mat_<int>(1,3)<<1,2,3);   
Mat c = Mat::diag(b);   // 对角矩阵,参数是Mat类型的一维变量,用来存放堆笑元素的值
Mat d = Mat::ones(3,3, CV_8UC1);  // 全为1
Mat e = Mat::zeros(3,3, CV_8UC3);  // 全为0

利用数组进行赋值
float a[] = {1,2,3,4,5,1,2,3};
Mat b = Mat(2,2,CV_32FC2,a);   // 拆分方式根据矩阵的尺寸和通道数,数组不够填充别的值。多了就省略

Mat类的运算,看作普通的矩阵就行
e = a+b  矩阵加减法,两个矩阵类型相同
f = 2*a
h = d/2.  与常数的运算,保留矩阵的类型
g = a-1  逐元素减1 

j = a*b  矩阵的乘法  
double k = a.dot(b)   矩阵的内积,一个行向量和列向量的点乘
m = a.mul(b)    逐元素对应相乘

元素的读取

比如之前的 at方法对有每一位的读取,多通道的Mat矩阵存储的形式类似于三维数组,先存储第一个元素的每个通道的数据,然后第二个元素每个通道的数据,每一行就如此存储。找到每个元素的起始位置,就可以找到这个元素中每个通道的数据
在这里插入图片描述

  1. at<type>,at方法读取单通道矩阵元素。多通道就返回一个元素多个数据,使用Vec3b, Vec3s, Vec3d, Vec3f, Vec3i ,Vec3w,6种类型用于表示同意元素的3个通道数据。b uchar, s short , w ushort ,d double ,f float ,i int ,3表示三个通道,Vec2b表示二通道uchar类型
Mat a = (Mat_<uchar>(3,3) << 1,2,3,4,5,6,7,8,9);
int Value = (int) a.at<uchar>(0,0);   

Mat b(3,4,CV_8UC3,Scalar(0,0,1);
Vec3b vc3 = b.at<Vec3b>(0,0);   // 与矩阵类型对应
int first = (int)vc3.val[0];
int second = (int)vc3.val[1];
int third = (int)vc3.val[2];
  1. ptr指针读取元素
    矩阵中每一行的元素都是挨着存放,找到每一行元素的起始位置,读取不同位置的元素只要移动指针就可。
Mat b(3,4,CV_8UC3,Scalar(0,0,1);
for (int i=0; i<b.rows;i++) // 每一行
{
	uchar* ptr = b.ptr<uchar>(i);  // 定义指针,声明指向哪一行
	for (int j =0;j<b.clos*b.channels();j++) // 每一行存储的数据数量为列数与通道的乘积。
	{
		cout<<(int)ptr[j]<<endl;
	}
}
// 读取第2行数据中第三个数据,a.ptr<uchar>(1)[2]; 
  1. 通过迭代器访问元素
    Mat类也是一个容器,也有迭代器。
MatIterator_<uchar> it = a.begin<uchar>();   // 迭代器遍历类型
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;}
}
  1. 通过矩阵元素地址定位的方式读取元素
(int)(*(b.data+b.step[0] * row + b.step[1] * col + channel));

row 某个数据的行数,col列数,channel某个数据所在元素的通道。就是第row行,第col列,第channel通道的数据。也是通过首地址移动若干位读取数据。

图片的读取和显示

Mat cv::imread	(	const String & 	filename,
int 	flags = IMREAD_COLOR 
)	

读取图片,返回一个Mat,读取失败返回空矩阵,可以使用Mat.empty() 判断。Win中默认使用自带的编码器 libjpeg,libpng等来读取图像文件,linux就需要自己下载,能否读取文件看文件的内容而不是后缀,.jpg 改成.exe 一样可以读取,相反就不行。

flags参数设置读取样式,原样读取,灰度,彩图,多位数,缩小等。功能不冲突的前提下可以同时声明多个,用 | 隔开
在这里插入图片描述

void cv::namedWindow	(	const String & 	winname,
int 	flags = WINDOW_AUTOSIZE 
)	

显示图像时没有主动定义窗口,会自动生成。需要添加滑动条这类的需要手动创建。创建一个窗口变量,用于显示图像和滑动条。创建窗口时已经存在同名的,就不会再执行任何操作,创建的窗口使用 cv::destroyWindow() and cv::destroyAllWindows() 来释放资源,一个指定名字,一个释放全部。但实际上程序的退出会自动关闭应用程序的所有资源,可以不用主动释放。

flags声明窗口属性,设置是否可调大小,显示图像是否填充+
在这里插入图片描述

void cv::imshow	(	const String & 	winname,
InputArray 	mat 
)	

再指定窗口中显示图像,第二个参数是InputArray,是一个类型声明引用,用于输出参数的标记,就当作Mat就行。

VideoCapture ()
VideoCapture (const String &filename, int apiPreference=CAP_ANY)

apiPreference 读取视频时设置的属性,编码格式,是否使用OpenNI等。
可以读取处理视频流,也可以是图片序列或者视频的URL,图片序列将多个图像的名字统一为 "前缀+数字“ 的形式,比如 img_%02d.jpg ,可以读取文件夹下 img_00.jpg, img_01.jpg…就可以自动搜索合适的标志。 isOpened() 函数进行判断是否读取成。

通过VideoCapture 将视频文件加载到了这个类变量里,使用视频中的图片使用,使用>> 将图片导入Mat中,当VideoCapture里所有图像赋值给了Mat,后再次赋值就变成空矩阵了。使用 empty() 判断是否读取完毕。

还提供了查看视频属性的get() 函数,通过输入指定的标记获取视频属性。
在这里插入图片描述

#include <iostream>                       
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
	system("color F0");   // 摄像头就 video(0)
	VideoCapture video("D:\\opencv\\sources\\samples\\data\\Megamind.avi");
	if (video.isOpened())
	{
		cout << video.get(3) << video.get(4) << video.get(5) << video.get(7);
	}
	while (1)
	{
		Mat frame;
		video >> frame;
		if (frame.empty())
		{
			break;
		}
		imshow("img", frame);
		waitKey(1000 / video.get(CAP_PROP_FPS));
	}
	waitKey();
	return 0;
}

在这里插入图片描述

数据保存

bool cv::imwrite	(	const String & 	filename,
	InputArray 	img,
	const std::vector< int > & 	params = std::vector< int >() 
)	

将Mat保存为图片,

  • 16 位无符号 CV 16U 图像可以保存成 PNG JPEG TIFF 式文件
  • 32 位浮点 CV_32F 图像可以保存成 PFM TIFF OpenEXR Radiance HDR 格式文化
  • 4 通道 (Alpha 通道 图像可以保存成 PNG 式文件.

函数第三个参数在一般情况下不需要填写 ,保存成指定的文件格式只需要直接在第一个参数
后面更改文件后缀,但是当需要保存的 Mat 类矩阵中数据比较特殊 〈如 16 位深度数据 )就需要
要设置第 3个参数.
在这里插入图片描述

// 保存图像
#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;     // maximum unsigned char value 蓝色通道,255
			/*
			saturate_cast 是为了防止颜色溢出
			原理大致如下
			if(data<0) 
					data=0; 
			elseif(data>255) 
				data=255;
			*/
			bgra[1] = saturate_cast<uchar>((float(mat.cols - j)) / ((float)mat.cols));  //绿色
			bgra[2] = saturate_cast<uchar>((float(mat.rows - j)) / ((float)mat.rows)); //red
			bgra[3] = saturate_cast<uchar>(0.5 * (bgra[1] + bgra[2])); //alpha 通道
			
		}
	}
}


int main(int argc, char *argv[])
{
	system("color E");
	Mat mat(480, 640, CV_8UC4);  // 创建个四通道的mat
	AlphaMat(mat);

	vector<int> compression_params;
	compression_params.push_back(IMWRITE_PNG_COMPRESSION);  // imwrite 第三个参数设置的方式
	compression_params.push_back(9);     
	bool result = imwrite("alpha.png", mat, compression_params);

	if (!result)
	{
		cout << "error";
		return -1;
	}
	cout << "okk";
	return 0;
}

视频的保存

cv::VideoWriter::VideoWriter	(	const String & 	filename,
	int 	fourcc,
	double 	fps,
	Size 	frameSize,
	bool 	isColor = true 
)	

fourcc视频编码器,-1 自动搜索合适的。fps保存的视频帧率,可以实现二倍速,慢速等。frameSize保存视频的尺寸,但是要与图像的尺寸相同。。。isColor 是否保存彩色视频。保存视频就使用 << or write() 。isOpened() 判断是否创建成功,release() 关闭视频流。
在这里插入图片描述

#include <iostream>                       
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
 
int main(int argc, char *argv[])
{
	Mat img;
	VideoCapture video(0);
	if (!video.isOpened())
	{
		cout << "error";
		return -1;
	}
	video >> img;  // 读取视频
	if (img.empty())
	{
		cout << "error";
		return -1;
	}
	bool isColor = (img.type() == CV_8UC3); // 判断相机类型是否为彩色

	VideoWriter writer;
	int codec = VideoWriter::fourcc('M', 'J', 'P', 'G');  // 选择编码格式

	double fps = 25.;
	string filename = "a.avi";
	writer.open(filename, codec, fps, img.size(), isColor); // 创建保存视频文件的视频流

	if (!writer.isOpened())  // 视频流是否创建成功
	{
		cout << "error";
		return -1;
	}
	while (1)
	{
		if (!video.read(img)) // 摄像头断开,或视频读取完成,执行完毕
		{
			cout << "error";
			break;
		}
		writer.write(img);   // 写入视频流
		// writer<<img;
		imshow("img", img);
		char c = waitKey(50);   // ESC退出视频程序
		if (c == 27)
		{
			break;
		}
	}
	video.release();  // 程序退出自动释放资源,可以不用
	writer.release();
	return 0;
}

保存读取XML 和YMAL文件

程序中尺寸较小的Mat类矩阵,字符串,数组等数据也需要保存,这种数据通常保存为XML or YAML文件。
YAML文件通过”变量:数值“的方式表示每个数据,通过缩进表示数据之间的结构和隶属关系。扩展就是 .ymal .yml

cv::FileStorage::FileStorage	(	const String & 	filename,
int 	flags,
const String & 	encoding = String() 
)	

flags对文件进行的操作类型标志,encoding,编码格式 UTF-8 XML编码。声明打开的文件名和操作的类型。但是文件的操作需要已经存在的文件
在这里插入图片描述
默认的FileSotrage的构造函数没有参数,一般需要open() 函数单独声明,传入参数。isOpened() 判断是否打开文件,使用 << 写入数据(file<<“age”<<24;)(write() 变量名,变量值),表示某个变量是个数组使用”[] " 来表示,比如“ file<<“age”<<"["<<21<<22<<"]" “。隶属关系使用{} 表示,例如” file<<“age”<<”{"<<“qbx”<<20<<“lh”<<22<<"}" ".

>> 读取数据。通过变量名读取变量值 file[“x”] >> xRead ,读取名为x的变量。当某个变量有多个数据或自变量时,需要通过FileNode 节点类型和迭代器 FileNodeIterator 读取。


virtual bool cv::FileStorage::open	(	const String & 	filename,
		int 	flags,
		const String & 	encoding = String() 
)	

void cv::FileStorage::write	(	const String & 	name,
int 	val   // 经过函数重载,可以是很多其他格式
)	


#include <iostream>                       
#include <opencv2/opencv.hpp>
#include <string>
using namespace std;
using namespace cv;
 
int main(int argc, char *argv[])
{
	system("color E");
	string filename = "data.xml";
	// string filename = "data.yaml";

	FileStorage fwrite(filename, FileStorage::WRITE); // 写入模式,需要加前缀的

	Mat mat = Mat::eye(3, 3, CV_8U);
	fwrite.write("mat", mat); // 写入数据。使用write方法
	
	float x = 100;
	fwrite << "x" << x;   // 跟python的字典一样,使用流方法
	String str = "hi, hhh";  
	fwrite << "str" << str;
	fwrite << "number_array" << "[" << 4 << 5 << 6 << "]";   // 表示一个数组 [] 
	fwrite << "multi_nodes" << "{" << "month" << 8 << "day" << 28 << "year" //{} 表示隶属关系
		<< 2020 << "time" << "[" << 0 << 1 << 2 << 3 << "]" << "}";
	fwrite.release();  // 关闭文件

	FileStorage fread(filename, FileStorage::READ);  // 读取文件数据
	if (!fread.isOpened())
	{
		cout << "error";
		return -1;
	}
	float xRead;
	fread["x"] >> xRead;    // 通过变量名读取
	cout << "x=" << xRead << endl;
	string strRead;
	fread["str"] >> strRead;
	cout << "str=" << strRead << endl;

	FileNode fileNode = fread["number_array"];  // 含有多个数据或自变量的,通过节点类型和迭代器进行读取
	cout << "number_array = [";
	for (FileNodeIterator i = fileNode.begin(); i != fileNode.end(); i++)
	{
		float a;
		*i >> a;
		cout << a << " ";
	}
	cout << "]" << endl;

	Mat matRead;
	fread["mat"] >> matRead;
	cout << "mat = " << mat << endl;

	FileNode fileNodel = fread["multi_nodes"];
	int mouth = (int)fileNodel["mouth"];   // 从节点中读取自变量
	int day = (int)fileNodel["day"];
	int year = (int)fileNodel["year"];
	cout << "multi_nodes:" << endl
		<< " mouth=" << mouth << " day=" << day << " year=" << year;
	cout << " time = [";
	for (int i = 0; i < 4; i++)
	{
		int a = (int)fileNodel["time"][i];   // 另一种方法不用迭代器,就一级一级的找
		cout << a << " ";
	}
	cout << "]" << endl;

	fread.release();
	return 0;
}

在这里插入图片描述

小结

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值