文件最后提交记录最后更新时间
camera_isp: tighten close detection and fix focus/gain bugs Address PR105 review feedback: * calibrator close: only treat WND_PROP_VISIBLE < 0 as destroyed so minimise / transient unmap no longer self-exits the calibrator. X-click is still caught via _trackbar_panel_closed() (the most reliable signal under GTK). * run() finally: join the AUTO/search worker (timeout 2s) before destroying windows, so background v4l2 writes don't outlive the GUI. * v4l2_ctl.apply_params now returns (ok, msg, handled_keys) so the calibrator can route unrecognised keys (e.g. focus) to the ROS fallback instead of silently dropping them. * evaluate_gain_step_b takes gain_caps and clamps the halved gain via gain_caps.minimum (was incorrectly using brightness_caps.minimum, which forced final_gain=64 on UVCs whose brightness floor was 64). Signed-off-by: grangerxsp <xingshiping@huawei.com> 27 天前
dataset_tools: add camera alignment tool Move the marker-based camera alignment helper into dataset_tools so the interactive calibration workflow stays outside robot_config. Extract the OpenCV GUI resolution logic into a reusable helper, register the ros2 run entry point in dataset_tools, and move the related tests and documentation while keeping the original CLI options intact. Signed-off-by: Shi Xin <shixin21@h-partners.com> 2 个月前
robot_config: add recording support and dataset_tools package Introduce 'dataset_tools' to handle episode recording and dataset conversion. Update 'robot_config' to integrate recording launch builders. - Create 'dataset_tools' package with episode recorder and CLI. - Add 'recording.py' launch builder in 'robot_config'. - Update 'so101_single_arm.yaml' and 'robot.launch.py' for recording support. Signed-off-by: XiaoqiangWu <wuxiaoqiang.rtos@huawei.com> 2 个月前
dataset_tools: clean rerun environment setup Move the user-site guard out of rerun_viewer and into the launch environment so the sidecar no longer mutates sys.path during normal startup. Remove the remaining virtualenv import fallback, keep launch coverage for PYTHONNOUSERSITE, and rely on the build workflow to generate venv-backed entrypoints with the correct interpreter. Signed-off-by: XiaoqiangWu <wuxiaoqiang.rtos@huawei.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> 1 个月前
camera_isp: add ISP calibration pipeline, GUI calibrator and search Introduce dataset_tools/camera_isp as a new pure-Python subpackage providing the full camera ISP calibration stack used with usb_cam_node, and a single-window cv2 GUI orchestrator that drives it interactively. New source files (no test files, no images, no temporary artifacts): camera_isp/__init__.py - public API surface declaration camera_isp/color_space.py - BGRtoLab, BGRtoXYZ, McCamy Kelvin camera_isp/exposure_units.py - lux/EV/shutter conversion helpers camera_isp/lut.py + lut_data - white-balance Kelvin lookup table camera_isp/build_lut.py - offline LUT rebuild script camera_isp/hw_stages.py - per-parameter V4L2 stage drivers camera_isp/hw_pipeline.py - 4-stage pipeline orchestrator camera_isp/v4l2_ctl.py - v4l2-ctl subprocess wrapper camera_isp/solver.py - least-squares CCM / WB / exposure camera_isp/sw_isp.py - software ISP (diagonal + 3x3 CCM) camera_isp/colorchecker24.py - standard 24-card sRGB ground truth camera_isp/color_search.py - unified K/C/Sat search module color_search.py implements three cost modes for the grid-search driver: (1) 24-card: sum of CIEDE2000 against ColorChecker ground truth (2) AUTO: chroma-weighted Sliced Wasserstein on (a*,b*) distribution, stable across scene-layout changes, no per-eval re-clustering (3) m/ROI: per-patch CIEDE2000 plus global SWD background term to prevent background color drift while honoring user-chosen patches camera_isp_calibrator.py - single-window cv2 GUI orchestrator: - Trackbars, ref/live split view, SW-ISP preview third pane - [a] 4-stage auto pipeline [m] manual ROI drag [c] color search - Live [SEARCH n/N] HUD refreshed per eval; q/Esc cancel with restore - Auto CCM re-solve after color search settles to new K/C/Sat state Robustness and UX fixes folded in: - Stop the GUI thread reverting every K/C/Sat candidate during color search by short-circuiting _maybe_apply_pending whenever the mode is not IDLE; previously only AUTO was guarded so SEARCH n/N let the trackbar seed values overwrite each candidate one frame later. - Chain a hardware K/C/Sat search after the ColorChecker24 wizard so cc24 mode now drives the camera through cost_24card against the X-Rite truth Lab values, mirroring the m-mode REF-pair workflow. - Drop the SW-only third pane in cc24 mode now that the live pane already reflects the corrected hardware output. - Widen the default white-balance search range from +/-600K to +/-1600K (step_K=400, n_K=9) so extreme indoor lighting (warm tungsten through overcast daylight) is reachable in one sweep. - Trim SettleConfig defaults (delay_ms 200->120, n_drop 2->1, n_capture 5->3) for ~40% faster per-eval turnaround now that the fresh-frame gate handles staleness explicitly. - Strip non-ASCII glyphs from cv2 HERSHEY-rendered banners and labels (arrows, em-dashes, Greek letters) so the GUI no longer shows '?'. robot_config: add camera_isp_overrides.py launch builder and wire it into perception.py so ISP parameters load from robot_config SSOT. Update so101_single_arm.yaml with camera ISP parameter defaults. Add scripts/camera_topic_viewer.py for quick camera topic inspection. Signed-off-by: grangerxsp <xingshiping@huawei.com> 27 天前
camera_isp: fix review blockers in manual pedestal and packaging Fix the manual pedestal reuse path to use the current in-progress ROI pair instead of resolving an index through the previously saved _roi_pairs state. This prevents first-run failures and stale-pair reuse after a rotated manual selection. Install camera_isp package resources so lut_data.npz and the offline table JSON are available when the calibrator is launched from an installed workspace. Declare scipy in both setup.py and package.xml because the runtime K/C/Sat search imports scipy.cluster.vq and scipy.optimize. Restore the default front camera and observation contract in so101_single_arm so the PR does not silently change the observation set expected by the default ACT model. Signed-off-by: grangerxsp <xingshiping@huawei.com> 27 天前
robot_config: add recording support and dataset_tools package Introduce 'dataset_tools' to handle episode recording and dataset conversion. Update 'robot_config' to integrate recording launch builders. - Create 'dataset_tools' package with episode recorder and CLI. - Add 'recording.py' launch builder in 'robot_config'. - Update 'so101_single_arm.yaml' and 'robot.launch.py' for recording support. Signed-off-by: XiaoqiangWu <wuxiaoqiang.rtos@huawei.com> 2 个月前
camera_isp: fix review blockers in manual pedestal and packaging Fix the manual pedestal reuse path to use the current in-progress ROI pair instead of resolving an index through the previously saved _roi_pairs state. This prevents first-run failures and stale-pair reuse after a rotated manual selection. Install camera_isp package resources so lut_data.npz and the offline table JSON are available when the calibrator is launched from an installed workspace. Declare scipy in both setup.py and package.xml because the runtime K/C/Sat search imports scipy.cluster.vq and scipy.optimize. Restore the default front camera and observation contract in so101_single_arm so the PR does not silently change the observation set expected by the default ACT model. Signed-off-by: grangerxsp <xingshiping@huawei.com> 27 天前
README.md

Dataset Tools

ROS 2 数据集采集与转换工具,用于 LeRobot v3 数据集格式。

概述

本包提供以下功能:

  • Episode 录制: 通过 Action Server 控制的分段录制
  • Bag 转 LeRobot: 将 ROS 2 bag 转换为 LeRobot v3 数据集格式

架构设计

单一真理来源 (Single Source of Truth)

所有数据集工具使用 robot_config 包下的配置文件作为唯一配置来源,例如:

src/robot_config/config/robots/so101_single_arm.yaml
├── contract.observations    ← 观测定义(相机、状态等)
├── contract.actions         ← 动作定义(arm、gripper)
├── contract.rate_hz         ← 采样率
└── control_modes            ← 运行时控制模式配置

这确保了:

  • 训练数据导出与在线推理配置一致
  • 无需维护重复的 contract 文件
  • 配置变更自动传播到所有组件

工具

1. record_cli - 交互式录制客户端

用于控制 episode 录制的命令行工具。

启动录制服务(Ubuntu 录制服务器):

ros2 launch robot_config robot.launch.py \
    robot_config:=so101_single_arm \
    control_mode:=teleop \
    record:=true \
    record_mode:=episodic \
    use_sim:=false

如需启用 Rerun 可视化

ros2 launch robot_config robot.launch.py \
    robot_config:=so101_single_arm \
    control_mode:=teleop \
    record:=true \
    record_mode:=episodic \
    record_visualizer:=rerun \
    use_sim:=false

启动录制客户端(同机或另一台设置了相同 ROS_DOMAIN_ID 的机器):

ros2 run dataset_tools record_cli

episodic 录制目录现在按 dataset 组织:

<bag_base_dir>/
└── <dataset_name>/
    ├── dataset.yaml
    └── episodes/
        ├── episode_000001/
        │   ├── metadata.yaml
        │   └── *.mcap
        └── episode_000002/
            ├── metadata.yaml
            └── *.mcap
  • bag_base_dir 来自 robot_config.recording.bag_base_dir
  • dataset_name 默认取 recording.dataset_name,未配置时回退到机器人名
  • dataset.yaml 保存 dataset 级元信息;可选通过 recording.default_taskrecording.task_family 预填任务语义
  • episode 级 prompt 仍写入各自 bag 的 metadata.yaml

使用方式

========================================================
Dataset Collection CLI
Enter prompt text to start recording. (Press Enter to reuse: 'get')
Type 'q' or 'quit' to exit.
========================================================
Prompt > get        # 输入任务描述开始录制
[INFO] 🔴 RECORDING STARTED. (Press Enter to stop early)
[INFO] ✅ RECORDING SAVED: Wrote 1894 messages to /path/to/episode
Prompt > q          # 退出

录制完成后,推荐直接把整个 dataset 根目录转换成 LeRobot v3 数据集:

ros2 run dataset_tools bag_to_lerobot \
    --bags-dir ~/rosbag/episodes/so101_single_arm \
    --robot-config src/robot_config/config/robots/so101_single_arm.yaml \
    --out /path/to/output_dataset

2. bag_to_lerobot - Bag 转 LeRobot 数据集

将 ROS 2 episodic dataset 根目录转换为 LeRobot v3 数据集格式。

基本用法

ros2 run dataset_tools bag_to_lerobot \
    --bags-dir ~/rosbag/episodes/so101_single_arm \
    --robot-config src/robot_config/config/robots/so101_single_arm.yaml \
    --out /path/to/output_dataset

参数说明

参数 说明 默认值
--bags-dir dataset 根目录或 episodes 目录,自动发现多个 episode bag 必需
--robot-config robot_config.yaml 路径 必需
--out 输出数据集目录 必需
--repo-id 数据集 repo_id rosbag_v30
--no-videos 存储 PNG 图像而非视频 false
--timestamp 时间戳来源 (contract/bag/header) contract
--image-threads 图像写入线程数 4
--chunk-size 每个 chunk 的帧数 1000

输出结构

output_dataset/
├── videos/
│   ├── observation.images.front/
│   │   └── chunk-000/file-000.mp4
│   ├── observation.images.top/
│   └── observation.images.wrist/
├── data/
│   └── chunk-000/file-000.parquet
└── meta/
    ├── info.json
    ├── tasks.parquet
    ├── stats.json
    └── episodes/

3. episode_recorder - 录制服务节点

由 launch 文件自动启动的录制服务,提供 record_episode Action Server。

通常不需要直接运行,由 robot.launch.py 根据 record_mode:=episodic 参数自动加载。 录制结果会写到 <bag_base_dir>/<dataset_name>/episodes/episode_XXXXXX/,并在 dataset 根目录生成 dataset.yaml

4. camera_alignment - 基于 ArUco 的相机对齐工具

用于在数据采集或复现前,直接读取本机视频设备并对齐摄像头视角。

基本用法

ros2 run dataset_tools camera_alignment \
    --cameras_index_or_path /dev/video0 \
    --reference-path /tmp/camera_reference_multi.json \
    --reference-image-path /tmp/reference_img.png

工具支持:

  • 保存当前 ArUco 角点作为参考基准
  • 实时显示与参考画面的平均像素误差
  • 进入“虚影对齐”界面辅助恢复视角

详细说明见:

  • docs/tools/camera_alignment.md

5. camera_isp_calibrator - 基于参考图的相机色彩对齐工具

让一台 USB 摄像头(usb_cam 节点)的画面在曝光、白平衡、增益、对比度等 方面尽可能接近一张参考图片,并把结果保存为 override JSON,下次启动 robot.launch.py 时自动复用,不修改 YAML SSOT

前置条件

  1. 已经通过 robot.launch.py 启动 usb_cam 节点(节点名形如 /top_camera);
  2. 准备一张参考图片或视频(视频会取首帧)。

基本用法

# 终端 A:启动机器人 / 摄像头
source .shrc_local
ros2 launch robot_config robot.launch.py robot_config:=so101_single_arm control_mode:=teleop

# 终端 B:运行校准工具
source .shrc_local
ros2 run dataset_tools camera_isp_calibrator \
    --camera top \
    --reference /path/to/reference.png

界面交互(单窗口 cv2 GUI,傻瓜操作):

  • a:自动模式(Lab + Planckian 投影),4 次迭代收敛后自动停下
  • c统一 K/C/Sat 搜索(实验性,详见 §5.1)。有 ROI pair 时走 m 模式 cost,没有就走 AUTO ref-cluster cost;找不到改进会自动回退到 seed。
  • 拖动滑条:手动微调 exposure / wb_kelvin / gain / brightness / contrast / saturation / sharpness(松手 0.4s 后才下发,避免抖动)
  • s:保存到 ~/.ros/ibrobot/camera_isp_overrides/{camera}.json
  • r:恢复启动时快照的初始值
  • p:保存当前 live 帧 PNG 到工作目录
  • ? / h:显示 keybinding 提示
  • q:退出(有未保存改动会先警告一次,再按 q 才退出)

算法

模式 触发 说明
Auto a sRGB→Lab 计算 P50 亮度匹配曝光;CIE xy 色度通过 McCamy 公式投影到 Planckian locus,按 delta-form 调节 white_balance kelvin。每帧迭代再读、最多 4 次。
手动滑条 拖动 直接下发 V4L2 参数到 usb_cam 节点(已强制 auto_white_balance=false / autoexposure=false)。

保存生效:保存后下次 robot.launch.py 启动时,perception.py 会自动 读取 override 并覆盖 YAML 默认值;删除 JSON 即可回退。

详细算法说明:见 临时/camera_isp_plan.md §Phase 2。

5.1 统一 K/C/Sat 色彩搜索(实验性,独立模块)

模块 dataset_tools/camera_isp/color_search.py 实现了 临时/camera_isp_unified_color_search_plan.md v4 的统一搜索路径, 与既有 4 阶段流水线(曝光/增益/亮度/锐度)并行存在,不修改任何曝光相关代码。 旧 solver / hw_pipeline 全部保留作为初值估计器与失败回退。

公共接口(pure-numpy + scipy;无 ROS / cv2 依赖):

from dataset_tools.camera_isp.color_search import (
    KCS, SettleConfig, SearchConfig, ClusterConfig,
    kmeans_signature_lab,     # Lab 单边聚类签名
    nn_match_signatures,      # 匈牙利 ΔE2000 指派
    delta_e2000,              # CIEDE2000 (vectorised)
    quantile_distance_L,      # L* 分位数 L1
    cost_24card,              # 24 色卡 cost 工厂
    cost_ref_cluster,         # AUTO ref cost 工厂
    cost_manual_roi,          # m / ROI cost 工厂(带正则)
    frame_capture,            # settle + drop + trimmed-mean
    search_KCS,               # 主搜索 driver(直接 3D 网格 + 可选精修)
    OfflineTables,            # JSON 配置加载
)

设计原则(开放给后续迭代):

  • 三模式同构search_KCS 接收任意 cost_fn,driver 不感知模式。
  • 失败安全:未找到改进时回退到 seed 并把硬件值写回 seed。
  • 可注入边界HwWriter / FrameGrabber 协议 + sleeper 钩子让单元测试无需真实相机。
  • 离线表外置camera_isp_offline_tables.json(per device 可覆盖)承载 K/C/Sat 曲线、settle、search 参数;不再硬编码。

测试:test/test_camera_isp_color_search.py(16 个用例,覆盖 ΔE2000、聚类、匈牙利、settle、driver fallback、device caps 裁剪)。

数据流

┌─────────────────────────────────────────────────────────────┐
│   src/robot_config/config/robots/so101_single_arm.yaml     │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ contract (单一真理来源)                               │   │
│  │ - observations (front, top, wrist, state)           │   │
│  │ - actions (arm, gripper)                            │   │
│  │ - rate_hz: 20                                       │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘
                              │
          ┌───────────────────┼───────────────────┐
          ▼                   ▼                   ▼
   ┌─────────────┐     ┌─────────────┐     ┌─────────────┐
   │  录制服务    │     │  数据转换    │     │  推理服务    │
   │ episode_    │     │ bag_to_     │     │ lerobot_    │
   │ recorder    │     │ lerobot     │     │ policy_node │
   └─────────────┘     └─────────────┘     └─────────────┘
          │                   │                   │
          ▼                   ▼                   ▼
   ROS 2 Bag          LeRobot Dataset      Model Inference

配置示例

src/robot_config/config/robots/so101_single_arm.yaml 中的 contract 配置:

robot:
  name: so101_single_arm
  
  contract:
    rate_hz: 20
    max_duration_s: 90.0
    
    observations:
      - key: observation.images.front
        topic: /camera/front/image_raw
        type: sensor_msgs/msg/Image
        image:
          resize: [480, 640]
          
      - key: observation.images.top
        topic: /camera/top/image_raw
        type: sensor_msgs/msg/Image
        image:
          resize: [480, 640]
          
      - key: observation.images.wrist
        topic: /camera/wrist/image_raw
        type: sensor_msgs/msg/Image
        image:
          resize: [480, 640]
          
      - key: observation.state
        topic: /joint_states
        type: sensor_msgs/msg/JointState
        selector:
          names: [position.1, position.2, position.3, position.4, position.5, position.6]
    
    actions:
      # Arm joints (1-5)
      - key: action
        selector:
          names: [action.0, action.1, action.2, action.3, action.4]
        publish:
          topic: /arm_position_controller/commands
          type: std_msgs/msg/Float64MultiArray
          
      # Gripper joint (6) - same key for consolidation
      - key: action
        selector:
          names: [action.5]
        publish:
          topic: /gripper_position_controller/commands
          type: std_msgs/msg/Float64MultiArray

注意事项

  1. Action 合并: 多个 action spec 使用相同的 key: action 会被自动合并为一个 6-DOF action
  2. 观测过滤: 推理服务会根据模型的 config.json 自动过滤需要的观测
  3. 录制模式:
    • record_mode:=continuous - 持续录制到一个文件
    • record_mode:=episodic - 分段录制,需要 record_cli 控制