VRML解析——C++处理CASIA-3D-Face数据库wrl文件

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

 

发表评论