当前位置: 首页 > article >正文

Dexcap复现代码数据预处理全流程(一)——replay_human_traj_vis.py

此脚本的主要功能是通过视觉化工具和键盘交互,动态调整和校准人体轨迹数据中的点云数据(PCD)和位姿(pose)

  • 校准模式下,通过键盘输入动态调整点云与位姿偏移量,并保存调整结果
  • 非校准模式下,按顺序回放所有帧的点云和位姿数据

首先,录制完的数据结构如下:

save_data_scenario_1
├── frame_0
│   ├── color_image.jpg           # Chest camera RGB image
│   ├── depth_image.png           # Chest camera depth image
│   ├── pose.txt                  # Chest camera 6-DoF pose in world frame
│   ├── pose_2.txt                # Left hand 6-DoF pose in world frame
│   ├── pose_3.txt                # Right hand 6_DoF pose in world frame
│   ├── left_hand_joint.txt       # Left hand joint positions (3D) in the palm frame
│   └── right_hand_joint.txt      # Right hand joint positions (3D) in the palm frame
├── frame_1
└── ...

为了可视化收集的数据,显示点云和捕获的手部动作,可以运行:

cd DexCap/STEP1_collect_data
python replay_human_traj_vis.py --directory save_data_scenario_1

此外,此脚本还提供了一个用于纠正 SLAM 初始漂移的接口。运行以下脚本,并使用数字键来纠正漂移,校正将应用于整个视频:

python replay_human_traj_vis.py --directory save_data_scenario_1 --calib
python calculate_offset_vis_calib.py --directory save_data_scenario_1

接下来详细解释一下此脚本的代码逻辑

目录

1 库函数引用

2 主函数

3 ReplayDataVisualizer 类

4 键盘交互逻辑

5 pycharm 优化


1 库函数引用

"""
可视化保存的点云(PCD)文件和位姿

使用示例:
(1) 可视化所有帧:
python replay_human_traj_vis.py --directory ./saved_data/

(2) 用于校准:
python replay_human_traj_vis.py --directory ./saved_data/ -calib
"""

import argparse  # 用于解析命令行参数
import os  # 用于文件和目录操作
import copy  # 用于创建对象的副本
import zmq  # 用于进程间通信(在本脚本中未直接使用)
import cv2  # 用于图像处理(在本脚本中未直接使用)
import sys  # 用于系统特定参数和功能
import shutil  # 用于文件操作,如删除目录
import open3d as o3d  # 用于3D可视化
import numpy as np  # 用于数值计算
import platform  # 用于判断操作系统
from pynput import keyboard  # 用于监听键盘输入
from visualizer import *  # 导入自定义可视化工具

2 主函数

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="可视化保存的帧数据。")  # 创建参数解析器
    parser.add_argument("--directory", type=str, default="./saved_data", help="保存数据的目录")  # 数据目录参数
    parser.add_argument("--default", type=str, default="default_offset", help="默认校准目录")  # 默认校准偏移目录
    parser.add_argument("--calib", action='store_true', help="启用校准模式")  # 校准模式标志

    args = parser.parse_args()  # 解析命令行参数

    assert os.path.exists(args.directory), f"给定的目录不存在: {args.directory}"  # 检查目录是否存在
    visualizer = ReplayDataVisualizer(args.directory)  # 初始化可视化工具

    if args.calib:  # 如果启用了校准模式
        # 加载校准偏移量
        visualizer.right_hand_offset = np.loadtxt(os.path.join(args.default, "calib_offset.txt"))
        visualizer.right_hand_ori_offset = np.loadtxt(os.path.join(args.default, "calib_ori_offset.txt"))
        visualizer.left_hand_offset = np.loadtxt(os.path.join(args.default, "calib_offset_left.txt"))
        visualizer.left_hand_ori_offset = np.loadtxt(os.path.join(args.default, "calib_ori_offset_left.txt"))

        # 检查临时校准目录是否存在
        if os.path.exists("tmp_calib"):
            response = (
                input(
                    f"tmp_calib 已存在。是否覆盖?(y/n): "
                )
                .strip()
                .lower()
            )
            if response != "y":  # 如果用户选择不覆盖
                print("退出程序,未覆盖现有目录。")
                sys.exit()  # 退出程序
            else:
                shutil.rmtree("tmp_calib")  # 删除现有目录
        os.makedirs("tmp_calib", exist_ok=True)  # 创建临时校准目录
        visualizer.replay_keyframes_calibration()  # 启动校准模式
    else:  # 如果未启用校准模式
        # 加载校准偏移量
        visualizer.right_hand_offset = np.loadtxt("{}/calib_offset.txt".format(args.directory))
        visualizer.right_hand_ori_offset = np.loadtxt("{}/calib_ori_offset.txt".format(args.directory))
        visualizer.left_hand_offset = np.loadtxt("{}/calib_offset_left.txt".format(args.directory))
        visualizer.left_hand_ori_offset = np.loadtxt("{}/calib_ori_offset_left.txt".format(args.directory))
        visualizer.replay_all_frames()  # 回放所有帧

代码入口 if __name__ == "__main__":,解析命令行参数并执行不同模式的逻辑,包括:

1. 检查 --directory 指定的目录是否存在

2. 如果启用校准模式 (--calib):

  • 加载默认偏移值文件(如 calib_offset.txt 和 calib_ori_offset.txt)
  • 检查并处理可能已有的临时校准文件目录(tmp_calib)
  • 调用 replay_keyframes_calibration 方法启动校准模式

否则,加载对应帧数据并调用 replay_all_frames 方法,逐帧回放所有数据

3. 数据校验与文件保存

校验数据目录和默认偏移文件是否存在

确保 tmp_calib 目录可用,如果已有旧文件,会提示用户是否覆盖。

校准结果存储为 .txt 文件,按帧编号命名(如 frame_10.txt)

3 ReplayDataVisualizer 类

# 可视化类,用于回放和校准 PCD 数据
class ReplayDataVisualizer(DataVisualizer):
    def __init__(self, directory):
        super().__init__(directory)  # 使用数据目录初始化基类

    def replay_keyframes_calibration(self):
        """
        可视化并逐帧校准。
        """
        global delta_movement_accu, delta_ori_accu, next_frame, frame
        if self.R_delta_init is None:  # 检查是否初始化了基准帧
            self.initialize_canonical_frame()

        self._load_frame_data(frame)  # 加载当前帧的数据

        # 将 3D 对象添加到可视化工具中
        self.vis.add_geometry(self.pcd)
        self.vis.add_geometry(self.coord_frame_1)
        self.vis.add_geometry(self.coord_frame_2)
        self.vis.add_geometry(self.coord_frame_3)
        for joint in self.left_joints + self.right_joints:
            self.vis.add_geometry(joint)
        for cylinder in self.left_line_set + self.right_line_set:
            self.vis.add_geometry(cylinder)

        next_frame = True  # 准备加载下一帧
        try:
            with keyboard.Listener(on_press=on_press) as listener:  # 启动键盘监听器
                while True:
                    if next_frame == True:  # 如果准备好,加载下一帧
                        next_frame = False
                        frame += 10
                    self._load_frame_data(frame)  # 加载帧数据

                    self.step += 1  # 步数计数器递增

                    # 更新可视化工具中的几何体
                    self.vis.update_geometry(self.pcd)
                    self.vis.update_geometry(self.coord_frame_1)
                    self.vis.update_geometry(self.coord_frame_2)
                    self.vis.update_geometry(self.coord_frame_3)
                    for joint in self.left_joints + self.right_joints:
                        self.vis.update_geometry(joint)
                    for cylinder in self.left_line_set + self.right_line_set:
                        self.vis.update_geometry(cylinder)

                    self.vis.poll_events()  # 处理可视化事件
                    self.vis.update_renderer()  # 更新可视化渲染器
                listener.join()  # 等待监听器结束
        finally:
            print("cumulative_correction ", self.cumulative_correction)  # 打印累计修正值

此类继承自 DataVisualizer,实现了点云和轨迹数据的校准与回放功能

1. 初始化

通过 __init__ 方法调用父类构造函数,初始化路径和可视化工具

校准模式下,加载偏移量并初始化临时存储目录

2. 校准方法 replay_keyframes_calibration

核心方法,用于处理校准模式下逐帧的动态调整:

  • 如果尚未初始化基准帧(R_delta_init),调用 initialize_canonical_frame
  • 加载当前帧的点云和姿态数据,并添加到 open3d 的可视化窗口
  • 启动键盘监听器(keyboard.Listener),实时监听按键调整点云和位姿的偏移量
  • 每次按下保存键时,更新临时文件中的偏移量,并加载下一帧

4 键盘交互逻辑

# 脚本中使用的全局变量
def on_press(key):
    global next_frame, delta_movement_accu, delta_ori_accu, delta_movement_accu_left, delta_ori_accu_left, adjust_movement, adjust_right, frame, step

    # 判断当前操作系统类型
    os_type = platform.system()

    # 根据当前模式(右手/左手,平移/旋转)选择需要调整的数据
    if adjust_right:
        data_to_adjust = delta_movement_accu if adjust_movement else delta_ori_accu
    else:
        data_to_adjust = delta_movement_accu_left if adjust_movement else delta_ori_accu_left

    if os_type == "Linux":  # Linux 特定的按键绑定
        # 根据按键调整平移/旋转偏移量
        if key.char == '6':  # 沿负 x 方向移动
            data_to_adjust[0] += step
        elif key.char == '4':  # 沿正 x 方向移动
            data_to_adjust[0] -= step
        elif key.char == '8':  # 沿正 y 方向移动
            data_to_adjust[1] += step
        elif key.char == '2':  # 沿负 y 方向移动
            data_to_adjust[1] -= step
        elif key.char == "7":  # 沿正 z 方向移动
            data_to_adjust[2] += step
        elif key.char == "9":  # 沿负 z 方向移动
            data_to_adjust[2] -= step
        elif key.char == "3":  # 重置所有偏移量
            delta_movement_accu *= 0.0
            delta_ori_accu *= 0.0
            delta_movement_accu_left *= 0.0
            delta_ori_accu_left *= 0.0
            next_frame = True
        elif key.char == "0":  # 保存偏移量并加载下一帧
            # 将偏移量保存到临时校准文件
            if (delta_movement_accu != np.array([0.0, 0.0, 0.0])).any() or (delta_ori_accu != np.array([0.0, 0.0, 0.0])).any() or (delta_movement_accu_left != np.array([0.0, 0.0, 0.0])).any() or (delta_ori_accu_left != np.array([0.0, 0.0, 0.0])).any():
                frame_dir = "./tmp_calib/"
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}.txt"), delta_movement_accu)
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}_ori.txt"), delta_ori_accu)
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}_left.txt"), delta_movement_accu_left)
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}_ori_left.txt"), delta_ori_accu_left)

            # 重置偏移量,准备处理下一帧
            delta_movement_accu *= 0.0
            delta_ori_accu *= 0.0
            delta_movement_accu_left *= 0.0
            delta_ori_accu_left *= 0.0
            next_frame = True
        elif key == keyboard.Key.space:  # 切换平移和旋转调整模式
            adjust_movement = not adjust_movement
        elif key == keyboard.Key.enter:  # 切换左右手调整模式
            adjust_right = not adjust_right
        else:
            print("Key error", key)  # 对于不支持的按键打印错误

    elif os_type == "Windows":  # Windows 特定的按键绑定
        # 与 Linux 类似的逻辑,但使用不同的按键(方向键、Page Up/Down 等)
        if key == keyboard.Key.right:  # 沿负 x 方向移动
            data_to_adjust[0] += step
        elif key == keyboard.Key.left:  # 沿正 x 方向移动
            data_to_adjust[0] -= step
        elif key == keyboard.Key.up:  # 沿正 y 方向移动
            data_to_adjust[1] += step
        elif key == keyboard.Key.down:  # 沿负 y 方向移动
            data_to_adjust[1] -= step
        elif key == keyboard.Key.home:  # 沿正 z 方向移动
            data_to_adjust[2] += step
        elif key == keyboard.Key.page_up:  # 沿负 z 方向移动
            data_to_adjust[2] -= step
        elif key == keyboard.Key.page_down:  # 重置偏移量
            delta_movement_accu *= 0.0
            delta_ori_accu *= 0.0
            delta_movement_accu_left *= 0.0
            delta_ori_accu_left *= 0.0
            next_frame = True
        elif key == keyboard.Key.insert:  # 保存偏移量并加载下一帧
            # 将偏移量保存到临时校准文件
            if (delta_movement_accu != np.array([0.0, 0.0, 0.0])).any() or (delta_ori_accu != np.array([0.0, 0.0, 0.0])).any() or (delta_movement_accu_left != np.array([0.0, 0.0, 0.0])).any() or (delta_ori_accu_left != np.array([0.0, 0.0, 0.0])).any():
                frame_dir = "./tmp_calib/"
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}.txt"), delta_movement_accu)
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}_ori.txt"), delta_ori_accu)
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}_left.txt"), delta_movement_accu_left)
                np.savetxt(os.path.join(frame_dir, f"frame_{frame}_ori_left.txt"), delta_ori_accu_left)

            # 重置偏移量,准备处理下一帧
            delta_movement_accu *= 0.0
            delta_ori_accu *= 0.0
            delta_movement_accu_left *= 0.0
            delta_ori_accu_left *= 0.0
            next_frame = True
        elif key == keyboard.Key.space:  # 切换平移和旋转调整模式
            adjust_movement = not adjust_movement
        elif key == keyboard.Key.enter:  # 切换左右手调整模式
            adjust_right = not adjust_right
        else:
            print("Key error", key)  # 对于不支持的按键打印错误

键盘输入是代码的交互核心,定义在 on_press 方法中

1. 调整逻辑

(1)判断调整目标:

  • 右手(adjust_right=True)或左手(adjust_right=False)
  • 调整移动偏移量(adjust_movement=True)或姿态偏移量(adjust_movement=False)

(2)根据操作系统选择按键:

  • Linux 使用数字键(如 6 表示 x 轴负方向移动)
  • Windows 使用方向键和功能键(如 Key.right 表示 x 轴负方向移动)

2. 特殊按键功能

  • Space:切换移动和姿态调整模式
  • Enter:切换左右手调整
  • 0 / Insert:保存当前偏移量到文件并加载下一帧
  • 3 / Page Down:重置所有偏移量

3. 偏移量存储

  • 偏移量存储在 delta_movement_accu 和 delta_ori_accu 等全局变量中
  • 保存时调用 np.savetxt,将每帧的调整结果存储到临时目录(tmp_calib)

5 pycharm 优化

因为想用 pychram 调试,所以简单改一下进入条件,变成选择模式:

# 添加用户交互菜单
print("Select mode:")
print("1. Calibration Mode")
print("2. Visualization Mode")
mode = input("Enter your choice (1/2): ").strip()

if mode == "1":
    print("Entering calibration mode...")
    visualizer.right_hand_offset = np.loadtxt(os.path.join(args.default, "calib_offset.txt"))
    visualizer.right_hand_ori_offset = np.loadtxt(os.path.join(args.default, "calib_ori_offset.txt"))
    visualizer.left_hand_offset = np.loadtxt(os.path.join(args.default, "calib_offset_left.txt"))
    visualizer.left_hand_ori_offset = np.loadtxt(os.path.join(args.default, "calib_ori_offset_left.txt"))

    # 检查临时校准目录是否存在
    if os.path.exists("tmp_calib"):
        response = input("tmp_calib already exists. Do you want to override? (y/n): ").strip().lower()
        if response != "y":
            print("Exiting program without overriding the existing directory.")
            sys.exit()
        else:
            shutil.rmtree("tmp_calib")
    os.makedirs("tmp_calib", exist_ok=True)
    visualizer.replay_keyframes_calibration()  # 启动校准模式
elif mode == "2":
    print("Entering visualization mode...")
    visualizer.right_hand_offset = np.loadtxt(f"{args.directory}/calib_offset.txt")
    visualizer.right_hand_ori_offset = np.loadtxt(f"{args.directory}/calib_ori_offset.txt")
    visualizer.left_hand_offset = np.loadtxt(f"{args.directory}/calib_offset_left.txt")
    visualizer.left_hand_ori_offset = np.loadtxt(f"{args.directory}/calib_ori_offset_left.txt")
    visualizer.replay_all_frames()  # 启动可视化模式
else:
    print("Invalid choice. Exiting program.")
    sys.exit()

此时再运行会进入选择:


http://www.kler.cn/a/469202.html

相关文章:

  • 自动驾驶相关知识学习笔记
  • 我的nvim的init.lua配置
  • Backend - EF Core(C# 操作数据库 DB)
  • 【HarmonyOS-ArkTS语言】计算器的实现【合集】
  • vue3组件化开发优势劣势分析,及一个案例
  • 条件期望窥探
  • leecode1143.最长公共子序列
  • 成语接龙游戏生成器:结合 ZhipuAI 的 Python 实现
  • MySql核心面试面试问题解析
  • Redis - 4 ( 9000 字 Redis 入门级教程 )
  • VSCode突然消失,只好重新下载安装
  • Redis(基础篇 + 实践篇 )
  • 青少年编程与数学 02-006 前端开发框架VUE 04课题、组合式API
  • 后端Java开发:第八天
  • 【网络云SRE运维开发】2025第1周-每日【2025/01/04】小测-【第5章 交换机的工作原理】理论和实操-解析
  • MySQL数据表设计 系统权限表设计 权限、角色、用户表设计
  • 详解云桌面3种主流架构
  • 前端编码技巧与规范
  • 结合前端的响应式开发深入理解设备像素比
  • 【MyBatis源码分析】Spring与MyBatis整合深入解析
  • 8. C++ 面向对象之特性一(封装)
  • Arm Cortex - M3 MCU 全流程设计:从前端到后端全方位掌握
  • Transformer:如何颠覆深度学习格局并引领AI的未来
  • 青少年编程与数学 02-006 前端开发框架VUE 08课题、列表渲染
  • 偏振测量——典型光学元件的穆勒矩阵
  • 使用Python实现健康跟踪应用:打造智能健康管理助手