一、概述
最近复现了几个点云相关的算法,复现完成后进行测试。由于算法的输入要求点云具有法线,所以一开始进行测试的时候,利用MeshLab对点云进行了法线的计算。但是后来在对算法封装的时候,在内部用了PCL的NormalEstimation来计算点云法线。封装完成后进行测试时发现输出的结果与之前测试相比差了一些。后来排查问题的时候发现原因就是出在法线计算上。在对比了PCL计算的法线与MeshLab计算的法线后得到结论:PCL计算的法线没有MeshLab计算的法线精准。所以问题来了,就要去看MeshLab的源码,把MeshLab中如何计算法线的功能提取出来,再与算法一起进行封装。大概花了一天半的时间,终于把MeshLab中计算法线的功能给提取了出来,这里做个记录,以后提取其余的功能也是一样。
二、定位计算法线的功能模块
一开始想着可能要编译整个MeshLab的源码了,但是来来回回搞了很久也没成功,所以我打算另辟蹊径,动起了大脑。
-
首先打开MeshLab,然后依次点击 Filters→Point Set→Compute normals for point setsFilters \rightarrow Point\ Set\rightarrow Compute\ normals\ for\ point\ setsFilters→Point Set→Compute normals for point sets,当最后把鼠标挪到 compute normals for point setscompute\ normals\ for\ point\ setscompute normals for point sets 选项上时,会弹出来一个小窗口,在小窗口的最下方标识了filter_meshing.dllfilter\_meshing.dllfilter_meshing.dll。如下图所示。
这个不就说明了所要找的计算法线的功能在filter_meshingfilter\_meshingfilter_meshing这个模块吗。 -
在MeshLab的源码(没有源码的话肯定就要先下载下来)中找到filter_meshingfilter\_meshingfilter_meshing文件夹,路径一般都是
.\src\meshlabplugins\filter_meshing
- 然后可以用QT或者有QT插件的VS打开里面的filter_meshing.profilter\_meshing.profilter_meshing.pro文件,打开后就能看到如下图所示的目录结构。
点进meshfilter.hmeshfilter.hmeshfilter.h,发现里面跟法线相关的就只有两个枚举类型 FP_NORMAL_EXTRAPOLATION, FP_NORMAL_SMOOTH_POINTCLOUDFP\_NORMAL\_EXTRAPOLATION,\ FP\_NORMAL\_SMOOTH\_POINTCLOUDFP_NORMAL_EXTRAPOLATION, FP_NORMAL_SMOOTH_POINTCLOUD。顾名思义,第一个枚举类型名字就是“法线推导”。然后到meshfilter.cppmeshfilter.cppmeshfilter.cpp中找所有与FP_NORMAL_EXTRAPOLATIONFP\_NORMAL\_EXTRAPOLATIONFP_NORMAL_EXTRAPOLATION相关的代码。 - 进入meshfilter.cppmeshfilter.cppmeshfilter.cpp中ctrl+F查找关键字,能够找到如下图所示部分。
可以看到,“Compute normals for point sets”这句话应该就是对应着第1步中的那个选项,可以确定计算法线的模块就在这里面八九不离十了。 - 继续在当前这个文件中查找关键字。
一直找到这里,看到了【PointCloudNormal】这个字样,立刻就反应过来,这里就是计算点云法线了!简单看一下代码就能知道,里面的 p.fittingAdjNum、p.smoothingIterNum、p.viewPoint、p.useViewPointp.fittingAdjNum、p.smoothingIterNum、p.viewPoint、p.useViewPointp.fittingAdjNum、p.smoothingIterNum、p.viewPoint、p.useViewPoint 这四个参数,正好就是对应着在MeshLab中计算法线的窗口里需要填的四个参数,如下图。
那么那个tri::PointCloudNormal<∗∗∗>::Compute()tri::PointCloudNormal<***>::Compute()tri::PointCloudNormal<∗∗∗>::Compute()就是计算法线的函数了。按住ctrl键点击ComputeComputeCompute就能定位到 vcglib 文件夹下,那么就说明,MeshLab中计算法线也只不过是调用的VCGLib的库而已。这样就好做多了,定位到此就结束了。
三、调用VCGLib库计算法线
3.1 包含VCGLib目录
首先用VS新建一个空项目,然后右击源文件,导入 ‘.\vcglib\wrap\ply\ply.cpp’ 文件,如果不导入这个文件的话,最后运行就会出现一堆如下图所示的错误。
然后新建一个 pointset_normal.cpp 文件,叫其他名字当然也行。
然后再项目属性中,将vcglib根目录以及eigenlib目录包含进来,如下图。注:如果不包含eigenlib目录,编译时可能会出现无法打开源文件 <Eigen/Eigen>之类的错误
这样环境配置就完成了。
3.2 计算法线
3.2.1 代码
一开始不知道怎么样用这个VCGLib,但是在 vcglib\apps\sample 文件夹下有很多示例代码,多跑几个代码看一看就知道怎么样用了。计算法线并保存到本地的代码如下所示。
#include<vcg/complex/complex.h> //这个一般都要有,否则编译会有很多错误
#include <vcg/complex/algorithms/update/color.h>
#include <vcg/complex/algorithms/pointcloud_normal.h>
#include <wrap/io_trimesh/import.h>
#include <wrap/io_trimesh/export.h>
class MyFace;
class MyVertex;
struct MyUsedTypes : public vcg::UsedTypes< vcg::Use<MyVertex>::AsVertexType, vcg::Use<MyFace>::AsFaceType> {};
class MyVertex : public vcg::Vertex< MyUsedTypes, vcg::vertex::Coord3f, vcg::vertex::Normal3f, vcg::vertex::Color4b, vcg::vertex::BitFlags > {};
class MyFace : public vcg::Face < MyUsedTypes, vcg::face::VertexRef, vcg::face::Normal3f, vcg::face::Color4b, vcg::face::BitFlags > {};
class MyMesh : public vcg::tri::TriMesh< std::vector<MyVertex>, std::vector<MyFace> > {};
int main()
{
MyMesh m;
vcg::tri::io::ImporterPLY<MyMesh>::Open(m, "00000000_00_world.ply");
vcg::tri::PointCloudNormal<MyMesh>::Param p;
p.fittingAdjNum = 40;
p.useViewPoint = true;
vcg::tri::PointCloudNormal<MyMesh>::Compute(m, p);
vcg::tri::io::PlyInfo pi;
pi.mask = vcg::tri::io::Mask::IOM_VERTNORMAL;
vcg::tri::io::ExporterPLY<MyMesh>::Save(m, "00000000_00_world_normal.ply", true, pi);
return 0;
}
3.2.2 结果展示并验证
找一个点云文件,先用MeshLab计算一下法线保存,然后再利用上面这个代码计算一下法线并进行对比。注意:在用两种方式计算法线时,参数要一致才能对比。
计算的法线如图,上下分别为MeshLab计算的法线和代码计算的法线,可以看出来是一模一样的。
进一步验证,利用记事本分别打开这两个点云文件,可以看到:
这两个的文件完全一致,说明我们调用的VCGLib计算得到的法线与MeshLab计算的法线是一致的。