如果你手头有一台Ubuntu系统的电脑,桌上摆着一台Lerobot系列的机械臂,想给HIL-SERL训练采数据,这篇文章应该能帮你把这条路走通。HIL-SERL比我们熟悉的“录演示、喂BC”要复杂一点,它要求人类在训练循环里持续提供干预和奖励信号。这意味着数据采集不只是把关节角度写进文件,而是要把机械臂状态、摄像头图像、键盘指令和人类奖励标注按同一条时间轴对齐存下来。用键盘采集在实验室场景里很有价值——不需要额外购买遥操作设备,一个人就能同时扮演演示者和标注者,特别适合快速验证“小样本、高难度”的桌面操控任务。下面从环境搭建、输入映射、配置拆解到实际采集,完整走一遍。
1. 先把HIL-SERL的数据需求搞清楚:键盘采集到底要采什么
1.1 HIL-SERL不是简单录演示
很多刚开始接触机器人学习的朋友,会把HIL-SERL理解成“用强化学习替代模仿学习”。这个方向对了一半,但容易忽略一个关键差异:HIL-SERL的“HIL”(Human-in-the-loop)指的是人类要持续进入训练循环,而不是只做一次数据标注就撤。具体来说,人类在环境交互过程中一边纠正策略行为,一边给奖励信号,算法再拿这些在线数据做策略更新。
这就直接决定了数据采集的形态。不是录一段“目标轨迹”就完事,而是要记录完整的交互序列:当前状态、执行的动作、下个状态、人类给予的奖励反馈、回合是否结束。这样训练脚本才能构造出强化学习所需的结构。更直白地说,BC采集的核心是一堆“专家轨迹文件”,HIL-SERL采集的核心是一堆“带实时评价的交互轨迹文件”。
所以你在准备配置文件时,不能只写机械臂参数和摄像头参数,还要预留出奖励通道和回合控制通道。后面我会给出一个实际能用的配置结构,里面把reward映射、episode开始/结束按键都单独设置了字段,原因就在这里。
1.2 训练一条策略需要的最小数据单元
HIL-SERL的一条经验数据,在代码里通常表现为一个transition,包含以下内容:
- 观测(observation):机械臂各关节位置、速度、末端执行器位姿,以及一个或多个摄像头画面
- 动作(action):当前时间步发给机械臂的控制指令,可以是关节目标位置,也可以是末端增量速度
- 奖励(reward):这一条数据里人类给出的奖励值,HTE模式里常见的是正数、负数、零
- 下一个观测(next_observation):动作执行后拿到的新状态
- 终止标记(done):当前episode是否结束
如果你是第一次写采集脚本,最容易漏掉的是next_observation和done。只要你想让数据能被SAC这类强化学习算法直接用,过渡到下一步的状态就必须存在,否则只能自己从HDF5的连续帧里硬切,很麻烦且容易错位。
另外,相机图像不是可选项。视觉运动策略基本都要依赖图像,建议至少两个视角:一个俯视全局,一个装在手腕上看接触细节。只有状态向量没有图像,策略在真实桌面上会很难泛化。
1.3 键盘采集的适用边界
键盘遥操作有它天然的短板——没有力反馈,动作不够顺滑。但它的优势同样突出:零成本、可复现、训练流程友好。你不需要额外买一台3D鼠标或者主从遥操作设备,只要笔记本键盘和一个能跑Ubuntu的主机就能开始。
我个人的判断是,键盘采集适合以下任务场景:
- 桌面抓取、放置、堆叠这类离散操控任务
- 动作空间在4到6维的末端位置/姿态控制
- 不需要精确插拔、拧螺丝等力控要求较高的操作
- 想快速验证算法和数据处理流程是否通顺的早期阶段
如果你的任务需要非常精细的力反馈,或者涉及高柔性物体的操作,键盘方案就不太合适。那种场景老老实实上遥操作臂或者主从设备,否则你采出来的数据质量会让自己怀疑人生。
2. Ubuntu环境搭建:从硬件上电到lerobot跑起来
2.1 系统与协议栈基线
先说系统。Ubuntu 20.04和22.04我都试过,20.04在ROS生态兼容性上更稳,22.04对相机驱动和Python 3.10支持更好。如果你机器比较新,建议直接上22.04;如果还要兼顾ROS Noetic,那就20.04。内核版本只要不是太老基本没有卡点。
机械臂这一侧,Lerobot官方支持的主要是一批高性价比桌面关节模组臂,比如SO-100、SO-101这类,也有对Aloha和Fourier的支持。它们控制协议各不相同,但一般都会暴露成串口设备或者虚拟串口。Ubuntu下你只需要关心出现在/dev/ttyUSB0或者/dev/ttyACM0的是哪个设备。
这里我要特别强调:使用/dev/ttyACM0的机械臂固件,通常插上就能见设备,但权限不够时你连打开串口都做不到。最常见的报错是PermissionError: [Errno 13] Permission denied: '/dev/ttyACM0'。这不是驱动坏了,是用户组权限问题。
2.2 安装lerobot和依赖:两条路线
安装Lerobot有两种主流方式。第一种是直接用pip装发布版,适合只想采数据、不想改源码的人:
sudo apt update && sudo apt install -y python3.10-venv git python3.10 -m venv ~/.venvs/lerobot source ~/.venvs/lerobot/bin/activate pip install --upgrade pip pip install "lerobot[all]"第二种是从源码安装,适合你要在数据采集脚本里加自定义键盘逻辑、或者要改动Lerobot内部数据格式的场景。源码方式也能在lerobot/scripts里直接看到现成的控制、评测脚本,学习成本其实更低:
git clone https://github.com/huggingface/lerobot.git cd lerobot pip install -e ".[all]"不管哪种方式,都建议用虚拟环境,不要往系统Python里硬塞依赖。Lerobot起步就需要PyTorch、opencv、h5py、mcap一坨库,系统环境很容易被搞乱。另外,如果只用CPU跑采集,PyTorch装CPU版就够;如果后面要训练策略,再按CUDA版本装GPU版。
2.3 串口权限与相机设备检测
安装完库先别急着跑控制,先确认硬件通路。我把这套检查顺序放在一个固定的三步流程里:
- 查看串口设备是否出现:
ls -la /dev/ttyUSB* /dev/ttyACM* 2>/dev/null如果你的机械臂插上电,但这里什么都看不到,先换一根数据线,再检查设备供电是否独立。很多桌面臂插主板USB口会供电不足,连电脑都识别不到。
- 给当前用户添加串口访问权限:
sudo usermod -aG dialout $USER改完用户组之后,必须注销重新登录或者重启,然后再次执行ls -la /dev/ttyACM0查看权限,组名应该从dialout能看到你的用户名,否则还是会报权限错误。
- 检查摄像头:
v4l2-ctl --list-devices如果提示没有v4l2-ctl,装一下v4l-utils。这里你要注意设备索引顺序,同一个摄像头用不同的USB口插拔后索引可能改变。配置里写死device_id: 0、device_id: 1这种方案在实验环境里勉强能用,但长期采集建议给摄像头创建稳定的udev软链接,否则重启一次就可能把top和wrist弄反。
2.4 冒烟测试:确认采集链路的每个环节
环境装好、设备都识别到之后,我会建议做一次15分钟的冒烟测试,而不是直接开始采正式数据。
第一步,用Python打开相机,逐帧显示图像:
import cv2 cap = cv2.VideoCapture(0) while True: ok, frame = cap.read() if not ok: print("camera failed") break cv2.imshow("test", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break第二步,用Lerobot自带脚本控制机械臂单关节运动。不同版本的Lerobot入口不同,常见的是:
python lerobot/scripts/control_robot.py --robot-type so100能通过这个命令正常读取关节角度并下发简单运动,说明串口链路和控制频率都没问题。如果卡在这里,后面所有采集流程都无从谈起。
3. 键盘遥操作的底层设计:增量控制与同步时钟
3.1 为什么必须用增量式控制
键盘只有“按下”和“松开”两种状态,这是典型的二值输入。你不可能指望键盘直接给出“末端去坐标(x, y, z)”这种绝对目标,否则按一下W就把机械臂送到桌子尽头,按键一松开反而不知道它该停在哪。
所以键盘遥操作必须走增量式控制,也就是把按键转化成速度指令。按住W的时候,末端以某个速度沿机器人基座坐标系的X轴正方向移动;松开就归零。主控制循环每帧按固定周期读取按键状态,用速度乘以时间增量,累加到当前目标位置上。这样操作员可以通过控制按键时长,间接控制运动距离。
类比一下:这就像用方向盘开车,你打方向的角度决定转向速率,而不是直接决定车头角度。键盘的W/S/A/D就是这组方向盘的开关量版本。
3.2 按键映射与参考坐标系
我给常见6自由度末端控制任务设计的按键布局是这样的,配置里按这个映射:
| 功能 | 按键 | 说明 |
|---|---|---|
| 向前/向后平移 | W / S | 沿机器人基座X轴移动 |
| 向左/向右平移 | A / D | 沿机器人基座Y轴移动 |
| 上升/下降 | Q / E | 沿机器人基座Z轴移动 |
| 横滚翻转 | Z / C | 绕末端工具坐标X轴旋转 |
| 俯仰翻转 | R / F | 绕末端工具坐标Y轴旋转 |
| 偏航翻转 | T / G | 绕末端工具坐标Z轴旋转 |
| 紧急停止 | Space | 立即发送零速度/保持位置 |
| 开始记录 | Enter | 启动一个episode |
| 结束记录 | Esc | 结束当前episode并保存 |
| 正面奖励 | J | 记录当前transition奖励+1 |
| 负面奖励 | K | 记录当前transition奖励-1 |
| 无奖励 | L | 记录当前transition奖励0 |
这里有个容易搞混的点:平移和旋转的参考坐标系不同。平移用基座坐标系,因为人对“前后左右”的直觉是相对机器人底座而言的;旋转用末端工具坐标系,因为抓取末端转到不同姿态后,绕自身轴旋转更符合操作直觉。你在写配置文件时一定要把坐标系定义写清楚,否则某个轴的方向会反。
3.3 固定频率循环与时间戳同步
键盘采集最大的隐患是“帧率不稳”。如果主循环一会儿跑50Hz一会儿跑20Hz,录出来的动作序列在时间轴上的分布就会不均匀,训练时要么需要重采样,要么直接影响策略质量。
我的建议是把控制频率固定到30Hz。这个值不高不低,Python主循环、相机读取、简单图像编码都跟得上,机械臂的增量运动也够平滑。如果关节空间控制且任务本身动作很快,可以上50Hz,但务必先做压力测试。
主循环的核心时间同步逻辑不要用裸的time.sleep(1/fps),那样会因为每次循环耗时不同累积漂移。每次迭代用time.perf_counter()计算距离下一帧超帧点的等待时间,可以简单写成:
fps = 30 dt = 1.0 / fps next_tick = time.perf_counter() while running: now = time.perf_counter() if now < next_tick: time.sleep(next_tick - now) # 采集状态、图像、按键 # 生成增量动作 next_tick += dt另外,不要在每个线程里各自调用time.time()打时间戳。正确做法是主循环里的第t帧直接使用整数帧索引t作为关联标识,图像数据、状态数据、按键数据都打同一个索引。如果需要绝对时间,统一在帧循环开始时取一次时间,赋值给当前索引对应的所有字段。
4. 完整配置文件逐段拆解
4.1 配置文件全貌
下面这份配置是我在Ubuntu 22.04、六自由度桌面臂、两个USB摄像头环境下实际用过的结构,Lerobot版本大概对应3.x。具体字段在不同小版本之间会有差异,但核心逻辑一致。
# configs/hil_serl_keyboard.yaml hardware: robot_type: so100 port: /dev/ttyUSB0 cameras: - name: camera_top device_id: 0 width: 640 height: 480 fps: 30 - name: camera_wrist device_id: 2 width: 640 height: 480 fps: 30 teleop: method: keyboard fps: 30 command_space: ee speed: linear: 0.02 angular: 0.5 joint: 0.1 key_bindings: forward: w backward: s left: a right: d up: q down: e roll_left: z roll_right: c pitch_up: r pitch_down: f yaw_left: t yaw_right: g stop: space reward_positive: j reward_negative: k reward_neutral: l start_episode: return stop_episode: escape dataset: root: /home/user/data/hil_serl task_name: pick_bottle_from_table format: hdf5 record_video: true video_fps: 30 max_episode_frames: 600 observation: state_keys: - joint_positions - joint_velocities - ee_pose image_keys: - camera_top - camera_wrist action: type: delta_joint_positions normalize: true reward: mode: key_mapping positive_value: 1.0 negative_value: -1.0 neutral_value: 0.04.2 硬件与机械臂字段
hardware.robot_type就是Lerobot库里支持的那个字符串。如果你的机械臂没有在官方列表里,后续Lerobot很可能压根起不了机器人对象,会直接抛“unsupported robot type”。选型前先去官网看支持的型号列表。
hardware.port是机械臂串口。如果你机器上同时插了Arduino、3D打印机等设备,/dev/ttyUSB0不一定对应机械臂。最简单的确认方法:拔掉机械臂数据线,记录ls /dev/ttyUSB*结果;插上机械臂再执行一次,多出来的那个设备就是它。
cameras里的device_id是OpenCV的VideoCapture索引。多个摄像头插在同一台机器上时,索引顺序由USB枚举顺序决定,重启后可能变化。比较稳妥的做法是先用v4l2-ctl --list-devices确认每个物理摄像头的编号,再回填到config里。
4.3 遥操作与按键映射字段
teleop.method设置成keyboard,采集脚本会根据这个字段决定走键盘输入逻辑。
teleop.command_space是控制空间,我建议默认用ee(末端空间)。末端空间的好处是按键映射直观,人不需要关心单个关节怎么转。如果你想实验某些特定的关节学习任务,可以改成joint,此时按键就会逐个映射到关节的增量角度。joint模式适合调试机械臂本身,但采任务数据时,末端空间通常更自然,策略学起来也更容易迁移。
speed.linear和speed.angular是末端速度。比如linear: 0.02表示按住一次方向键,末端以每秒2厘米的速度移动。配合30fps,每一帧的位移增量是0.02/30约0.00067米。这个速度适合桌面级轻量任务,动作稳定,不容易撞限位。如果你的任务需要更大幅度的手臂摆动,可以调到0.04到0.05,但一定要先低速试运行。
speed.joint只在command_space: joint时生效,单位是rad/s。比如0.1 rad/s意味着按住一个关节键一秒,该关节转5.7度。这个值看起来小,但在关节空间里已经足够细腻。
key_bindings字段直接对应用户按下的具体按键名。注意不同库的按键名规范不同,pygame里是K_w,pynput里是w。我这里统一用简单字符串,实际读取时由脚本做一层转换。
reward_positive、reward_negative、reward_neutral是HIL-SERL的关键。每次按压这些键,当前帧的reward就被记录成对应的正数、负数或零。我强烈建议,即使你暂时不训练,采集时也要把奖励按键按起来。因为事后如果要补标注,靠人眼逐帧回看视频是非常痛苦的事情。
4.4 数据集与奖励字段
dataset.root和task_name组合起来,就是数据集的根目录路径。脚本会在运行时自动创建形如/home/user/data/hil_serl/pick_bottle_from_table/的目录,每个episode存成独立文件。
dataset.format建议默认hdf5。HDF5在Lerobot体系里最成熟,打开、读取、切片都对Python友好。如果你的训练代码明确要求ROS风格的Mcap,也可以改成mcap,Lerobot新版本里有对应支持,但我个人还是习惯HDF5,因为它一个文件里能同时放json元数据、图像数据和数值状态,结构非常清晰。
dataset.record_video设为true时,脚本会把实时画面通过h5py里的数据集写进文件,同时还会拼接一个预览视频。这个预览视频不是给训练用的,是给你核对任务执行过程是否正常用的。担心磁盘空间的话,可以同时把图像压缩率调高一些。
observation.state_keys列出每个transition要记录哪些机械臂状态字段。joint_positions、joint_velocities、ee_pose基本覆盖了常见状态;ee_pose是6DoF位姿,能帮助策略学会末端对齐。
action.type写delta_joint_positions的意思是,每条transition里存的是“从当前关节位置到目标关节位置的位移量”。采集过程中,键盘给出的速度指令会被积分成目标关节位置,训练时就拿这个位移当动作监督信号。这也是SERL系列比较常用的一种动作表示。
reward.mode只用了key_mapping,即纯键盘映射。更复杂的模式可以结合机械臂的接触力传感器,在碰撞时自动给负奖励,但这个要硬件支持,普通桌面臂默认没有,所以我先不讲。
4.5 容易写错的字段
配置字段最常翻车的地方,我整理成一张表:
| 常见错误 | 后果 | 检查方式 |
|---|---|---|
| robot_type写错 | 控制脚本无法实例化机器人 | 运行lerobot --help查看支持类型 |
| port指定了错误的串口 | 打开设备失败 | 插拔前后对比ls /dev/tty* |
| device_id填错 | 打开错误的摄像头 | 逐个索引测试cv2.VideoCapture(i) |
| fps不匹配 | 动作和图像时间线错位 | 统一所有fps为30 |
| key_bindings里按键名写错 | 按键无响应 | 打印调试事件确认key名称 |
| format写mcap但训练代码只认hdf5 | 数据无法用于训练 | 先看训练仓库数据加载代码 |
| action.type与策略期望不一致 | 维度对不上或训练不收敛 | 确认使用同样的delta表示 |
5. 手把手采集:从启动脚本到检查hdf5/mcap
5.1 采集前的检查清单
正式采集前,我建议准备一个固定的检查流程,避免采完发现白干:
- 机械臂已经上电,串口权限可用,
ls /dev/ttyUSB0能看到设备 - 两个摄像头都能出图,且
camera_top确实对着桌子全局,camera_wrist确实能看到末端接触部位 - 激活了虚拟环境,
python -c "import lerobot"不报错 - 机械臂处于安全范围内,周围没有障碍物,末端装好夹爪或吸盘
- 预估每个episode时长,确保
max_episode_frames足够。30fps下600帧等于20秒,不够就调大
还有一点容易被忽略:先手动把机械臂移动到任务初始位置附近,比如物体正上方15厘米。键盘采集不像主从遥操作,没有“捕捉当前姿态作为起点”那么顺滑,预先把臂摆在上电零位附近能省大量微调时间。
5.2 启动与回零校准
启动脚本的命令大概是:
source ~/.venvs/lerobot/bin/activate python collect_hil_serl.py --config configs/hil_serl_keyboard.yaml启动后先看控制台输出。机械臂连接成功时会打印当前每个关节的弧度或角度值,摄像头打开时会显示一个或者两个预览窗口。如果只有一个预览窗口,检查配置里是不是把两个device_id写成了同一个索引。
接下来是回零校准。这个步骤很多人跳过,但增量式控制非常依赖零点。具体操作:用键盘把机械臂末端移动到任务方便操作的中间位置,按一个专门设计的“设置零点”按键,脚本会记录当前关节位置作为偏移基准。之后所有增量动作都是相对这个基准累加出来的。
回零之后先别急着录数据,花一两分钟手动操作一下末端,确认每个方向都和你的预期一致。尤其检查Z/Q/E上下方向,很多人在这一步发现“上升”键实际是“下降”,如果不及时改映射,采集全程都在逆向操作。
5.3 开始录制:操作节奏与信号
确认方向无误后,把物体放在桌面上,开始正式录制。
第一步,按Enter触发start_episode,控制台会打印“episode 0 started”。此时你后面的每一步都会被记录。
第二步,按键盘控制机械臂到物体上方,抓取,移动到目标位置,放下。这个过程中如果有几步不完美,比如末端撞了一下物体、抓取偏了一点,不用停。HIL-SERL这类人类在环方法反而希望看到一些“纠正过程”,因为人类会在过程中给出奖励调整策略。
第三步,在关键节点按J(正面奖励)或者K(负面奖励)。比如抓取成功瞬间按J,抓偏了按K。这个反馈不需要每帧都给,但也不能只在episode结束时给一次,否则训练算法很难定位奖励到底对应哪个transition。
第四步,任务完成后,按Esc结束当前episode。此时脚本做收尾工作:写HDF5文件、生成预览视频、关闭文件。控制台会输出保存路径。
5.4 文件落盘后的三分钟验证
每采完几个episode,我建议花三分钟打开文件验证一次。用HDF5格式时,最简单的检查是:
import h5py f = h5py.File("/home/user/data/hil_serl/pick_bottle_from_table/episode_000.hdf5", "r") print(f.keys()) print(f["/observation.state"][:5]) print(f["/observation.images.camera_top"].shape) print(f["/reward"][:5]) f.close()重点看三件事:状态数据shape是否符合预期,图像数据是否是四维数组(帧数、高度、宽度、通道),reward里有没有非零值。
如果你只想确认文件能正常打开且大小不为0,也可以直接用命令行工具:
h5dump -n /home/user/data/hil_serl/pick_bottle_from_table/episode_000.hdf5Mcap格式则可以用mcap命令行工具读取:
mcap info /path/to/episode_000.mcap6. 高频问题排查:按键失灵、数据不同步、关节漂移
6.1 按键没反应或延迟高
按键失灵我遇到过一个最隐蔽的原因:预览窗口没有获得键盘焦点。用pygame读取键盘时,你得先点击预览窗口激活窗口,否则按键事件不会发送给采集脚本。控制台接收不到指令,你却以为程序坏了。
解决方法是把窗口名和配置一起打印,并加一个窗口激活提示:“Click window before teleop”。同时,把急停键单独用全局监听实现,不要只依赖窗口焦点。
延迟高通常不是键盘问题,而是主循环被阻塞。最典型的阻塞源是print。每帧打印一次状态看着很直观,但30Hz下每秒30次IO会把控制循环拖慢不少。处理办法:只打印状态变化事件,不要逐帧打印;真要逐帧记录,写日志文件而不是控制台。
第二个阻塞源是图像编码。如果你用opencv的imwrite逐帧存JPEG,每帧可能消耗几十毫秒,严重拖垮控制频率。正确做法是把相机读取和图像写入放到独立线程,用队列把原始帧传给写盘线程,主控制循环只负责放阻塞式的动作指令。
6.2 图像和状态对不上时间线
状态是30Hz,相机如果也是30Hz,看起来是对齐的,但因为相机读取本身就需要时间,实际上图像总会比状态晚几毫秒甚至几十毫秒。在时间敏感的任务里,这种错位可能让策略学到错误的“图像→动作”对应关系。
我的建议是:采集时不要试图用时间戳严格对齐每一帧,而是用一个核心计数器。每次主循环生成新transition时,计数器加1;图像线程不管内部帧率多少,把最新图像附带当前计数器值压入队列;写入时统一以计数器为主键。这样即使某帧图像晚到了,也能归到最近的transition里,不会插入到错误位置。
训练时如果算法对时间戳要求严格,可以在预处理里用线性插值重采样图像时间戳,但数据集里至少要保留原始计数器,这是多机、多线程方案里最可靠的对齐锚点。
6.3 关节越走越偏
增量式操作跑久了,末端位置和期望位置之间会产生累计误差。原因有两个层面:
第一,机械臂本身存在关节回差和摩擦,实际到达位置和目标位置不可能完全一致。第二,键盘速度指令在积分过程中可能丢帧,比如某个瞬间主循环卡了一下,速度积分少了,后面的动作就整体偏了。
最直接的解法是,在采集脚本里定期记录“绝对关节位置”而不是只记录相对增量。这样即使视觉上偏了,至少数据文件里没有错误累积。其次是给机械臂加回零频率,比如每2分钟提醒操作员回到零点进行一次位置校准,把累计误差拉回正常范围。
如果你的机械臂支持电流反馈或者抱闸,还可以在停止时保持位置模式,防止末端因重力下垂。没有抱闸的桌面臂,上电后一定要用手扶着末端回零,否则会瞬间往下坠。
6.4 录制中断导致文件损坏
采集中途断电、Ctrl+C强制终止、脚本崩溃,都可能导致HDF5文件没有正常关闭修复,肉眼看到文件大小异常,但h5py打开报错。
我建议的工程化做法是:写入逻辑始终使用临时文件,正常结束后再重命名。脚本里捕获KeyboardInterrupt和Exception,在退出前强行flush并close所有h5py文件。单线程采集脚本里,h5py每次写入后调用flush()可以降低崩溃丢数据的风险,但要权衡IO开销,不用每写一帧都flush,每10帧一次比较平衡。
如果已经遇到文件损坏,可以尝试在h5py里以driver="core"方式读回内存修复,但成功率看情况;更稳妥的办法是直接从episode的预览视频里恢复操作流程,重新补采一次。这也是我强调“预览视频不是给训练用,但一定要开”的原因,它能帮你快速判断这个episode是否值得补救。
我在实际项目中还有个习惯:每采完20个episode就立刻抽出2个,自己肉眼过一遍状态数据和图像序列。键盘采集最大的风险不是数据量不够,而是“数据量看起来很多,但质量参差不齐,里面混着很多无效动作”。把质量检查嵌入采集流程之后,后期训练节省的时间远超那几分钟的成本。如果你准备长期用同一套键盘方案给不同任务采数据,我建议把按键映射、坐标系、速度增益做成一个独立配置文件,每次换任务只改数据集路径和任务名,操作人员也能很快上手。