【CUDA-BEVFusion】src/main.cpp—— visualize函数说明

CUDA-BEVFusion中,src/main.cpp—— visualize函数的主要功能是将激光雷达(LiDAR)点云数据、3D边界框(Bounding Box)以及摄像头图像进行可视化,并将结果保存为一张图片。代码使用了CUDA进行加速,并且涉及到多个模块的协同工作,包括LiDAR点云的可视化、3D边界框的绘制、摄像头图像的投影等。

1. 函数参数

  • bboxes: 3D边界框的列表,类型为std::vector<bevfusion::head::transbbox::BoundingBox>
  • lidar_points: LiDAR点云数据,类型为nv::Tensor
  • images: 摄像头图像数据,类型为std::vector<unsigned char*>
  • lidar2image: LiDAR到图像的变换矩阵,类型为nv::Tensor
  • save_path: 可视化结果的保存路径,类型为std::string
  • stream: CUDA流,用于异步操作。

  • paddinglidar_sizecontent_widthcontent_height等变量用于定义场景的尺寸和布局。
  • scene_artist_param用于设置场景画布的参数,包括宽度、高度、步长等。
  • scene_device_image是一个3通道的图像张量,用于存储最终的场景图像。

  • 使用scene_artist_param创建一个场景画布scene,并将scene_device_image作为画布的背景图像。
  • bev_artist_param用于设置BEV可视化的参数,包括图像尺寸、旋转角度、归一化尺寸等。
  • bev_visualizer用于绘制LiDAR点云、3D边界框以及自车(ego)的位置。

2. visualize代码

static void visualize(const std::vector<bevfusion::head::transbbox::BoundingBox>& bboxes, const nv::Tensor& lidar_points,
                      const std::vector<unsigned char*> images, const nv::Tensor& lidar2image, const std::string& save_path,
                      cudaStream_t stream) {
  // 将检测框数据拷贝到 predictions 向量中
  std::vector<nv::Prediction> predictions(bboxes.size());
  memcpy(predictions.data(), bboxes.data(), bboxes.size() * sizeof(nv::Prediction));

  // 定义可视化图像的尺寸和布局参数
  int padding = 300;  // 图像边缘的填充大小
  int lidar_size = 1024;  // 点云图像的宽度
  int content_width = lidar_size + padding * 3;  // 最终图像的宽度
  int content_height = 1080;  // 最终图像的高度

  // 初始化 SceneArtist 的参数
  nv::SceneArtistParameter scene_artist_param;
  scene_artist_param.width = content_width;  // 图像宽度
  scene_artist_param.height = content_height;  // 图像高度
  scene_artist_param.stride = scene_artist_param.width * 3;  // 图像步长(每行的字节数)

  // 创建设备上的图像张量,并初始化为全零(黑色)
  nv::Tensor scene_device_image(std::vector<int>{scene_artist_param.height, scene_artist_param.width, 3}, nv::DataType::UInt8);
  scene_device_image.memset(0x00, stream);

  // 将设备图像指针配置到 SceneArtistParameter 中
  scene_artist_param.image_device = scene_device_image.ptr<unsigned char>();
  auto scene = nv::create_scene_artist(scene_artist_param);  // 创建 SceneArtist 实例

  // 初始化 BEVArtist 的参数
  nv::BEVArtistParameter bev_artist_param;
  bev_artist_param.image_width = content_width;  // 图像宽度
  bev_artist_param.image_height = content_height;  // 图像高度
  bev_artist_param.rotate_x = 70.0f;  // 点云绕 x 轴的旋转角度
  bev_artist_param.norm_size = lidar_size * 0.5f;  // 点云的归一化大小
  bev_artist_param.cx = content_width * 0.5f;  // 图像中心点的 x 坐标
  bev_artist_param.cy = content_height * 0.5f;  // 图像中心点的 y 坐标
  bev_artist_param.image_stride = scene_artist_param.stride;  // 图像步长

  // 将点云数据转移到设备上
  auto points = lidar_points.to_device();
  auto bev_visualizer = nv::create_bev_artist(bev_artist_param);  // 创建 BEVArtist 实例

  // 渲染点云、预测框和自车位置
  bev_visualizer->draw_lidar_points(points.ptr<nvtype::half>(), points.size(0));  // 绘制点云
  bev_visualizer->draw_prediction(predictions, false);  // 绘制预测框
  bev_visualizer->draw_ego();  // 绘制自车位置
  bev_visualizer->apply(scene_device_image.ptr<unsigned char>(), stream);  // 将渲染结果应用到图像上

  // 初始化 ImageArtist 的参数
  nv::ImageArtistParameter image_artist_param;
  image_artist_param.num_camera = images.size();  // 相机数量
  image_artist_param.image_width = 1600;  // 相机图像的宽度
  image_artist_param.image_height = 900;  // 相机图像的高度
  image_artist_param.image_stride = image_artist_param.image_width * 3;  // 相机图像的步长
  image_artist_param.viewport_nx4x4.resize(images.size() * 4 * 4);  // 视口矩阵的大小
  memcpy(image_artist_param.viewport_nx4x4.data(), lidar2image.ptr<float>(),
         sizeof(float) * image_artist_param.viewport_nx4x4.size());  // 拷贝视口矩阵数据

  // 定义相机图像的布局参数
  int gap = 0;  // 相机图像之间的间隔
  int camera_width = 500;  // 相机图像的宽度
  int camera_height = static_cast<float>(camera_width / (float)image_artist_param.image_width * image_artist_param.image_height);  // 相机图像的高度
  int offset_cameras[][3] = {
      {-camera_width / 2, -content_height / 2 + gap, 0},  // 相机 1 的位置和翻转标志
      {content_width / 2 - camera_width - gap, -content_height / 2 + camera_height / 2, 0},  // 相机 2 的位置和翻转标志
      {-content_width / 2 + gap, -content_height / 2 + camera_height / 2, 0},  // 相机 3 的位置和翻转标志
      {-camera_width / 2, +content_height / 2 - camera_height - gap, 1},  // 相机 4 的位置和翻转标志
      {-content_width / 2 + gap, +content_height / 2 - camera_height - camera_height / 2, 0},  // 相机 5 的位置和翻转标志
      {content_width / 2 - camera_width - gap, +content_height / 2 - camera_height - camera_height / 2, 1}};  // 相机 6 的位置和翻转标志

  // 创建 ImageArtist 实例
  auto visualizer = nv::create_image_artist(image_artist_param);
  for (size_t icamera = 0; icamera < images.size(); ++icamera) {
    // 计算相机图像在最终图像中的位置
    int ox = offset_cameras[icamera][0] + content_width / 2;
    int oy = offset_cameras[icamera][1] + content_height / 2;
    bool xflip = static_cast<bool>(offset_cameras[icamera][2]);  // 是否水平翻转

    // 在相机图像上绘制预测框
    visualizer->draw_prediction(icamera, predictions, xflip);

    // 将相机图像数据拷贝到设备上
    nv::Tensor device_image(std::vector<int>{900, 1600, 3}, nv::DataType::UInt8);
    device_image.copy_from_host(images[icamera], stream);

    // 如果需要水平翻转,则对图像进行翻转
    if (xflip) {
      auto clone = device_image.clone(stream);
      scene->flipx(clone.ptr<unsigned char>(), clone.size(1), clone.size(1) * 3, clone.size(0), device_image.ptr<unsigned char>(),
                   device_image.size(1) * 3, stream);
      checkRuntime(cudaStreamSynchronize(stream));  // 同步 CUDA 流
    }

    // 将绘制结果应用到相机图像上
    visualizer->apply(device_image.ptr<unsigned char>(), stream);

    // 将相机图像调整大小并放置到最终图像中
    scene->resize_to(device_image.ptr<unsigned char>(), ox, oy, ox + camera_width, oy + camera_height, device_image.size(1),
                     device_image.size(1) * 3, device_image.size(0), 0.8f, stream);
    checkRuntime(cudaStreamSynchronize(stream));  // 同步 CUDA 流
  }

  // 保存最终图像到指定路径
  printf("Save to %s\n", save_path.c_str());
  stbi_write_jpg(save_path.c_str(), scene_device_image.size(1), scene_device_image.size(0), 3,
                 scene_device_image.to_host(stream).ptr(), 100);  // 保存为 JPG 文件
}

3. 代码功能总结

visualize 函数的主要功能是将点云、检测框和相机图像拼接成一张完整的可视化图像,并保存为 JPG 文件。具体步骤包括:

  1. 处理检测框数据:将检测框数据拷贝到 predictions 向量中。
  2. 初始化场景图像:创建并初始化设备上的场景图像。
  3. 渲染点云和检测框:使用 BEVArtist 渲染点云、检测框和自车位置。
  4. 处理相机图像:将相机图像数据拷贝到设备上,绘制检测框,并根据需要翻转图像。
  5. 拼接图像:将相机图像调整大小并放置到场景图像中。
  6. 保存结果:将最终的可视化图像保存为 JPG 文件。
/yolo88(SE)/yolov5-master/detect.py" detect: weights=/home/zzc/yolo88(SE)/yolov5-master/best.pt, source=0, data=/home/zzc/yolo88(SE)/yolov5-master/data/smoke_data.yaml, imgsz=[640, 640], conf_thres=0.25, iou_thres=0.45, max_det=1000, device=, view_img=False, save_txt=False, save_csv=False, save_conf=False, save_crop=False, nosave=False, classes=None, agnostic_nms=False, augment=False, visualize=False, update=False, project=runs/detect, name=exp, exist_ok=False, line_thickness=3, hide_labels=False, hide_conf=False, half=False, dnn=False, vid_stride=1 requirements: Ultralytics requirement ['Pillow>=10.0.1'] not found, attempting AutoUpdate... WARNING ⚠️ requirements: ❌ AutoUpdate skipped (offline) YOLOv5 🚀 2025-5-13 Python-3.11.2 torch-2.7.0+cpu CPU Fusing layers... YOLOv5_aux summary: 167 layers, 10126476 parameters, 0 gradients, 21.4 GFLOPs [ WARN:0@6.414] global cap_v4l.cpp:1848 getProperty VIDEOIO(V4L2:/dev/video0): Unable to get camera FPS 1/1: 0... Success (inf frames 640x480 at 99.00 FPS) Traceback (most recent call last): File "/home/zzc/yolo88(SE)/yolov5-master/detect.py", line 298, in <module> main(opt) File "/home/zzc/yolo88(SE)/yolov5-master/detect.py", line 293, in main run(**vars(opt)) File "/home/zzc/.local/lib/python3.11/site-packages/torch/utils/_contextlib.py", line 116, in decorate_context return func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^ File "/home/zzc/yolo88(SE)/yolov5-master/detect.py", line 109, in run dataset = LoadStreams(source, img_size=imgsz, stride=stride, auto=pt, vid_stride=vid_stride) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/zzc/yolo88(SE)/yolov5-master/utils/dataloaders.py", line 411, in __init__ s = np.stack([letterbox(x, img_size, stride=stride, auto=auto)[0].shape for x in self.imgs]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/zzc/yolo88(SE)/yolov5-master/utils/dataloaders.py", line 411, in <listcomp> s = np.stack([letterbox(x, img_size, stride=stride, auto=auto)[0].shape for x in self.imgs]) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/zzc/yolo88(SE)/yolov5-master/utils/augmentations.py", line 113, in letterbox shape = im.shape[:2] # current shape [height, width] ^^^^^^^^ AttributeError: 'NoneType' object has no attribute 'shape'
最新发布
06-01
### mmdetection3d BEVFusion 可视化实现方法 BEVFusion 是一种基于鸟瞰图(Bird's Eye View, BEV)的多模态融合技术,用于处理激光雷达和相机数据的联合特征提取与目标检测。为了对 BEVFusion 的结果进行可视化,可以按照以下方式操作: #### 1. 数据准备 在开始之前,需确保已准备好 NuScenes 数据集,并将其存储路径设置为 `mmdetection3d` 中指定的位置。如果仅希望测试少量样本,则可将配置文件中的版本号由 `v1.0-trainval` 修改为 `v1.0-mini`[^3]。 此外,在运行任何脚本前,请确认预训练模型已被成功下载至 `/mmdetection/checkpoints` 文件夹下。对于 SMOKE 模型而言,其对应的权重文件应命名为类似于 `smoke_dla34_pytorch_dlaneck_gn-all_8x4_6x_kitti-mono3d.pth`[^1]。 #### 2. 安装依赖项 确保安装了最新版的 MMDetection3D 和其他必要的 Python 库。可以通过执行以下命令完成环境搭建: ```bash pip install mmcv-full==latest+torch.cuda_version torchvision torchaudio -f https://download.openmmlab.com/mmcv/dist/cuda_version/torch_version/index.html pip install openmim mim install mmdet3d ``` #### 3. 配置文件调整 进入项目根目录下的 `configs/bevfusion` 路径,找到适用于当前任务的具体配置文件(如 `bevfusion_fpn_r50-dcn_lidar-cam_nus.py`)。在此基础上修改输入参数以适配本地实验条件。 #### 4. 编写可视化脚本 编写一段简单的 Python 脚本来加载模型并对单张图像或点云序列实施推理过程。以下是针对 BEVFusion 结果可视化的代码片段示例: ```python from mmdet3d.apis import init_detector, inference_detector import numpy as np import cv2 # 初始化模型实例 config_file = 'configs/bevfusion/bevfusion_fpn_r50-dcn_lidar-cam_nus.py' checkpoint_file = '/path/to/pretrained_model.pth' # 替换为实际路径 model = init_detector(config_file, checkpoint=checkpoint_file, device='cuda:0') # 加载测试图片及LiDAR扫描帧 image_path = 'demo/data/nuscenes/sample_image.jpg' lidar_path = 'demo/data/nuscenes/sample_pointcloud.pcd' img = cv2.imread(image_path) # 执行推断 result, _ = inference_detector(model, img, lidar_path=lidar_path) # 提取预测边界框信息 bboxes = result['boxes_3d'] scores = result['scores_3d'] # 绘制矩形标注于原图之上 for i in range(len(bboxes)): bbox = bboxes[i].corners.numpy().astype(np.int32).reshape(-1, 2) score = scores[i] if score > 0.3: # 设置阈值过滤低可信度对象 color = (0, 255, 0) # 绿色表示高概率区域 thickness = 2 for j in range(4): # 连接顶点形成闭合轮廓线 start_point = tuple(bbox[j]) end_point = tuple(bbox[(j + 1) % 4]) cv2.line(img, start_point, end_point, color=color, thickness=thickness) # 显示最终效果图 cv2.imshow('Visualization', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 上述程序通过调用 OpenMMLab 提供的标准接口完成了从加载预定义网络结构到渲染三维物体位置标记的整体流程[^2]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值