虽然VRML语言已经被HTML5取代,但是仍有许多早期的三维模型文件采用wrl文件存储,并且偶尔会在转存成其它文件的过程中遇到问题,于是我们常常需要直接解析wrl文件。这里给需要解析wrl文件的同学指一条明路:Openvrml。
Openvrml本身调用了许多库,可以借助ARToolKit或者OGN进行安装,据说OGN比较容易一些,博主并没有使用过上述工具。而编译openvrml的过程极其痛苦,于是博主自己写了一个解析CASIA-3D-Face数据库中wrl文件的程序,仅供参考。这里附上中科院三维人脸数据库的链接,感谢中科院免费提供的资源。
这里特别提醒:真正完全解析wrl文件,还是需要使用一个树来存储各个节点的信息,然后遍历树来去除所需节点的信息,实现文件的完全解析。而这篇博文中所使用的代码并没有采用这种思路,博主处理的仅仅是特定的一种结构,节点类型都已经是确定的,仅仅将数据读出来,所以,仅作参考。
废话不多讲,看了上节对VRML文件的介绍,直接来看解析wrl文件的代码:
#include "stdafx.h" #include "glog/logging.h" #include "io.hpp" #include "acf.pb.h" #include <string> #include "wrl.h" #include "icp.h" #ifdef _DEBUG #pragma comment(lib, "debug/libglog_static.lib") #pragma comment(lib, "debug/libprotobuf.lib") #pragma comment(lib, "debug/libprotoc.lib") #else #pragma comment(lib, "release/libglog_static.lib") #pragma comment(lib, "release/libprotobuf.lib") #pragma comment(lib, "release/libprotoc.lib") #endif int main(int argc, _TCHAR* argv[]) { google::InitGoogleLogging((const char *)argv[0]); google::SetLogDestination(google::GLOG_INFO, "./log/myInfo"); google::SetStderrLogging(google::GLOG_INFO); std::string filename = "./default.prototxt"; acf::Opt opt; acf::ReadProtoFromTextFileOrDie(filename, &opt); LOG(INFO) << "file: " << opt.filepath() + opt.filename(); std::vector<std::vector<unsigned int>> surface_index; std::vector<cv::Scalar> surface_point, surface_color, surface_vector; wrl wrlFile(opt.filepath() + opt.filename()); wrlFile.read("point", surface_point); wrlFile.read("color", surface_color); wrlFile.read("vector", surface_vector); wrlFile.read("coordIndex", surface_index); CHECK_EQ(surface_color.size(), surface_point.size()); CHECK_EQ(surface_point.size(), surface_vector.size()) << "The color point size and the vertext point size should be equal"; CHECK_GT(surface_point.size(), wrlFile.maxIndex); LOG(INFO) << "point: " << surface_point.size() << " index label count: " << wrlFile.maxIndex+1 << "\nRead .wrl file succesful!"; icp opengl(opt.filename()); opengl.setVertex(surface_point, surface_color, surface_vector); opengl.setIndex(surface_index); opengl.paint(); return 0; google::ShutdownGoogleLogging(); return 0; }
博主没有给上面的代码添加注释,之作简单解释。代码前半部分在读取wrl文件并解析,提取出文件中各节点中的信息,如点云,颜色,视角等。代码后半部分是icp类,该类使用Opengl对模型进行绘制,这一节博客主要讲解wrl类解析wrl文件,关于使用opengl对模型进行绘制的代码将在下一节的博客中进行讲解。
代码中使用到了google的glog库,protobuf库,和用于绘制图形的glew opengl库。glog是谷歌的开源日志系统,使用glog有诸多好处,详情请google<glog>。此外,程序还是用了google的protobuf,protobuf是谷歌的开源项目,作用类似于xml和json,但由于protobuf简洁的格式和卓越的性能,已经被越来越多的程序员接受,作为程序协议 配置文件等的必备良品。在预处理的宏定义中添加如下宏,可以在静态库中使用google的库和opengl的库,就不需要在程序运行时添加相关的dll(动态链接库)了。
GLEW_STATIC FREEGLUT_STATIC GOOGLE_GLOG_DLL_DECL= GLOG_NO_ABBREVIATED_SEVERITIES
当然,你也可以选择不使用静态库,这样会使程序本体较小,但是请务必添加上述宏中最后一条,这条宏指明了程序使用glog,否则在编译时会报错。
好了,我们来直接看wrl类的代码吧:
#pragma once #include <string> #include <vector> #include <iostream> #include <fstream> #include <sstream> #ifdef __OPEN_CV #include <opencv2\opencv.hpp> #else #include "Scalar.h" #endif #include "glog\logging.h" class wrl{ public: wrl(std::string); ~wrl(); int read(const char*, cv::Scalar &); int read(const char*, std::vector<cv::Scalar> &); int read(const char*, std::vector<std::vector<unsigned int>> &); unsigned int maxIndex=0; private: cv::Scalar point; std::ifstream fin; double num; std::string str; std::stringstream ss, sst; std::string name; };
public下定义了外部访问的接口,在类构造时打开文件,使用read来读取指定节点的数据,节点名是第一个参数,读出的数据会被存入第二个参数(容器)中。由于在处理CASIA数据库中的wrl文件时,需要读取两类节点,于是对read函数重载,第二个read读取的信息是多边形,每个多边形被存储在一个vector中,多边形的定点被存储在第二级(嵌套在里面的)vector容器中。
下面来看wrl类的实现:
#include "wrl.h" std::string& replace_all(std::string& str, const std::string& old_value, const std::string& new_value) { while (true) { std::string::size_type pos(0); if ((pos = str.find(old_value)) != std::string::npos) str.replace(pos, old_value.length(), new_value); else break; } return str; } wrl::wrl(std::string filename){ fin.open(filename); if (!fin){ int *file = NULL; CHECK_NOTNULL(file); } getline(fin, name); CHECK_EQ(name, "#VRML V2.0 utf8") << filename << " is not a .wrl file!"; } wrl::~wrl(){ fin.close(); } int wrl::read(const char* node_name, std::vector<cv::Scalar> & surface){ bool record[3] = { false, false, false }; int point_index = 0; while (getline(fin, name)){ if (name.find_first_of("#") <= name.length()){ name.erase(name.find_first_of("#"), name.length()); } replace_all(name, ",", " "); name.erase(0, name.find_first_not_of(" \t,")); name.erase(name.find_last_not_of(" \t,{}") + 1); //LOG(INFO) << name; ss.clear(); ss << name; if (record[2] == true) break; while (ss >> str){ //LOG(INFO) << str; if ((str == node_name) || record[0]){ record[0] = true; if (str.find_first_of("[") < str.length()){ if ((str == "[")){ record[1] = true; continue; } record[1] = true; str.erase(str.find_first_of("["), str.find_first_of("[")); } if (record[1]){ if ((str == "]")){ record[2] = true; break; } sst.clear(); sst << str; sst >> num; point.val[point_index % 3] = num; point_index++; if (point_index == 3){ surface.push_back(point); //LOG(INFO) << surface.size(); point_index = 0; } } } } } LOG(INFO) << "detected " << node_name << " point count: " << surface.size(); return surface.size(); } int wrl::read(const char* node_name, std::vector<std::vector<unsigned int>> & surface){ bool record[4] = { false, false, false, false }; int point_index = 0, no; std::vector<unsigned int> tri; while (getline(fin, name)){ if (name.find_first_of("#") <= name.length()){ name.erase(name.find_first_of("#"), name.length()); } name.erase(0, name.find_first_not_of(" \t,")); name.erase(name.find_last_not_of(" \t,{}") + 1); // LOG(INFO) << name; ss.clear(); ss << name; if (record[2]) break; while (ss >> str){ // LOG(INFO) << str; str.erase(str.find_last_not_of(",{}") + 1); if ((str == node_name) || record[0]){ record[0] = true; if (str.find_first_of("[") < str.length()){ if ((str == "[")){ record[1] = true; continue; } record[1] = true; str.erase(str.find_first_of("["), str.find_first_of("[")+1); } // LOG(INFO) << str; if (record[1]){ if ((str == "]")){ record[2] = true; break; } sst.clear(); sst << str; sst >> no; if (no >= 0){ if (no > maxIndex){ maxIndex = no; } tri.push_back(no); } else if(no == -1){ surface.push_back(tri); //LOG(INFO) << tri[0] << "," << tri[1] << "," << tri[2]; tri.clear(); } else{ continue; } } } } } LOG(INFO) << "detected " << node_name << " point count: " << surface.size(); return maxIndex; } int wrl::read(const char* node_name, cv::Scalar & point){ bool record[3] = { false, false, false }; int index = 0; double data; while (getline(fin, name)){ if (name.find_first_of("#") <= name.length()){ name.erase(name.find_first_of("#"), name.length()); } replace_all(name, ",", " "); name.erase(0, name.find_first_not_of(" \t,")); name.erase(name.find_last_not_of(" \t,{}") + 1); //LOG(INFO) << name; ss.clear(); ss << name; if (record[2] == true) break; while (ss >> str){ //LOG(INFO) << str; if ((str == node_name) || record[0]){ if (str == node_name){ record[0] = true; continue; } sst.clear(); sst << str; sst >> data; point.val[index] = data; index++; if (index == 3){ record[2] = true; break; } } } } //LOG(INFO) << "detected " << node_name << " point count: " << surface.size(); return 0; }
这个类的实现就不多介绍了,通过字符串的处理,搜索节点名,然后对结点名后的数字进行解析,直到节点结束,数据都被保存在容器中了。
点云数据读取进来之后,就可以被OpenGL绘制出来,关于OpenGL绘制的部分将在下一节进行讲解。
See You Next Chaper!