目前各行各业都应用了激光点云,包括目前非常火的自动驾驶行业,本人目前在排水管道检测行业,因此封装了应用于排水管道的点云库。激光雷达测得点云数据存储下来后,解析出坐标点,然后传递到函数入口中,即可获得三维点云模型。
处理点云数据的工具有很多,这里没有直接采用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#include #include #include #include #include #include #include #include #include #include #include #include //加载模型数据 #include //采用圆柱作为中介 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //解决no override found for "" #include "vtkAutoInit.h" #include VTK_MODULE_INIT(vtkRenderingOpenGL2); VTK_MODULE_INIT(vtkInteractionStyle); VTK_MODULE_INIT(vtkRenderingFreeType); VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2); vtkSmartPointer< vtkOrientationMarkerWidget> widget; vtkSmartPointer renderWindow; vtkSmartPointer renderer; vtkSmartPointer actor; vtkSmartPointer polyData; int diameter = 600; //管道直径 int totalframeCnt = 0;//总帧数
然后是外部调用头文件ZYPointCloudLib.h,声明函数:
#pragma once #define NOMINMAX #include#include #include /// /// 雷达帧数据 /// struct frameData { float* points; //点坐标x,y,z循环(x为行走方向,为当前距离值) int pointsLen; //points指针数组长度 float distance; //当前距离(单位:m) float circleX; //拟合圆心坐标X(单位:mm) float circleY; //拟合圆心坐标Y(单位:mm) }; ////// 视图 /// enum CamOrientation { Front = 0, Back, Left, Right, Up, Down, Axonometric }; ////// 绑定显示控件句柄 /// /// 控件句柄 /// /// ///extern "C" _declspec(dllexport) void* _stdcall BindingHandle(HWND hwd, int width, int height); /// /// 传入数据 /// /// 帧数据 /// 帧总数 ///extern "C" _declspec(dllexport) int _stdcall EntryData(frameData * frameDatas, int frameCount); /// /// 设置管道参数 /// /// 管道直径 ///extern "C" _declspec(dllexport) int _stdcall SetPipePara(int pipeDiameter); /// /// 设置视角方向 /// /// 视角方向 ///extern "C" _declspec(dllexport) void _stdcall SetCameraOrientation(CamOrientation t_camOrientation); /// /// 选择帧 /// /// index(0-max) ///extern "C" _declspec(dllexport) void _stdcall Selectframe(int index);
ZYPointCloudLib.cpp:
// ZYPointCloudLib.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include#include "ZYPointCloudLib.h" #include "vtk.h" /// /// 绑定控件 /// /// /// /// ///void* _stdcall BindingHandle(HWND hwd, int width, int height) { if (renderWindow == NULL) { renderWindow = vtkSmartPointer ::New(); renderer = vtkSmartPointer ::New(); actor = vtkSmartPointer ::New(); renderWindow->SetParentId(hwd); renderWindow->SetSize(width, height); renderer->AddActor(actor); renderWindow->AddRenderer(renderer); //获取渲染窗口上发生的鼠标,键盘,事件事件 vtkSmartPointer iren = vtkSmartPointer ::New(); iren->SetRenderWindow(renderWindow); //设置鼠标交互方式 vtkSmartPointer style = vtkSmartPointer ::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; } /// /// 传入数据 /// /// /// ///int _stdcall EntryData(frameData* frameDatas, int frameCount) { if (polyData == NULL) polyData = vtkSmartPointer ::New(); vtkSmartPointer cells = vtkSmartPointer ::New(); vtkSmartPointer mapper = vtkSmartPointer ::New(); vtkSmartPointer points = vtkSmartPointer ::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; } /// /// 设置视图方向 /// /// 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(); } } ////// 选择帧 /// /// index(0-max) ///void _stdcall Selectframe(int index) { unsigned char red[3]{ 255,0,0 }; unsigned char white[3]{ 255,255,255 }; vtkNew 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 { ////// 雷达帧数据 /// 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) }; ////// 视图 /// 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); } ////// 打开文件 /// /// /// 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++) { ListCoordinatePoints = 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("打开文件失败!"); } } } /// /// 窗体大小变化 /// /// /// private void Form1_Resize(object sender, EventArgs e) { vtkPtr = PointCloudLib.BindingHandle(pictureBox1.Handle, pictureBox1.Width, pictureBox1.Height); } ////// 选择视角 /// /// /// private void cmbViewAngle_SelectedIndexChanged(object sender, EventArgs e) { int index = this.cmbViewAngle.SelectedIndex; if(index>=0) PointCloudLib.SetCameraOrientation((PointCloudLib.CamOrientation)index); } ////// /// /// /// 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全部复制到运行目录下,然后打开雷达文件测试:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)