前言
Meshlab是一个非常实用的工具,无论是在点云处理还是纹理贴图方面都得到了非常广泛的使用,并且其算法的底层主要是使用vcglib头文件库来实现。在某些特定的情况下,我们只需要使用Meshlab中的部分功能而不是全部功能(比如不需要显示功能),此时我们就需要截取Meshlab的部分功能来满足我们的需要。
值得注意的是,PCL是另外一个被广泛使用的点云处理开源代码,但是此处并没有使用它,原因是由于PCL的环境配置十分复杂,令人发指的复杂;而vcglib是一个头文件库,只需要包含头文件就可以完成项目的配置。
下文中,主要通过截取Meshlab中的点云法向量计算工具为例,逐步介绍我是如何完成此功能的截取。
准备工作
下载Meshlab和vcglib的源代码,其地址分别如下:
https://github.com/cnr-isti-vclab/meshlab
https://github.com/cnr-isti-vclab/vcglib
解压后使用文本编辑软件打开,此处我使用VS Code(因为需要搜索寻找代码所在处)
代码定位
最重要的工作就是如何找到代码所在地,即明白Meshlab是如何实现本文中对应的功能。此处我采用了一种非常逗比的方式来找代码的位置,即通过查看功能字符串“Compute Normals for Point Sets”来定位代码,在meshlab代码中搜索结果如下:
case FP_NORMAL_EXTRAPOLATION : return tr("Compute normals for point sets");
看来宏FP_NORMAL_EXTRAPOLATION与法向量计算相关,再在meshlab代码搜索一次:
case FP_NORMAL_EXTRAPOLATION :
{
tri::PointCloudNormal<CMeshO>::Param p;
p.fittingAdjNum = par.getInt("K");
p.smoothingIterNum = par.getInt("smoothIter");
p.viewPoint = par.getPoint3m("viewPos");
p.useViewPoint = par.getBool("flipFlag");
tri::PointCloudNormal<CMeshO>::Compute(m.cm, p,cb);
} break;
BINGO!到目前我就找到了Meshlab的法向量计算实现方法。
分析上文中的代码,发现了一些奇怪的类CMesh0,因此接下来需要找一下该类的定义方法,通过meshlab代码搜索发现其定义在ml_mesh_types.h文件中,贴出部分代码如下:
#ifndef __ML_MESH_TYPE_H
#define __ML_MESH_TYPE_H
#include <vcg/complex/complex.h>
#ifndef MESHLAB_SCALAR
#error "Fatal compilation error: MESHLAB_SCALAR must be defined"
#endif
typedef MESHLAB_SCALAR Scalarm;
typedef vcg::Point2<MESHLAB_SCALAR> Point2m;
typedef vcg::Point3<MESHLAB_SCALAR> Point3m;
typedef vcg::Point4<MESHLAB_SCALAR> Point4m;
typedef vcg::Plane3<MESHLAB_SCALAR> Plane3m;
typedef vcg::Segment2<MESHLAB_SCALAR> Segment2m;
typedef vcg::Segment3<MESHLAB_SCALAR> Segment3m;
typedef vcg::Box3<MESHLAB_SCALAR> Box3m;
typedef vcg::Matrix44<MESHLAB_SCALAR> Matrix44m;
typedef vcg::Matrix33<MESHLAB_SCALAR> Matrix33m;
typedef vcg::Shot<MESHLAB_SCALAR> Shotm;
typedef vcg::Similarity<MESHLAB_SCALAR> Similaritym;
...
...
class CMeshO : public vcg::tri::TriMesh< vcg::vertex::vector_ocf<CVertexO>, vcg::face::vector_ocf<CFaceO> >
{
public :
int sfn; //The number of selected faces.
int svn; //The number of selected vertices.
Matrix44m Tr; // Usually it is the identity. It is applied in rendering and filters can or cannot use it. (most of the filter will ignore this)
const Box3m &trBB()
{
static Box3m bb;
bb.SetNull();
bb.Add(Tr,bbox);
return bb;
}
};
#endif
简单阅读一下代码,发现还需要定义一下MESHLAB_SCALAR,在meshlab代码搜索得到其被定义为float,因此在上边的代码中添加一句定义代码即可:
typedef float MESHLAB_SCALAR;
代码重构
由于所有需要的代码都已经完全准备好,接下来就需要构建一个工程来计算点云的法向量。下边以VS2015为例,当然CMake也是一样的,只需要加一个include_path就好了。
首先创建一个空的工程,然后新建一个pointset_normal.cpp,把CMesh0定义拷贝进去,然后将法向量计算的代码放到main函数中。
然后在include path中添加vcglib的根目录,大概如下:
但是此时还缺少点云的读取和保存方式,此处可以翻看一下vcglib/apps/sample下边的代码即可,我贴一下我找到的读取相关的代码:
// input output
#include <wrap/io_trimesh/import_ply.h>
#include <wrap/io_trimesh/export_ply.h>
//open a mesh
tri::io::ImporterPLY<CMeshO>::Open(m,in_file.c_str());
//save a mesh
tri::io::ExporterPLY<CMeshO>::Save(m,out_file.c_str());
注意需要手动添加vcglib/wrap/ply/plylib.cpp,工程目录如下:
完成以上操作后,还有一个地方需要注意,ExporterPLY默认并不会保存法向量,我阅读了该函数的定义然后添加了一些代码来保存法向量:
tri::io::PlyInfo pi;
pi.mask = tri::io::Mask::IOM_VERTNORMAL;
//save a mesh
tri::io::ExporterPLY<CMeshO>::Save(m,out_file.c_str(),true, pi);
因此我们终于完成了Meshlab的功能截取。
实验结果
程序调用
mesh_normal.exe" cloud.ply cloud_normal.ply
结果展示
完整代码
/****************************************************************************
* VCGLib o o *
* Visual and Computer Graphics Library o o *
* _ O _ *
* Copyright(C) 2004-2016 \/)\/ *
* Visual Computing Lab /\/| *
* ISTI - Italian National Research Council | *
* \ *
* All rights reserved. *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt) *
* for more details. *
* *
****************************************************************************/
/*! \file trimesh_smooth.cpp
\ingroup code_sample
\brief the minimal example of using the lib
This file contain a minimal example of the library
*/
#include<vcg/complex/complex.h>
#include<vcg/complex/algorithms/pointcloud_normal.h>
// input output
#include <wrap/io_trimesh/import_ply.h>
#include <wrap/io_trimesh/export_ply.h>
using namespace vcg;
#include <iostream>
using namespace std;
typedef double MESHLAB_SCALAR;
typedef MESHLAB_SCALAR Scalarm;
typedef vcg::Point2<MESHLAB_SCALAR> Point2m;
typedef vcg::Point3<MESHLAB_SCALAR> Point3m;
typedef vcg::Point4<MESHLAB_SCALAR> Point4m;
typedef vcg::Plane3<MESHLAB_SCALAR> Plane3m;
typedef vcg::Segment2<MESHLAB_SCALAR> Segment2m;
typedef vcg::Segment3<MESHLAB_SCALAR> Segment3m;
typedef vcg::Box3<MESHLAB_SCALAR> Box3m;
typedef vcg::Matrix44<MESHLAB_SCALAR> Matrix44m;
typedef vcg::Matrix33<MESHLAB_SCALAR> Matrix33m;
typedef vcg::Shot<MESHLAB_SCALAR> Shotm;
typedef vcg::Similarity<MESHLAB_SCALAR> Similaritym;
template<typename T>
struct MeshLabScalarTest
{
};
template<>
struct MeshLabScalarTest<float>
{
static const char* floatingPointPrecision() { return "fp"; }
static const char* floatingPointPrecisionIOToken() { return "%f"; }
static bool doublePrecision() { return false; }
};
template<>
struct MeshLabScalarTest<double>
{
static const char* floatingPointPrecision() { return "dp"; }
static const char* floatingPointPrecisionIOToken() { return "%lf"; }
static bool doublePrecision() { return true; }
};
namespace vcg
{
namespace vertex
{
template <class T> class Coord3m : public Coord<vcg::Point3<MESHLAB_SCALAR>, T> {
public: static void Name(std::vector<std::string> & name) { name.push_back(std::string("Coord3m")); T::Name(name); }
};
template <class T> class Normal3m : public Normal<vcg::Point3<MESHLAB_SCALAR>, T> {
public: static void Name(std::vector<std::string> & name) { name.push_back(std::string("Normal3m")); T::Name(name); }
};
template <class T> class CurvatureDirmOcf : public CurvatureDirOcf<CurvatureDirTypeOcf<MESHLAB_SCALAR>, T> {
public: static void Name(std::vector<std::string> & name) { name.push_back(std::string("CurvatureDirmOcf")); T::Name(name); }
};
template <class T> class RadiusmOcf : public RadiusOcf<MESHLAB_SCALAR, T> {
public: static void Name(std::vector<std::string> & name) { name.push_back(std::string("RadiusmOcf")); T::Name(name); }
};
}//end namespace vertex
namespace face
{
template <class T> class Normal3m : public NormalAbs<vcg::Point3<MESHLAB_SCALAR>, T> {
public: static void Name(std::vector<std::string> & name) { name.push_back(std::string("Normal3m")); T::Name(name); }
};
template <class T> class CurvatureDirmOcf : public CurvatureDirOcf<CurvatureDirOcfBaseType<MESHLAB_SCALAR>, T> {
public: static void Name(std::vector<std::string> & name) { name.push_back(std::string("CurvatureDirdOcf")); T::Name(name); }
};
}//end namespace face
}//end namespace vcg
// Forward declarations needed for creating the used types
class CVertexO;
class CEdgeO;
class CFaceO;
// Declaration of the semantic of the used types
class CUsedTypesO : public vcg::UsedTypes < vcg::Use<CVertexO>::AsVertexType,
vcg::Use<CEdgeO >::AsEdgeType,
vcg::Use<CFaceO >::AsFaceType > {};
// The Main Vertex Class
// Most of the attributes are optional and must be enabled before use.
// Each vertex needs 40 byte, on 32bit arch. and 44 byte on 64bit arch.
class CVertexO : public vcg::Vertex< CUsedTypesO,
vcg::vertex::InfoOcf, /* 4b */
vcg::vertex::Coord3m, /* 12b */
vcg::vertex::BitFlags, /* 4b */
vcg::vertex::Normal3m, /* 12b */
vcg::vertex::Qualityf, /* 4b */
vcg::vertex::Color4b, /* 4b */
vcg::vertex::VFAdjOcf, /* 0b */
vcg::vertex::MarkOcf, /* 0b */
vcg::vertex::TexCoordfOcf, /* 0b */
vcg::vertex::CurvaturefOcf, /* 0b */
vcg::vertex::CurvatureDirmOcf, /* 0b */
vcg::vertex::RadiusmOcf /* 0b */
> {
};
// The Main Edge Class
class CEdgeO : public vcg::Edge<CUsedTypesO,
vcg::edge::BitFlags, /* 4b */
vcg::edge::EVAdj,
vcg::edge::EEAdj
> {
};
// Each face needs 32 byte, on 32bit arch. and 48 byte on 64bit arch.
class CFaceO : public vcg::Face< CUsedTypesO,
vcg::face::InfoOcf, /* 4b */
vcg::face::VertexRef, /*12b */
vcg::face::BitFlags, /* 4b */
vcg::face::Normal3m, /*12b */
vcg::face::QualityfOcf, /* 0b */
vcg::face::MarkOcf, /* 0b */
vcg::face::Color4bOcf, /* 0b */
vcg::face::FFAdjOcf, /* 0b */
vcg::face::VFAdjOcf, /* 0b */
vcg::face::CurvatureDirmOcf, /* 0b */
vcg::face::WedgeTexCoordfOcf /* 0b */
> {};
class CMeshO : public vcg::tri::TriMesh< vcg::vertex::vector_ocf<CVertexO>, vcg::face::vector_ocf<CFaceO> >
{
public:
int sfn; //The number of selected faces.
int svn; //The number of selected vertices.
Matrix44m Tr; // Usually it is the identity. It is applied in rendering and filters can or cannot use it. (most of the filter will ignore this)
const Box3m &trBB()
{
static Box3m bb;
bb.SetNull();
bb.Add(Tr, bbox);
return bb;
}
};
int main(int argc, char *argv[])
{
string in_file = argv[1];
string out_file = argv[2];
CMeshO m;
//open a mesh
tri::io::ImporterPLY<CMeshO>::Open(m,in_file.c_str());
tri::PointCloudNormal<CMeshO>::Param p;
//p.fittingAdjNum = 10;
p.smoothingIterNum = 2;
p.useViewPoint = true;
tri::PointCloudNormal<CMeshO>::Compute(m, p);
tri::io::PlyInfo pi;
pi.mask = tri::io::Mask::IOM_VERTNORMAL;
//save a mesh
tri::io::ExporterPLY<CMeshO>::Save(m,out_file.c_str(),true, pi);
return 0;
}