在写程序时候最好使用release版本。比较快。。。
这时候发现pcl解压后文件读取不进去??excuse me?包含路径什么的都是对的啊就是找不到头文件。。最后实在没有办法,重新建了一个文件夹。重新分子文件夹就能找到到头文件了。宴陆。。。。。配祥兆E:\PCL 1.8.0\include\pcl-1.8\pcl。。。。新建了一个大文件夹PCL 1.8.0 。建立子文件夹include include里建立子文件夹pcl_1.8。子文件夹pcl_1.8里面建立子文件pcl。最后在这个pcl 里面放进那些头文件。。。
其他的文件夹比如3rdpart.,bin,lib,等就培租不用改子文件夹了。。。
VC++目录:可执行文件:E:\PCL 1.8.0\3rdParty\VTK\binE:\PCL 1.8.0\binE:\PCL 1.8.0\3rdParty\Qhull\binE:\PCL 1.8.0\3rdParty\FLANN\bin$(ExecutablePath)
:包含目录:E:\PCL 1.8.0\3rdParty\VTK\include\vtk-7.0E:\PCL 1.8.0\3rdParty\Qhull\includeE:\PCL 1.8.0\3rdParty\OpenNI2\IncludeE:\PCL 1.8.0\3rdParty\FLANN\includeE:\PCL 1.8.0\3rdParty\Eigen\eigen3E:\PCL 1.8.0\include\pcl-1.8E:\PCL 1.8.0\3rdParty\Boost\include\boost-1_61$(IncludePath)
:库目录:E:\PCL 1.8.0\3rdParty\VTK\libE:\PCL 1.8.0\3rdParty\Qhull\libE:\PCL 1.8.0\3rdParty\OpenNI2\LibE:\PCL 1.8.0\3rdParty\FLANN\libE:\PCL 1.8.0\3rdParty\Boost\libE:\PCL 1.8.0\lib$(LibraryPath)
:源目录:E:\PCL 1.8.0\3rdParty\Eigen\eigen3\unsupported\Eigen\srcE:\PCL 1.8.0\3rdParty\Eigen\eigen3\Eigen\src$(SourcePath)
C++预处理器:NDEBUG_CONSOLE_SCL_SECURE_NO_WARNINGS_CRT_SECURE_NO_WARNINGS%(PreprocessorDefinitions)
链接器输入:::(release版本)pcl_common_release.libpcl_features_release.libpcl_filters_release.libpcl_io_release.libpcl_io_ply_release.libpcl_kdtree_release.libpcl_keypoints_release.libpcl_ml_release.libpcl_octree_release.libpcl_outofcore_release.libpcl_people_release.libpcl_recognition_release.libpcl_registration_release.libpcl_sample_consensus_release.libpcl_search_release.libpcl_segmentation_release.libpcl_stereo_release.libpcl_surface_release.libpcl_tracking_release.libpcl_visualization_release.libflann_cpp_s.libflann_s.libflann.liblibboost_atomic-vc140-mt-1_61.liblibboost_chrono-vc140-mt-1_61.liblibboost_container-vc140-mt-1_61.liblibboost_context-vc140-mt-1_61.liblibboost_coroutine-vc140-mt-1_61.liblibboost_date_time-vc140-mt-1_61.liblibboost_exception-vc140-mt-1_61.liblibboost_filesystem-vc140-mt-1_61.liblibboost_graph-vc140-mt-1_61.liblibboost_iostreams-vc140-mt-1_61.liblibboost_locale-vc140-mt-1_61.liblibboost_log-vc140-mt-1_61.liblibboost_log_setup-vc140-mt-1_61.liblibboost_math_c99-vc140-mt-1_61.liblibboost_math_c99f-vc140-mt-1_61.liblibboost_math_c99l-vc140-mt-1_61.liblibboost_math_tr1-vc140-mt-1_61.liblibboost_math_tr1f-vc140-mt-1_61.liblibboost_math_tr1l-vc140-mt-1_61.liblibboost_mpi-vc140-mt-1_61.liblibboost_prg_exec_monitor-vc140-mt-1_61.liblibboost_program_options-vc140-mt-1_61.liblibboost_random-vc140-mt-1_61.liblibboost_regex-vc140-mt-1_61.liblibboost_serialization-vc140-mt-1_61.liblibboost_signals-vc140-mt-1_61.liblibboost_system-vc140-mt-1_61.liblibboost_test_exec_monitor-vc140-mt-1_61.liblibboost_thread-vc140-mt-1_61.liblibboost_timer-vc140-mt-1_61.liblibboost_unit_test_framework-vc140-mt-1_61.liblibboost_wave-vc140-mt-1_61.liblibboost_wserialization-vc140-mt-1_61.libqhullstatic.libqhull.libqhull_p.libqhull_r.libqhullcpp.libqhullstatic_r.libvtkalglib-7.0.libvtkChartsCore-7.0.libvtkCommonColor-7.0.libvtkCommonComputationalGeometry-7.0.libvtkCommonCore-7.0.libvtkCommonDataModel-7.0.libvtkCommonExecutionModel-7.0.libvtkCommonMath-7.0.libvtkCommonMisc-7.0.libvtkCommonSystem-7.0.libvtkCommonTransforms-7.0.libvtkDICOMParser-7.0.libvtkDomainsChemistry-7.0.libvtkexoIIc-7.0.libvtkexpat-7.0.libvtkFiltersAMR-7.0.libvtkFiltersCore-7.0.libvtkFiltersExtraction-7.0.libvtkFiltersFlowPaths-7.0.libvtkFiltersGeneral-7.0.libvtkFiltersGeneric-7.0.libvtkFiltersGeometry-7.0.libvtkFiltersHybrid-7.0.libvtkFiltersHyperTree-7.0.libvtkFiltersImaging-7.0.libvtkFiltersModeling-7.0.libvtkFiltersParallel-7.0.libvtkFiltersParallelImaging-7.0.libvtkFiltersProgrammable-7.0.libvtkFiltersSelection-7.0.libvtkFiltersSMP-7.0.libvtkFiltersSources-7.0.libvtkFiltersStatistics-7.0.libvtkFiltersTexture-7.0.libvtkFiltersVerdict-7.0.libvtkfreetype-7.0.libvtkGeovisCore-7.0.libvtkhdf5-7.0.libvtkhdf5_hl-7.0.libvtkImagingColor-7.0.libvtkImagingCore-7.0.libvtkImagingFourier-7.0.libvtkImagingGeneral-7.0.libvtkImagingHybrid-7.0.libvtkImagingMath-7.0.libvtkImagingMorphological-7.0.libvtkImagingSources-7.0.libvtkImagingStatistics-7.0.libvtkImagingStencil-7.0.libvtkInfovisCore-7.0.libvtkInfovisLayout-7.0.libvtkInteractionImage-7.0.libvtkInteractionStyle-7.0.libvtkInteractionWidgets-7.0.libvtkIOAMR-7.0.libvtkIOCore-7.0.libvtkIOEnSight-7.0.libvtkIOExodus-7.0.libvtkIOExport-7.0.libvtkIOGeometry-7.0.libvtkIOImage-7.0.libvtkIOImport-7.0.libvtkIOInfovis-7.0.libvtkIOLegacy-7.0.libvtkIOLSDyna-7.0.libvtkIOMINC-7.0.libvtkIOMovie-7.0.libvtkIONetCDF-7.0.libvtkIOParallel-7.0.libvtkIOPLY-7.0.libvtkIOSQL-7.0.libvtkIOVideo-7.0.libvtkIOXML-7.0.libvtkIOXMLParser-7.0.libvtkjpeg-7.0.libvtkjsoncpp-7.0.libvtklibxml2-7.0.libvtkmetaio-7.0.libvtkNetCDF-7.0.libvtkNetCDF_cxx-7.0.libvtkoggtheora-7.0.libvtkParallelCore-7.0.libvtkpng-7.0.libvtkproj4-7.0.libvtkRenderingAnnotation-7.0.libvtkRenderingContext2D-7.0.libvtkRenderingCore-7.0.libvtkRenderingFreeType-7.0.libvtkRenderingImage-7.0.libvtkRenderingLabel-7.0.libvtkRenderingLOD-7.0.libvtkRenderingVolume-7.0.libvtksqlite-7.0.libvtksys-7.0.libvtktiff-7.0.libvtkverdict-7.0.libvtkViewsContext2D-7.0.libvtkViewsCore-7.0.libvtkViewsInfovis-7.0.libvtkzlib-7.0.libOpenNI2.lib%(AdditionalDependencies)
选择x64平台。release版本。
如何设计好词袋模型的类类型回顾过去自己写过的一些词袋模型,比如 BoW图像检索Python实战 、 图像检索(CBIR)三剑客之BoF、VLAD、FV 以及Bag of Words cpp实现,这些写出来的要么只是助于自己理解词袋模型的有关理论,要么也只是面向实验的一些验证,或者更直接点可以说只是些小玩具摆了。
在我2016年的计划列表里,存放着一条由2015年拖过来的告孝目标,就是写出一个可以面向商业级别的词袋模型,这条计划伴随着成功将VLfeat的一些c接口打通而变成了可能,并且在过去的大半年里,自己也一直留意在具体编写的时候选用哪些库比较合适的问题。机缘巧合,这一段时间又重新开始造起了轮子,并有了初步的成功,所以在此梳理总结一下。在谈怎样设计一个词袋模型的类类型之前,先谈谈库的选用问题。
选取合适的库
在具体编写一个面向应用级别的词袋模型的时候,大概会经历这么几个步骤:SIFT特征抽取,特征采样,聚类,构建KD树,统计词频,计算词频权重,计算词频直方图,如镇保存数据。这8个步骤在具体实现的时候,会设计到一些库的选取问题,下面对其进行细谈。
1) SIFT特征抽取提取选用哪个库?
提取SIFT的库有很多,主要有以下几个大家用得比较多:
Lowe的 SIFT ,效果只提供SIFT的二进制可执行文件,弃用;
Robwhess的 OpenSIFT ,开源,效果也还不错,需要一些别的依赖库,不再更新,弃用;
OpenCV的SIFT,这个当然在使用上是最方便的,文档全,不依赖别的库,但SIFT的实现效果并不是很好,弃用;
VLfeat里的 SIFT ,SIFT的实现效果是比较好的,缺点是c接口文档不怎么全,网上提供的资料也比较少,但多读读它的C源码,还是可以搞定的,而且在不用依赖其他的库,所以选择这个库来提取SIFT还是很好的,在实际提取的时候,我选用的是 covdet 函数来提取SIFT,它是一个功能更强大的提取co-variant特征提取器。
在去年的时候,基本弄清了VLfeat中的一些函数的C接口调用方式,covdet这个函数通过阅读写给matlab的接口源码转成的C,对比matlab提取的结果和自己转成C之后提取的结果,两者完全一致。
2) 矩阵运算库的选取
虽然使用矩阵 *** 作并不是必须的,除了OpenCV的矩阵,还有可能引入其他的矩阵运算库,这些矩渣友粗阵的引入会给后面的实现带来巨大的方便,比如聚类,KD树的构建以及后面词频统计等。作为运算的基础库,在矩阵库的选择上主要有下面几个用得比较多:
Eigen ,使用的只需要把头文件包含进工程里即可,提供了多个平台的版本,比如可以运行于安卓上,矩阵运算 *** 作还是比较方便的,更新得比较快,不过在PC平台上开发,我比较倾向于使用下面要说的Armadillo。
Armadillo ,这个库是我非常喜欢的矩阵运算库,此矩阵库在使用语法上Matlabjie借鉴了Matlab的语法使用习惯,所以熟悉Matlab的开发者在使用此库的时候会觉得非常的舒服,并且有名的MLPack是建立在它的基础之上,另外它的矩阵运算效率也是非常高的,使用的时候同Eigen一样只需要包含头文件目录即可,最新版本中添加了KMeans聚类。因而,基于以上这些优点,在实现词袋模型的时候,对于矩阵运算库的选取,选择这个无疑是最优的。
选用矩阵库虽然能极大的方便我们的程序编写,但是会涉及到数据类型的转换,比如STL的vector存储的数据要转成Armadillo的矩阵进行运算,如果数据频繁的进行类型的转换,必然会降低程序的运行效率,因而在程序的编写中,不必要转换的尽量避免转换。
3) 多线程并行处理
为了使程序的SIFT特征提取、KMeans聚类、统计词频等过程支持并行处理,在选择并行计算库的时候,有两种选择,一种是采用OpenMP,另一种是选择MPI。OpenMP是采用的是内存共享的方式,只需要对原程序进行小幅调整即可实现并行处理,并且语法易读已写;MPI需要对原来的程序进行大幅重构,写出来的代码也不是很好读。所以,在多线程并处计算库选择这块,选择OpenMP是比较好的。
词袋模型的类类型设计
终于可以讲核心的了,这一部分讲讲在编写程序的时候自己对词袋模型的类类型设计的一点心得。先上自己写的词袋模型的类类型,设计了两个类,一个是 SIFT特征提取的类类型 ,另一个是 词袋模型的类类型 。先谈谈 SIFT特征提取的类类型 :
class siftDesctor{
public:
siftDesctor(){}
std::string imageName
std::vector<std::vector<float>>frame
std::vector<std::vector<float>>desctor
void covdet_keypoints_and_descriptors(cv::Mat &img, std::vector<std::vector<float>>&frames, std::vector<std::vector<float>>&desctor, bool rooSIFT, bool verbose)
std::vector<float>rootsift(std::vector<float>&dst)
void Serialize(std::ofstream &outfile) const {
std::string tmpImageName = imageName
int strSize = (int)imageName.size()
outfile.write((char *)&strSize, sizeof(int))
outfile.write((char *)&tmpImageName[0], sizeof(char)*strSize)// 写入文件名
int descSize = (int)desctor.size()
outfile.write((char *)&descSize, sizeof(int))
// 写入sift特征
for(int i = 0i <descSizei++ ){
outfile.write((char *)&(desctor[i][0]), sizeof(float) * 128)
outfile.write((char *)&(frame[i][0]), sizeof(float) * 6)
}
}
static siftDesctor Deserialize(std::ifstream &ifs) {
siftDesctor siftDesc
int strSize = 0
ifs.read((char *)&strSize, sizeof(int))// 写入文件名
siftDesc.imageName = ""
siftDesc.imageName.resize(strSize)
ifs.read((char *)&(siftDesc.imageName[0]), sizeof(char)*strSize)// 读入文件名
int descSize = 0
ifs.read((char *)&descSize, sizeof(int))
// 读入sift特征和frame
for(int i = 0i <descSizei++ ){
std::vector<float>tmpDesc(128)
ifs.read((char *)&(tmpDesc[0]), sizeof(float) * 128)
siftDesc.desctor.push_back(tmpDesc)
std::vector<float>tmpFrame(6)
ifs.read((char *)&(tmpFrame[0]), sizeof(float) * 6)
siftDesc.frame.push_back(tmpFrame)
}
return siftDesc
}
}
在设计SIFT特征提取的类类型的时候,对于每一幅图像,提取SIFT特征之后,由于需要保存图像名、128维的SIFT特征以及6维的frame,因为 imageName 、 desctor 和 frame 这三个成员是必须的,这里说一下对于 imageName 这个成员,在最后保存方式文件名的时候,更合理的方式是不应该带入文件所在目录路径的,因为最终写入的数据有可能转移到别的计算机上,带入路径不便于共享后使用者对数据的处理,这个地方我在刚开始设计的时候有欠考虑。另外,刚开始在设计 covdet_keypoints_and_descriptors() 方法的时候,是在方法里读入图片然后在提取特征的,当时想得是想让这样一个特征提取器更加的方便使用(只需要传入图像待路径的文件名就可以处理),但后来发现其实根本没必要这么设计这个方法,这种蹩脚的方式使得后面在显示结果的时候,你需要再次读入图片,降低了程序的执行效率,因而改成了现在的这种传入已读入数据的方式。
上面除了三个重要的成员变量,两个序列化和反序列化的方法是极其重要的,序列化的目的使得上面三个重要的成员变量得以保存,这样可以避免当我们想再次聚类时又要提取特征的尴尬;反序列化使得我们可以读取保存的数据,因而,三个成员变量加两个方法都是必不可少的。
再谈 词袋模型的类类型 ,先看类定义:
class bowModel {
public:
bowModel(){}
bowModel(int _numWords,std::vector<siftDesctor>_imgFeatures, std::vector<std::vector<int>>_words):numWords(_numWords),imgFeatures(_imgFeatures),words(_words){}
int numNeighbors = 1
int numWords
std::vector<siftDesctor>imgFeatures
std::vector<std::vector<int>>words
cv::Mat centroids_opencvMat
cv::flann::Index opencv_buildKDTree(cv::Mat &centroids_opencvMat)
void Serialize(std::ofstream &outfile) const {
int imgFeatsSize = (int)imgFeatures.size()
outfile.write((char *)&imgFeatsSize, sizeof(int))
// 写入imgFeatures和words
for(int i = 0i <imgFeatsSizei++ ){
imgFeatures[i].Serialize(outfile)
outfile.write((char *)&(words[i][0]), sizeof(int) * imgFeatures[i].desctor.size())
}
}
static bowModel Deserialize(std::ifstream &ifs) {
bowModel BoW
int imgFeatsSize
ifs.read((char *)&imgFeatsSize, sizeof(int))
BoW.words.resize(imgFeatsSize)
for (int i = 0i <imgFeatsSizei++) {
// 读入imgFeatures
auto siftDesc = siftDesctor::Deserialize(ifs)
BoW.imgFeatures.push_back(siftDesc)
// 读入words
BoW.words[i].resize(siftDesc.desctor.size())
ifs.read((char *)&(BoW.words[i][0]), sizeof(int) * siftDesc.desctor.size())
}
return BoW
}
}
上面最重要的有三个东西,一是成员 std::vector<siftDesctor>imgFeatures ,另外是序列化和反序列化方法。对于每一个图片提取的特征,将 imageName 、 desctor 和 frame 通过实例化一个siftDesctor将其保存起来,这样我们将所有图片的siftDesctor实例用STL的vector保存下来,在序列化的时候,对每一个实例通过调用 SIFT特征提取的类类型 中定义的序列化方法将其保存下来,读取数据的时候,其过程基本就是原来的一个拟过程。通过这样设计这样两个 SIFT特征提取的类类型 和 词袋模型的类类型 ,在数据读写的时候,通过内外两重循环,内部循环完成一个实例的数据读写,外部循环完成所有图片实例的读写,使得我们可以比较优雅地完成图片的特征提取、数据保存以及读写。
对于数据读写,做过一番调研,一种是通过HDF5的方式,一种是通过BOOST库。HDF5很适合大数据量的保存,而且读写高效,但在C++里,写起来没有在Python里使用HDF5方便,BOOST也查阅过相应的资料,写起来也比较繁杂,所以最后选择了只用fstream来进行数据读写保存了,测了一下,数据读写还是比较高效的,所以暂且采用这种方案。
一、特征点(角点)匹配图像匹配能够应用的场合非常多,如目标跟踪,检测,识别,图像拼接等,而角点匹配最核心的技术就要属角点匹配了,所谓角点匹配是指寻找两幅图像之间的特征像素点的对应关系,从而确定两幅图像的位置关系。
角点匹配可以分为以下四个步骤:
1、提取检测子:在两张待匹配的图像中寻找那些最容易识别的像素点(角点),比如纹理丰富的物体边缘点等。
2、提取描述子:对于检测出的角点,用一些数学上的特征对其进行描述,如梯度直方图,局部随机二值特征等。检测子和描述子的常用提取方法有:sift,harris,surf,fast,agast,brisk,freak,brisk,brief/orb等。
3、匹配:通过各个角点雀迹的描述子来判断它们在两张图像中的对应关系,常用方法如 flann等。
4、消噪:去除错误匹配的外点,保留正确的匹配点。常用方法有KDTREE,BBF,Ransac,GTM等。
二、SIFT匹配方法的提出
为了排除因为图像遮挡和背景混乱而产生的无匹配关系的关键点,SIFT的作者Lowe提出了比较最近邻距离与次近邻距离的SIFT匹配方式:取一幅图像中的一个SIFT关键点,并找出其与另一幅图像中欧式距离最近的前两个关键点,在这两个关键点中,如果最近的距离除以次近的距离得到的比率ratio少于某个阈值T,则接受这一对匹配点。因为对于错误匹配,由于特征空间的高维性,相似的距离可能有大量其他的错误匹配,从而它的ratio值比慎渣较高。显然降低这个比例阈值T,SIFT匹配点数目会减少,但更加稳定,反之亦然。
Lowe推荐ratio的阈值为0.8,但作者对大量任顷孝并意存在尺度、旋转和亮度变化的两幅图片进行匹配,结果表明ratio取值在0. 4~0. 6 之间最佳,小于0. 4的很少有匹配点,大于0. 6的则存在大量错误匹配点,所以建议ratio的取值原则如下:
ratio=0. 4:对于准确度要求高的匹配;
ratio=0. 6:对于匹配点数目要求比较多的匹配;
ratio=0. 5:一般情况下。
三、常见的SIFT匹配代码
1、vlfeat中sift toolbox中的vl_ubcmatch.c使用的是普通的欧氏距离进行匹配(该SIFT代码贡献自Andrea
Vedaldi)。
2、Lowe的C++代码中使用的是欧氏距离,但是在matlab代码中为了加速计算,使用的是向量夹角来近似欧氏距离:先将128维SIFT特征向量归一化为单位向量(每个数除以平方和的平方根),然后点乘来得到向量夹角的余弦值,最后利用反余弦(acos函数)求取向量夹角。实验证明Lowe的办法正确率和耗时都很不错。
同样,也可以采用knnsearch函数求最近点和次近点:knnsearch采用euclidean距离时得到的结果与lowe采用的近似方法结果几乎一致,正好印证了模拟欧氏距离的效果。
3、Rob Hess的OpenSIFT采用了KDTREE来对匹配进行优化。
4、CSDN大神v_JULY_v实现了KDTREE+BBF对SIFT匹配的优化和消除错误匹配:从K近邻算法、距离度量谈到KD树、SIFT+BBF算法
- 结构之法 算法之道 - 博客频道 - CSDN.NET。
5、OpenCV中features2d实现的SIFT匹配有多种matcher:VectorDescriptorMatcher,BFMatcher(Brute-force descriptor matcher),FernDescriptorMatcher,OneWayDescriptorMatcher,FlannBasedMatcher 等等。目前只知道采用knnsearch,提供了多种距离度量方式,具体区别不懂。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)