虽然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!