core组件进阶
- 1.访问图像中的像素
- 1.1 图像在内存之中的存储方式
- 1.2 颜色空间缩减
- 1.3 LUT函数:Look up table
- 1.4 计时函数
- 2. ROI区域图像叠加&图像混合
- 2.1 感兴趣区域:ROI
- 2.2 ROI案例
- 2.2 线性混合操作
- 2.3 计算数组加权和:addWeighted()函数
- 3 分离颜色通道、多通道图像混合
- 3.1 通道分离:split()函数
- 3.2 通道合并:merge()函数
- 3.3 案例:多通道图像混合
- 4.图像对比度、亮度值调整
- 4.1 亮度和对比度综合案例
- 5.离散傅里叶变换
- 5.1 离散傅里叶变换的原理
- 5.2 dft()函数详解
- 5.3 返回DFT最优尺寸大小:getOptimalDFTSize()函数
- 5.4 扩充图像边界:copyMakeBorder()函数
- 5.5 计算二维矢量的幅值:magnitude()函数
- 5.6 计算自然对数:log()函数
- 5.7 矩阵归一化:normalize()函数
- 6.输入输出XML和YAML文件
- 6.1 XML和YAML文件简介
- 6.2 FileStorage类操作文件的使用引导
- 6.3 示例程序:XML和YAML文件的写入
1.访问图像中的像素
1.1 图像在内存之中的存储方式
矩阵的大小取决于使用的颜色系统。更准确地说,它取决于使用的通道数量。如果是灰度图像,矩阵如图:
对于多通道图像,列中包含与通道数相同数量的子列。例如,在BGR色彩系统的情况下:
1.2 颜色空间缩减
若矩阵元素存储的是单通道像素,那么像素可有256个不同的值。但若是三通道,这种存储格式的颜色就有1600多万种。用如此之多的颜色来进行处理,可能对我们的算法性能造成严重的影响。
颜色空间缩减(color space reduciton)便可以派上用场了,它在很多应用中可以大大降低运算复杂度。
颜色空间缩减的做法是:
将现有的颜色空间值除以某个输入值,以获得较少的颜色数。也就是“做减法”,比如颜色值从0到9可取为新值0,10到19可取为10。
这样操作将颜色取值降低为262626种情况。这个操作可以用一个简单的公式来实现。因为C++中int类型除法操作会自动截余。
例如:Iold=14
Inew=(Iold/10)*10=(14/10)*10=1*10=10
1.3 LUT函数:Look up table
1.4 计时函数
可以利用两个简单的计时函数——getTickCount()和getTickFrequency()。
- getTickCount()函数返回CPU自某个时间(如启动电脑)以来走过的时钟周期数。
- getTickFrequency()函数返回CPU一秒钟所走的时钟周期数。这样我们就可以轻松地以秒为单位进行计时。
double time0=static_cast<double>(getTickCount());//记录起始时间
time0=((double)getTickCount()-time0)/getTickFrequency();
cout<<"运行时间为:"<<time0<<"秒"<<endl;
2. ROI区域图像叠加&图像混合
2.1 感兴趣区域:ROI
在图像处理领域,我们常常需要设置感兴趣区域(ROI,region of interest)来专注或者简化工作过程。也就是从图像中选择的一个图像区域,这个区域是图像分析所关键的重点。我们圈定这个区域,以便进行进一步处理。而且,使用ROI指定想读入的目标,可以减少处理时间,增加精度,给图像处理带来不小的便利。
定义ROI区域有两种方法:
- 第一种是使用表示矩形区域的Rect。它指定矩形的左上角坐标和矩形的长宽,以定义一个矩形区域。
Mat imageROI;
imageROI = image(Rect(500,250,logo.cols,logo.rows));
- 第二种是指定感兴趣的行或列的范围(Range)。Range是指从起始索引到终止索引(不包括终止索引)的一段连续序列。cRange可以用来定义Range。如果使用Range来定义ROI,那么前例中定义ROI的代码可以重写为
imageROI = image(Range(250,250+logo.rows),Range(500,500+logo.cols));
2.2 ROI案例
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//利用ROI将一个图像加到另一个图像指定位置
int main() {
Mat srcImage1 = imread("dota_pa.jpg");
Mat srcImage2 = imread("dota_logo.jpg");
if (!srcImage1.data) {
cout << "srcImage1读取错误" << endl;
return -1;
}
if (!srcImage2.data) {
cout << "srcImage2读取错误" << endl;
return -1;
}
//定义一个Mat类型并给其设定ROI区域
Mat imageROI = srcImage1(Rect(200, 250, srcImage2.cols, srcImage2.rows));
//加载掩码
Mat mask = imread("dota_logo.jpg", 0);
srcImage2.copyTo(imageROI, mask);
//显示结果
namedWindow("利用ROI实现图像叠加窗口");
imshow("利用ROI实现图像叠加窗口", srcImage1);
waitKey(0);
return 0;
}
2.2 线性混合操作
线性混合操作是一种典型的二元(两个输入)的像素操作,它的理论公式:
g
(
x
)
=
(
1
−
a
)
∗
f
1
(
x
)
+
a
∗
f
2
(
x
)
g(x)=(1-a)*f1(x)+a*f2(x)
g(x)=(1−a)∗f1(x)+a∗f2(x)
我们通过在范围0到1之间改变alpha的值,来对两幅图像或者两个视频产生时间上的画面重叠。就像幻灯片放映和电影制作中的那样,也就是在幻灯片翻页时设置过度叠加效果。
实现方面主要运用了OpenCV中addWeighted函数
2.3 计算数组加权和:addWeighted()函数
void addWeighted(InputArray src1, double alpha, InputArray src2, double deta, double gamma, outputArray dst, int dtype=-1)
- 第一个参数:表示需要加权的第一个数组
- 第二个参数:表示第一个数组的权重
- 第三个参数:表示需要加权的第二个数组
- 第四个参数:表示第二个数组的权重
- 第五个参数:gamma,一个加到权重总和上的标量值。
- 第六个参数:输出的数组,他和输入的两个数组拥有相同 的尺寸和通道数。
- 第七个参数:int类型的dtype,输出阵列的可选深度,有默认值-1。当两个输入数组具有相同的深度是,这个参数设置为-1。
下面属数学公式表示:用addWeighted()计算一下两个数组的加权和,得到结果输出给第四个参数,也就是adWeighted函数的作用的矩阵表达式。
d
s
t
=
s
r
c
1
[
I
]
∗
a
l
p
h
a
+
s
r
c
2
[
I
]
∗
b
e
t
a
+
g
a
m
m
a
dst=src1[I]*alpha+src2[I]*beta+gamma
dst=src1[I]∗alpha+src2[I]∗beta+gamma
addWeighted()案例
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//利用addWeighted()实现图像线性混合
int main() {
double alphaValue = 0.5;
double betaValue;
Mat srcImage1, srcImage2, dstImage;
srcImage1 = imread("mogu.jpg");
srcImage2 = imread("rain.jpg");
if (!srcImage1.data) {
cout << "srcImage1读取错误" << endl;
return -1;
}
if (!srcImage2.data) {
cout << "srcImage2读取失败" << endl;
return -1;
}
//图像混合加权操作
betaValue = (1.0 - alphaValue);
addWeighted(srcImage1, alphaValue, srcImage2, betaValue, 0.0, dstImage);
namedWindow("原图", 0);
imshow("原图", srcImage1);
namedWindow("效果图", 0);
imshow("效果图", dstImage);
waitKey();
return 0;
}
3 分离颜色通道、多通道图像混合
3.1 通道分离:split()函数
split函数用于将一个多通道数组分离成几个单通道数组。
void split(InputArray m, OutputArrayOfArrays mv)
- 第一个参数:填我们需要进行分离的多通道数组
- 第二个参数:填函数的输出数组或者输出的vector容器
split函数分割多通道数组转换成独立的单通道数组,公式如下:
m
v
[
c
]
(
I
)
=
s
r
c
(
I
)
mv[c](I)=src(I)
mv[c](I)=src(I)
3.2 通道合并:merge()函数
merge()函数是split()函数的逆向操作——将多个数组合并成一个多通道的数组。
void merge(InputArrayOfArrays mv,OutputArray dst)
- 第一个参数:需要被合并的输入矩阵或vector容器的阵列,这个mv参数中所有的矩阵必须有着一样的尺寸和深度。
- 第二个参数:输出矩阵,和mv[0]有一样的尺寸和深度,并且通道数的是矩阵阵列中的通道数总和。
一般用at()函数对某个通道进行存取:channels.at(0)
3.3 案例:多通道图像混合
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
bool MultiChannelBlending();
int main()
{
system("color 9F");
if (MultiChannelBlending())
{
cout << endl << "\n运行成功,得出了需要的图像~! ";
}
waitKey(0);
return 0;
}
//-----------------------------【MultiChannelBlending( )函数】--------------------------------
// 描述:多通道混合的实现函数
bool MultiChannelBlending()
{
//【0】定义相关变量
Mat srcImage;
Mat logoImage;
vector<Mat> channels;
Mat imageBlueChannel;
//=================【蓝色通道部分】=================
// 描述:多通道混合-蓝色分量部分
//============================================
// 【1】读入图片
logoImage = imread("dota_logo.jpg", 0);
srcImage = imread("dota.jpg");
if (!logoImage.data) { printf("Oh,no,读取logoImage错误~! \n"); return false; }
if (!srcImage.data) { printf("Oh,no,读取srcImage错误~! \n"); return false; }
//【2】把一个3通道图像转换成3个单通道图像
split(srcImage, channels);//分离色彩通道
//【3】将原图的蓝色通道引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
imageBlueChannel = channels.at(0);
//【4】将原图的蓝色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageBlueChannel中
addWeighted(imageBlueChannel(Rect(500, 250, logoImage.cols, logoImage.rows)), 1.0,
logoImage, 0.5, 0, imageBlueChannel(Rect(500, 250, logoImage.cols, logoImage.rows)));
//【5】将三个单通道重新合并成一个三通道
merge(channels, srcImage);
//【6】显示效果图
namedWindow(" <1>游戏原画+logo蓝色通道",0);
imshow(" <1>游戏原画+logo蓝色通道", srcImage);
//=================【绿色通道部分】=================
// 描述:多通道混合-绿色分量部分
//============================================
//【0】定义相关变量
Mat imageGreenChannel;
//【1】重新读入图片
logoImage = imread("dota_logo.jpg", 0);
srcImage = imread("dota.jpg");
if (!logoImage.data) { printf("读取logoImage错误~! \n"); return false; }
if (!srcImage.data) { printf("读取srcImage错误~! \n"); return false; }
//【2】将一个三通道图像转换成三个单通道图像
split(srcImage, channels);//分离色彩通道
//【3】将原图的绿色通道的引用返回给imageGreenChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
imageGreenChannel = channels.at(1);
//【4】将原图的绿色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageGreenChannel中
addWeighted(imageGreenChannel(Rect(500, 250, logoImage.cols, logoImage.rows)), 1.0,
logoImage, 0.5, 0., imageGreenChannel(Rect(500, 250, logoImage.cols, logoImage.rows)));
//【5】将三个独立的单通道重新合并成一个三通道
merge(channels, srcImage);
//【6】显示效果图
namedWindow("<2>游戏原画+logo绿色通道",0);
imshow("<2>游戏原画+logo绿色通道", srcImage);
//=================【红色通道部分】=================
// 描述:多通道混合-红色分量部分
//============================================
//【0】定义相关变量
Mat imageRedChannel;
//【1】重新读入图片
logoImage = imread("dota_logo.jpg", 0);
srcImage = imread("dota.jpg");
if (!logoImage.data) { printf("Oh,no,读取logoImage错误~! \n"); return false; }
if (!srcImage.data) { printf("Oh,no,读取srcImage错误~! \n"); return false; }
//【2】将一个三通道图像转换成三个单通道图像
split(srcImage, channels);//分离色彩通道
//【3】将原图的红色通道引用返回给imageRedChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
imageRedChannel = channels.at(2);
//【4】将原图的红色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageRedChannel中
addWeighted(imageRedChannel(Rect(500, 250, logoImage.cols, logoImage.rows)), 1.0,
logoImage, 0.5, 0., imageRedChannel(Rect(500, 250, logoImage.cols, logoImage.rows)));
//【5】将三个独立的单通道重新合并成一个三通道
merge(channels, srcImage);
//【6】显示效果图
namedWindow("<3>游戏原画+logo红色通道 ",0);
imshow("<3>游戏原画+logo红色通道 ", srcImage);
return true;
}
4.图像对比度、亮度值调整
图像亮度和对比度的调整操作,其实属于图像处理中比较简单的一种——点操作。仅仅根据输入像素值来计算输出像素值。这类算子包括亮度(brightness)和对比度(contrast)调整、颜色校正(colorcorrection)和变换(transformations)。
两种最常用的点操作是乘上一个常数以及加上一个常数:
g
(
x
)
=
a
∗
f
(
x
)
+
b
g(x)=a*f(x)+b
g(x)=a∗f(x)+b
- f(x)表示源图像像素
- g(x)表示输出图像像素
- 参数a(a>0)被称为增益,常常被用来控制图像的对比度
- 参数b通常被称为偏置,常常被用来控制图像的亮度
更进一步,可以这样改写这个式子:其中i和j表示该像素位于第i行和第j列。
g
(
i
,
j
)
=
α
⋅
f
(
i
,
j
)
+
β
g(i,j)=α⋅f(i,j)+β
g(i,j)=α⋅f(i,j)+β
4.1 亮度和对比度综合案例
#include<opencv2/opencv.hpp>
using namespace std;
using namespace cv;
//全局变量声明
int g_nContrastValue; //对比度值
int g_nBrightValue;//亮度值
Mat g_srcImage, g_dstImage;
//on_ContrastAndBright():改变图像对比度和亮度值的回调函数
static void on_ContrastAndBright(int, void*) {
//创建窗口
namedWindow("【原始图窗口】", 1);
//三个for循环,执行g_dstImage(i,j)=a*g_srcImage(i,j)+b
for (int x = 0; x < g_srcImage.rows; x++) {
for (int y = 0; y < g_srcImage.cols; y++) {
for (int c = 0; c < 3; c++) {
g_dstImage.at<Vec3b>(x, y)[c] =
saturate_cast<uchar>((g_nContrastValue * 0.01) * (g_srcImage.at < Vec3b>(x, y)[c]) + g_nBrightValue);
}
}
}
imshow("【原始图窗口】", g_srcImage);
imshow("【效果图窗口】", g_dstImage);
}
int main() {
g_srcImage = imread("../../image/1.tif");
if (!g_srcImage.data) {
cout << "读取图片错误" << endl;
return false;
}
g_dstImage = Mat::zeros(g_srcImage.size(), g_srcImage.type());
//设置对比度和亮度
g_nContrastValue = 80;
g_nBrightValue = 80;
//创建效果图窗口
namedWindow("【效果图窗口】", 1);
//创建轨迹条
createTrackbar("对比度:", "【效果图窗口】", &g_nContrastValue, 300, on_ContrastAndBright);
createTrackbar("亮 度:", "【效果图窗口】", &g_nBrightValue, 200, on_ContrastAndBright) ;
//回调函数初始化
on_ContrastAndBright(g_nContrastValue, 0);
on_ContrastAndBright(g_nBrightValue, 0);
//按下q键退出
while(char(waitKey(1))!='q'){}
return 0;
}
saturate_cast模板函数,其用于颜色溢出保护
if(data<0) data=0; else if(data>255) data=255;
at函数
单通道图像:image.at<uchar>(i,j)就表示在第i行第j列的像素值。
对于多通道图像如RGB:image.at<Vec3b>(i,j)[c]来表示某个通道中(i,j)位置的像素值。
- uchar、Vec3b表示元素的类型:Vec3b =vector<uchar,3> 即一个uchar类型、长度为3的vector向量
简单的来说Vec3b就是一个uchar类型的数组,长度为3。
5.离散傅里叶变换
5.1 离散傅里叶变换的原理
傅里叶变换(Discrete Fourier Transform, 缩写为DFT)将把一个图像分解成正弦和余弦成分。换句话说,它将把图像从其空间域转化为其频率域。其原理是,任何函数都可以用无限的正弦和余弦函数的总和来精确近似。傅里叶变换是一种如何做到这一点的方法。在数学上,一个二维图像的傅里叶变换是:
式中
f
f
f是空间域值,
F
F
F是频域值。转换后的频域值是负数。因此,显示傅里叶变换之后的结果需要使用实数图像(real image)加虚数图像(complex image),或者幅度图像(magitude image)加相位图像(phase image)的形式。在实际的图像处理过程中,仅仅使用了幅度图像,因为幅度图像包含了原图像的几乎所有我们需要的几何信息。然而,如果想通过修改幅度图像或者相位图像的方法来间接修改原空间图像,需要使用逆傅里叶变换得到修改后的空间图像,这样就必须同时保留幅度图像和相位图像了。
在此示例中,我们将展示如何计算以及显示傅里叶变换后的幅度图像。由于数字图像的离散性,像素值的取值范围也是有限的。比如在一张灰度图像中,像素值灰度值一般在0-255之间。
在频域里面,对于一幅图像,高频部分代表了图像的细节、纹理信息;低频部分代表了图像的轮廓信息。如果对一幅精细的图像使用低通滤波器 那么滤波后的结果就只剩下轮廓了。这与信号处理的基本思想是相通的。如果图像受到的噪声恰好位于某个特定的"频率"范围内,则可以通过滤波器来恢复原来的图像。傅里叶变换在图像处理中可以做到图像增强与图像去噪、图像分割之边缘检测、图像特征提取、图像压缩等。
5.2 dft()函数详解
dft函数的作用是对一维或二维浮点数数组进行正向或反向离散傅里叶变换。
void dft(InputArray src, outputArray dst, int flags=0,int nonzeroRows=0)
- 第一个参数:输入矩阵,可以为实数或者虚数
- 第二个参数:函数调用后的运算结果存在这里,其尺寸和类型取决于标识符,也就是第三个参数flags。
- 第三个参数:转换的标识符,默认值为0,取值可参考下表
- 第四个参数:nonezeroRows,有默认值为0。当此参数设为非零时(最好是取值为想要处理的那一行的值,比如C.rows),函数会假设只有输入矩阵的第一个非零行元素(没有设置DFT_INVERSE标识符),或只有输出矩阵的第一个非零行包含非零元素(设置了DFT_INVERSE标识符)。这样的话函数就可对其他行进行更高效的处理,以节省时间开销。这项技术尤其是在采用DFT计算矩阵卷积时非常有效。
5.3 返回DFT最优尺寸大小:getOptimalDFTSize()函数
getOptimalDFTSize()函数返回给定向量尺寸的傅里叶最优尺寸大小。为了提高离散傅里叶变换的运行速度,需要扩充图像,而具体扩充多少,就由这个函数来计算得到。
int getOptimalDFTSize(int vecsize)
此函数的为一个参数为int类型的vecsize,向量尺寸,也就是图片的rows和cols。
5.4 扩充图像边界:copyMakeBorder()函数
copyMakeBorder()函数就是扩充图像边界
void copyMakeBorder(InputArray src, OutputArray dst,int top,int bottom, int left, int right, int borderType,const Scalar & value=Scalar())
- 第一个参数:源图像,填Mat类的对象即可。
- 第二个参数:这个参数用于存放函数调用后的输出结果,需和源图片有一 样的尺寸和类型,且 size应该为Size (src.cols+left+right, src.rows+top+bottom)
- 接下来 参数分别为 int 类型的 top、bottom、left、right。分别表示在源图像的四个向上扩充多少像素 ,例如 top=2、bottom=2 、left=2、right=2 就意味着在源图像的上下左右各扩充两个像素宽度的边。
- 第七个参数:边界类型。常见的取值类型为BORDER_CONSTANT,可参考borderInterpolate()。
- 第八个参数:const Scalar&类型的 value ,有默认值Scalar()可以理解为默认值为0。当 bordeType 值为 BORDER_CONSTANT 时,这个参数表示
边界值。
5.5 计算二维矢量的幅值:magnitude()函数
void magnitude(InputArray x,InputArray y, Output Array magnitude)
- 第一个参数:表示矢量的浮点型X坐标值,也就是实部。
- 第二个参数:标识适量的浮点型Y坐标值,也就是虚部。
- 第三个参数:输出的幅值,他和第一个参数x有着同样的尺寸和类型。
5.6 计算自然对数:log()函数
log()函数的功能是计算每个数组元素绝对值的自然对数。
void log(InputArray src, OutputArray dst)
5.7 矩阵归一化:normalize()函数
normalize(InputArray src, OutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray())
- 第一个参数:输入图像,即源图像,填 Mat类的对象即可。
- 第二个参数:函数调用后得到运算结果存这里,和原图片有一样的尺寸和类型。
- 第三个参数:alpha:归一化的规范值,或者在范围归一化的情况下,归一化的下限范围边界。
- 第四个参数:beta:在范围归一化的情况下,是范围的上限边界;它不用于规范归一化。
- 第五个参数:norm_type
- 第六个参数:当为负数时,输出数组的类型与src相同;否则,它的通道数与src相同,深度=CV_MAT_DEPTH(type)。
-第七个参数:可选的操作掩码。
6.输入输出XML和YAML文件
6.1 XML和YAML文件简介
所谓XML,即eXtensible Markup Language,翻译成中文为“可扩展标识语言”。首先,XML是一种元标记语言。所谓“元标记”,就是开发者可以根据自身需要定义自己的标记,比如可以定义标记、。任何满足XML命名规则的名称都可以标记,这就向不同的应用程序打开了的大门。此外,XML是一种语义/结构化语言,它描述了文档的结构和语义。
YAML是“YAML Ain’t a Markup Language”(译为“YAML不是一种置标语言”)的递回缩写。在开发的这种语言时,YAML的原意是:“Yet Another MarkupLanguage”(仍是一种置标语言),但为了强调这种语言以数据为中心,而不是以置标语言为重点,而用返璞词进行重新命名。YAML是一个可读性高,用来表达资料序列的格式。它参考了其他多种语言,包括:XML、C语言、Python、Perl,以及电子邮件格式RFC2822。
6.2 FileStorage类操作文件的使用引导
我们一般使用如下过程来写入或者读取数据到XML或YAML文件中。
- 实例化一个FileStorage类的对象,用默认带参数的构造函数来完成初始化或者用FileStorage::open()成员函数辅助初始化。
- 使用流操作符<<进行文件写入操作,或者>>进行文件读取操作,类似C++中的而文件输入输出流。
- 使用FileStorage::release()函数析构掉FileStorage类对象,同时关闭文件。
第一步:XML、YAML文件的打开
(1)准备文件写操作
FileStorage是Opencv中XML和YAML文件的存储类,封装了所有相关的信息。它是OpenCV从文件中读取数据或向文件中写数据时必须要使用的一个类。
FileStorage::FileSotrage()
FileStorage::FileStorage(const string& source, int flags, const string& encoding=string())
1)对于第二种带参数的构造函数,进行写操作返利如下
FileStorage fs("abs.xml",FileStorage::WRITE);
2)对于第一种不带参数的构造函数,可以使用其他成员函数FileStorage::open进行数据的写操作FileStorage fs; fs.open("abc.xml",FileStorage::WRITE);
(2)准备文件读操作
1)第一种方式
FileStorage fs("abc.xml",FileStorage::READ);
2)第二种方式
FileStorage fs;
fs.open("abc.xml",FileStorage::READ);
第二步:进行文件读写操作
(1)文本和数字的输入和输出
写入文件可以使用"<<"运算符。
fs<<"iteartionNr"<<100;
而读文件,使用">>"运算符
int itNr;
fs["iterationNr"]>>itNr;
tNr=(int) fs["iterationNr"];
(2)OpenCV数据结构的输入和输出
Mat R=Mat_<uchar>::eye(3,3),
Mat T=Mat_<double>::zeros(3,1);
//写数据
fs<<"R"<<R;
fs<<"T"<<T;
//读数据
fs["R"]>>R;
fs["T"]<<T;
第三步:vector(arrays)和maps的输入和输出
对于vector结构的输入和输出,要注意在第一个元素前加上"[“,在最后一个元素前加上”]"。例如:
fs<<"strings"<<"[";//开始读入stirng文本序列
fs<<"image1.jpg"<<"Awesomeness"<<"baboon.jpg";
fs<<"]";//关闭序列
而对于map结构的操作,使用的符号是“{}”
fs<<"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;
}
FileNodeIteraotr it=n.begin(),it_end=n.end();//遍历节点
for(;it!=it_end;it++)
cout<<(string)*it<<endl;
第四步:文件关闭
文件关闭操作会在FileStorage类销毁时自动进行,但我们也可现实的调用其析构函数FileStorage::release()实现。FileStorage::release()函数会析构掉FileStorage类对象,同时关闭文件。
fs.release();
6.3 示例程序:XML和YAML文件的写入
#include<opencv2/opencv.hpp>
#include<time.h>
using namespace std;
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 << "features" << "[";
for (int i = 0; i < 3; i++) {
int x = rand() % 640;
int y = rand() % 480;
uchar bp = rand() % 256;
fs << "{:" << "x" << x << "y" << y << "bp" << "[:";
for (int j = 0; j < 8; j++)
fs << ((bp >> j) & 1);
fs << "]" << "}";
}
fs << "]";
fs.release();
cout << "文件读写完毕" << endl;
getchar();
return 0;
}