OpenCV3学习笔记(3)- core组件

10 篇文章 7 订阅

详见《OpenCV3编程入门(毛星云、冷雪飞)》

3.1 OpenCV数据结构

基础图像容器Mat

Mat类:不必手动开辟空间, 不必再不需要时立即将空间释放。由两部分组成:矩阵头(包含矩阵尺寸、存储方法、存储地址等)、一个指向存储所有像素值的矩阵(根据所选存储方法的不同,矩阵可以是不同维数)。

计数机制:让每个Mat对象有自己的信息头,但共享同一矩阵,通过让矩阵指针指向同一地址实现。拷贝构造函数只复制信息头矩阵指针,而不复制矩阵。

创建一个ROI,只需要创建包含边界信息的信息头:

Mat D (A, Rect(10,10,100,100)); //使用矩形界定
Mat E = A(Range:all(), Range(1,3)); //用行和列界定

当矩阵属于多个Mat对象,当不再需要它时,由最后一个使用它的对象负责清理。无论什么时候复制一个Mat对象的信息头,都会增加矩阵的引用个数,反之当一个头被释放时,这个计数减一,当计数值为0矩阵被清理。

复制矩阵本身(并非信息头和矩阵指针):clone()或copyTo()

Mat F = A.clone();
Mat G;
A.copyTo(G);

改变F或G不会影响Mat信息头指向的矩阵

像素值存储:最小数据类型是char,占一个字节或8位,使用3个char型元素可以表示1600万种可能的颜色;float占4个字节,double占8个字节,可以给出更加精细的颜色分辨能力,但会增加内存。

创建Mat对象的七种方法
【1】使用Mat()构造函数:Mat M(2,2,CV_8UC3,Scalar(0,0,255)); //表示使用8位的unsigned char型,每个像素由三个元素组成三通道
定义行数、列数、存储元素的数据类型、每个矩阵点的通道数
CV_[位数][带符号与否][类型前缀]C[通道数]
【2】在C\C++中通过构造函数初始化:int sz[3]={2,2,2}; Mat L(3, sz, CV_8UC, Scalar::all(0))
指定维数,然后传递一个指向数组的指针,包含每个维度的尺寸
【3】为已存在的IplImage指针创建信息头:IplImage* img = cvLoadImage("1.jpg",1); Mat mtx(img); // 转换IplImage*->Mat
【4】利用Mat类的Create()成员函数:M.create(4,4,CV_8UC(2)) 不能为矩阵设初值,只是改变尺寸时重新为矩阵数据开辟内存
【5】采用Matlab式的初始化方式zeros(), ones(), eyes():

Mat E = Mat::eye(4,4,CV_64F);
Mat O = Mat::ones(2,2,CV_32F);
Mat Z = Mat::zeros(3,3,CV_8UC1);

【方法6】对小矩阵使用逗号分隔式初始化函数:Mat C = (Mat_<double>(3,3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
【方法7】使用clone()或copyTo()为已存在的对象创建新信息头:Mat RowClone = C.row(1).clone();
OpenCV中的格式化输出方法

Mat r = Mat(10, 3, CV_8UC3);
randu(r, Scalar::all(0), Scalar::all(255));

【1】OpenCV默认风格

cout << "r (OpenCV默认风格)=" << r << ";" << endl << endl;

在这里插入图片描述

【2】Python风格

cout << "r (Python风格)=" << format(r, Formatter::FMT_PYTHON) << ";" << endl << endl;

在这里插入图片描述

【3】逗号分割风格(Comma separated values, CSV)

cout << "r (逗号分隔风格)=" << format(r, Formatter::FMT_CSV) << ";" << endl << endl;

在这里插入图片描述

【4】Numpy风格

cout << "r (Numpy风格)=" << format(r,Formatter::FMT_NUMPY) << ";" << endl << endl;

在这里插入图片描述

【5】C语言风格

cout << "r (C语言风格)=" << format(r, Formatter::FMT_C) << ";" << endl << endl;

在这里插入图片描述
输出其他常用数据结构:
【1】定义和输出二维点:Point2f p(6,2); cout << "【二维点】p = " << endl;在这里插入图片描述
【2】定义和输出三维点:Point3f p3f(8,2,0); cout << "【三维点】p3f = " << p3f << ";\n" << endl;
在这里插入图片描述
【3】定义和输出基于Mat的std::vector

vector<float> v;
v.push_back(3);
v.push_back(5);
v.push_back(7);
cout << "【基于Mat的vector】shortvec=" << Mat(v) << ";\n" << endl;

在这里插入图片描述
【4】定义和输出std:;vector点

vector<Point2f> points(20);
for (size_t i=0; i < points.size(); ++i) points[i] = Point2f((float)(i*5), (float)(i % 7));

常用数据结构

点的表示:Point类:由图像坐标x和y指定的2D点

Point point;
point.x = 10; point.y = 8;

或者

Point point = Point(10, 8);

在OpenCV有如下定义:

typedef Point_<int> Point2i;
typedef Point2i Point;
typedef Point_<float> Point2f;

因此Point_, Point2i, Point互相等价,Point_, Point2f互相等价

颜色的表示:Scalar类:表示具有4个元素的数组,用于传递像素值如RGB颜色值。Scalar类的源头是Scalar_类,是Vec4x的一个变种,我们常用的Scalar其实是Scalar_,解释了很多函数的参数输入可以是Mat也可以是Scalar。

尺寸的表示:Size类:opencv\sources\modules\core\include\opencv2\core\core.hpp下对Size类的源代码,Size_, Size2i, Size三个类型名等价

typedef Size_<int> Size2i;
typedef Size2i Size;

使用频率最高的构造函数:Size_(_Tp _width, _Tp _height);

矩阵的表示:Rect类

Rect.Size(); //返回值为Size
Rect.area(); //返回矩形的面积
Rect.contains(Point); //判断点是否在矩形内
Rect.inside(Rect); //判断矩形是否在该矩形内
Rect.tl(); //返回左上角点的坐标
Rect.br(); //返回右下角点的坐标
Rect rect = rect1 & rect2; //求两个矩阵的交集
Rect rect = rect1 | rect2; //求两个矩阵的并集
Rect rectShift = rect + point; //让矩阵进行平移操作
Rect rectScale = rect + size; //让矩阵进行缩放操作

颜色空间转换:cvtColor()函数

void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)

code为颜色空间转换的标识符,dstCn为目标图像的通道数

cvtColor(srcImage, dstImage, COLOR_GRAY2BGR); //转换原始图像为灰度图

OpenCV2的CV_前缀的宏命名规范,被OpenCV3的COLOR_式的宏命名前缀取代。OpenCV默认的图片通道存储顺序是BGR。
在这里插入图片描述
在这里插入图片描述
其他知识点
1、Matx是一个轻量级的Mat,必须在使用前规定好大小,比如2×3的float类型的Matx可以声明为Matx23f
2、Vec是Matx的一个派生类,是一个一维的Matx,和vector类似

template<typename_Tp, int n> class Vec : public Matx<_Tp, n, 1>{...};
typedef Vec<uchar, 2> Vec2b;

3、Range类是为了使OpenCV更像MATLAB产生的,比如Range::all()是MATLAB里的符号,而Range(a,b)即MATLAB里的a:b
4、OpenCV中防止内存泄漏的函数有alignPtr, alignSize, allocate, deallocate, fastMalloc, fastFree等
5、<math.h>中一些方便的函数,如计算向量角度的fastAtan2,计算立方根的函数cubeRoot,向上取整函数cvCeil,向下取整函数cvFloor,四舍五入函数cvRound,判断自变量是否无穷大cvIsInf,判断自变量是否不是一个数cvIsNaN
6、显示文字相关的函数:getTextSize,cvInitFont, putText
7、作图相关函数:circle,clipLine,ellipse,ellipse2Poly,line,rectangle,polylines,类LineIterator
8、填充相关函数fillConvexPoly, fillPoly
9、RNG()函数为初始化随机数状态的生成器

3.2 基本图形绘制

ellipse:将椭圆画到图像img上,中心点为point,范围为size,角度为angle

ellipse(img, Point(), Size(), angle, 0, 360, Scalar(255,129,0),thickness, lineType);

circle:将圆画到图像img上,中心点为center,半径为WINDOR_WIDTH/32

circle(img, center, WINDOW_WIDTH/32, Scalar(0,0,255), thickness, lineType);

fillPoly:将多边形画到图像img上,多边形顶点集为ppt,要绘制的多边形顶点数目为npt,要绘制的多边形数量为1

fillPoly(img, ppt, npt, 1, Scalar(255,255,255), lineType);

line:在图像img上画一条从点start到点end的直线段

line(img, start, end, Scalar(0,0,0), thickness, lineType);

3.3 访问图像中的像素

RGB颜色模型的矩阵排列,使用isContinuous()可以判断矩阵是否连续存储:
在这里插入图片描述
颜色空间缩减:将现有颜色空间值除以某个输入值来获得较少的颜色数,如unchar类型的三通道图像有256×256×256个不同的值,若将0-9范围的像素值为0,10-19范围的像素值为10,20-29范围的像素值为20,可以将颜色取值降低为26×26×26种情况。
C++中int类型除法会自动截余,可以把256种计算好的结果提前存放在table中,不需计算直接从table中取结果即可,table[i]存放的是值为i的像素减小颜色空间的结果。

int divideWith=10;
uchar table[256];
for (int i = 0; i < 256; ++i) table[i]=divideWith * (i/divideWith); //遍历图像每一个像素,应用缩减公式

p[j]=table[p[j]];

LUT函数:Look Up Table,用于批量进行图像元素查找、扫描与操作图像。原型为operationsOnArrays:LUT()<lut>

//建立一个mat型用于查表
Mat lookUpTable(1,256,CV_8U);
uchar* p = lookUpTable.data;
for (int i=0; i < 256; i++) p[i]=table[i];

//调用函数,I是输入,J是输出
for( int i = 0; i < times; ++i) LUT(I, lookUpTable, J);

计时函数:getTickCount()返回CPU自某个时间以来走过的时钟周期数, getTickFrequency()返回CPU一秒钟所走的时钟周期数

double time0 = static_cast<double>(getTickCount()); //记录起始时间
time0 = ((double)getTickCount() - time0)/getTickFrequency();
cout << "此方法运行时间为:" << time0 <<“秒”<< endl; //输出运行时间

访问图像像素的三类方法指针访问C操作符[]迭代器iterator动态地址计算。三种访问方式debug模式下访问速度差异明显,release模式下不明显。

void colorReduce(Mat& inputImage, Mat& outputImage, int div);

【方法一】用指针访问像素

void colorReduce(Mat& inputImage, Mat& outputImage, int div){
	outputImage = inputImage.clone();
	int rowNumber = outputImage.rows; //行数
	int colNumber = outputImage.cols*outputImage.channels();
	for (int i = 0; i < rowNumber; i++){
		unchar* data = outputImage.ptr<uchar>(i);  //获取第i行的首地址,ptr为Mat类为简化指针运算提供的函数
		for (int j = 0; j < colNumber; j++){
			data[j]=data[j]/div*div + div/2;
		}
	}
}

【方法二】用迭代器操作像素

void colorReduce(Mat& inputImage, Mat& outputImage, int div){
	outputImage = inputImage.clone();
	//获取迭代器
	Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>(); //初始位置迭代器
	Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>(); //终止位置迭代器
	//获取彩色图像像素
	for(; it != itend; ++it){
		(*it)[0] = (*it)[0]/div*div + div/2;
		(*it)[1] = (*it)[1]/div*div + div/2;
		(*it)[2] = (*it)[2]/div*div + div/2;
	}
}

【方法三】动态地址计算,配合at

void colorReduce(Mat& inputImage, Mat& outputImage, int div){
	outputImage = inputImage.clone();
	int rowNumber = outputImage.rows;
	int colNumber = outputImage.cols;
	for(int i = 0; i < rowNumber; i++){
		for ( int j = 0; j < colNumber; j++){
			outputImage.at<Vec3b>(i,j)[0] = outputImage.at<Vec3b>(i,j)[0]/div*div + div/2;
			outputImage.at<Vec3b>(i,j)[1] = outputImage.at<Vec3b>(i,j)[1]/div*div + div/2;
			outputImage.at<Vec3b>(i,j)[2] = outputImage.at<Vec3b>(i,j)[2]/div*div + div/2;
		}
	}
}

需注意Mat的成员函数at(int y, int x)要确保指定的数据类型要和矩阵中的数据类型相符合,at方法本身不会对任何数据类型进行转换。
Vec3b:对于一个包含彩色图像的Mat,会返回一个由三个8位数组成的向量,此类型向量为Vec3b,即三个unsigned char组成的向量。

3.4 ROI区域图像叠加 & 图像混合

定义ROI的两种方法:1、使用Rect;2、指定行或列的范围Range

Mat imageROI;
imageROI = image(Rect(500,250,logo.cols,logo.rows));
imageROI = image(Range(250, 250+logoImage.rows), Range(200, 200+logoImage.cols));

线性混合操作:addWeighted()

void(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);

gamma为一个加到权重总和上的标量值;dtype为输出阵列的可选深度,默认值为-1

3.5 分离颜色通道、多通道图像混合

通道分离:split()

void split(cosnt Mat& src, Mat* mvbegin);
void split(InputArray m, OutputArrayOfArrays mv);

参数1 InputArray类型的m或const Mat&类型的src,填需要分离的多通道数组
参数2 OutputArrayOfArrays类型的mv,填函数的输出数组或者输出的vector容器

Mat srcImage;
Mat imageROI;
vector<Mat> channels;
Mat imageBlueChannel;
Mat imageGreenChannel;
Mat imageRedChannel;
srcImage = cv::imread("dota.jpg");
split(srcImage, channels);  //把一个3通道图像转换成3个单通道图像
imageBlueChannel = channels.at(0);  //返回一个引用到指定数组元素
imageGreenChannel = channels.at(1);
imageRedChannel = channels.at(2);

通道合并:merge()

void merge(const Mat* mv, size_tcount, OutputArray dst);
void merge(InputArrayOfArrays mv, OutputArray dst);

参数1 mv,填需要被合并的输入矩阵或vector容器阵列,所有矩阵必须有一样的尺寸和深度
参数2 count,当mv为一个空白的C数组时,代表输入矩阵的个数,必须大于1
数3 dst,和mv[0]有一样的尺寸和深度,通道数量是矩阵阵列中的通道总数

3.6 图像对比度、亮度值调整

都属于点操作,仅根据输入像素值来计算相应的输出像素值。

# include <opencv2/core/core.hpp>
# include <opencv2/highgui/highgui.hpp>
# include <iostream>
using namespace std;
using namespace cv;
static void on_ContrastAndBright(int, void*);
static void ShowHelpText();
int g_nContrastValue;  //对比度值
int g_nBrightValue;  //亮度值
Mat g_srcImage, g_dstImage;
int main(){
	g_srcImage = imread("1.jpg");
	if(!g_srcImage.data){printf("读取图片错误,确认目录下是否有imread函数指定图片存在");return false;}
	g_dstImage = Mat::zeros(g_srcImage.size(), g_srcImage.type());
	g_nContrastValue=80;
	g_nBrightValue=80;
	nameWindow("【效果图窗口】",1);
	//创建滑动条
	creatTrackbar("对比度:","【效果图窗口】", &g_nContrastValue, 300, on_ContrastAndBright);
	creatTrackbar("亮度:","【效果图窗口】", &g_nBrightValue, 200, on_ContrastAndBright);
	//回调函数初始化
	on_ContrastAndBright(g_nContrastValue,0);
	on_ContrastAndBright(g_nBrightValue,0);
	while(char(waitKey(1))!='q'){}
	return 0;

	static void on_ContrastAndBright(int, void *){
		nameWindow("【原始图窗口】",1);
		for(int y =0; y < g_srcImage.rows; y++){
			for(int x=-; x < g_srcImage.cols; x++){
				for(int c = 0; c <3; c++){
					g_dstImage.at<Vec3b>(y,x)[c]=saturate_cast<uchar>((g_nContrastValue*0.01)*(g_srcImage.at<Vec3b>(y,x)[c]) + g_nBrightValue);
				}
			}
		}
	}
	imshow("原始图窗口",g_srcImage);
	imshow("效果图窗口",g_dstImage);

}

saturate_cast模板函数:用于溢出保护,超出像素取值范围(溢出)或非整数(如果是浮点数)

if (data < 0) data = 0;
else if (data > 255) data = 255;

*0.01:轨迹条取值都为整数,因此我们设置成0~80之间的整型,最后乘0.01,代表0-0.8的对比度范围

3.7 离散傅里叶变换

离散傅里叶变换DFT:将时域信号的采样变换为在离散时间傅里叶i变换(DTFT)频域的采样,把它分解成正弦和余弦两部分。显示傅里叶变换之后的结果需要使用实数图像加虚数图像,或者幅度图像加相位图象的形式。实际图像处理中仅使用幅度图像。

离散傅里叶变换函数:dtf()

void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);

参数1 InputArray类型的src,输入矩阵可以为实数或虚数
参数2 OutputArray类型的dst,函数运算结果,尺寸和类型取决于标识符即第三个参数flags
参数3 int类型的flags,转换的标识符,默认值0
在这里插入图片描述
参数4 int类型的nonzeroRows,有默认值0。当非0时函数会假设只有输入矩阵的第一个非0行包含非0元素,或只有输出矩阵的第一个非0行包含非0元素,这样函数可以进行高效处理来节省时间开销。

例:计算两个二维实矩阵卷积

void convolveDFT(InputArray A, InputArray B, OutputArray C){
	//初始化输出矩阵
	C.create(abs(A.rows - B.rows)+1, abs(A.cols - B.cols) + 1, A.type());
	Size dftSize;
	//计算DFT变换尺寸
	dftSize.width = getOptimalDFTSize(A.cols + B.cols - 1);
	dftSize.height = getOptimalDFTSzie(A.rows + B.rows - 1);
	//分配临时缓冲区并初始化置0
	Mat tempA(dftSize, A.type(), Scalar::all(0));
	Mat tempB(dftSize, B.type(), Scalar::all(0));
	//分别复制A和B到tempA和tempB的左上角
	Mat roiA(tempA, Rect(0,0,A.cols, A.rows));
	A.copyTo(roiA);
	Mat roiB(tempB, Rect(0,0,B.cols, B.rows));
	B.copyTo(roiB);
	//就地操作进行傅里叶变化,将nonzeroRows参数置为非0来更快处理
	dft(tempA, tempA, 0, A.rows);
	dft(tempB, tempB, 0, B.rows);
	//两个傅里叶频谱每个元素相乘,结果存放于tempA中
	mulSpectrums(tempA,tempB,tempA);
	//将结果变换为频域,尽管结果行都非0,我们只需使用C.rows的第一行
	dft(tempA, tempA, DFT_INVERSE + DFT_SCALE, C.rows);
	//将结果复制到C中
	tempA(Rect(0,0,C.cols, C.rows)).copyTo(C);
}

返回DFT最优尺寸大小:getOptimalDFTSize()

int getOptimalDFTSize(int vecsize)    //为提高离散傅里叶变换的运行速度需要扩充图像

vecsize为图片的rows, cols
扩充图像边界:copyMakeBorder()

void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar& value = Scalar())

参数1 InputArray类型的src,源图像
参数2 OutputArray类型的dst,存放函数调用后的结果,size应该为Size(src.cols+left+right, src.rows+top+bottom)
参数3 int类型的top, bottom, left, right为源图像四个方向上扩充多少像素
参数4 borderType类型,边界类型,常取值BORDER_CONSTANT
参数5 const Scalar&类型的value,有默认值Scalar()。当borderType取值为BORDER_CONSTANT时,这个参数表示边界值。
计算二维矢量的幅值:magnitude()

void magnitude(InputArray x, InputArray y, OutputArray magnitude)

参数1 InputArray类型的x,表示矢量的浮点型X坐标值,也就是实部
参数2 InputArray类型的y,表示矢量的浮点型Y坐标值,也就是虚部
参数3 OutputArray类型的magnitude,输出的幅值,和第一个参数x有同样的尺寸和类型
计算自然对数:log()

void log(InputArray src, OutputArray dst)

参数1 输入图像
参数2 得到的对数值
矩阵归一化:normalize()函数

void normalize(InputArray src, OutputArray dst, double alpha = 1, double beta = 0, int norm_type=NORM_L2, int dtype = -1, InputArray mask=noArray())

参数1 InputArray类型的src,源图像
参数2 OutputArray类型的dst,函数调用后的运算结果
参数3 double类型的alpha,归一后的最大值,默认值1
参数4 double类型的beta,归一后的最小值,默认值0
参数5 int类型的norm_type,归一化类型,有NORM_INF, NORM_L1, NORM_L2和NORM_MINMAX等,默认NORM_L2
参数6 int类型的dtype,默认值-1。当此参数为负时,输出矩阵和src有同样的类型,否则它和src有同样的通道数,且此时图像深度为CV_MAT_DEPTH(dtype)
参数7 InputArray类型的mask,可选的操作掩膜,有默认值noArray()

例:计算和显示傅里叶变换后的图像,以输入图像为单通道灰度图像为例

#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;

int main(){
	//【1】灰度图读取图片并显示
	Mat srcImage = imread("1.jpg",0);
	if (!srcImage.data) {printf("读取图片错误"); return false;}
	imshow("原始图像",srcImage);
	showHelpText();
	//【2】将输入图像延扩到最佳的尺寸,边界用0填充
	int m = getOptimalDFTSize(srcImage.rows);
	int n = getOptimalDFTSize(srcImage.cols);
	//将添加的像素初始化为0
	Mat padded;
	copyMakeBorder(srcImage, padded, 0, m-srcImage.rows, 0, n-srcImage.cols, BORDER_CONSTANT, Scalar::all(0));
	//【3】为傅里叶变换的结果分配存储空间
	//将planes数组组合合并成一个多通道数组complexI,两通道(实部和虚部)
	Mat planes[] = {Mat_<float>(padded), Mat::zeros(padded.size(),CV_32F)};
	Mat complexI;
	merge(planes, 2, complexI);
	//【4】就地离散傅里叶变换
	dft(complexI,complexI);
	//【5】将复数值转换为幅值
	split(complexI, planes);
	magnitude(planes[0], planes[1], planes[0]);
	Mat magnititudeImage = planes[0];
	//【6】进行对数尺度缩放
	magnititudeImage += Scalar::all(1);
	log(magnitudeImage, magnitudeImage); //求自然对数
	//【7】剪切和重分布幅度图像象限
	//若有奇数行或奇数列,进行频谱裁剪
	magnitudeImage = magnitudeImage(Rect(0,0,magnitudeImage.cols&-2, magnitudeImage.rows & -2));
	//重新排列傅里叶图像中的象限,使得原点位于图像中心
	int cx = magnitudeImage.cols/2;
	int cy = magnitudeImage.rows/2;
	Mat q0(magnitudeImage, Rect(0,0,cx,cy)); //ROI区域的左上
	Mat q1(magnitudeImage, Rect(cx,0,cx,cy)); //ROI区域的右上
	Mat q2(magnitudeImage, Rect(0, cy, cx, cy)); //ROI区域的左下
	Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); //ROI区域的右下
	//交换象限(左上与右下进行交换)
	Mat tmp;
	q0.copyTo(tmp);
	q3.copyTo(q0);
	//交换象限(右上与左下交换)
	q1.copyTo(temp);
	q2.copyTo(q1);
	tmp.copyTo(q2);
	//【8】归一化
	normalize(magnitudeImage, magnitudeImage, 0, 1, NROM_MINMAX);
	//【9】显示效果图
	imshow("频谱赋值", magnitudeImage);
	waitKey();
	return 0;
	
}

3.8 输入输出XML和YAML文件

XML:即eXtensible Markup Language,即可扩展标识语言。XML是一种元标记语言,使开发者可以根据自身需要定义自己的标记,比如定义,任何满足XML命名规则的名称都可以标记,向不同的应用程序打开了大门。还是一种语义/结构化语言,描述了文档的结构和语义。
YAML:即YAML Ain’t a Markup Language的递回缩写,强调这种语言以数据为中心,而不是以置标语言为重点。YAML是一个可读性高,用来表达资料序列的格式,比XML更敏捷。参考了XML, C语言,Python, Perl以及电子邮件格式RFC2822。

FileStorage类操作文件的使用引导

FileStorage是OpenCV中XML和YAML文件的存储类,封装了所有相关的信息,是OpenCV从文件中读取数据或向文件中写数据时必须的一个类。此类构造函数FileStorage::FileStorage有两个重载:

FileStorage::FileStorage()
FileStorage::FileStorage(const string& source, int flags, const string& encoding=string())

使用如下过程写入/读取数据到XML或YAML文件中:
1、实例化一个FileStorage类对象,用默认带参数的构造函数完成初始化,或用FileStorage::open()成员函数辅助初始化
2、使用流操作符<<进行文件写入操作,或>>进行文件读取操作
3、使用FileStorage::release()函数析构掉FileStorage类对象,同时关闭文件。
【第一步】XML, YAML文件的打开
准备文件操作,对于第一种不带参数的构造函数,可以使用其成员函数FileStorage::open进行数据的写操作

FileStorage fs;
fs.open("abc.xml", FileStorage::WRITE);

对于带参数的构造函数,写操作范例如下

FileStorage fs("abc.xml", FileStorage::WRITE);

准备文件操作,第一种方式

FileStorage fs("abc.xml",FileStorage::READ);

第二种方式

FileStorage fs;
fs.open("abc.xml", FileStorage::READ);

【第二步】进行文件读写操作
文本和数字的输入和输出:

//写入文件
fs << "iterationNr" << 100;
//读取文件
int itNr;
fs["iterationNr"] >> itNr;
itNr = (int)fs["iterationNr"];

OpenCV数据结构的输入和输出:

//数据结构的初始化
Mat R = Mat_<uchar>::eye(3,3);
Mat T = Mat_<double>::zeros(3,1);
//向Mat中写入数据
fs << "R" << R;
fs << "T" << T;
//从Mat中读取数据
fs["R"] >> R;
FS["T"] >> T;

【第三步】 vector(arrays)和maps的输入和输出
vector: 注意在第一个元素前加上"[",在最后一个元素前加上“]”

fs << "strings" << "["; //开始读入string文本序列
fs << "image.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]"; //关闭序列

map: 使用"{“和”}"

fs << "Mapping"; //开始读入Mapping文本
fs << "{" << "One" << 1;
fs << "Two" << 2 << "}";

读取这些结构时会用到FileNode和FileNodeIterator数据结构,对于FileStorage类的"[“和”]"操作符会返回FileNode数据类型,对于一连串的node可以使用FileNodeIterator结构

FileNode n = fs["strings"]; //读取字符串序列以得到节点
if (n.type()!=FileNode::SEQ){
cerr << "发生错误!字符串不是一个序列" << endl;
return 1;
}
FileNodeIterator it = n.begin(), it_end = n.end(); //遍历节点
for(;it !=it_end; ++it);
cout << (string)*it <<endl;

【第四步】文件关闭
文件关闭会在FileStorage类销毁时自动进行,也可显示调用析构函数FileStorage::release()实现

fs.release();

示例

XML和YAML文件的写入:

#include "opencv2/opencv.hpp"
#include <time.h>
using namespace cv;
int main(){
	//初始化
	FileStorage fs("test.yaml", FileStorage::WRITE);
	//开始文件写入
	fs << "frameCount" << 5;
	time_t rawtime; time(&rawtime);
	fs << "calibrationDate" << asctime(localtime(&rawtime));
	Mat cameraMatrix = (Mat_<double>(3,3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
	Mat distCoeffs = (Mat_<double>(5,1) << 0.1, 0.01, -0.001, 0, 0);
	fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
	fs << "feature" << "[";
	for (int i = 0; i < 3; i++){
		int x = rand() % 640;
		int y = rand() % 480;
		uchar lbp = rand() % 256;
		fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
		for (int j = 0; j < 8; j++) fs << ((lbp >> j) & 1);
		fs << "]" << "}";
	}
	fs << "]";
	fs.release();
	printf("文件读写完毕,请在工程目录下查看生成的文件");
	getchar();
	return 0;
}

工程下生成的test.yaml
在这里插入图片描述
XML和YAML文件的读取

#include "opencv2/opencv.hpp"
#include <time.h>
using namespace cv;
using namespace std;
int main(){
	system("color 6F");
	//初始化
	FileStorage fs2("test.yaml", FileStorage::READ);
	//第一种方法,对FileNode操作
	int frameCount = (int)fs2["frameCount"];
	//第二种方法,使用FileNode运算符
	fs2["calibrationDate"] >> data;
	Mat cameraMatrix2, distCoeffs2;
	fs2["cameraMatrix"] >> cameraMatrix2;
	fs2["distCoeffs"] >> distCoeffs2;
	cout << "frameCount:" << frameCount << endl
		<< "calibration date:" << date << endl
		<< "camera matrix" << cameraMatrix2 << endl
		<< "distortion coeffs:" << distCoeffs2 << endl;
	
	FileNode features = fs2["features"];
	FileNodeIterator it = features.begin(), it_end = features.end();
	int idx = 0;
	std::vector<uchar> lbpval;
	//使用FileNodeIterator遍历序列
	for(; it != it_end; ++it, idx++){
		cout << "feature #" << idx << ":";
		cout << "x=" << (int)(*it)["x"] << ", y=" << (int)(*it)["y"] << ", lbp:("
		//也可以使用filenode >> std::vector操作符来读取数值阵列
		(*it)["lbp"] >> lbpval;
		for (int i=0; i < (int)lbpval.size(); i++) cout << " " << (int)lbpval[i];
		cout << ")" << endl;
	}
	fs2.release();
	printf("\n文件读取完毕,输入任意键结束程序");
	getchar();
	return 0;
	
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值