VTK学习之激光点云动态库封装(排水管道)

        目前各行各业都应用了激光点云,包括目前非常火的自动驾驶行业,本人目前在排水管道检测行业,因此封装了应用于排水管道的点云库。激光雷达测得点云数据存储下来后,解析出坐标点,然后传递到函数入口中,即可获得三维点云模型。

        处理点云数据的工具有很多,这里没有直接采用OpenGL和D3D,而选择了封装得比较好,容易上手的vtk,本示例是基于vtk9.0+vs2019,封装好的库使用C#进行调用测试。废话不多说,直接上代码:

        首先是vtk.h头文件,这里将要用的vtk头文件都包含进来。

#pragma once
#include "vtkSmartPointer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderer.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkInteractorStyleTrackballCamera.h"
#include "vtkCylinderSource.h"
#include "vtkSphereSource.h"
#include "vtkPolyDataMapper.h"
#include "vtkActor.h"
#include "vtkBMPReader.h"
#include "vtkJPEGReader.h"
#include "vtkTexture.h"
#include "vtkLight.h"
#include "vtkCamera.h"
#include <vtkLine.h>
#include <vtkCellArray.h>
#include <vtkTubeFilter.h>
#include <vtkLineSource.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkProperty.h>
#include <vtkConeSource.h>
#include <vtkCubeSource.h>
#include <vtkStructuredPointsReader.h>
#include <vtkSphereSource.h>
#include <vtkMarchingCubes.h>
#include <vtkXMLPolyDataReader.h>//加载模型数据
#include <vtkTextureMapToCylinder.h>//采用圆柱作为中介
#include <vtkOrientationMarkerWidget.h>
#include <vtkAxesActor.h>
#include <vtkImageData.h> 
#include <vtkImageViewer2.h>
#include <vtkTransformTextureCoords.h>
#include <vtkPoints.h>
#include <vtkLine.h>
#include <vtkCellArray.h>
#include <vtkPolyDataWriter.h>
#include <vtk3DSImporter.h>
#include <vtkStructuredPoints.h>
#include <vtkStructuredPointsReader.h>
#include <vtkVolumeTexture.h>
#include <vtkColorTransferFunction.h>
#include <vtkPiecewiseFunction.h>
#include <vtkImageShiftScale.h>
#include <vtkOpenGLGPUVolumeRayCastMapper.h>
#include <vtkVolumeProperty.h>
#include <vtkTransform.h>
#include <vtkUnsignedCharArray.h> 
#include <vtkCellData.h>
#include <vtkIdTypeArray.h>

//解决no override found for ""
#include "vtkAutoInit.h"
#include <vtkImageResliceMapper.h>

VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingFreeType);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);


vtkSmartPointer< vtkOrientationMarkerWidget> widget;
vtkSmartPointer<vtkRenderWindow> renderWindow;
vtkSmartPointer<vtkRenderer> renderer;
vtkSmartPointer<vtkActor> actor;
vtkSmartPointer<vtkPolyData> polyData;

int diameter = 600;   //管道直径
int totalFrameCnt = 0;//总帧数

        然后是外部调用头文件ZYPointCloudLib.h,声明函数:

#pragma once
#define NOMINMAX 

#include <string>
#include <wtypes.h> 
#include <vector>

/// <summary>
/// 雷达帧数据
/// </summary>
struct FrameData
{
    float* points;   //点坐标x,y,z循环(x为行走方向,为当前距离值)
    int pointsLen;      //points指针数组长度
    float distance;  //当前距离(单位:m)
    float circleX;   //拟合圆心坐标X(单位:mm)
    float circleY;   //拟合圆心坐标Y(单位:mm)
};

/// <summary>
/// 视图
/// </summary>
enum  CamOrientation
{
    Front = 0,
    Back,
    Left,
    Right,
    Up,
    Down,
    Axonometric
};

/// <summary>
/// 绑定显示控件句柄
/// </summary>
/// <param name="hwd">控件句柄</param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
extern "C" _declspec(dllexport) void* _stdcall BindingHandle(HWND hwd, int width, int height);

/// <summary>
/// 传入数据
/// </summary>
/// <param name="frameDatas">帧数据</param>
/// <param name="frameCount">帧总数</param>
/// <returns></returns>
extern "C" _declspec(dllexport) int _stdcall EntryData(FrameData * frameDatas, int frameCount);

/// <summary>
/// 设置管道参数
/// </summary>
/// <param name="pipeDiameter">管道直径</param>
/// <returns></returns>
extern "C" _declspec(dllexport) int _stdcall SetPipePara(int pipeDiameter);

/// <summary>
/// 设置视角方向
/// </summary>
/// <param name="t_camOrientation">视角方向</param>
/// <returns></returns>
extern "C" _declspec(dllexport) void _stdcall SetCameraOrientation(CamOrientation t_camOrientation);


/// <summary>
/// 选择帧
/// </summary>
/// <param name="index">index(0-max)</param>
/// <returns></returns>
extern "C" _declspec(dllexport) void _stdcall SelectFrame(int index);

         ZYPointCloudLib.cpp:

// ZYPointCloudLib.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include "ZYPointCloudLib.h"
#include "vtk.h"
 
/// <summary>
/// 绑定控件
/// </summary>
/// <param name="hwd"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <returns></returns>
void* _stdcall BindingHandle(HWND hwd, int width, int height)
{
    if (renderWindow == NULL)
    {
        renderWindow = vtkSmartPointer<vtkRenderWindow> ::New();
        renderer = vtkSmartPointer<vtkRenderer> ::New();
        actor = vtkSmartPointer<vtkActor> ::New();
        renderWindow->SetParentId(hwd);
        renderWindow->SetSize(width, height);
       
        renderer->AddActor(actor);
        renderWindow->AddRenderer(renderer);

        //获取渲染窗口上发生的鼠标,键盘,事件事件
        vtkSmartPointer<vtkRenderWindowInteractor> iren = vtkSmartPointer<vtkRenderWindowInteractor>::New();
        iren->SetRenderWindow(renderWindow);
        
        //设置鼠标交互方式
        vtkSmartPointer<vtkInteractorStyleTrackballCamera> style = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
        iren->SetInteractorStyle(style);
        iren->Initialize();

        //左下角坐标系
        vtkSmartPointer< vtkAxesActor> axes = vtkSmartPointer< vtkAxesActor>::New();
        //以Widget方式,在左下角的视口中显示坐标系,可进行鼠标交互
        if (widget == NULL)
            widget = vtkSmartPointer< vtkOrientationMarkerWidget>::New();
        widget->SetOutlineColor(0.9300, 0.5700, 0.1300);
        widget->SetOrientationMarker(axes);
        widget->SetInteractor(iren);
        widget->SetViewport(0.0, 0.0, 0.2, 0.2);
        widget->SetEnabled(1);     //使可用(显示)
        widget->InteractiveOff();  //禁止拖动    
    }  
    else
    {
        renderWindow->SetParentId(hwd);
        renderWindow->SetSize(width, height);
  
    }
 
    renderWindow->Render();
    return renderWindow;
}

/// <summary>
/// 传入数据
/// </summary>
/// <param name="frameDatas"></param>
/// <param name="frameCount"></param>
/// <returns></returns>
int _stdcall EntryData(FrameData* frameDatas, int frameCount)
{
    if (polyData == NULL)
        polyData  = vtkSmartPointer<vtkPolyData>::New();
 
    vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New();
    vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
    vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();

    totalFrameCnt = frameCount;
    for (int i = 0; i < frameCount; i++)
    {
        int cnt = frameDatas[i].pointsLen/3;
        vtkIdType* idtype =new vtkIdType[cnt];
        for (int j = 0; j < cnt; j++)
        {
            float x = frameDatas[i].points[j * 3];
            float y = frameDatas[i].points[j * 3 + 1];
            float z = frameDatas[i].points[j * 3 + 2];
            idtype[j] = points->InsertNextPoint(x, y, z);
        }
        cells->InsertNextCell(cnt, idtype);   //第一个参数值标是cell中点的个数,第二个参数指向那些点的坐标数据。          
        delete[] idtype;
        idtype = nullptr;
    }
   
    polyData->SetPoints(points);//SetPoints设置几何数据点的坐标;
    polyData->SetVerts(cells);  //SetVerts将vtkCellArray按照离散点拓扑结构处理;设置定义顶点的单元阵列。


    mapper->SetInputData(polyData);
   
    actor->SetMapper(mapper);
    actor->GetProperty()->SetRepresentationToWireframe();
    renderer->ResetCamera();
    renderWindow->Render();
    return 1;
}

int _stdcall SetPipePara(int pipeDiameter)
{
    diameter = pipeDiameter;
    return 1;
}

int _stdcall SetMarkArray(DrawMarks drawMarks)
{
    return 0;
}


int _stdcall SetMarksVisible(bool visible)
{
    return 0;
}


/// <summary>
/// 设置视图方向
/// </summary>
/// <param name="t_camOrientation"></param>
void _stdcall SetCameraOrientation(CamOrientation t_camOrientation)
{
    if (renderer == NULL)
        return;
     vtkCamera* camera = renderer->GetActiveCamera();
     if (renderer->GetActiveCamera())
     {
         switch (t_camOrientation)
         {
             case  CamOrientation::Left:
             {
                 camera->SetPosition(1, 0, 0); //相机位置
                 break;
             }
             case  CamOrientation::Right:
             {
                 camera->SetPosition(-1, 0, 0);
                 break;
             }
             case  CamOrientation::Front:
             {
                 camera->SetPosition(0, -1, 0);
                 break;
             }
             case  CamOrientation::Back:
             {
                 camera->SetPosition(0, 1, 0);
                 break;
             }
             case  CamOrientation::Up:
             {
                 camera->SetPosition(0, 0, -1);
                 break;
             }
             case  CamOrientation::Down:
             {
                 camera->SetPosition(0, 0, 1);
                 break;
             }
         }
         camera->SetViewUp(0, 0, 1);    //设置相机朝上方向  
         camera->SetFocalPoint(0, 0, 0);//相机焦点:从相机看向的点
         renderer->SetActiveCamera(camera);
         renderer->ResetCamera();
         renderWindow->Render();
     }
}


/// <summary>
/// 选择帧
/// </summary>
/// <param name="index">index(0-max)</param>
/// <returns></returns>
void _stdcall SelectFrame(int index)
{
    unsigned char red[3]{ 255,0,0 };
    unsigned char white[3]{ 255,255,255 };
    vtkNew<vtkUnsignedCharArray> cellColor;
    cellColor->SetNumberOfComponents(3);
    int n = polyData->GetNumberOfCells();
    for (int i = 0; i < n; i++)
    {
        vtkCell* cell = polyData->GetCell(i);
        int cnt = cell->GetPoints()->GetNumberOfPoints();
        if (i == index)
            cellColor->InsertNextTypedTuple(red);
        else
            cellColor->InsertNextTypedTuple(white);
    }
    polyData->GetCellData()->SetScalars(cellColor);
    renderer->Render();
    renderWindow->Render();
}
 
 
 

        编译生成 ZYPointCloudLib.dll。接下来就是测试了,使用winform测试:

        先导入C++函数:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Sample
{
  
    public class PointCloudLib
    {
        /// <summary>
        /// 雷达帧数据
        /// </summary>
        public struct FrameData
        {
            public IntPtr points;     //点坐标x,y,z循环(x为行走方向,为当前距离值)
            public int pointsLen;      //points数组长度
            public float circleX;    //拟合圆心坐标X(单位:mm)
            public float circleY;    //拟合圆心坐标Y(单位:mm)
            public float radius;     //拟合圆半径(单位:mm)
        };

        /// <summary>
        /// 视图
        /// </summary>
        public enum CamOrientation
        {
            Front,
            Back,
            Left,
            Right,
            Up,
            Down,
            Axonometric
        };

        public const string dllPath = "ZYPointCloudLib.dll";

        [DllImport(dllPath, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
        public static extern IntPtr BindingHandle( IntPtr hwd, int width, int height);


        [DllImport(dllPath, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
        public static extern int EntryData(FrameData[] frameDatas, int frameCount);

        [DllImport(dllPath, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
        public static extern int SetPipePara(int pipeDiameter);

        [DllImport(dllPath, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
        public static extern void SetCameraOrientation(CamOrientation  camOrientation);


        [DllImport(dllPath, CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
        public static extern void SelectFrame(int index); 

    }
}

        然后在Form1创建一个PictureBox和Button,如下图:

后台代码:

   public partial class Form1 : Form
    {
        IntPtr vtkPtr;
        int curIndex = 0;
        public Form1()
        {
            InitializeComponent();
            cmbViewAngle.SelectedIndex = 0;
            vtkPtr = PointCloudLib.BindingHandle(pictureBox1.Handle, pictureBox1.Width, pictureBox1.Height);
        }


        /// <summary>
        /// 打开文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button1_Click(object sender, EventArgs e)
        {
            int ret = -1; ;
            OpenFileDialog fileDia = new OpenFileDialog();
            fileDia.Filter = "*.rad|*.rad";
            if (fileDia.ShowDialog() == DialogResult.OK)
            {
                ILidarFile lidarFile =  FileManagerLib.Lidar.LidarFile.GetInstance(fileDia.FileName);
                LidData lidData = new LidData();
                bool result = lidarFile.ReadAllData(true, out lidData);
                if(result)
                {
                    PointCloudLib.FrameData[] frameDatas = new PointCloudLib.FrameData[lidData.frameDatas.Length];
                    for (int i = 0; i < lidData.frameDatas.Length; i++)
                    {
                        List<PointF> CoordinatePoints = DataProcess.DataProcessing(lidData.frameDatas[i].FrameData, 0, lidData.frameDatas[i].ValidDataCount, 0, 360, true);
                        float[] points = new float[CoordinatePoints.Count * 3];
                        frameDatas[i].pointsLen = CoordinatePoints.Count * 3;
                        int index = 0;
                        for (int j = 0; j < CoordinatePoints.Count; j++)
                        {
                            points[index] = lidData.frameDatas[i].Distance * 1000;
                            points[index + 1] = CoordinatePoints[j].X;
                            points[index + 2] = CoordinatePoints[j].Y;
                            index += 3;
                        }
                        frameDatas[i].points = Marshal.AllocHGlobal(CoordinatePoints.Count * 3* sizeof(float));
                        Marshal.Copy(points, 0, frameDatas[i].points, points.Length);
                        frameDatas[i].circleX = lidData.frameDatas[i].CircleX;
                        frameDatas[i].circleY = lidData.frameDatas[i].CircleY;
                        frameDatas[i].radius = lidData.frameDatas[i].FittingCircleDia / 2;
                    }
                    ret = PointCloudLib.SetPipePara(lidData.headInfo.PipeSize[0]);

                    ret = PointCloudLib.EntryData(frameDatas, frameDatas.Length);
                }
                else
                {
                    MessageBox.Show("打开文件失败!");

                }

               
                

            }
        }


        /// <summary>
        /// 窗体大小变化
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Form1_Resize(object sender, EventArgs e)
        {
            vtkPtr = PointCloudLib.BindingHandle(pictureBox1.Handle, pictureBox1.Width, pictureBox1.Height);



        }

        /// <summary>
        /// 选择视角
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmbViewAngle_SelectedIndexChanged(object sender, EventArgs e)
        {
            int index = this.cmbViewAngle.SelectedIndex;
            if(index>=0)
                PointCloudLib.SetCameraOrientation((PointCloudLib.CamOrientation)index);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnFront_Click(object sender, EventArgs e)
        {
            curIndex--;
            PointCloudLib.SelectFrame(curIndex);
        }

        private void btnNext_Click(object sender, EventArgs e)
        {
            curIndex++;
            PointCloudLib.SelectFrame(curIndex);
        }


    }

将依赖的VTK全部复制到运行目录下,然后打开雷达文件测试:

posted on 2022-03-21 21:51  FreshBreezes  阅读(488)  评论(0编辑  收藏  举报

导航