Python解析Las(点云)格式文件

Python解析Las(点云)格式文件

tips:

  • 本文代码基于python3编写
  • 代码仓库
  • 推荐使用laspy或者plcpy包,本文做基础研究。

一、点云

  • 点云:

    在逆向工程中通过测量仪器得到的产品外观表面的点数据集合也称之为点云,通常使用三维坐标测量机所得到的点数量比较少,点与点的间距也比较大,叫稀疏点云;而使用三维激光扫描仪或照相式扫描仪得到的点云,点数量比较大并且比较密集,叫密集点云。

  • 点云格式:

    *.las*.pcd*.txt

二、Las格式

1. 简介

las文件是一个二进制文件,其中定义的数据类型与C语言中数据类型一致。到目前为止,las共有6版分别是:

2. Las规范数据类型

数据类型字节
char1 byte
unsigned char1 byte
short2 bytes
unsigned short2 bytes
long4 bytes
unsigned long4 bytes
double8 bytes

3. las文件整体构成

las1.0~las1.2las1.3~las1.4
公共头公共头
变长记录域变长记录域
点数据域点数据域
扩展变长记录域

4. 公共头不同版本构成

las1.0~las1.2las1.3~las1.4
文件标识(“LASF”)文件标识(“LASF”)
保留字节文件ID
GUID数据全球编码
版本信息GUID数据
系统标识版本信息
飞行时间系统标识
头部大小文件创建时间
数据偏移头部大小
变长记录域数目数据偏移
点数据格式、长度、数目、不同回波点数目变长记录域数目
X、Y、Z刻度因子点数据格式、长度
X、Y、Z偏移值老格式点数目、不同回波点数目
X、Y、Z最大最小值X、Y、Z刻度因子
X、Y、Z偏移值
X、Y、Z最大最小值
波形数据包记录起始位置
扩展变长记录起始、数目
点数目、不同回波点数目

5. 点记录格式说明

在las1.0版本中定义了点数据格式0,其一共20字节数据,在las1.0~las1.4的版本中点数据格式1到5都是在点数据格式0基础上增添字段。具体字段说明可参见不同版本的官方文档

在las1.4版本中增加了点格式6,其一共30字节数据,在las1.4版本中点格式7到10都是在点数据格式6基础上增添字段。具体字段说明可参见不同版本的官方文档

备注

在点格式0~5中Return NumberNumber of Returns (given pulse)Scan Direction FlagEdge of Flight Line等字段共占1字节数据。

在点格式6~10中Return NumberNumber of ReturnsClassification FlagsScan Direction FlagEdge of Flight Line等字段共占2字节数据。

这些字段的值都是通过位计算获得。

三、Python解析二进制

Python解析二进制文件使用内置struct

  1. Python数据类型与C语言数据类型对应关系,参考链接

    FormatC TypePython字节数
    xpad byteno value1
    ccharbytes of length 11
    bsigned charinteger1
    Bunsigned charinteger1
    ?_Boolbool1
    hshortinteger2
    Hunsigned shortinteger2
    iintinteger4
    Iunsigned intinteger4
    llonginteger4
    Lunsigned longinteger4
    qlong longinteger8
    Qunsigned long longinteger8
    ffloatfloat4
    ddoublefloat8
    schar[]bytes1
    pchar[]bytes1
    Pvoid *integer
  2. 简单使用struct

    import struct
    
    # 创建float型二进制数据
    a = struct.pack('f', 12.34)
    print(f"转换的二进制:{a}", )
    # 创建char[]类型二进制数据
    b = struct.pack('4s', 'test'.encode('utf-8'))
    print(f"转换的二进制:{b}", )
    
    # 写入到二进制文件
    f = open('test.dat', 'wb+')
    f.write(a)
    f.write(b)
    f.close()
    
    # 读取二进制文件
    f = open('test.dat', 'rb')
    a = f.read(4)
    print(f"读取的二进制:{a}", a)
    a = struct.unpack('f', a)
    print(f"转换为Python数据类型:{a}", )
    b = f.read(4)
    print(f"读取的二进制:{b}", )
    b = struct.unpack('4s', b)
    print(f"转换为Python数据类型:{b}", )
    f.close()
    

四、读取las文件并提取XYZ坐标值

1. 读取公共头

  • 新建head.py,定义las1.0~las1.4版本不同的公共头的类,具体实现参考代码

    class OneZeroHeader(object):
      """
      1.0版本
      """
      def __init__():
          pass
      
      def read_header(f):
          pass
    
    class OneOneHeader(object):
      """
      1.1版本
      """
      def __init__():
          pass
      
      def read_header(f):
          pass
    
    class OneTwoHeader(object):
      """
      1.2版本
      """
      def __init__():
          pass
      
      def read_header(f):
          pass
    
    class OneThreeHeader(object):
      """
      1.3版本
      """
      def __init__():
          pass
      
      def read_header(f):
          pass
    
    class OneFourHeader(object):
      """
      1.4版本
      """
      def __init__():
          pass
      
      def read_header(f):
          pass
    
    def get_header(f, version):
      """
      根据不同版本读取不同头
      """
      if version == (1, 0):
          new_header = OneZeroHeader()
      elif version == (1, 1):
          new_header = OneOneHeader()
      elif version == (1, 2):
          new_header = OneTwoHeader()
      elif version == (1, 3):
          new_header = OneThreeHeader()
      elif version == (1, 4):
          new_header = OneFourHeader()
      else:
          raise Exception("未找到对应文件版本")
    
      new_header.read_header(f)
      return new_header
    
  • 新建test_read_las.py,读取公共头数据

    
    import struct
    from head import get_header
    
    
    def get_version(f):
        f.read(24)
        version_major, version_minor = struct.unpack('2B', f.read(2))
        print(f"las版本:{version_major}.{version_minor}")
        return version_major, version_minor
    
    
    if __name__ == '__main__':
        f = open('test.las', 'rb')
        version = get_version(f)
        header = get_header(f, version)
        print(header.__dict__)
    
      
    

2. 读取点数据

  • 新建point.py,定义不同点格式。由本文只需要提取xyz值,所有只需要xyz值,其他数据解析跳过

    """
    定义点数据0~10格式解析
    """
    import struct
    
    
    class Point(object):
    
        def __init__(self, x_s_f, y_s_f, z_s_f, x_o, y_o, z_o):
            self.x_scale_factor = x_s_f
            self.y_scale_factor = y_s_f
            self.z_scale_factor = z_s_f
            self.x_offset = x_o
            self.y_offset = y_o
            self.z_offset = z_o
        
        def get_offset_bytes(self, point_data_record_format):
            """
            根据不同的点格式跳过的字节数
            :param point_data_record_format:点格式0~10
            :return:
            """
            # x,y,z共占12字节
            data_format = {
                0: 8,  # 点格式0 共20字节
                1: 16,  # 点格式1 共28字节
                2: 14,  # 点格式2 共26字节
                3: 22,  # 点格式3 共34字节
                4: 45,  # 点格式4 共57字节
                5: 51,  # 点格式5 共63字节
                6: 18,  # 点格式6 共30字节
                7: 24,  # 点格式7 共36字节
                8: 26,  # 点格式8 共38字节
                9: 47,  # 点格式9 共59字节
                10: 55,  # 点格式10 共67字节
            }
        
            offset_bytes = data_format.get(point_data_record_format, None)
            if offset_bytes is None:
                raise Exception(f"不存在当前的点格式{point_data_record_format}")
            return offset_bytes
        
        def read_point(self, f, offset_to_point_data, point_data_record_format, num):
            """
            读取当前文件中的点数据
            :param f:  数据文件
            :param offset_to_point_data: 点数据开始读取的地方
            :param point_data_record_format:  点数据格式0~10
            :param num: 读取多少个点
            :return: 读取的数据点
            """
            offset_bytes = self.get_offset_bytes(point_data_record_format)
            f.seek(offset_to_point_data)
            points = list()
        
            i = 0
            while i < num:
                point_bytes = f.read(12)
                x_record, y_record, z_record = struct.unpack_from('3l', point_bytes)
                x = x_record * self.x_scale_factor + self.x_offset
                y = y_record * self.y_scale_factor + self.y_offset
                z = z_record * self.z_scale_factor + self.z_offset
                i += 1
                f.read(offset_bytes)
                points.append((x, y, z))
        
            return points
    
  • test_read_las.py中读取点数据

    import struct
    from head import get_header
    from point import Point
    
    
    def get_version(f):
        f.read(24)
        version_major, version_minor = struct.unpack('2B', f.read(2))
        print(f"las版本:{version_major}.{version_minor}")
        return version_major, version_minor
    
    
    if __name__ == '__main__':
        f = open('test.las', 'rb')
        version = get_version(f)
        header = get_header(f, version)
        print(header.__dict__)
    
        points = Point(header.x_scale_factor,
                       header.y_scale_factor,
                       header.z_scale_factor,
                       header.x_offset,
                       header.y_offset,
                       header.z_offset,
                       )
    
        data = points.read_point(f, header.offset_to_point_data,
                                 header.point_data_record_format,
                                 header.number_of_point_records)
    
        print(data)
      
    

3. 输出XYZ坐标值到文件

test_read_las.py将解析出来的点数据按照一定的格式(x,y,z)输出到txt文件。

import struct
from head import get_header
from point import Point


def get_version(f):
    f.read(24)
    version_major, version_minor = struct.unpack('2B', f.read(2))
    print(f"las版本:{version_major}.{version_minor}")
    return version_major, version_minor


if __name__ == '__main__':
    f = open('test.las', 'rb')
    version = get_version(f)
    header = get_header(f, version)
    print(header.__dict__)

    points = Point(header.x_scale_factor,
                   header.y_scale_factor,
                   header.z_scale_factor,
                   header.x_offset,
                   header.y_offset,
                   header.z_offset,
                   )

    data = points.read_point(f, header.offset_to_point_data,
                             header.point_data_record_format,
                             header.number_of_point_records)

    f.close()

    with open("point.txt", 'a+') as f:
        for item in data:
            f.write(f'{item[0]},{item[1]},{item[2]}\n')

总结

  • 本文完成了对las文件的读取并输出到文件
  • 下一篇将讲解多进程分块加速读取las文件并输出到对应文件
评论 23 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页

打赏作者

那些年踩过的坑丶

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值