Skip to content

BigJohnn/sensor_proto

Repository files navigation

sensor_proto

多相机同步采集、推流与 host 侧可视化工具集,支持 RealSense 与 Hikrobot USB3 Vision 相机。

当前这套流程已经收敛为:

  • 容器内自动探测当前在线相机(RealSense / Hikrobot)
  • 自动生成运行时配置
  • 对齐多路时间戳后推流到 host
  • host 侧可用 OpenCV 实时查看
  • host 侧代码可直接拿到同步后的 frames, timestamp

下一阶段的数据面迁移约束已经冻结在 docs/zmq-transport-contract.md

  • HTTP 继续承担 control-plane 和 preview
  • ZMQ multipart 作为后续 aligned-set data-plane
  • 迁移期间 /api/latest-set 仅保留兼容语义,不再作为长期目标

最常用命令

进入仓库:

cd /home/corenetic/Code/sensor_proto

启动推流服务:

make stream-up

打开实时多路预览:

make stream-viewer

用 Rerun 可视化一个已录好的 episode:

make episode-rerun EPISODE=artifacts/lerobot/hw-10s-episode-20260312T134201

抓取当前最新一组同步帧:

make stream-shot

录制一条目标为 300 个对齐帧的 LeRobot v3 episode(约等于 10 秒 @ 30fps):

make stream-record-10s

查看推流日志:

make stream-logs

停止推流服务:

make stream-down

这套命令做了什么

make stream-up 会自动:

  1. 启动 sensor-stream 容器
  2. 探测当前有效连接的 RealSense
  3. 提取序列号、型号、USB 端口等关键信息
  4. 自动生成运行时配置
  5. 在 host 暴露 http://127.0.0.1:8787

默认会按当前在线相机数自适应启动,不要求必须满 8 台。

如果要显式验证 ZMQ data-plane,可直接把运行时模板切到 configs/realsense-8cam-zmq-session.json; 这份模板会保留 HTTP control-plane / preview,同时开启 transport.enabled=true

Host 侧代码如何拿到同步后的 frames

主入口在 stream_client.py

最常用接口:

from sensor_proto.stream_client import AlignedStreamClient

client = AlignedStreamClient("http://127.0.0.1:8787")
frames, timestamp = client.get_latest_aligned_frames()

frame_rs00 = frames["rs-00"]   # numpy.ndarray, BGR
print(timestamp, frame_rs00.shape)

这里的语义是:

  • frames: dict[camera_id, numpy.ndarray]
  • timestamp: 该同步帧集的统一参考时间

注意:

  • 这是同步后的统一 timestamp
  • 不是每路各自一份 timestamps
  • 当前是软同步,所以各路仍可能存在小的 offsets_ms

如果你还需要诊断信息,用详细接口:

from sensor_proto.stream_client import AlignedStreamClient

client = AlignedStreamClient("http://127.0.0.1:8787")
aligned = client.get_latest_aligned_set()

print(aligned.set_id)
print(aligned.timestamp)
print(aligned.skew_ms)
print(aligned.offsets_ms)
print(aligned.device_timestamps_ms)

详细接口返回:

  • set_id
  • timestamp
  • frames
  • offsets_ms
  • device_timestamps_ms
  • skew_ms
  • camera_order
  • raw_payload

如果启用了 ZMQ data-plane,host 侧还可以使用最小阻塞客户端:

from sensor_proto.stream_client import ZmqAlignedStreamClient

client = ZmqAlignedStreamClient("tcp://127.0.0.1:5555")
aligned = client.recv_aligned_set(timeout_ms=1000)
print(aligned.set_id, aligned.camera_order)

当前决策是:

  • make stream-up 继续启动单个 sensor-stream 进程
  • 该进程同时承载 HTTP control-plane / preview 与 ZMQ data-plane
  • 是否启用 ZMQ 由运行时 transport.enabled 控制
  • /api/latest-set 和逐帧 BMP endpoint 现阶段只保留 debug fallback,不再作为长期数据面目标

Host 侧实时查看

实时 viewer 入口在 stream_viewer.py

直接使用:

make stream-viewer

行为:

  • 自动按网格布局显示多路相机
  • 自动 resize,尽量完整铺进屏幕
  • 顶部显示 set_id / timestamp / skew_ms / camera_count
  • 每个子画面显示 camera_id / offset
  • qESC 退出

viewer 当前的数据面选择规则:

  • 默认 auto
  • 如果 /api/health 广播了启用中的 ZMQ transport,则 viewer 直接消费 ZMQ aligned-set data-plane 并在 host 侧渲染网格
  • 否则回退到现有 HTTP preview mosaic

Host 侧用 Rerun 看已录好的 episode

命令:

make episode-rerun EPISODE=artifacts/lerobot/hw-10s-episode-20260312T134201

底层脚本是:

bash scripts/run_episode_rerun.sh artifacts/lerobot/hw-10s-episode-20260312T134201

行为:

  • 脚本运行在 host
  • 输入是一个已经落盘完成的 LeRobot v3 episode 目录
  • 自动从 meta/info.jsonvideos/ 发现相机和视频文件
  • 如果 episode 带有 meta/aligned_timestamps.json,则优先按真实 aligned-set 时间轴重播
  • 否则回退到 MP4 自带的名义帧时间轴
  • 默认会自动拉起本地 Rerun viewer

Host 侧抓拍

命令:

make stream-shot

底层使用 stream_client_cli.py,会把当前最新同步帧集保存到:

CLI 当前的数据面选择规则也默认为 auto

  • 若健康检查显示 ZMQ data-plane 已启用,则优先从 ZMQ 接收 aligned set
  • 否则回退到 HTTP /api/latest-set
  • 也可以显式传 --transport http|zmq

运行时产物

推流服务启动后,运行时配置会写到:

这里能看到:

  • 当前参与推流的相机列表
  • 每台相机的 serial
  • 自动识别到的 model
  • device_inventory

如果同事想确认“这次到底连了哪几台”,优先看这个文件。

依赖说明

服务端

  • Docker
  • docker compose
  • RealSense 设备访问权限

Host 侧 Python/OpenCV

host 侧 viewer 和 Python client 依赖:

  • numpy
  • cv2
  • pyzmq(仅在使用 ZMQ data-plane 时需要)
  • uv
  • ffmpeg 可执行文件,版本需 >= 5.1

基准工具入口在 transport_benchmark.py

PYTHONPATH=src python -m sensor_proto.transport_benchmark --base-url http://127.0.0.1:8787 --transport http
PYTHONPATH=src python -m sensor_proto.transport_benchmark --base-url http://127.0.0.1:8787 --transport zmq

真实双机部署和 LAN benchmark 步骤见 docs/dual-machine-zmq-benchmark.md

当前已经跑过的 mock / 硬件 / 正式镜像验证快照见 docs/zmq-transport-validation-snapshot-2026-03-13.md

如果要用 make episode-rerun,host 上需要先有 ffmpeg >= 5.1。 注意:Ubuntu 22.04 系统仓库自带的 ffmpeg 4.4.x 不够新,Rerun 仍会拒绝解码。

脚本会在启动前检查版本;如果你的新版 ffmpeg 不在默认 PATH 里,可以显式指定:

SENSOR_PROTO_RERUN_FFMPEG_PATH=/path/to/ffmpeg make episode-rerun EPISODE=artifacts/lerobot/<episode_dir>

Ubuntu 22.04 若临时只想装系统包,可以先装上旧版用于其他工具:

sudo apt-get install -y ffmpeg

Python 侧的 replay 依赖已经写在项目 extras 里:.[replay]

make episode-rerun 启动前还会把 Rerun 的本地持久化配置同步到当前 host 的 ffmpeg 路径,避免 viewer 自己的 spawn/config 链拿不到系统 PATH 时仍然报 “Couldn't find an installation of the FFmpeg executable”。

仓库已经通过脚本和 make 目标固化了必要环境变量,不需要手写长串命令。

排障顺序

如果同事用不起来,按这个顺序查:

  1. make stream-logs
  2. realsense-8cam-stream-runtime.json
  3. 运行 make stream-shot
  4. 最后再看 GUI 显示问题

常见判断:

  • make stream-shot 成功,make stream-viewer 失败 说明同步流和 host client 是通的,问题在 GUI 显示链路
  • 127.0.0.1:8787 连不上 先看 make stream-logs
  • 相机数量不是 8 默认允许按当前在线相机数自适应启动

录制 300 个对齐帧的 episode

最短入口:

make stream-record-10s

底层实际调用的是:

bash scripts/record_stream_episode.sh 10

这个脚本会:

  • realsense-8cam-stream.json 作为模板
  • 在启动前临时注入 recording 配置,但不写死真实相机列表
  • 启动时自动探测当前在线 RealSense,并生成运行时配置
  • 300 个对齐帧作为默认停止条件(约等于 10 秒 @ 30fps
  • 通过独立 recording worker + bounded queue 落盘,避免 LeRobot 写盘直接阻塞主同步消费链
  • LeRobot video 路径优先启用流式编码,避免默认的 per-frame PNG 临时文件落盘
  • 默认在 recording 队列打满时将 recording 标记为 failed,但保持 stream 服务继续运行
  • 如果本次批量录制过程中 recording 已失败,命令会在 shutdown 后以非零状态退出,避免误报成功
  • 给 LeRobot v3 的视频编码和 finalize() 预留足够的收尾时间
  • 另外保留一个独立 watchdog,防止硬件异常时无限挂住

默认产物:

  • 数据集输出到 artifacts/lerobot/hw-10s-episode-<timestamp>
  • 运行时配置输出到 realsense-8cam-stream-recording-runtime.json
  • 真实对齐时间轴输出到 meta/aligned_timestamps.json,供 host 侧 replay 保持实际采集节奏

默认 recording 队列配置:

  • recording.queue_maxsize = 32
  • recording.overflow_policy = fail_recording_keep_stream

可选环境变量:

  • SENSOR_PROTO_RECORD_OUTPUT_DIR
  • SENSOR_PROTO_RECORD_FINALIZE_GRACE_S
  • SENSOR_PROTO_RECORD_MAX_RUNTIME_S
  • SENSOR_PROTO_RECORD_TARGET_ALIGNED_SETS
  • SENSOR_PROTO_RECORD_FPS
  • SENSOR_PROTO_RECORD_QUEUE_MAXSIZE
  • SENSOR_PROTO_RECORD_OVERFLOW_POLICY
  • SENSOR_PROTO_RECORD_VIDEO_CODEC
  • SENSOR_PROTO_RECORD_ENCODER_QUEUE_MAXSIZE
  • SENSOR_PROTO_RECORD_ENCODER_THREADS
  • SENSOR_PROTO_RECORD_TASK
  • SENSOR_PROTO_RECORD_REPO_ID
  • SENSOR_PROTO_RECORD_ROBOT_TYPE

开发与测试

运行测试:

make test

运行 mock:

make mock-run

录制为 LeRobot v3

当前支持把同步后的多相机观测直接录制为本地 LeRobot v3 数据集。

前提:

  • 运行环境已安装官方 lerobot
  • 参与录制的相机都启用了 capture_image_data
  • 如果多路相机 fps 不一致,需要在配置里显式设置 recording.fps

最小 mock 示例配置见 mock-lerobot-recording.json

启动录制:

source $HOME/.local/bin/env
UV_CACHE_DIR=/tmp/uv-cache PYTHONPATH=src uv run --no-project --python "$(command -v python3)" python -m sensor_proto.stream_main --config configs/mock-lerobot-recording.json

说明:

  • 录制入口复用同步流服务,不走 host 侧 HTTP 轮询
  • 一次进程生命周期默认保存为一个 episode
  • 使用 Ctrl-C 退出时会执行 save_episode()finalize()
  • 默认输出目录由配置里的 recording.root_dir 控制,例如 artifacts/lerobot/mock-session

Hikrobot 排障

常用命令

make hikrobot-stream-up       # 启动流服务
make hikrobot-stream-logs     # 查看容器日志
make hikrobot-stream-shot     # 抓取最新同步帧
make hikrobot-stream-down     # 停止流服务
make hikrobot-stream-record-10s  # 录制 10s LeRobot v3 episode

检查相机 USB 速度

两台相机都应连在 USB 3.x 口(5000Mbps),否则带宽不够:

for dev in $(find /sys/bus/usb/devices -maxdepth 1 -name "[0-9]*" -not -name "*:*" 2>/dev/null); do
  vendor=$(cat "$dev/idVendor" 2>/dev/null)
  if [[ "$vendor" == "2bdf" ]]; then
    serial=$(cat "$dev/serial" 2>/dev/null)
    speed=$(cat "$dev/speed" 2>/dev/null)
    busnum=$(cat "$dev/busnum" 2>/dev/null)
    devpath=$(cat "$dev/devpath" 2>/dev/null)
    echo "Serial=$serial  Bus=$busnum  Path=$devpath  Speed=${speed}Mbps"
  fi
done

期望输出(两台均为 5000Mbps,且在同一 Bus):

Serial=DA5404760  Bus=2  Path=2.4  Speed=5000Mbps
Serial=DA5404769  Bus=2  Path=2.3  Speed=5000Mbps

若某台显示 480Mbps,则该相机插在 USB 2.0 口,需换插 USB 3.0 蓝色口。

判断流是否正常

  1. make hikrobot-stream-logs — 确认无 OpenDevice failed 错误
  2. curl -s http://localhost:8787/api/health | python3 -m json.tool — 查看 published_sets 是否在增长
  3. make hikrobot-stream-shot — 抓帧,确认两台相机都有图像

常见问题

现象 原因 处理
OpenDevice failed: 0x80000301 两相机并发打开,MVS SDK 不线程安全 已修复(全局锁序列化打开)
流 stall,无 aligned sets 两相机时钟漂移超出 45ms 同步窗口 已修复(15s watchdog 自动重启相机会话)
视频快进 exposure_us 超过帧周期(30fps 最大 ~33000µs) 检查 configs/hikrobot-stream.jsonexposure_us
某相机 480Mbps 接在 USB 2.0 口,带宽不足 换插 USB 3.0 口

进一步文档

About

用于实现多相机同步,相机性能测试等

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors