时间同步 --- PTP 与 Scheduled Action Command

在机器人视觉系统中,多相机同步并不只是"向多台相机同时发送触发命令"。对于高速抓取、多视角定位、动态三维重建等应用,真正需要解决的是:各相机是否在足够接近的物理时刻曝光,曝光时间戳能否与机器人位姿对应,以及网络抖动是否会影响这一过程。

对于支持相关功能的 GigE Vision 相机,一种常见方案是 PTP(Precision Time Protocol)+ Scheduled Action Command(定时 Action 指令):PTP 建立统一时钟,Scheduled Action Command 让相机在约定的未来时间执行触发。两者解决的是不同层次的问题。
#mermaid-svg-UKdodAgFtOHyQvuz{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UKdodAgFtOHyQvuz .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UKdodAgFtOHyQvuz .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UKdodAgFtOHyQvuz .error-icon{fill:#552222;}#mermaid-svg-UKdodAgFtOHyQvuz .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UKdodAgFtOHyQvuz .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UKdodAgFtOHyQvuz .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UKdodAgFtOHyQvuz .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UKdodAgFtOHyQvuz .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UKdodAgFtOHyQvuz .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UKdodAgFtOHyQvuz .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UKdodAgFtOHyQvuz .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UKdodAgFtOHyQvuz .marker.cross{stroke:#333333;}#mermaid-svg-UKdodAgFtOHyQvuz svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UKdodAgFtOHyQvuz p{margin:0;}#mermaid-svg-UKdodAgFtOHyQvuz .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-UKdodAgFtOHyQvuz .cluster-label text{fill:#333;}#mermaid-svg-UKdodAgFtOHyQvuz .cluster-label span{color:#333;}#mermaid-svg-UKdodAgFtOHyQvuz .cluster-label span p{background-color:transparent;}#mermaid-svg-UKdodAgFtOHyQvuz .label text,#mermaid-svg-UKdodAgFtOHyQvuz span{fill:#333;color:#333;}#mermaid-svg-UKdodAgFtOHyQvuz .node rect,#mermaid-svg-UKdodAgFtOHyQvuz .node circle,#mermaid-svg-UKdodAgFtOHyQvuz .node ellipse,#mermaid-svg-UKdodAgFtOHyQvuz .node polygon,#mermaid-svg-UKdodAgFtOHyQvuz .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UKdodAgFtOHyQvuz .rough-node .label text,#mermaid-svg-UKdodAgFtOHyQvuz .node .label text,#mermaid-svg-UKdodAgFtOHyQvuz .image-shape .label,#mermaid-svg-UKdodAgFtOHyQvuz .icon-shape .label{text-anchor:middle;}#mermaid-svg-UKdodAgFtOHyQvuz .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-UKdodAgFtOHyQvuz .rough-node .label,#mermaid-svg-UKdodAgFtOHyQvuz .node .label,#mermaid-svg-UKdodAgFtOHyQvuz .image-shape .label,#mermaid-svg-UKdodAgFtOHyQvuz .icon-shape .label{text-align:center;}#mermaid-svg-UKdodAgFtOHyQvuz .node.clickable{cursor:pointer;}#mermaid-svg-UKdodAgFtOHyQvuz .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-UKdodAgFtOHyQvuz .arrowheadPath{fill:#333333;}#mermaid-svg-UKdodAgFtOHyQvuz .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-UKdodAgFtOHyQvuz .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-UKdodAgFtOHyQvuz .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UKdodAgFtOHyQvuz .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UKdodAgFtOHyQvuz .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UKdodAgFtOHyQvuz .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-UKdodAgFtOHyQvuz .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-UKdodAgFtOHyQvuz .cluster text{fill:#333;}#mermaid-svg-UKdodAgFtOHyQvuz .cluster span{color:#333;}#mermaid-svg-UKdodAgFtOHyQvuz div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-UKdodAgFtOHyQvuz .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UKdodAgFtOHyQvuz rect.text{fill:none;stroke-width:0;}#mermaid-svg-UKdodAgFtOHyQvuz .icon-shape,#mermaid-svg-UKdodAgFtOHyQvuz .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UKdodAgFtOHyQvuz .icon-shape p,#mermaid-svg-UKdodAgFtOHyQvuz .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-UKdodAgFtOHyQvuz .icon-shape .label rect,#mermaid-svg-UKdodAgFtOHyQvuz .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UKdodAgFtOHyQvuz .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-UKdodAgFtOHyQvuz .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-UKdodAgFtOHyQvuz :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-UKdodAgFtOHyQvuz .root>*{fill:#e74c3c!important;stroke:#c0392b!important;color:#fff!important;stroke-width:2px!important;}#mermaid-svg-UKdodAgFtOHyQvuz .root span{fill:#e74c3c!important;stroke:#c0392b!important;color:#fff!important;stroke-width:2px!important;}#mermaid-svg-UKdodAgFtOHyQvuz .root tspan{fill:#fff!important;}#mermaid-svg-UKdodAgFtOHyQvuz .main>*{fill:#3498db!important;stroke:#2c3e50!important;color:#fff!important;stroke-width:1.5px!important;}#mermaid-svg-UKdodAgFtOHyQvuz .main span{fill:#3498db!important;stroke:#2c3e50!important;color:#fff!important;stroke-width:1.5px!important;}#mermaid-svg-UKdodAgFtOHyQvuz .main tspan{fill:#fff!important;}#mermaid-svg-UKdodAgFtOHyQvuz .sub>*{fill:#ecf0f1!important;stroke:#7f8c8d!important;color:#2c3e50!important;}#mermaid-svg-UKdodAgFtOHyQvuz .sub span{fill:#ecf0f1!important;stroke:#7f8c8d!important;color:#2c3e50!important;}#mermaid-svg-UKdodAgFtOHyQvuz .sub tspan{fill:#2c3e50!important;}#mermaid-svg-UKdodAgFtOHyQvuz .result>*{fill:#2ecc71!important;stroke:#27ae60!important;color:#fff!important;stroke-width:2px!important;}#mermaid-svg-UKdodAgFtOHyQvuz .result span{fill:#2ecc71!important;stroke:#27ae60!important;color:#fff!important;stroke-width:2px!important;}#mermaid-svg-UKdodAgFtOHyQvuz .result tspan{fill:#fff!important;} 多相机同步
PTP

统一时钟
Scheduled Action

定时执行
各设备共享时间基准
提前下发未来触发时刻 T
相机按计划触发采集
用硬件时间戳验证采集时序

一、为什么需要定时 Action 指令

1.1 普通软件触发的问题

最直观的方式,是主机依次调用多台相机的软件触发接口:

text 复制代码
Host ──> Camera A:SoftwareTrigger
     ├─> Camera B:SoftwareTrigger
     ├─> Camera C:SoftwareTrigger
     └─> Camera D:SoftwareTrigger

这种方式实现简单,但每次调用都会受到主机线程调度、驱动队列、网络转发和相机接收处理等因素影响。即使代码连续调用,命令发出的时间、到达的时间以及相机实际曝光的时间也不必相同。

例如,运动速度为 1 m/s,两台相机的有效采样时刻相差 500 μs,则仅从运动距离来看,就可能引入:

Δx=vΔt=1×0.0005=0.5 mm \Delta x=v\Delta t=1\times0.0005=0.5\ \mathrm{mm} Δx=vΔt=1×0.0005=0.5 mm

这个关系反映的是运动造成的时间错位量,并不等同于完整的视觉测量误差;实际误差还与拍摄方向、标定精度、目标运动和算法有关。

1.2 常见4种触发方式

触发方式 触发时刻由谁决定 主要特点
逐台软件触发 主机调用与命令到达 简单,但多相机时序受主机与网络影响
普通 Action Command 命令到达后执行 一条广播控制多台相机,仍存在到达时间差
Scheduled Action Command 相机本地同步时钟 提前发送,在约定的未来时刻执行
外部硬件触发 外部电信号 可获得较高确定性,但需要布线与电气设计

这里需要区分触发执行时间一致与传感器曝光起点一致 :Scheduled Action 主要约束触发执行时刻,最终曝光精度还受相机内部时序影响。

二、PTP 与 Action Command

2.1 PTP:让各设备拥有可比较的时钟

PTP 是 IEEE 1588 精密时间协议。系统选择主时钟(Grandmaster/Master),其余设备通过报文交换和时钟伺服调整自身时间及频率,尽可能与主时钟保持一致。

text 复制代码
              PTP Grandmaster
                     │
             PTP 网络 / 交换机
          ┌──────────┼──────────┐
          ▼          ▼          ▼
       Camera A   Camera B   Camera C
        clock A    clock B    clock C
          └──────────┼──────────┘
                     ▼
            同一可比较的时间域

若没有统一时钟,不同相机的本地时间戳即使数值相近,也未必对应同一物理时刻。启用 PTP 后,才能将相机的采集时间戳放在同一基准下比较。

需要注意:时钟分辨率不等于同步精度。 对支持 PTP 的 Basler 相机,相关时间参数常以 tick 表示,PTP 启用时通常 1 tick = 1 ns;这并不表示相机间已经达到 1 ns 的同步误差。另外,所有相机处于同一个 PTP 时间域,也不代表该时间一定与 UTC 或机器人控制器时间一致。

2.2 普通 Action:通过匹配参数选择相机

Action Command 是主机发送的 GigE Vision 控制消息。相机只有在命令参数与自身配置满足匹配条件时,才执行对应 Action。参数为:

  • ActionDeviceKey:设备/应用匹配键,命令与相机配置必须一致;它不是密码学意义上的安全认证机制。
  • ActionGroupKey:组匹配键,只有组值一致的相机才进入后续匹配。
  • ActionGroupMask:子组掩码;相机掩码与命令掩码按位与的结果非零,才属于本次目标子组。

例如,相机 A、B、C、D 分别配置 0x01、0x02、0x04、0x08,主机发送 GroupMask = 0x05,在其他匹配条件满足时,A、C 将执行 Action。

text 复制代码
Camera A  0001 ──┐
Camera B  0010   │       Host GroupMask = 0101
Camera C  0100 ──┼─────> 命中 A、C
Camera D  1000 ──│

普通 Action 虽然只需一次广播,但各相机收到报文后便执行,网络到达时刻不同,仍会产生触发偏差。

2.3 Scheduled Action:预约统一的未来时刻

Scheduled Action 在普通 Action 的匹配机制上增加 Action Time = T。主机提前广播命令;相机收到后先保存触发计划,当本地同步时钟到达 T,再执行相应 Action。
#mermaid-svg-idpWGbdePdEZKByT{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-idpWGbdePdEZKByT .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-idpWGbdePdEZKByT .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-idpWGbdePdEZKByT .error-icon{fill:#552222;}#mermaid-svg-idpWGbdePdEZKByT .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-idpWGbdePdEZKByT .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-idpWGbdePdEZKByT .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-idpWGbdePdEZKByT .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-idpWGbdePdEZKByT .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-idpWGbdePdEZKByT .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-idpWGbdePdEZKByT .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-idpWGbdePdEZKByT .marker{fill:#333333;stroke:#333333;}#mermaid-svg-idpWGbdePdEZKByT .marker.cross{stroke:#333333;}#mermaid-svg-idpWGbdePdEZKByT svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-idpWGbdePdEZKByT p{margin:0;}#mermaid-svg-idpWGbdePdEZKByT .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-idpWGbdePdEZKByT .cluster-label text{fill:#333;}#mermaid-svg-idpWGbdePdEZKByT .cluster-label span{color:#333;}#mermaid-svg-idpWGbdePdEZKByT .cluster-label span p{background-color:transparent;}#mermaid-svg-idpWGbdePdEZKByT .label text,#mermaid-svg-idpWGbdePdEZKByT span{fill:#333;color:#333;}#mermaid-svg-idpWGbdePdEZKByT .node rect,#mermaid-svg-idpWGbdePdEZKByT .node circle,#mermaid-svg-idpWGbdePdEZKByT .node ellipse,#mermaid-svg-idpWGbdePdEZKByT .node polygon,#mermaid-svg-idpWGbdePdEZKByT .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-idpWGbdePdEZKByT .rough-node .label text,#mermaid-svg-idpWGbdePdEZKByT .node .label text,#mermaid-svg-idpWGbdePdEZKByT .image-shape .label,#mermaid-svg-idpWGbdePdEZKByT .icon-shape .label{text-anchor:middle;}#mermaid-svg-idpWGbdePdEZKByT .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-idpWGbdePdEZKByT .rough-node .label,#mermaid-svg-idpWGbdePdEZKByT .node .label,#mermaid-svg-idpWGbdePdEZKByT .image-shape .label,#mermaid-svg-idpWGbdePdEZKByT .icon-shape .label{text-align:center;}#mermaid-svg-idpWGbdePdEZKByT .node.clickable{cursor:pointer;}#mermaid-svg-idpWGbdePdEZKByT .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-idpWGbdePdEZKByT .arrowheadPath{fill:#333333;}#mermaid-svg-idpWGbdePdEZKByT .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-idpWGbdePdEZKByT .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-idpWGbdePdEZKByT .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-idpWGbdePdEZKByT .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-idpWGbdePdEZKByT .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-idpWGbdePdEZKByT .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-idpWGbdePdEZKByT .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-idpWGbdePdEZKByT .cluster text{fill:#333;}#mermaid-svg-idpWGbdePdEZKByT .cluster span{color:#333;}#mermaid-svg-idpWGbdePdEZKByT div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-idpWGbdePdEZKByT .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-idpWGbdePdEZKByT rect.text{fill:none;stroke-width:0;}#mermaid-svg-idpWGbdePdEZKByT .icon-shape,#mermaid-svg-idpWGbdePdEZKByT .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-idpWGbdePdEZKByT .icon-shape p,#mermaid-svg-idpWGbdePdEZKByT .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-idpWGbdePdEZKByT .icon-shape .label rect,#mermaid-svg-idpWGbdePdEZKByT .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-idpWGbdePdEZKByT .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-idpWGbdePdEZKByT .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-idpWGbdePdEZKByT :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-idpWGbdePdEZKByT .host>*{fill:#E3F2FD!important;stroke:#1E88E5!important;stroke-width:2px!important;color:#0D47A1!important;}#mermaid-svg-idpWGbdePdEZKByT .host span{fill:#E3F2FD!important;stroke:#1E88E5!important;stroke-width:2px!important;color:#0D47A1!important;}#mermaid-svg-idpWGbdePdEZKByT .host tspan{fill:#0D47A1!important;}#mermaid-svg-idpWGbdePdEZKByT .sched>*{fill:#FFF8E1!important;stroke:#F9A825!important;stroke-width:2px!important;color:#795548!important;}#mermaid-svg-idpWGbdePdEZKByT .sched span{fill:#FFF8E1!important;stroke:#F9A825!important;stroke-width:2px!important;color:#795548!important;}#mermaid-svg-idpWGbdePdEZKByT .sched tspan{fill:#795548!important;}#mermaid-svg-idpWGbdePdEZKByT .cam>*{fill:#E8F5E9!important;stroke:#43A047!important;stroke-width:2px!important;color:#1B5E20!important;}#mermaid-svg-idpWGbdePdEZKByT .cam span{fill:#E8F5E9!important;stroke:#43A047!important;stroke-width:2px!important;color:#1B5E20!important;}#mermaid-svg-idpWGbdePdEZKByT .cam tspan{fill:#1B5E20!important;}#mermaid-svg-idpWGbdePdEZKByT .exec>*{fill:#F3E5F5!important;stroke:#8E24AA!important;stroke-width:2px!important;color:#4A148C!important;}#mermaid-svg-idpWGbdePdEZKByT .exec span{fill:#F3E5F5!important;stroke:#8E24AA!important;stroke-width:2px!important;color:#4A148C!important;}#mermaid-svg-idpWGbdePdEZKByT .exec tspan{fill:#4A148C!important;} Host:读取当前相机时间 now
设定 T = now + 50 ms
广播 Scheduled Action(T)
Camera A

提前收到
Camera B

提前收到
Camera C

提前收到
各自等到 PTP 时间 T
执行触发事件

这使触发时序不再直接取决于广播报文何时抵达每台相机,前提是所有相机已正确同步,并且在 T 到来之前收到和接受命令。

若指定的 Action Time 为零或已经过去,合法的 Scheduled Action 可能立即执行 ,并返回 ActionLate 状态。此时不是简单地"丢弃触发",而是可能出现各相机拍到了图像、但拍摄时间已经错开的情况。业务侧应将这种帧组判为同步异常,不能仅凭"收到了图像"判断成功。

三、相机与主机的配置流程

3.1 系统架构

#mermaid-svg-uQXJ7MzFhP9fl5if{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uQXJ7MzFhP9fl5if .error-icon{fill:#552222;}#mermaid-svg-uQXJ7MzFhP9fl5if .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uQXJ7MzFhP9fl5if .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uQXJ7MzFhP9fl5if .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uQXJ7MzFhP9fl5if .marker.cross{stroke:#333333;}#mermaid-svg-uQXJ7MzFhP9fl5if svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uQXJ7MzFhP9fl5if p{margin:0;}#mermaid-svg-uQXJ7MzFhP9fl5if .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uQXJ7MzFhP9fl5if .cluster-label text{fill:#333;}#mermaid-svg-uQXJ7MzFhP9fl5if .cluster-label span{color:#333;}#mermaid-svg-uQXJ7MzFhP9fl5if .cluster-label span p{background-color:transparent;}#mermaid-svg-uQXJ7MzFhP9fl5if .label text,#mermaid-svg-uQXJ7MzFhP9fl5if span{fill:#333;color:#333;}#mermaid-svg-uQXJ7MzFhP9fl5if .node rect,#mermaid-svg-uQXJ7MzFhP9fl5if .node circle,#mermaid-svg-uQXJ7MzFhP9fl5if .node ellipse,#mermaid-svg-uQXJ7MzFhP9fl5if .node polygon,#mermaid-svg-uQXJ7MzFhP9fl5if .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uQXJ7MzFhP9fl5if .rough-node .label text,#mermaid-svg-uQXJ7MzFhP9fl5if .node .label text,#mermaid-svg-uQXJ7MzFhP9fl5if .image-shape .label,#mermaid-svg-uQXJ7MzFhP9fl5if .icon-shape .label{text-anchor:middle;}#mermaid-svg-uQXJ7MzFhP9fl5if .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uQXJ7MzFhP9fl5if .rough-node .label,#mermaid-svg-uQXJ7MzFhP9fl5if .node .label,#mermaid-svg-uQXJ7MzFhP9fl5if .image-shape .label,#mermaid-svg-uQXJ7MzFhP9fl5if .icon-shape .label{text-align:center;}#mermaid-svg-uQXJ7MzFhP9fl5if .node.clickable{cursor:pointer;}#mermaid-svg-uQXJ7MzFhP9fl5if .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uQXJ7MzFhP9fl5if .arrowheadPath{fill:#333333;}#mermaid-svg-uQXJ7MzFhP9fl5if .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uQXJ7MzFhP9fl5if .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uQXJ7MzFhP9fl5if .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uQXJ7MzFhP9fl5if .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uQXJ7MzFhP9fl5if .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uQXJ7MzFhP9fl5if .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uQXJ7MzFhP9fl5if .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uQXJ7MzFhP9fl5if .cluster text{fill:#333;}#mermaid-svg-uQXJ7MzFhP9fl5if .cluster span{color:#333;}#mermaid-svg-uQXJ7MzFhP9fl5if div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uQXJ7MzFhP9fl5if .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uQXJ7MzFhP9fl5if rect.text{fill:none;stroke-width:0;}#mermaid-svg-uQXJ7MzFhP9fl5if .icon-shape,#mermaid-svg-uQXJ7MzFhP9fl5if .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uQXJ7MzFhP9fl5if .icon-shape p,#mermaid-svg-uQXJ7MzFhP9fl5if .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uQXJ7MzFhP9fl5if .icon-shape .label rect,#mermaid-svg-uQXJ7MzFhP9fl5if .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uQXJ7MzFhP9fl5if .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uQXJ7MzFhP9fl5if .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uQXJ7MzFhP9fl5if :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-uQXJ7MzFhP9fl5if .rc>*{fill:#E3F2FD!important;stroke:#1E88E5!important;stroke-width:2px!important;color:#0D47A1!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .rc span{fill:#E3F2FD!important;stroke:#1E88E5!important;stroke-width:2px!important;color:#0D47A1!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .rc tspan{fill:#0D47A1!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .mod>*{fill:#E1F5FE!important;stroke:#0288D1!important;stroke-width:2px!important;color:#01579B!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .mod span{fill:#E1F5FE!important;stroke:#0288D1!important;stroke-width:2px!important;color:#01579B!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .mod tspan{fill:#01579B!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .net>*{fill:#E8F5E9!important;stroke:#43A047!important;stroke-width:2px!important;color:#1B5E20!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .net span{fill:#E8F5E9!important;stroke:#43A047!important;stroke-width:2px!important;color:#1B5E20!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .net tspan{fill:#1B5E20!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .cam>*{fill:#F3E5F5!important;stroke:#8E24AA!important;stroke-width:2px!important;color:#4A148C!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .cam span{fill:#F3E5F5!important;stroke:#8E24AA!important;stroke-width:2px!important;color:#4A148C!important;}#mermaid-svg-uQXJ7MzFhP9fl5if .cam tspan{fill:#4A148C!important;} Industrial PC / ROS 2
触发
触发
触发
触发
图像
图像
图像
图像
Robot Controller

位姿与运动计划
触发调度模块
图像接收与关联模块
GigE / PTP Network
Camera A
Camera B
Camera C

实际部署前,要确认相机型号、固件和 SDK 均支持 PTP、Action Command 和 Scheduled Action Command。不同厂商的 GenICam 节点名、Action 编号和时间戳能力可能不同;同一厂商不同系列也未必一致。

3.2 相机侧:先统一时钟,再配置触发

首先启用 PTP,例如某些相机使用 PtpEnable,另一些机型使用 GevIEEE1588。启用后应检查主时钟 ID 和稳定性:支持 PtpServoStatus 的型号可观察是否 Locked;其他型号可持续采样 GevIEEE1588OffsetFromMaster 并检查指定窗口内的最大值。

验收阈值要根据业务误差预算设定 ,不能简单把 Offset < 1 μs 当成所有相机和网络的通用标准。主时钟切换、设备重新上线后,也要重新评估同步状态。

接着配置 Action 与采集触发。以下参数以支持 Action1 的 Basler 机型为例:

ini 复制代码
ActionSelector   = 1
ActionDeviceKey  = 0x12345678
ActionGroupKey   = 1
ActionGroupMask  = 0x00000001  # 相机 A 示例

TriggerSelector  = FrameStart
TriggerMode      = On
TriggerSource    = Action1

ExposureAuto     = Off
GainAuto         = Off

其余相机可采用相同的 DeviceKey / GroupKey,并按子组设计不同的 GroupMask。若使用 LUCID Arena SDK,则可能是 ActionSelector = 0、TriggerSource = Action0。必须以相机实际 NodeMap 和厂商文档为准,不能直接跨品牌复制参数。

在发送 Action 前,还要按 SDK 要求打开图像采集流程,让设备进入可接收帧触发的状态。否则即使网络 Action 到达,也不意味着一定产生有效图像。

3.3 主机侧:读取参考时钟并发送计划

主机不应简单使用 std::chrono::system_clock 推算 Action Time,除非已证明它与相机时间属于同一时间域。更直接的方法是从已同步相机锁存当前设备时间,再加上足够的提前量。

cpp 复制代码
// 逻辑伪代码:SetInteger、WaitPtpStable 等为示意封装,非可直接编译的 SDK API

constexpr uint32_t DEVICE_KEY = 0x12345678;
constexpr uint32_t GROUP_KEY  = 1;
constexpr uint32_t GROUP_MASK = 0x00000007; // A/B/C,取决于掩码配置

ConfigureActionAndTrigger(cameras);
StartAcquisition(cameras);
WaitPtpStable(cameras);

// 使用相机设备时钟,而非未经映射的主机系统时钟
uint64_t now_ns = LatchCameraTimestamp(reference_camera);
uint64_t lead_ns = 50'000'000ULL;       // 示例:提前 50 ms
uint64_t action_time_ns = now_ns + lead_ns;

// 广播地址必须与实际子网匹配,此处假设为 192.168.10.0/24
IssueScheduledActionCommand(
    DEVICE_KEY,
    GROUP_KEY,
    GROUP_MASK,
    action_time_ns,
    "192.168.10.255"
);

// 生产代码还须检查每台相机的 Action 返回状态与后续帧结果

此处的 50 ms 仅是调试初值,不是通用要求;发送延迟、网络负载、相机响应和业务周期都可能要求调整。

四、曝光时间戳与机器人位姿对齐

4.1 同一触发时刻,不等于完全相同的曝光起点

Scheduled Action 使相机按照同步时钟执行触发事件;实际曝光起点仍取决于机型及内部时序。

相机内部还有触发识别、传感器启动、曝光和读出过程:

text 复制代码
T:Scheduled Action 执行
             │
             ▼
       FrameStart 触发
             │  触发/传感器内部延迟
             ▼
       ExposureStart
             │  曝光时间
             ▼
        ExposureEnd
             │  读出与封包
             ▼
        网络传输
             │
             ▼
        Host Receive

部分型号相机可以通过 ExposureStartDelay 查询触发信号检测到曝光开始之间的延迟。

若采用 Rolling Shutter,还需要考虑各行曝光起始时刻不同。进行高速三维测量时,应优先依据具体传感器、成像模式与精度要求评估 Global Shutter 或其他补偿方案。

4.2 Chunk Timestamp

不能把所有相机的 ChunkTimestamp 都直接认定为 ExposureStart。在部分型号中,可通过 ChunkTimestampSelector 选择 FrameStart、ExposureStart 或 ExposureEnd;其他型号可能仅提供表示采集开始的 ChunkTimestamp。

在项目中明确区分这些字段:

字段 语义 用途
expected_action_time 计划触发时间 T 关联调度周期
frame_start_timestamp 帧采集开始时间 判断采集时序
exposure_start_timestamp 实际曝光起点,若设备支持 评价曝光同步性
block_id / frame_id 帧序号 检查丢帧、重复帧、帧组匹配
host_receive_time 主机接收时间 评价链路延迟,不用于直接证明曝光同步

记录元数据时,应同时保存时间戳类型、时钟域和时间单位,避免后续把不同语义的数据相减。部分厂商的 Trigger Input Counter 仅统计硬件输入触发,也不能默认拿来统计网络 Action 次数。

4.3 将相机时间映射到机器人时间

在机器人系统中,同时存在相机 PTP 时间、机器人控制器时间和主机 OS/ROS 2 时间。它们不能在没有映射关系的情况下直接相减或插值。

如果机器人控制器无法直接同步到相同的 PTP 时钟,可根据双端时间观测建立近似映射:

tPTP=a⋅trobot+b t_{PTP}=a\cdot t_{robot}+b tPTP=a⋅trobot+b

其中 a 表示相对时钟速率,b 表示时间偏移。映射精度取决于时间采样方式、通信延迟与漂移估计,不能只凭收到机器人消息的主机时间来推断机器人实际位姿采样时刻。

对于机器人位姿约 60 Hz、相机 200 Hz 的系统,还需要根据真正的位姿采样时间戳进行插值或状态估计。即使相机之间能很好地同步,如果机器人位姿时间基准不准确,视觉与运动融合仍然会产生空间偏差。

五、定时调度与异常处理

5.1 提前量要覆盖最坏命令分发延迟

定时调度的前提是命令能提前到达全部目标相机。可将提前量近似设计为:

T−tnow>Ddispatch,max+M T-t_{now}>D_{dispatch,max}+M T−tnow>Ddispatch,max+M

其中 D_dispatch,max 是从读取参考时钟到最慢目标相机能够接受命令的最坏分发延迟,M 是为负载波动预留的安全裕量。实际测量时要覆盖网络繁忙、主机高负载、多网卡和设备数量增加等工况。

可以先以较大的提前量(例如 100 ms)完成连通性和稳定性验证,再在不触发超时、不影响业务响应的前提下逐步缩短。实时内核与 CPU 亲和性有助于改善主机调度时延的确定性,但不会自动消除相机间的 PTP 时钟偏差。

5.2 Action 应答、帧完成与业务验收是三件事

定时 Action 的可靠性不能只看发送 API 是否返回成功。以 Basler pylon 的相关状态为例:

Action 状态 说明 处理
Ok 相机确认接受命令 继续等待并核查图像
ActionLate 调度时间已过去,合法命令可能立即执行 该同步帧组判异常
NoRefTime 缺乏可用参考时钟 暂停定时触发并检查 PTP
Overflow 定时 Action 队列已满 降低排队密度并处理积压
无应答/超时 未能确认设备接受命令 记录设备与网络异常,不能假定成功

其中 Ok 只表示命令接受确认 ,不代表曝光已经完成,更不代表帧组完整。工程中最好分开记录 Action ACK → Frame Acquired → Frame Received → Synchronization Valid,并为不完整帧组设置超时与丢弃策略。

5.3 连续采集需要考虑 Action 队列和状态机

部分相机支持 ActionQueueSize,可以在设备内排队多个未来的 Scheduled Action,并按时间先后执行;队列满时新增命令会被忽略。不同型号容量可能很小,不能假设所有相机都能提前缓存任意数量的触发指令。

系统层面建议采用简洁的状态管理:

text 复制代码
INIT(设备初始化)
  │
  ▼
SYNCING(等待 PTP 稳定)
  │
  ▼
READY(配置完成,可调度)
  │
  ▼
RUNNING(发送 / 接收 / 验证帧组)
  │
  ├─ PTP 失锁 / ActionLate / 帧组缺失
  ▼
DEGRADED(暂停有效输出,记录异常)
  │
  └────────> SYNCING(恢复后重新校验)

若系统涉及运动控制,异常时应根据工艺风险决定停止拍摄、停止视觉结果输出或执行安全降级,而不是直接将不同步的数据继续送往运动规划模块。

六、网络设计:同步曝光与同步传输

6.1 PTP 控制面与图像数据面的要求不同

PTP 主要用于时钟同步,Scheduled Action 是触发控制,GigE Vision 图像流则要搬运大量数据。多台相机在同一时刻曝光,不意味着图像必须在同一时刻抵达主机。

例如,多台高分辨率相机即使完全同步拍照,也可能在后续传输时共同占满交换机出口、网卡和主机接收缓冲。应根据实际 分辨率 × 位深 × 帧率 × 相机数量 估算负载,并考虑协议开销与突发流量。

6.2 网络设计

对于多相机生产系统,应优先做到:视觉网络独立或有明确的流量隔离;根据精度需要选用支持适当 PTP 机制的交换机;确认交换机、网卡、相机对 MTU/Jumbo Frame 的设置一致;根据相机型号配置 GevSCPD(包间延迟)与 Frame Transmission Delay(帧传输延迟),必要时错峰发送图像数据。

这里的传输错峰不等于曝光错峰:在设备支持的条件下,可以保持曝光时刻相同,随后分别安排数据发送。也要保证多网卡路由、广播接口选择和 PTP 报文不会因为网络拓扑而出现意外变化。

七、同步精度验证与故障排查

7.1 按照"单机---多机---系统"逐层验证

第一阶段:单机功能确认。 检查软件触发、Action 匹配、曝光参数、图像取流及时间戳能力;确认采集流程和 SDK 配置正确。

第二阶段:多相机定时同步。 在统一 PTP 时钟下观察主时钟与锁定状态,使用足够大的提前量测试 Scheduled Action,验证 Action 应答、帧组完整性和设备时间戳。

第三阶段:动态工况与系统集成。 引入网络负载、机器人运动和光源时序,评估持续运行时的同步偏差、帧组丢失率以及机器人位姿对齐误差,并测试 PTP 失锁、相机掉线和重新恢复。

7.2 不只看平均值,还要验证异常尾部

若相机提供语义明确的曝光起点时间戳,在同一触发周期 k 中,可以计算:

Δtsync,k=max⁡i(texposure,i,k)−min⁡i(texposure,i,k) \Delta t_{sync,k}=\max_i(t_{exposure,i,k})-\min_i(t_{exposure,i,k}) Δtsync,k=imax(texposure,i,k)−imin(texposure,i,k)

再观察多个采集周期的平均值、P99、最大值以及有效帧组比例。同时记录 t_exposure - T,用于分析触发执行到曝光发生的总延迟及其变化。

如果设备只能提供 FrameStart Timestamp,那么算出来的是帧采集事件的偏差,不能直接宣称是实际曝光起点的误差。 对要求非常严格的测量系统,还应结合硬件输出、示波器、共同光学事件等外部方法进行交叉验证。

7.3 常见问题

现象 优先排查
相机显示 Slave,但时间偏差大 PTP Servo/Offset、Grandmaster ID、交换机、伺服收敛时间
部分相机漏拍或错拍 Action 参数、采集状态、提前量、ActionLate、队列容量
同步触发但时间戳差异大 时间戳语义、TriggerDelay、ExposureStartDelay、快门方式
曝光同步但收图延迟高 带宽、Jumbo Frame、包间延迟、帧传输错峰、主机缓冲
图像与机器人位姿错位 时钟域映射、位姿采样时间、插值与外参标定

八、工程配置模板

下面的 YAML 用于表达工程配置意图。实际软件应在初始化阶段将这些字段映射到设备支持的 GenICam 节点,并进行能力检查。

yaml 复制代码
ptp:
  enabled: true
  lock_timeout_ms: 10000
  # 按设备实测与业务误差预算设置,不将固定值作为通用标准
  max_offset_from_master_ns: 1000

action:
  device_key: 305419896        # 0x12345678
  group_key: 1
  group_mask: 7                # 0x00000007
  trigger_source: Action1     # 以设备实际支持的枚举为准

schedule:
  initial_lead_ms: 100
  nominal_lead_ms: 50
  require_action_ack: true
  reject_late_action: true

camera:
  trigger_selector: FrameStart
  trigger_mode: On
  exposure_auto: Off
  gain_auto: Off
  timestamp_event: ExposureStart # 不支持时标明实际回退语义

network:
  broadcast_address: 192.168.10.255
  jumbo_frame: true
  transfer_stagger: true

总结

PTP 解决"设备是否拥有统一时间基准";Scheduled Action 解决"是否能够提前约定同一触发时刻";曝光事件时间戳和外部测量负责验证"实际拍摄是否达到所需同步精度"。

完整的机器人视觉同步方案还必须同时处理命令是否准时到达、相机是否接受触发、传感器何时真正曝光、图像是否完整到达,以及相机时间如何对应机器人位姿时间。只有把这些层次分开设计并分别验收,Scheduled Action 才是可用于工业系统的可验证时间同步链路。

相关推荐
沉下心来学鲁班3 小时前
具身智能进阶:让 MicroDuck 踩着高跷走路
人工智能·python·机器人
脆性薄片搬运谢工3 小时前
横架机械手安全光栅保护区域划分的五个核心维度
网络·安全·机器人·脆性薄片搬运专机·伯努利吸盘·超薄玻璃搬运·非接触式上料
syagain_zsx4 小时前
WebSocket 教程:websocketpp 写 echo 服务端
linux·网络·c++·websocket·网络协议·工具库
大陈子编程4 小时前
C++ CSP-J 与 CSP-S 题单:洛谷 140 题知识点全覆盖
c++
akinggw4 小时前
阿福老年陪护机器人 v1.7 发布
机器人
鲁邦通物联网4 小时前
地下管廊机器狗接入老旧竖井电梯的机器人梯控设计
机器人·巡检机器人·机器人梯控·agv梯控·非侵入式采集·配送机器人·机器人乘梯
张小姐的猫4 小时前
【MySQL】数据类型
android·linux·数据库·c++·mysql·adb·php
讳疾忌医丶5 小时前
编译器眼中的 C++ 循环:从指针别名、IEEE 754 结合律到 SIMD 语义证明
开发语言·c++