更新时间:2026-09-28 GMT+08:00
分享

Transformer使用指导

一、Transformer概述

Transformer(值变换器)是配置驱动映射中的数据转换单元,位于mapping rule的transforms链上,对单个值做转换:类型转换、切片索引、数值缩放、图像编解码、机器人夹爪换算等。

Transformer只在device_to_r2c(上行:将设备数据转换到Observations)和r2c_to_device(下行:将Actions数据转换到设备指令)两个映射段里生效。

三条使用路径如表1所示。

表1 三条使用路径说明

场景

做法

章节

用现成能力

在内置清单里找名字,写进YAML。

内置Transformer清单

单次覆盖(进程内)

build_transformer_registry(overrides=...) 传实例。

程序化注入overrides

需要分发/复用

打包成wheel,用entry_point注册。

基于entry_point扩展Transformer

二、快速上手

通过以下示例帮助您快速上手。

YAML写法

机器人配置YAML里,transforms写在一条mapping rule内部:

device_to_r2c:
  mappings:
    # 腕部相机:ndarray → JPEG bytes(Observations.images.* 必须是 bytes)
    - target: "images.color.wrist"
      source: "wrist"
      transforms:
        - ndarray_to_jpeg: {quality: 85}
    # 末端位姿:numpy → list
    - target_key: "joint_states.position"
      source_path: "eef_pose"
      transforms: array_to_list
r2c_to_device:
  mappings:
    # 模型输出 7D → 前 6 维给 eef_pose,第 7 维给夹爪
    - target: "eef_pose"
      source: "joint_states.position"
      transforms:
        - slice: {start: 0, end: 6}

完整可运行样例详见在CloudRobo平台下载的R2C SDK包中的config/robot_so101_lerobot_config.yaml。

程序化写法

from r2c_sdk.core.config_mapper import ConfigDrivenMapper
mapper = ConfigDrivenMapper.from_rule_mappings([
    {"source": "eef_pose", "target": "joint_states.position",
     "transforms": ["array_to_list"]},
])
mapper.map({"eef_pose": [0.1, 0.2, 0.3, 0.0, 0.0, 0.0]})

查看当前有哪些transformer

from r2c_sdk.core.transformers import TransformerRegistry
print(TransformerRegistry.available_transformers())   # 内置 + 第三方,按字母序
print(TransformerRegistry.lookup("ndarray_to_jpeg"))  # 拿到实例,或 None

三、配置语法详解

如下详细说明各配置语法。

transform与transforms

表2 transform与transforms说明

写法

含义

transform: <条目>

单数,等价于只有一个元素的链。

transforms: [<条目>, ...]

复数,按顺序串联。

两者互斥,同时出现直接报错:

ValueError: Use 'transforms' or 'transform' (singular) — not both. 

transforms: [] 和 transforms:(值为 null)都会报错,不需要转换就删掉这个key。

单个条目的四种写法

表3 四种写法示例

序号

写法

示例

传给transform() 的config

1

裸名字符串

to_float

None

2

名字:参数 简写

"crop:bottom_left"

字符串 "bottom_left"

3

单键映射

{ndarray_to_jpeg: {quality: 85}}

映射 {"quality": 85}

4

单键映射(标量)

{subtract: 1.0}

1.0

transforms: to_float                       # 写法 1
transforms: "crop:bottom_left"             # 写法 2(注意整个条目要加引号)
transforms: [{"ndarray_to_jpeg": {"quality": 85}}]   # 写法 3
transforms: [{"subtract": 1.0}]            # 写法 4

写法2的config永远是字符串,其适配 crop(区域名)、decode_image(bgr/rgb/gray)这类字符串参数;但对ndarray_to_jpeg这种要dict的会失败:"ndarray_to_jpeg:85" → Invalid config for transformer 'ndarray_to_jpeg': ... got str。

不支持name/config双键写法

下面这种写法(在部分旧文档里出现过)会直接报错:

transforms:
  - name: my_scale          # ✗ 非法
    config:
      factor: 0.001

正确写法是将名字作为唯一key,config作为它的值:

transforms:
  - my_scale: {factor: 0.001}     # ✓

流水线:链式组合

列表即流水线,前一个的输出是后一个的输入:

transforms:
  - "crop:center"                  # ndarray 裁剪
  - {resize: [224, 224]}           # 缩放
  - {bgr_to_rgb: null}             # 通道序(无参数用 null 占位)
  - {ndarray_to_jpeg: {quality: 90}}

链中任何一步抛出异常,整条rule失败,异常向上抛给调用方。

程序化注入overrides

build_transformer_registry(overrides=...) 接受实例(注意与entry_point注册类不同),可用于测试或临时替换:

from r2c_sdk.core.config_mapper import ConfigDrivenMapper
from r2c_sdk.core.transformers import build_transformer_registry
registry = build_transformer_registry(overrides={"my_double": MyDouble()})
mapper = ConfigDrivenMapper.from_rule_mappings(rules, transformers=registry)

context参数

transform(value, config, context) 的第三个参数是整个源 payload,供transformer按名称反查索引(subtract / euler_modulo 的names模式用它)。

mapper.map({"pose": [0.1, 7.0], "pose_names": ["roll", "yaw"]})

四、内置Transformer清单

当前版本共63个内置transformer。权威清单以TransformerRegistry.available_transformers()为准(版本升级会新增)。

类型与结构

表4 类型与结构转换器说明

名称

说明

配置

identity

原样返回。

无配置。

to_float / to_int / to_str

标量类型转换。

无配置。

to_bool

支持 "true"/"yes"/"on"/"1" 等字符串。

无配置。

to_list

可迭代对象转换为list(拒绝str/bytes)。

无配置。

list_wrapper

整体包成[value](与 to_list 展平语义相反)。

无配置。

序列/数值

表5 序列/数值说明

名称

说明

配置

slice

value[start:end]转换为新list。

{start: N, end: M}(至少一个)。

index

取第N个元素。

{index: N}

subtract

逐元素相减。

标量/等长list/,参考subtract的三种模式。

euler_modulo

指定位置取模(欧拉角回绕)。

{modulus, indices} 或 {modulus, names, names_path}

array_to_list

ndarray转换为list(依赖 .tolist())。

无配置。

list_to_ndarray

序列 转换为float32 ndarray。

无配置。

crop

ndarray裁剪。

参考crop的三种模式。

resize

ndarray缩放。

[height, width]

normalize

归一化/缩放。

{type: scale, range, source_min, source_max}

format

HWC与CHW布局转换(双向转换)(仅3D)。

"HWC" / "CHW"

transpose

任意轴序转置。

[2, 0, 1]这类整数列表

  • subtract的三种模式
    transforms: [{"subtract": 1.0}]                       # 标量:所有元素 -1.0
    transforms: [{"subtract": [0.0, 0.0, 1.0]}]           # 等长列表:逐元素相减
    transforms:                                           # 定向模式:只改指定位置
      - subtract: {values: [1.0], indices: [2]}
    transforms:                                           # 定向模式(按名,需 payload 带名字表)
      - subtract: {values: [1.0], names: [yaw], names_path: pose_names}

    indices 与 names 互斥;names 模式要求 context 中存在 names_path 指向的名字列表,否则抛错。

  • crop的三种模式
    transforms: "crop:bottom_left"                       # 预设区域名
    transforms: [{"crop": {"region": "center"}}]         # 映射形式
    transforms: [{"crop": {"box": [100, 300, 50, 400]}}] # 像素框 [y1, y2, x1, x2]

    9个预设:top_left、top_right、bottom_left、bottom_right、left_half、right_half、top_half、bottom_half、center。box越界或y1 >= y2会报错。

图像编解码

表6 图像编解码说明

名称

方向

配置

ndarray_to_png

ndarray转换为bytes。

无配置。

ndarray_to_jpeg

ndarray转换为bytes。

{quality: 0-100},默认95。

ndarray_to_webp

ndarray转换为bytes。

{quality: 0-100},默认80。

ndarray_to_bytes

ndarray转换为裸内存bytes。

无配置。

bytes_to_ndarray

裸 bytes转换为ndarray。

{dtype: "float32", shape: [...]}必填dtype。

png_to_ndarray

bytes转换为ndarray。

无配置。

jpeg_to_ndarray

bytes转换为ndarray。

无配置。

webp_to_ndarray

bytes转换为ndarray。

无配置。

decode_image

bytes转换为ndarray(自动识别格式)。

"bgr" / "rgb" / "gray"

bgr_to_rgb / rgb_to_bgr

通道序互换。

无配置。

jpeg_to_base64

bytes/ndarray转换为base64字符串。

无配置。

WebP体积通常比同画质JPEG小30~40%,编码耗时高约10~15倍,适合带宽敏感场景。

ROS消息

表7 ROS 消息说明

名称

说明

ros_image_to_ndarray / ros_image_to_png / ros_image_to_jpeg / ros_image_to_webp

sensor_msgs/Image转换为各种目标。

ros_compressed_image_to_ndarray / _png / _jpeg / _webp

sensor_msgs/CompressedImage转换为各种目标。

ros_compress_image_to_*(4 个)

上一行的别名,兼容compress拼写。

ros_message_to_mapping

任意ROS消息转换为递归dict。

list_to_ros_float64_multi_array

list转换为std_msgs/Float64MultiArray。

scalar_to_ros_float64

标量转换为std_msgs/Float64。

list_to_ros_joint_state

list转换为sensor_msgs/JointState({names, velocity, effort}可选)。

list_to_ros_move_request

6维位姿转换为jaka_msgs/srv/Move.Request。

select_joints_by_name

按名字筛选/重排关节数组,如下所示:

select_joints_by_name

输入必须是含name(或 names)列表的Mapping,其余等长列表会同步筛选重排:

transforms:
  - select_joints_by_name:
      names: ["joint_1", "joint_3", "joint_5"]

请求的名字在源消息中找不到时直接报错(并打印requested / available / missing三行对照),不会静默补0。

ros_image_to_* 支持 rgb8、bgr8、rgba8、bgra8、mono8、8uc1、mono16、16uc1编码。

ROS 消息及机器人夹爪中涉及ROS消息类型的transformer(ros_*、list_to_ros_*、scalar_to_ros_float64、scalar_to_step_motor_gripper*)在调用时才import对应的ROS消息包,因此必须在装有对应ROS运行时的机器上运行;纯Python环境调用会抛ImportError。

机器人夹爪

表8 机器人夹爪说明

名称

适用

方向

parallel_gripper_fingers_to_stroke

平行夹爪

两指 [left, right] 转换为行程(求和)。

parallel_gripper_stroke_to_fingers

平行夹爪

行程 转换为[S/2, S/2]。

scalar_to_step_motor_gripper

步进电机夹爪

标量 转换为 step_motor/Motor 开合指令(按阈值)。

scalar_to_step_motor_gripper_debounce

同上

连续N次越阈才发指令(去抖动)。

so101_gripper_value_to_joint / so101_gripper_joint_to_value

SO101

模型值 ↔ 关节弧度(双向转换)。

extract_gripper_states

青龙

关节列表 转换为 左右夹爪二值状态。

qinglong_gripper_real_transform / qinglong_gripper_visual_transform

青龙

夹爪值 转换为 真实 / 视觉关节指令。

moz1_gripper_value_to_joints / moz1_extract_gripper_states

Moz1

夹爪值 ↔ 8 关节(双向转换)。

jaka_gripper_value_to_joints / jaka_extract_gripper_state

Jaka

夹爪值 ↔ 2 关节(双向转换)。

r1_gripper_value_to_joint / r1_gripper_joint_to_value

星海图 R1

夹爪值 ↔ 2 关节(双向转换)。

scalar_to_step_motor_gripper配置项:open_threshold(80.0)、close_threshold(20.0)、id、speed、mode、angle、state、sub_divide。值落在两个阈值之间时返回None(不产生指令)。

五、常用配置

图1 常用配置图示

相机图像上行(ndarray)

- target: "images.color.wrist"
  source_path: "wrist"
  transforms:
    - "crop:center"
    - {resize: [224, 224]}
    - {ndarray_to_jpeg: {quality: 85}}

ROS2图像上行

- target: "images.color.front"
  source_path: "camera.image"
  transforms:
    - {ros_image_to_jpeg: {quality: 85}}     # 一步到位,无需中间 ndarray

模型输出拆分

- target: "eef_pose"
  source: "joint_states.position"
  transforms: [{slice: {start: 0, end: 6}}]
- target: "gripper_position"
  source: "joint_states.position"
  source_index: 6

夹爪双向换算(上行提取状态、下行下发指令)

device_to_r2c:
  mappings:
    - target: "gripper_stroke"
      source: "joint_states.position"
      transforms: [parallel_gripper_fingers_to_stroke]
r2c_to_device:
  mappings:
    - target: "finger_targets"
      source: "gripper_stroke"
      transforms: [parallel_gripper_stroke_to_fingers]

六、校验、调试与排错

参考以下说明进行校验、调试及处理常见报错。

启动即校验(fail fast)

构造ConfigDrivenMapper时(服务启动阶段)会对每条 rule 的每个transform调用validate_config并检查名字是否存在。配置错误在启动期暴露,不会拖到运行期。

表9 启动时常见报错的处理方法

错误原文

原因分析

处理方法

Unknown transformer 'x' in rule <src> → <tgt>

名字拼错,或第三方包未安装/未生效。

用available_transformers()核对;检查第三方包,可参考注意事项。

Invalid config for transformer 'x' in rule ...: <原因>

config不合法。

根据报错原因修改YAML配置文件。

Each transform mapping must include exactly one transform name

用了name + config双键写法。

修改为{转换器名字:config}。

Use 'transforms' or 'transform' (singular) — not both.

两个key同时出现。

任意删掉一个key。

'transforms' must not be an empty list.

空列表/ null。

删掉该key。

targets under 'images.*' require an image-encoding transform

images.* 目标没配编码转换。

添加ndarray_to_jpeg等。

运行期才会出现的错误:Unknown mapper transform: 'x',以及各transformer自己的类型/形状校验异常。

开启日志

import logging
logging.getLogger("r2c_sdk.core.transformers").setLevel(logging.DEBUG)

entry_point的冲突与加载失败都以WARNING级别打在这个logger上。

快速定位某个值经过了什么

临时在rule上加identity收敛范围,或写单元测试直接调mapper:

mapper = ConfigDrivenMapper.from_rule_mappings([...])
print(mapper.map(payload))

七、基于entry_point扩展Transformer

当内置能力不够,或希望把转换逻辑独立打包复用时,用Python标准entry_point机制注册第三方transformer,无需修改SDK源码。

加载扩展包的时序图

加载扩展包的时序图如图2所示。

图2 加载扩展包的时序图

两个关键点:

  • 扫描只发生一次(_scanned标志),时机是r2c_sdk.core.transformers首次被import。装完包必须重启进程才能生效。
  • 实例被缓存复用(参考注意事项)。

加载转换器的优先级

图3 加载转换器的优先级图示

同名时内置永远胜出,并记录日志:

WARNING r2c_sdk.core.transformers: Transformer 'identity' from entry_point is ignored because a builtin with the same name exists. 

第三方名字请添加厂商/项目前缀,例如my_gripper_stroke_to_fingers。

实操:以examples/ur5e_transformer为示例

该示例把内置的平行夹爪“手指 ↔ 行程”(双向转换)换算在SDK之外重新实现了一遍(并换成项目自己的比例系数),是标准的第三方transformer包。

examples/ur5e_transformer/
├── pyproject.toml                        # entry_point 声明
├── my_parallel_gripper_transformers.py   # transformer 实现
└── README.md                             # 安装说明

第 1 步:实现类

my_parallel_gripper_transformers.py:

from dataclasses import dataclass
from typing import Any, Mapping
from r2c_sdk.core.interfaces import IValueTransformer
@dataclass(frozen=True)
class MyGripperFingersToStrokeTransformer(IValueTransformer):
    """两手指位置 -> 行程。"""
    def transform(self, value: Any, config: Any = None, context: Any = None) -> Any:
        left, right = value
        return (float(left) + float(right)) * 1250.0
@dataclass(frozen=True)
class MyGripperStrokeToFingersTransformer(IValueTransformer):
    """行程 -> 两手指位置(逆变换)。"""
    def transform(self, value: Any, config: Any = None, context: Any = None) -> Any:
        return [float(value) / 2500.0, float(value) / 2500.0]

第 2 步:声明entry_point

pyproject.toml:

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "my-parallel-gripper-r2c-transformers"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["hw-r2c-sdk"]
[tool.setuptools]
py-modules = ["my_parallel_gripper_transformers"]
[project.entry-points."r2c_sdk.transformers"]
my_gripper_fingers_to_stroke = "my_parallel_gripper_transformers:MyGripperFingersToStrokeTransformer"
my_gripper_stroke_to_fingers = "my_parallel_gripper_transformers:MyGripperStrokeToFingersTransformer"

要点:

  • group名固定为r2c_sdk.transformers。
  • 等号左边是用户写在YAML里的名字。
  • 等号右边是 模块:类,必须是类,不是实例。
  • 值用引号包起来(含 : 的TOML值)。

第 3 步:安装

pip install -e .          # 开发期(示例目录 README 用的是 pip install --no-build-isolation -e .)

若构建后端在当前环境不可用(受限网络 / 内网源),添加 --no-build-isolation 并预装 setuptools,即示例 README 的做法。uv 用户可直接 uv pip install -e .。

第 4 步:在YAML中使用

device_to_r2c:
  mappings:
    - target: "gripper_stroke"
      source: "joint_states.position"          # [left, right]
      transform: my_gripper_fingers_to_stroke
r2c_to_device:
  mappings:
    - target: "gripper_fingers"
      source: "gripper_stroke"
      transform: my_gripper_stroke_to_fingers

第 5 步:验证

from r2c_sdk.core.transformers import TransformerRegistry, build_transformer_registry
print([n for n in TransformerRegistry.available_transformers() if n.startswith("my_")])
reg = build_transformer_registry()
print(reg["my_gripper_fingers_to_stroke"].transform([0.02, 0.04]))  # 75.0
print(reg["my_gripper_stroke_to_fingers"].transform(75.0))          # [0.03, 0.03]

接口契约

class IValueTransformer(ABC):
    @abstractmethod
    def transform(self, value: Any, config: Any = None, context: Any = None) -> Any: ...
    @staticmethod
    def validate_config(config: Any) -> None:
        """在加载期校验配置,非法则抛 ValueError。默认空实现。"""
  • 必须实现transform,签名固定三参数(context 可忽略)。
  • validate_config可选覆盖;覆盖了就会在启动期被调用,可在那里做早失败检查。
  • transform返回None是合法语义:该rule本帧不产生值(如夹爪去抖动、阈值中间态)。
  • 推荐 @dataclass(frozen=True) 写无状态实现;需要内部状态(计数器等)时用普通类。

注意事项

表10 注意事项规避方法

注意事项

说明

规避方法

注册的是类

SDK调ep.load() 后执行cls(),无参实例化。

__init__ 不能有必填参数;需要参数请在transform()里读config。

实例被缓存复用

同一名字全进程共用一个实例(_entry_cache),多个mapper共享。

有状态transformer 要按需重置状态,或改用纯函数式实现。

扫描只做一次

import时扫描,装包后当前进程不感知。

重启进程;重装后仍不生效先确认装进了运行时的那个虚拟环境。

同名内置优先

第三方被静默忽略(WARNING日志)。

名字加前缀;检查日志关键字 is ignored because a builtin。

加载失败被跳过

ep.load() 抛错只记 WARNING,名字不出现在注册表里。

排查时看日志Failed to load transformer entry_point,或表现为启动期 Unknown transformer。

非子类被跳过

不是IValueTransformer子类只记WARNING。

继承IValueTransformer并实现transform。

装了没用

打包时模块没被包含(py-modules/packages漏配)。

python -c "import 你的模块" 先确认可导入。

打包发布

python -m build                     # 产出 wheel + sdist
pip install dist/xxx-0.1.0-py3-none-any.whl

发布到PyPI或内网源后,用户只需pip install <包名> 即可在YAML里按名引用。运行时不需要--extra-index之外的特殊配置,entry_point元数据随wheel一起安装。

相关文档