不同格式点云存储结构(txt、pcd、las、ply)整理以及基本的读写、可视化方法

不同格式点云存储结构整理以及基本的读写、可视化方法

  • 一、文本(txt)
    • 1.1、存储结构
    • 1.2、读取
  • 二、PCD格式
    • 1.1、存储结构
    • 1.2、读写
      • 1.2.1、open3d读写(python)
      • 1.2.2、PCL读写(C++)
  • 三、LAS格式
    • 3.1、存储结构
    • 3.2、读写
      • 3.2.1、使用laspy 读写(Python)
      • 3.2.2、使用laslib读写(C++)
  • 四、PLY格式
    • 4.1、存储结构
      • 文件头(header)
      • 数据区域
    • 4.2、读写
      • 4.2.1、使用plyfile读写(Python)
      • 4.2.2、使用pcl读写(C++)

一、文本(txt)

1.1、存储结构

使用文本格式存储的点云数据文件结构比较简单,每个点是一行记录,点的信息存储格式为 x y z或者 x y z r g b。

1.2、读取

读取文本格式的点云数据时,可以按照一般的文本读取方法,这里记录一下如何使用open3d读取txt格式的点云数据

import open3d as o3d
txt_file=r"D:\test_data\bildstein_station1_xyz_intensity_rgb.txt"

pcd=o3d.io.read_point_cloud(txt_file,format='xyz')
o3d.visualization.draw([pcd])

二、PCD格式

一个PCD文件是文件头部分和数据部分组成

1.1、存储结构

字段名 字段解释
VERSION 指定PCD文件版本
FIELDS 指定一个点可以有的每一个维度和字段的名字
SIZE 用字节数指定每一个维度的大小
TYPE 用一个字符指定每一个维度的类型
COUNT 指定每一个维度包含的元素数据
WIDTH 无序点的数量或者有序点一行中点的数目
HEIGHT 无序点云中设置为1,有序点云中表示行数
POINTS 点云中点的总数
DATA 数据类型,二进制或者ASCII

1.2、读写

1.2.1、open3d读写(python)

读取pcd点云文件

import open3d as o3d
import numpy as np

#读取pcd数据并可视化
pcd_file=r""
pcd=o3d.io.read_point_cloud(pcd_file,format='pcd')

#将点云的坐标和颜色转换为numpy格式
points=np.array(pcd.points)
colors=np.array(pcd.colors)

#可视化
o3d.visualization.draw([pcd])

保存pcd点云文件

#写入pcd格式
save_file="test2.pcd"

#手动定义点云
points=np.array([[8,5,3],
                 [9,0,1],
                 [2,5,3],
                 [0,4,2],
                 [7,2,9],
                 [8,8,4],
                 [9,5,8],
                 [2,5,9],
                 [0,7,5],
                 [11,2,8],
                 [10,9,0]
                 ])
colors=np.array([[255,0,0],
                 [255,0,0],
                 [0,255,0],
                 [0,255,0],
                 [0,0,255],
                 [0,0,255],
                 [255,0,255],
                 [255,0,255],
                 [255,255,0],
                 [255,255,0],
                 [255,255,0]
                 ])
colors=colors/255

pcd=o3d.geometry.PointCloud()

pcd.points=o3d.utility.Vector3dVector(points)
pcd.colors=o3d.utility.Vector3dVector(colors)

#可视化
o3d.visualization.draw_geometries([pcd])

#使用draw方法可视化需要将颜色归一化到0-1之间
# o3d.visualization.draw([pcd])

#保存
#o3d.io.write_point_cloud(save_file,pcd,write_ascii=True) #以ascii格式存储点数据集部分
o3d.io.write_point_cloud(save_file,pcd)#以二进制格式存储点数据集部分
pcd点数据集部分保存为ASCII格式
不同格式点云存储结构(txt、pcd、las、ply)整理以及基本的读写、可视化方法_第1张图片

pcd点数据集部分保存为二进制格式,其中rgb是用位存储的方式写入的。

不同格式点云存储结构(txt、pcd、las、ply)整理以及基本的读写、可视化方法_第2张图片

1.2.2、PCL读写(C++)

我们用上面生成的pcd文件做测试

#include
#include
#include

using namespace std;
int main()
{
	string pcd_file = "D:\\project\\Python\\PointCloud\\test2.pcd";
	//pcl::PointCloud::Ptr cloud(new pcl::PointCloud);
	pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZRGB>);
	if (pcl::io::loadPCDFile<pcl::PointXYZRGB>(pcd_file,*cloud)==-1)
	{
		PCL_ERROR("couldn't read file\n");
		return(-1);
	}

	for (size_t i=0;i<cloud->points.size();++i)
	{
		cout << " " << cloud->points[i].x << " " <<
			cloud->points[i].y << " " <<
			cloud->points[i].z << " " <<
			(int)cloud->points[i].r<< " "<<
			(int)cloud->points[i].g<<" "<<
			(int)cloud->points[i].b<< endl;

	}

	return 0;
}

打印结果

不同格式点云存储结构(txt、pcd、las、ply)整理以及基本的读写、可视化方法_第3张图片

保存pcd文件

#include 

#include 
#include 

using namespace std;
int main()
{
	string pcd_file = "test2.pcd";
	pcl::PointCloud<pcl::PointXYZRGB> cloud;

	int data[11][6] = {
		{8,5,3,255,0,0},
		{9,0,1,255,0,0},
		{2,5,3,0,255,0},
		{0,4,2,0,255,0},
		{7,2,9,0,0,255},
		{8,8,4,0,0,255},
		{9,5,8,255,0,255},
		{2,5,9,255,0,255},
		{0,7,5,255,255,0},
		{11,2,8,255,255,0},
		{10,9,0,255,255,0}};

	cloud.width = 11;
	cloud.height = 1;
	cloud.is_dense = false;
	cloud.points.resize(cloud.width*cloud.height);


	for (size_t i = 0; i < cloud.points.size(); ++i)
	{
		cloud.points[i].x = data[i][0];
		cloud.points[i].y = data[i][1];
		cloud.points[i].z = data[i][2];
		cloud.points[i].r = data[i][3];
		cloud.points[i].g = data[i][4];
		cloud.points[i].b = data[i][5];
	}

	pcl::io::savePCDFileASCII(pcd_file, cloud);
	//pcl::io::savePCDFileBinary(pcd_file, cloud);
	return 0;
}

打开输出的ascii文件

不同格式点云存储结构(txt、pcd、las、ply)整理以及基本的读写、可视化方法_第4张图片

可以看到pcl生成的pcd点云文件与open3d生成的点云文件在颜色部分不一致,主要是数据存储类型导致的,pcl1.7之后颜色部分会保存成整型。具体请参看这里。

三、LAS格式

3.1、存储结构

LAS是一种存储点云的公开数据格式,主要用来存储雷达点云数据,LAZ是对LAS格式的无损压缩。

最新的LAS规格版本是LAS 1.4。laspy库支持1.2~1.4版本的LAS文件。

一个LAS文件有三部分组成:文件头区(Header)、可变长记录区(VLRs)、点集记录区(Point Records)

文件头包含数据版本,点的格式(每个点存储的不同维度)等信息;可变长记录包括一些元信息,如坐标系信息,额外的维度等信息;点集记录区是las文件的核心,记录点的x、y、z坐标信息和r、g、b、classification、intensity等其他属性信息。
点的格式共有11个版本,其中第0版为基础,其他后续版本在第0版的基础上增加了其他字段,具体字段参考这里。

3.2、读写

3.2.1、使用laspy 读写(Python)

在python中可以使用laspy包来读写las点云文件

import laspy
import numpy as np

las_file=r""
las=laspy.read(las_file)
#获取文件头
header=las.header
#点类型
point_format=las.point_format
print(point_format.id)
#属性字段名
dimension_names=point_format.dimension_names
print(list(dimension_names))

#点集的外边框
print(header.mins)
print(header.maxs)

#点个数
point_num=header.point_count

#获取坐标和颜色
las_x=np.array(las.x)
las_y=np.array(las.y)
las_z=np.array(las.z)
las_r=np.array(las.red)
las_g=np.array(las.green)
las_b=np.array(las.blue)

#组合
pt=np.stack([las_x,las_y,las_z],axis=1)
colors=np.stack([las_r,las_g,las_b],axis=1)

使用laspy写las文件

save_las_file=r"save_las.las"

#以x y z r g b的方式定义点云数据
my_data=np.array([[8,5,3,255,0,0],
                 [9,0,1,255,0,0],
                 [2,5,3,0,255,0],
                 [0,4,2,0,255,0],
                 [7,2,9,0,0,255],
                 [8,8,4,0,0,255],
                 [9,5,8,255,0,255],
                 [2,5,9,255,0,255],
                 [0,7,5,255,255,0],
                 [11,2,8,255,255,0],
                 [10,9,0,255,255,0]
                 ])
                 
 #创建点云文件
 las=laspy.create(file_version="1.2",points_format=3)
 las.x=my_data[:,0]
 las.y=my_data[:,1]
 las.z=my_data[:,2]
 las.red=my_data[:,3]
 las.green=my_data[:,4]
 las.blue=my_data[:,5]
	
 #保存las文件
 las.write(save_las_file)                

3.2.2、使用laslib读写(C++)

使用C++操作las文件可以使用laslib库。

#include 
#include "lasreader.hpp"
#include "laswriter.hpp"
using namespace std;
int main()
{
	const string las_file="";
	
	LASreadOpener lasreadopener;
	lasreadopener.set_file_name(las_file.data());
	
	LASreader* lasreader=lasreadopener.open();

	LASheader header=lasreader->header;
	lasreader->header.unlink();

	int nbPoints = header.number_of_point_records;

	float x,y,z;
	int r,g,b;

	while(lasreader->read_point())
	{
		LASpoint& pointReader= lasreader->point;

		x=pointReader.get_x();
		y=pointReader.get_y();
		z=pointReader.get_z();

		r=pointReader.get_R();
		g=pointReader.get_G();
		b=pointReader.get_B();
	}
	delete lasreader;
}

写入las文件

#include 
#include "lasreader.hpp"
#include "laswriter.hpp"
using namespace std;
int main()
{
	const string las_file="test.las";
	
	int data[11][6] = {
			{8,5,3,255,0,0},
			{9,0,1,255,0,0},
			{2,5,3,0,255,0},
			{0,4,2,0,255,0},
			{7,2,9,0,0,255},
			{8,8,4,0,0,255},
			{9,5,8,255,0,255},
			{2,5,9,255,0,255},
			{0,7,5,255,255,0},
			{11,2,8,255,255,0},
			{10,9,0,255,255,0}
			};
	LASwriteOpener laswriteropener;
	laswriteropener.set_file_name(las_file.data());
	
	LASheader header;
	header.point_data_format=3;
	header.point_data_record_length=34;
	header.number_of_point_records=11;

	LASWriter* laswriter = laswriteropener.open(&header);
	LASpoint point;
	point.init(&header,header.point_data_format,header.point_data_record_length,&header);

	double minx=DBL_MAX,miny=DBL_MAX,minz=DBL_MAX;
	double maxx=-DBL_MAX,maxy=-DBL_MAX,maxz=-DBL_MAX;
	
	for(size_t i=0;i<header.point_data_record_length;++i)
	{
		point.set_x(data[i][0]);
		point.set_y(data[i][1]);
		point.set_z(data[i][2]);
		point.set_R(data[i][3]);
		point.set_G(data[i][4]);
		point.set_B(data[i][5]);

		if(data[i][0]<minx)
		{
			minx=data[i][0];
		}
		if(data[i][1]<miny)
		{
			minx=data[i][1];
		}
		if(data[i][2]<minz)
		{
			minx=data[i][2];
		}
		if(data[i][0]>maxx)
		{
			maxx=data[i][0];
		}
		if(data[i][1]>maxy)
		{
			maxy=data[i][1];
		}
		if(data[i][2]>maxz)
		{
			maxx=data[i][2];
		}

		laswriter->write_point(&point);
		laswriter->update_inventory(&point);
	}

	header.set_bounding_box(minx,miny,minz,maxx.maxy.maxz);
	laswriter->update_header(&header);
	I64 total_bytes=laswriter->close();
	delete laswriter;
}

四、PLY格式

4.1、存储结构

PLY(Polygon File Format)是一种常见的点云存储格式,由斯坦福大学开发,其最早主要用于存储三维扫描仪器的点云数据。

PLY文件是由文件头和数据区两部分组成。

文件头(header)

其中文件头记录的是点云文件中的注释、元素类别和属性,以ply开头,以end header结尾。

ply
...
...
end header

文件头的第二行是文件的存储方式和版本,以format开头,依次是编码方式、版本。编码方式有三种分别是ascii、binary_little_endian 、binary_big_endian。目前PLY只有1.0版本。

ply
format ascii 1.0
...
...
end header

format之后跟注释信息,以comment开头,制作者可以添加一些作者信息,点云基本信息。

ply
format ascii 1.0
comment made by anonymous
comment this file is a cube
...
end header

注释信息comment之后是element元素信息+该种元素的property属性信息,element元素信息包括种类、个数,property属性信息包括属性字段的存储类型和属性名。PLY文件中的元素一般包括顶点(vertex)、面(face)、边(edge)等。元素信息和属性信息应该组合出现,格式如下

element  
property  
property  
property  

我们定义一个包含6个顶点和8个面元素的ply文件,文件头如下,

ply
format ascii 1.0
comment made by anonymous
comment this file is a cube
element vertex 6
property float32 x
property float32 y
property float32 z
property uchar red
property uchar green
property uchar blue
element face 8
property list uint8 int32 vertex_index
end_header

数据区域

在文件头后直接开始存储数据,存储形式分为ASCII和二进制。以ASCII为例,先按行记录每个点,全部点记录完成后再按行记录每个面。

0 0 0 0 0 0 (开始记录点,按照x,y,z,r,g,b方式排列)
0 100 0 0 0 0
100 100 0 0 0 0
100 0 0 0 0 0
50 50 75 255 255 255
50 50 -75 255 255 255
3 0 1 4 (开始记录面,按照点个数,点的序号排列,3表示这个面由3个点组成,0,1,4代表该面由第0,1,4个点组成)
3 1 2 4
3 2 3 4
3 0 3 4
3 0 1 5
3 1 2 5
3 2 3 5
3 0 3 5

所以完整的一个ply文件如下

ply
format ascii 1.0
comment made by anonymous
comment this file is a cube
element vertex 6
property float32 x
property float32 y
property float32 z
property uchar red
property uchar green
property uchar blue
element face 8
property list uint8 int32 vertex_index
end_header
0 0 0 0 0 0
0 100 0 0 0 0
100 100 0 0 0 0
100 0 0 0 0 0
50 50 75 255 255 255
50 50 -75 255 255 255
3 0 1 4 
3 1 2 4
3 2 3 4
3 0 3 4
3 0 1 5
3 1 2 5
3 2 3 5
3 0 3 5

4.2、读写

4.2.1、使用plyfile读写(Python)

使用plyfile读取ply文件

from plyfile import PlyData,PlyElement

file=r"C:\Users\123\Desktop\test2.ply"
plydata=PlyData.read(file)

print(plydata)
print("*************************************************")
#第一种读取方法
elements=plydata.elements
for element in elements:
    for data in element.data:
        print(data)
print("*************************************************")
#第二种读取方法
vertex_data=elements[0].data
face_data =elements[1].data
print(vertex_data)
print(face_data)

打印结果

ply
format ascii 1.0
comment made by anonymous
comment this file is a cube
element vertex 6
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
element face 8
property list uchar int vertex_index
end_header
*************************************************
(0., 0., 0., 0, 0, 0)
(0., 100., 0., 0, 0, 0)
(100., 100., 0., 0, 0, 0)
(100., 0., 0., 0, 0, 0)
(50., 50., 75., 255, 255, 255)
(50., 50., -75., 255, 255, 255)
(array([0, 1, 4]),)
(array([1, 2, 4]),)
(array([2, 3, 4]),)
(array([0, 3, 4]),)
(array([0, 1, 5]),)
(array([1, 2, 5]),)
(array([2, 3, 5]),)
(array([0, 3, 5]),)
*************************************************
[(  0.,   0.,   0.,   0,   0,   0) (  0., 100.,   0.,   0,   0,   0)
 (100., 100.,   0.,   0,   0,   0) (100.,   0.,   0.,   0,   0,   0)
 ( 50.,  50.,  75., 255, 255, 255) ( 50.,  50., -75., 255, 255, 255)]
[(array([0, 1, 4]),) (array([1, 2, 4]),) (array([2, 3, 4]),)
 (array([0, 3, 4]),) (array([0, 1, 5]),) (array([1, 2, 5]),)
 (array([2, 3, 5]),) (array([0, 3, 5]),)]

使用plyfile写入ply文件

def write_ply(output_file,text=True):
    points=[
        (0,0,0),
        (0,100,0),
        (100,100,0),
        (100,0,0),
        (50,50,75)
    ]

    face=np.array([
        ((0,1,2),255,0,0),
        ((0,2,3),255,0,0),
        ((0, 1, 4),0,255,0),
        ((1,2,4),0,0,255),
        ((2,3,4),255,255,0),
        ((0,3,4),0,0,0)],
        dtype=[('vertex_index','i4',(3,)),
               ('red','u1'),('green','u1'),
               ('blue','u1')]
    )
    print(face)
    vertex = np.array(points, dtype=[('x', 'f4'), ('y', 'f4'), ('z', 'f4')])
    el = PlyElement.describe(vertex, 'vertex')
    face = PlyElement.describe(face, 'face')
    PlyData([el,face], text=text).write(out_file)

在meshlab中打开

不同格式点云存储结构(txt、pcd、las、ply)整理以及基本的读写、可视化方法_第5张图片
不同格式点云存储结构(txt、pcd、las、ply)整理以及基本的读写、可视化方法_第6张图片

4.2.2、使用pcl读写(C++)

读取ply点云文件

#include 

#include 
#include 
#include 
#include 

using namespace std;
int main()
{
	string ply_file = "C:\\Users\\123\\Desktop\\test7.ply";
	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);

  	if (pcl::io::loadPLYFile<pcl::PointXYZ>(ply_file,*cloud)==-1)
  	{
  		return (-1);
  	}
  
  	cout << cloud->width << " " << cloud->height << endl;

	for (size_t i=0;i<cloud->points.size();++i)
	{
		cout << cloud->points[i].x << " " << cloud->points[i].y << " " << cloud->points[i].z << endl;
	}
  
  	return	(0);
}

保存ply点云文件

#include 

#include 
#include 

using namespace std;
int main()
{
	string ply_file = "test2.ply";
	pcl::PointCloud<pcl::PointXYZRGB> cloud;

	int data[11][6] = {
		{8,5,3,255,0,0},
		{9,0,1,255,0,0},
		{2,5,3,0,255,0},
		{0,4,2,0,255,0},
		{7,2,9,0,0,255},
		{8,8,4,0,0,255},
		{9,5,8,255,0,255},
		{2,5,9,255,0,255},
		{0,7,5,255,255,0},
		{11,2,8,255,255,0},
		{10,9,0,255,255,0}};

	cloud.width = 11;
	cloud.height = 1;
	cloud.is_dense = false;
	cloud.points.resize(cloud.width*cloud.height);


	for (size_t i = 0; i < cloud.points.size(); ++i)
	{
		cloud.points[i].x = data[i][0];
		cloud.points[i].y = data[i][1];
		cloud.points[i].z = data[i][2];
		cloud.points[i].r = data[i][3];
		cloud.points[i].g = data[i][4];
		cloud.points[i].b = data[i][5];
	}


	pcl::io::savePLYFile(ply_file, cloud);
	
	return 0;
}

你可能感兴趣的:(点云,点云,存储格式,读写)