python: lidar点云转BEV投影及pillar/voxel

本文介绍了一个Python脚本,用于从点云数据中生成BEV(BirdsEyeView)投影并进行pillar体素化处理,以便于自动驾驶车辆的传感器数据处理和分析。脚本详细展示了如何读取PCD文件、计算点云在BEV网格中的索引以及构建pillar数据结构。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点云BEV投影及pillar体素化

bev投影

点云bev投影代码

pillar/voxel

#!/usr/bin/env python
# -*- encoding: utf-8 -*-



import os
import numpy as np
import math
import pickle


n_max_points_per_pillar = 32
n_max_pillars = 20736  # 144*144 = 20736
n_in_features = 7  # x y z i xc yc zc

x_step = 0.32  # 32cm 32*144 = 4608 = 46.08m
y_step = 0.16  # 16cm 16*144 =  = 15.36m

max_intensity = 255

def read_pcd(pcd_file, readline=11):
    # np.fromfile read pcd 文本/二进制
    # cloud = np.fromfile(str(pcd_file), dtype=np.float32, count=-1)
    data = []
    with open(pcd_file, 'r') as f:
        lines = f.readlines()[readline:]
        for line in lines:
            line = np.array(line.strip('\n').split(' '), dtype=np.float32)
            # print(line.shape, line)
            x,y,z,i = line[0], line[1], line[2], line[3]
            # print(x,y,z,i)
            # r = math.sqrt(x**2 + y**2 + z**2) * i  # for rslidar
            # r = float(line[4]) # for ouster lidar
            data.append([x,y,z,i])
        # points = list(map(lambda line: list(map(lambda x: float(x), line.split(' '))), lines))
        points = np.array(data)

    return points

def points_to_pillars(points, list_roi_xyz = [0.0, 69.12, -11.52, 11.52, -2.5, 0.5],
                          list_pillar_xy = [0.48, 0.16],
                          list_grid_xy = [144, 144],  # (69.12 - 0) / 0.48
                          list_pillar_szie = [20736, 32, 7]
                          ):
    x_min, x_max, y_min, y_max, z_min, z_max = list_roi_xyz
    x_grid, y_grid = list_grid_xy
    x_pillar, y_pillar = list_pillar_xy
    n_max_pillars, n_max_points_per_pillar, n_in_features = list_pillar_szie

    # ROI
    idx = np.where((points[:, 0] > x_min) & (points[:, 0] < x_max) &
                   (points[:, 1] > y_min) & (points[:, 1] < y_max) &
                   (points[:, 2] > z_min) & (points[:, 2] < z_max))
    points_roi = points[idx[0]]
    # intensity normlize
    points_roi[:,3] /= 255

    # 计算每个点所在的voxel索引
    # Compute the indices of the pillars that each point belongs to.
    x_img = (points_roi[:,0] - x_min) // x_pillar
    y_img = (points_roi[:,1] - y_min) // y_pillar
    x_img = x_img.reshape(-1, 1).astype(int)
    y_img = y_img.reshape(-1, 1).astype(int)
    # pillar_coords = np.concatenate((x_img, y_img), axis=1)

    # 计算pillar的索引
    pillar_idx = x_img * y_grid + y_img

    # 找到每个pillar的点数
    unique_pillars, num_points_per_pillars = np.unique(pillar_idx, return_counts=True)
    # print("num pillars: ",unique_pillars, num_points_per_pillars)

    num_points_per_pillars = np.minimum(num_points_per_pillars, n_max_points_per_pillar)

    # Initialize the pillar representation with zeros.
    pillars = np.zeros([n_max_pillars, n_max_points_per_pillar, n_in_features]).astype(np.float32)
    pillar_indices = np.zeros((n_max_pillars, 3), dtype=int)
    for i, pillar_id in enumerate(unique_pillars):
        # 找出pillar对应的点
        indices = np.where(pillar_idx == pillar_id)[0]  # 每个pillar内所有点的索引
        
        # (可选)当pillar内点云大于n, 则对z进行排序,取前n个最小z的点
        if len(indices) > num_points_per_pillars[i]:
            points_z_sort = np.argsort(points_roi[indices, 2], axis=0)  # 返回z从小到大排序的索引值,axis=0 按列排序
            indices = indices[points_z_sort]
        indices = indices[:num_points_per_pillars[i]]  # 每个pillar内取前n个点的索引

        # 计算pillar的中心坐标
        pillar_c = np.mean(points_roi[indices, :3], axis=0)

        # 计算pillar的中心坐标所在pillar的index
        x_pillar_idx = ((pillar_c[0] - x_min) // x_pillar).astype(int)
        y_pillar_idx = ((pillar_c[1] - y_min) // y_pillar).astype(int)

        # 将每个pillar坐标位置存入
        pillar_indices[pillar_id, 1:] = [x_pillar_idx, y_pillar_idx]

        # 计算pillar的中心坐标和每个点的偏移
        offsets = points_roi[indices, :3] - pillar_c

        # 将点和偏移添加到pillar中
        pillars[pillar_id, :num_points_per_pillars[i], :4] = points_roi[indices]
        pillars[pillar_id, :num_points_per_pillars[i], 4:7] = offsets
    # print(pillars[:5, :5, :], np.unique(pillars[:, 0, 0]), pillar_indices[:, :])

    return pillars, pillar_indices

    # # initialize the number of points in each pillar
    # num_points_in_pillar = np.zeros((n_max_pillars,))
    # # Fill the pillar representation with the pointcloud data.
    # for i, p in enumerate(points_roi):
    #     pillar_index = pillar_idx[i]
    #     point_index = i
    #     # check if the pillar is full
    #     if num_points_in_pillar[pillar_index] < n_max_points_per_pillar:
    #         # add the point to the pillar
    #         pillars[pillar_index, point_index, :4] = p.reshape(1, -1)

    #     # calculate the centroid of the points in the pillar
    #     if num_points_in_pillar[pillar_index] > 0:
    #         points_num = num_points_in_pillar[pillar_index][0].astype(int)
    #         pillar_points = pillars[pillar_index, 0:points_num, 0:3]
    #         centroid = np.mean(pillar_points, axis=0)
    #         # print(p[:3] - centroid, p, centroid.shape)
    #         # print(pillars[pillar_index, 0:points_num, 4:8])
    #         pillars[pillar_index, 0:points_num, 4:8] = p[:3] - centroid

    #     # increment the number of points in the pillar
    #     num_points_in_pillar[pillar_index] += 1
    # print(pillars[pillar_index, i, ...].shape)


if __name__=="__main__":
    root_dir = "/dataset"
    # for i, f in enumerate(os.listdir(root_dir)):
    #     file = os.path.join(root_dir, f)
    #     print(i,f)
    for root, dirs, files in os.walk(root_dir):
        for file in files:
            file_name, suffix = os.path.splitext(file)
            if suffix == '.pcd':
                file = os.path.join(root, file)
                pillar_path = os.path.dirname(os.path.dirname(file)) + "/pillar_tensor_sort"
                pillar_file = os.path.join(pillar_path, file_name+'.pkl')
                if not os.path.exists(pillar_path):
                    print("pillar path is not exist.")
                    os.makedirs(pillar_path)
                # print(pillar_file)
                # print(pillar_path, suffix , file_name)
                if not os.path.exists(pillar_file):
                    points = read_pcd(file)
                    pillars, pillar_indices = points_to_pillars(points)
                    with open(pillar_file, 'wb') as f:
                        pickle.dump([pillars, pillar_indices], f, pickle.HIGHEST_PROTOCOL)
                        print(pillar_file)
                    print(pillars.shape, pillar_indices.shape)




### 处理BEV数据以去除投影信息的方法 在自动驾驶和机器人领域,鸟瞰图(Bird's Eye View, BEV)是一种重要的数据表示形式。为了从BEV数据中移除不必要的投影信息,可以采用以下几种技术手段: #### 数据预处理阶段 在构建BEV之前,可以通过调整传感器参数来减少原始点云中的冗余投影信息。例如,在激光雷达采集的数据中,地面点通常会引入大量无意义的垂直方向投影[^2]。因此,可以在点云分割过程中剔除这些地面点。 #### 地面滤波算法 一种常见的做法是利用基于RANSAC(随机抽样一致性)或其他平面拟合方法检测并过滤掉地面部分。这样能够有效消除由地形引起的多余投影干扰。 ```python import numpy as np from sklearn.linear_model import RANSACRegressor def filter_ground(points): # 使用RANSAC回归器估计地面模型 ransac = RANSACRegressor() X = points[:, :2].reshape(-1, 2) # 取xy坐标作为输入特征 y = points[:, 2].reshape(-1,) # z高度作为目标变量 try: ransac.fit(X, y) inlier_mask = ransac.inlier_mask_ ground_points = points[inlier_mask] non_ground_points = points[np.logical_not(inlier_mask)] return non_ground_points except Exception as e: print(f"Error during filtering: {e}") return None ``` 此代码片段展示了如何应用RANSAC算法识别并排除属于地面的部分,从而间接减少了因地面造成的不必要投影影响。 #### 投影矫正与重映射 对于已经形成的BEV图像,则可能需要考虑重新定义像素分配逻辑或者实施特定变换矩阵来进行校正操作。比如通过透视变换恢复原本的空间关系,进而达到清除错误投影的目的[^1]。 ```python import cv2 def correct_projection(bev_image, homography_matrix): """ 对BEV图像执行反向透视变换以修正潜在的投影失真。 参数: bev_image (numpy.ndarray): 输入的BEV图像. homography_matrix (numpy.ndarray): 描述所需几何换的单应性矩阵. 返回: numpy.ndarray: 经过矫正后的输出图像. """ height, width = bev_image.shape[:2] corrected_img = cv2.warpPerspective( src=bev_image, M=np.linalg.inv(homography_matrix), dsize=(width, height), flags=cv2.INTER_LINEAR) return corrected_img ``` 以上函数接受一个现有的BEV图片以及对应的逆透视变换矩阵(H),并通过`cv2.warpPerspective()`完成最终的投影修复工作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ywfwyht

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

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

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

打赏作者

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

抵扣说明:

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

余额充值