OpenCV学习笔记(六)——对XML和YAML文件实现I/O操作
1. XML、YAML文件的打开和关闭
XML\YAML文件在OpenCV中的数据结构为FileStorage,打开操作例如:
- string filename = "I.xml";
- FileStorage fs(filename, FileStorage::WRITE);
- \\...
- fs.open(filename, FileStorage::READ);
文件关闭操作会在FileStorage结构销毁时自动进行,但也可调用如下函数实现
2.文本和数字的输入和输出
写入文件使用 << 运算符,例如:
- fs << "iterationNr" << 100;
读取文件,使用 >> 运算符,例如
- int itNr;
- fs["iterationNr"] >> itNr;
- itNr = (int) fs["iterationNr"];
3. OpenCV数据结构的输入和输出,和基本的C++形式相同
- Mat R = Mat_::eye (3, 3),
- T = Mat_<double>::zeros(3, 1);
- fs << "R" << R;
- fs << "T" << T;
- fs["R"] >> R;
- fs["T"] >> T;
4. vector(arrays) 和 maps的输入和输出
vector要注意在第一个元素前加上“[”,在最后一个元素前加上"]"。例如:
- fs << "strings" << "[";
- 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 << "strings is not a sequence! FAIL" << endl;
- return 1;
- }
- FileNodeIterator it = n.begin(), it_end = n.end();
- for (; it != it_end; ++it)
- cout << (string)*it << endl;
5. 读写自己的数据结构
这部分比较复杂,参考最后的实例中的MyData结构自己领悟吧
最后,我这里上一个实例,供大家参考。
源文件里填入如下代码:
- #include
- #include
- #include
-
- using namespace cv;
- using namespace std;
-
- void help(char** av)
- {
- cout << endl
- << av[0] << " shows the usage of the OpenCV serialization functionality." << endl
- << "usage: " << endl
- << av[0] << " outputfile.yml.gz" << endl
- << "The output file may be either XML (xml) or YAML (yml/yaml). You can even compress it by "
- << "specifying this in its extension like xml.gz yaml.gz etc... " << endl
- << "With FileStorage you can serialize objects in OpenCV by using the << and >> operators" << endl
- << "For example: - create a class and have it serialized" << endl
- << " - use it to read and write matrices." << endl;
- }
-
- class MyData
- {
- public:
- MyData() : A(0), X(0), id()
- {}
- explicit MyData(int) : A(97), X(CV_PI), id("mydata1234")
- {}
- void write(FileStorage& fs) const
- {
- fs << "{" << "A" << A << "X" << X << "id" << id << "}";
- }
- void read(const FileNode& node)
- {
- A = (int)node["A"];
- X = (double)node["X"];
- id = (string)node["id"];
- }
- public:
- int A;
- double X;
- string id;
- };
-
-
- void write(FileStorage& fs, const std::string&, const MyData& x)
- {
- x.write(fs);
- }
- void read(const FileNode& node, MyData& x, const MyData& default_value = MyData()){
- if(node.empty())
- x = default_value;
- else
- x.read(node);
- }
-
-
- ostream& operator<<(ostream& out, const MyData& m)
- {
- out << "{ id = " << m.id << ", ";
- out << "X = " << m.X << ", ";
- out << "A = " << m.A << "}";
- return out;
- }
-
- int main(int ac, char** av)
- {
- if (ac != 2)
- {
- help(av);
- return 1;
- }
-
- string filename = av[1];
- {
- Mat R = Mat_::eye(3, 3),
- T = Mat_<double>::zeros(3, 1);
- MyData m(1);
-
- FileStorage fs(filename, FileStorage::WRITE);
-
- fs << "iterationNr" << 100;
- fs << "strings" << "[";
- fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
- fs << "]";
-
- fs << "Mapping";
- fs << "{" << "One" << 1;
- fs << "Two" << 2 << "}";
-
- fs << "R" << R;
- fs << "T" << T;
-
- fs << "MyData" << m;
-
- fs.release();
- cout << "Write Done." << endl;
- }
-
- {
- cout << endl << "Reading: " << endl;
- FileStorage fs;
- fs.open(filename, FileStorage::READ);
-
- int itNr;
-
- itNr = (int) fs["iterationNr"];
- cout << itNr;
- if (!fs.isOpened())
- {
- cerr << "Failed to open " << filename << endl;
- help(av);
- return 1;
- }
-
- FileNode n = fs["strings"];
- if (n.type() != FileNode::SEQ)
- {
- cerr << "strings is not a sequence! FAIL" << endl;
- return 1;
- }
-
- FileNodeIterator it = n.begin(), it_end = n.end();
- for (; it != it_end; ++it)
- cout << (string)*it << endl;
-
-
- n = fs["Mapping"];
- cout << "Two " << (int)(n["Two"]) << "; ";
- cout << "One " << (int)(n["One"]) << endl << endl;
-
-
- MyData m;
- Mat R, T;
-
- fs["R"] >> R;
- fs["T"] >> T;
- fs["MyData"] >> m;
-
- cout << endl
- << "R = " << R << endl;
- cout << "T = " << T << endl << endl;
- cout << "MyData = " << endl << m << endl << endl;
-
-
- cout << "Attempt to read NonExisting (should initialize the data structure with its default).";
- fs["NonExisting"] >> m;
- cout << endl << "NonExisting = " << endl << m << endl;
- }
-
- cout << endl
- << "Tip: Open up " << filename << " with a text editor to see the serialized data." << endl;
-
- return 0;
- }
编译后,在命令行进入到文件目录,执行test test.xml,运行结果如下, 生成一个test . xml文件,内容如下:
- xml version="1.0" ?>
- - <opencv_storage>
- <iterationNr>100iterationNr>
- <strings>image1.jpg Awesomeness baboon.jpgstrings>
- - <Mapping>
- <One>1One>
- <Two>2Two>
- Mapping>
- - <R type_id="opencv-matrix">
- <rows>3rows>
- <cols>3cols>
- <dt>udt>
- <data>1 0 0 0 1 0 0 0 1data>
- R>
- - <T type_id="opencv-matrix">
- <rows>3rows>
- <cols>1cols>
- <dt>ddt>
- <data>0. 0. 0.data>
- T>
- - <MyData>
- <A>97A>
- <X>3.1415926535897931e+000X>
- <id>mydata1234id>
- MyData>
- opencv_storage>
OpenCV学习笔记(七)——图像处理之滤波器ImgProc
先介绍几个最基本的核滤波器相关的类
2D图像滤波器基础类BaseFilter:dst(x,y) = F(src(x,y), src(x+1,y)... src(x+wdith-1,y), src(y+1,x)... src(x+width-1, y+height-1) ); 相关的调用函数为getLinearFilter、getMorphologyFilter
单行核滤波器基础类BaseRowFilter:dst(x,y) = F(src(x,y), src(x+1,y),...src(x+width-1,y));相关的调用函数为getLinearRowFilter、getMorphologyRowFilter
单列核滤波器基础类BaseColumnFilter:dst(x,y) = F(src(x,y), src(x,y+1),...src(x,y+width-1));相关的调用函数为getColumnSumFilter、getLinearColumnFilter、getMorphologyColumnFilter
类FilterEngine:该类可以应用在对图像的任意滤波操作当中,在OpenCV滤波器函数中扮演着很重要的角色,相关的函数有createBoxFitler、createDerivFitlter、createGaussianFilter、createLinearFilter、createMorphologyFilter、createSeparableLinearFilter
基于这些类有一些基本的滤波器bilateralFilter、blur、boxFilter
还有一些形态学操作如:dilate、erode、morphologyEx
还有基于核和图像卷积的滤波器filter2D
还有一些典型的滤波器如GaussianBlur、medianBlur、Laplacian、pyrMeanShiftFiltering、sepFilter2D
还有Sobel、Scharr运算符
其他一些函数有borderInterpolate、buildPyramid、copyMakeBorder、createBoxFilter、createDirivFilter、createGaussianFliter、createLinearFilter、createMorphologyFilter、createSeparableLinearFilter、getDerivKernels、getGaussianKernel、getKernelType、getStructuringElement、pyrDown、pyrUp
还老版本的滤波器cvSmooth
这里介绍一下我使用Laplacian滤波的心得,这个函数的第三个参数为输出的图像的深度,注意经过拉普拉斯算子处理后得到的值是有正有负的,所以输出图像的深度最好为输入图像深度的2倍,才能有效防止数据溢出,如必须要使用8位的数据,可以再使用函数convertScaleAbs处理。而且要注意使用的拉普拉斯算子掩膜的中心系数为负。
OpenCV学习笔记(八)——图像处理之直方图ImgProc
直方图histograms也是图像处理中经常用到的一种手段。新版本对直方图不再使用之前的histogram的形式,而是用统一的Mat或者MatND的格式来存储直方图,可见新版本Mat数据结构的优势。先介绍下其相关的函数
calcHist、calcBackProject、compareHist、EMD、equalizeHist。除了这几个常用的函数以为,还有一些c函数写的直方图类CvHistogram的相关操作,如下:cvCalcBackProjectPatch、cvCalcProbDensity、cvClearHist、cvCopyHist、cvCreateHist、cvGetHistValue_XD、cvGetMinMaxHistValue、cvMakeHistHeaderForArray、cvNormalizeHist、QueryHistValue_XD、cvReleaseHist、cvSetHistBinRanges、cvThreshHist、cvCalcPGH
calcHist函数为计算图像的直方图,使用方法如下:
-
- void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, OutputArray hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false )
-
- void calcHist(const Mat* arrays, int narrays, const int* channels, InputArray mask, SparseMat& hist, int dims, const int* histSize, const float** ranges, bool uniform=true, bool accumulate=false )
arrays为输入图像指针,narrays为输入图像的个数,channels为用来计算直方图的通道列表,mask为掩膜矩阵,不为空的时候,只计算arrays中的掩膜区域的直方图,hist为输出的直方图矩阵,dims为直方图矩阵的维度,histSize为每一维直方图矩阵的大小,ranges为每一维直方图元素的取值范围,是一个2维数组的地址,uniform为直方图是否为统一模式,统一模式下会拉伸为range的大小,accumulate为累计标志,方便直方图的更新,不需要重新计算
举几个实例方便大家理解:
对于图像为灰度图,调用方式如下:
- int histSize = 255;
- float ranges[] = {0, 255};
- const float* histRange = {ranges};
- calcHist(&img, 1, 0, Mat(), hist, 1, &histSize, &histRange);
直方图的归一化 已经不再适合cvNormalizeHist这个函数了,只需要用对矩阵的归一化函数 normalize 就可以实现了。
直方图均衡化函数为equalizeHist,这个函数比较简单,这里就不详细介绍了
直方图的比较函数为compareHist,函数返回值为两矩阵的相似程度,相似度衡量的办法目前支持4种
– CV_COMP_CORREL Correlation相关系数,相同为1,相似度范围为[ 1, 0 )
– CV_COMP_CHISQR Chi-Square卡方,相同为0,相似度范围为[ 0, +inf )
– CV_COMP_INTERSECT Intersection直方图交,数越大越相似,,相似度范围为[ 0, +inf )
– CV_COMP_BHATTACHARYYA Bhattacharyya distance做常态分别比对的Bhattacharyya 距离,相同为0,,相似度范围为[ 0, +inf )
计算反向投影图函数为 calcBackProject 。所谓反向投影图就是一个概率密度图。calcBackProject的输入为图像及其直方图,输出与待跟踪图像大小相同,每一个像素点表示该点为目标区域的概率。这个点越亮,该点属于物体的概率越大。关于反向直方图,可以参考一下这篇文章 http://blog.163.com/thomaskjh@126/blog/static/370829982010112810358501/
,这个函数使我们利用特征直方图寻找图片中的特征区域变得更加方便容易。这里举一个比较常用的例子:如果已经有一个肤色的特征直方图,则可以在待检测图像中利用直方图方向投影图找出图片中的肤色区域。
OpenCV学习笔记(九)——2维特征Feature2D
基于特征点的图像匹配是图像处理中经常会遇到的问题,手动选取特征点太麻烦了。比较经典常用的特征点自动提取的办法有Harris特征、SIFT特征、SURF特征。
先介绍利用SURF特征的特征描述办法,其操作封装在类SurfFeatureDetector中,利用类内的detect函数可以检测出SURF特征的关键点,保存在vector容器中。第二部利用SurfDescriptorExtractor类进行特征向量的相关计算。将之前的vector变量变成向量矩阵形式保存在Mat中。最后强行匹配两幅图像的特征向量,利用了类BruteForceMatcher中的函数match。代码如下:
-
-
-
-
-
-
- #include
- #include
- #include "opencv2/core/core.hpp"
- #include "opencv2/features2d/features2d.hpp"
- #include "opencv2/highgui/highgui.hpp"
-
- using namespace cv;
-
- void readme();
-
-
-
-
-
- int main( int argc, char** argv )
- {
- if( argc != 3 )
- { return -1; }
-
- Mat img_1 = imread( argv[1], CV_LOAD_IMAGE_GRAYSCALE );
- Mat img_2 = imread( argv[2], CV_LOAD_IMAGE_GRAYSCALE );
-
- if( !img_1.data || !img_2.data )
- { return -1; }
-
-
- int minHessian = 400;
-
- SurfFeatureDetector detector( minHessian );
-
- std::vector keypoints_1, keypoints_2;
-
- detector.detect( img_1, keypoints_1 );
- detector.detect( img_2, keypoints_2 );
-
-
- SurfDescriptorExtractor extractor;
-
- Mat descriptors_1, descriptors_2;
-
- extractor.compute( img_1, keypoints_1, descriptors_1 );
- extractor.compute( img_2, keypoints_2, descriptors_2 );
-
-
- BruteForceMatcher< L2<float> > matcher;
- std::vector< DMatch > matches;
- matcher.match( descriptors_1, descriptors_2, matches );
-
-
- Mat img_matches;
- drawMatches( img_1, keypoints_1, img_2, keypoints_2, matches, img_matches );
-
-
- imshow("Matches", img_matches );
-
- waitKey(0);
-
- return 0;
- }
-
-
-
-
- void readme()
- { std::cout << " Usage: ./SURF_descriptor " << std::endl; }
当然,进行强匹配的效果不够理想,这里再介绍一种FLANN特征匹配算法。前两步与上述代码相同,第三步利用FlannBasedMatcher类进行特征匹配,并只保留好的特征匹配点,代码如下:
-
- FlannBasedMatcher matcher;
- std::vector< DMatch > matches;
- matcher.match( descriptors_1, descriptors_2, matches );
-
- double max_dist = 0; double min_dist = 100;
-
-
- for( int i = 0; i < descriptors_1.rows; i++ )
- { double dist = matches[i].distance;
- if( dist < min_dist ) min_dist = dist;
- if( dist > max_dist ) max_dist = dist;
- }
-
- printf("-- Max dist : %f \n", max_dist );
- printf("-- Min dist : %f \n", min_dist );
-
-
-
- std::vector< DMatch > good_matches;
-
- for( int i = 0; i < descriptors_1.rows; i++ )
- { if( matches[i].distance < 2*min_dist )
- { good_matches.push_back( matches[i]); }
- }
-
-
- Mat img_matches;
- drawMatches( img_1, keypoints_1, img_2, keypoints_2,
- good_matches, img_matches, Scalar::all(-1), Scalar::all(-1),
- vector<char>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS );
-
-
- imshow( "Good Matches", img_matches );
在FLANN特征匹配的基础上,还可以进一步利用Homography映射找出已知物体。具体来说就是利用findHomography函数利用匹配的关键点找出相应的变换,再利用perspectiveTransform函数映射点群。具体代码如下:
-
- std::vector obj;
- std::vector scene;
-
- for( int i = 0; i < good_matches.size(); i++ )
- {
-
- obj.push_back( keypoints_1[ good_matches[i].queryIdx ].pt );
- scene.push_back( keypoints_2[ good_matches[i].trainIdx ].pt );
- }
-
- Mat H = findHomography( obj, scene, CV_RANSAC );
-
-
- Point2f obj_corners[4] = { cvPoint(0,0), cvPoint( img_1.cols, 0 ), cvPoint( img_1.cols, img_1.rows ), cvPoint( 0, img_1.rows ) };
- Point scene_corners[4];
-
-
- for( int i = 0; i < 4; i++ )
- {
- double x = obj_corners[i].x;
- double y = obj_corners[i].y;
-
- double Z = 1./( H.at<double>(2,0)*x + H.at<double>(2,1)*y + H.at<double>(2,2) );
- double X = ( H.at<double>(0,0)*x + H.at<double>(0,1)*y + H.at<double>(0,2) )*Z;
- double Y = ( H.at<double>(1,0)*x + H.at<double>(1,1)*y + H.at<double>(1,2) )*Z;
- scene_corners[i] = cvPoint( cvRound(X) + img_1.cols, cvRound(Y) );
- }
-
-
- line( img_matches, scene_corners[0], scene_corners[1], Scalar(0, 255, 0), 2 );
- line( img_matches, scene_corners[1], scene_corners[2], Scalar( 0, 255, 0), 2 );
- line( img_matches, scene_corners[2], scene_corners[3], Scalar( 0, 255, 0), 2 );
- line( img_matches, scene_corners[3], scene_corners[0], Scalar( 0, 255, 0), 2 );
-
-
- imshow( "Good Matches & Object detection", img_matches );
然后再看一下Harris特征检测,在计算机视觉中,通常需要找出两帧图像的匹配点,如果能找到两幅图像如何相关,就能提取出两幅图像的信息。我们说的特征的最大特点就是它具有唯一可识别这一特点,图像特征的类型通常指边界、角点(兴趣点)、斑点(兴趣区域)。角点就是图像的一个局部特征,应用广泛。harris角点检测是一种直接基于灰度图像的角点提取算法,稳定性高,尤其对L型角点检测精度高,但由于采用了高斯滤波,运算速度相对较慢,角点信息有丢失和位置偏移的现象,而且角点提取有聚簇现象。具体实现就是使用函数cornerHarris实现。
除了利用Harris进行角点检测,还可以利用Shi-Tomasi方法进行角点检测。使用函数goodFeaturesToTrack对角点进行检测,效果也不错。也可以自己制作角点检测的函数,需要用到cornerMinEigenVal函数和minMaxLoc函数,最后的特征点选取,判断条件要根据自己的情况编辑。如果对特征点,角点的精度要求更高,可以用cornerSubPix函数将角点定位到子像素。
OpenCV学习笔记(十)——图形交互和媒体接口HighGUI
OpenCV提供一个功能强大的UI接口,可以在MFC、Qt、WinForms、Cocoa等平台下使用,甚至不需要其他的平台。新版本的HighGUI接口包括:
创建并控制窗口,该窗口可以显示图片并记录其内容
为窗口添加了trackbars控件,可以方便利用鼠标进行控制而不是之前版本的只能利用键盘
读写硬盘和内存的图片
读取摄像头的视频、读写视频文件
先来介绍UI,包括函数createTrackbar、getTrackbarPos、setTrackbarPos、imshow、namedWindow、destroyWindow、destroyAllWindows、MoveWindow、ResizeWindow、SetMouseCallback、waitKey。这些函数保证了图像的基本处理、tarckbar的控制和鼠标键盘的响应
介绍一下读写图像视频的函数:图像相关的函数有imdecode、imencode、imread、imwrite。读取视频相关为VideoCapture类,负责捕捉文件和摄像头的视频,该类内有成员函数VideoCapture、open、isOpened、release、grab、retrieve、read、get、set,写视频的类为VideoWriter,类内有成员函数VideoWriter、open、isOpened、write
新版本还为Qt做了新函数,这里就不介绍了,有兴趣的朋友可以自己看一下参考手册的第四章第三节。
这里介绍几个常用的新功能,首先介绍一下添加滑杆控件Trackbar。调用函数为:
- createTrackbar( TrackbarName, "Linear Blend", &alpha_slider, alpha_slider_max, on_trackbar );
第一个参数为字符串作为标签,第二个参数为所在窗口的名字,第三个参数为存储滑杆位置的值地址,其范围为0~alpha_slider_max(第四个参数),最后一个参数为移动滑杆时调用的回调函数名。
OpenCV2.0版本加强了对视频处理的支持,不再需要对一组连续的图片进行处理,可以进行实时的图像采集和记录以及存储。视频的操作基本都被封装在VideoCapture类中。打开视频可以可以通过如下代码实现:
- VideoCapture captRefrnc(sourceReference);
-
- VideoCapture captUndTst;
- captUndTst.open(sourceCompareWith);
其中sourceReference和sourceCompareWith为string型,为文件名。还可以通过isOpened函数检测视频是否成功打开。也可以调用release函数提前关闭视频。还可以讲VideoCapture放到Mat结构中,因为视频流是一连串的,可以通过read函数或>>操作符逐帧的读取,例如:
- Mat frameReference, frameUnderTest;
- captRefrnc >> frameReference;
- captUndTst.open(frameUnderTest);
read函数只能逐帧的抓取,如果要抓取某一帧,可以成对的调用grab函数和retrieve函数。get函数可以获取视频相关信息。set函数可以控制视频的一些值,比如是指视频的当前位置或帧数。
可以使用VideoWriter类创建新视频,其open,isOpened函数调用方法类似,write函数或<<运算符向视频写入内容,可以使用split函数和merge函数单独调整RGB通道的值
今日,被一个网友指出,说OpenCV以前提供的读写功能采用VFW,效率低下且有些格式支持不好。而 OpenCV 2.0 内置了videoInput Library,可以自动在VFW和DirectShow间切换。videoInput是老外写的功能强大的开源视频处理库。是一个第三方库,2.0~2.2的版本专门有一个3rdparty对该库进行支持,而在最新的2.3版本中,已经讲videoInput库集成到highgui中了,想使用它的话,只需要在cmake中设置宏WITH_VIDEOiNPUT=OFF/ON即可。
以后有新学到的东西都会陆续补充进来。
from: http://blog.csdn.net/yang_xian521/article/category/910716