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) 编辑 收藏 举报