起因

MES 对接篇把上位机对外说话的路铺完,视觉是另一条还没接的线——摄像头找定位基准,把偏差换算成坐标补偿喂给运动层。这篇写它的全链路:UVC 摄像头怎么在 EHCI 的周期调度里挤到等时带宽,图像算法怎么把老师傅的放大镜数字化,补偿最后落在运动层的哪个格子里。

先说找正这门手艺的来历。线切割开工前要把丝对准穿丝孔中心或工件基准边,车间里传承的做法是"碰":丝带上高频的空载电压,移动轴去碰孔壁或侧壁,工件—丝一导通就停,四点取中——放电篇讲过间隙电压就是天然的接触传感器,这个做法不花一分钱硬件,精度能到几微米,代价是慢、依赖手感、碰轻了没信号碰重了顶弯丝。放大镜目测是另一路,装个 10 倍镜片在上线架旁,老师傅眯着眼估"丝在孔中间偏左半格"。视觉定位要数字化的就是这门手艺:拍照、算中心、算偏差、改零点——碰的方式保留着当粗定位和兜底,目测的方式整个退休。

摄像头这头也有来历可讲。2003 年 USB-IF 发布 USB Video Class 1.0 规范,2004 年 Windows XP SP2 内置 UVC 驱动,摄像头从"随盘附一张驱动光盘"变成"插上就用"——“免驱摄像头"这四个字是标准化的红利,我们直接吃现成的:UVC 是类协议不是厂商协议,设备框架里认类码不认品牌,换摄像头不用改一行驱动。还有一笔旧账正好在这篇清掉:USB 协议详解讲四种传输类型时说过"等时传输我们用不上,不展开”——现在用得上了,视频流是全系统第一个也是唯一一个等时传输用户。

一、这台机床要视觉干什么

需求先收窄,不收窄的视觉项目没有不变胖的。三件活:

  • 找孔中心:穿丝孔多为 φ3~φ10 的预钻孔,装夹后毛坯位置散差毫米级,程序零点要对到孔的实测中心
  • 找边:以外圆或侧边做基准的工件,找正基准面
  • 断丝续切重定位断电断丝篇的快照恢复的是命令侧账,重新穿丝后拍一次基准,机床坐标的信任就接回来了

明确不干的三件:不做测量机(计量是三坐标的家底,摄像头视场里的畸变够喝一壶)、不做外观检测(那是另一个行业)、不上深度学习(定位基准是几何问题,圆和边用小学几何加最小二乘,伺服控制律挑简单律好调的道理在这照用——算法越简单,车间越修得动)。这不是缩水的妥协:需求收窄是选型的第一杠杆,后文每一节的账都从这三件活反推。

二、UVC:等时传输终于有了用户

UVC 是类协议:接口类码 0x0E,底下分视频控制(VC)和视频流(VS)两组接口。枚举时按 USB 协议篇的类驱动路数走——读 CS 描述符拿格式表,跟摄像头谈好"哪一种帧格式、哪一档分辨率帧率",然后 SET_INTERFACE 切进那个备用配置,流就开始从等时端点里出来了。

等时传输是这笔买卖的核心,账摆开算:

等时传输 我们前面的用法(控制/批量/中断)
带宽 上帧列表前先预留,保证到货 批量见缝插针,剩余带宽全给它
重传 没有——丢了就丢了 批量有三次重试
适合 音视频流:晚到的帧不如丢 键鼠、U 盘:一个字节都不能错

视频流丢一帧无所谓——下一帧 133ms 后就来,找正算法对着哪一帧都能算。晚到的帧才是毒药(算法用了旧帧,补偿就是错账),所以"不重传"对视频恰好是正确语义,不是缺陷。规范给周期传输(等时+中断)的预算上限是总线时间的九成,控制传输保底一成——这个九成就是下一节排座位时的天花板。

选档是第一笔实际的账。同一颗传感器,UVC 一般同时报未压缩 YUY2 和压缩 MJPEG 两种格式。按 1280×960 算:YUY2 每像素两字节,7.5fps 就是 1280×960×2×7.5 ≈ 18.4MB/s ≈ 147Mbps——高速总线理论 480Mbps,占去三成还多,链路上再挂个别的设备就紧张了;MJPEG 同档位实测 1.5~3MB/s,一个零头都不到。定位任务帧率要求极低(找正不是监控,每秒五帧人眼都觉得流畅),分辨率优先、帧率让路、格式选 MJPEG:1280×960 MJPEG @ 7.5fps,这是这台机床的定盘星。

三、EHCI 周期帧列表:给视频排座位

EHCI 篇讲队列时主战场在异步链表(控制+批量,QH 串 qTD);等时传输住在另一半——周期帧列表。两种传输在 EHCI 里是物理分区:异步链表按需轮询,周期帧列表按 1ms 一帧的节拍履约式调度,规范上限就是上一节那九成。

高速模式下 1ms 帧切成 8 个 125µs 微帧,等时的调度单位是微帧。描述符换了新角色:iTD(isochronous Transfer Descriptor),一个 iTD 一口气管 8 个微帧——每微帧一笔事务,页指针指着 DMA 缓冲,事务长度写在对应的微帧槽里。跟 qTD 比,iTD 没有"完成状态、重试计数"这类字段,等时没有重试自然没有重试计数的账。驱动侧的活:

  1. 按协商的帧大小算每微帧字节数(一帧图像拆 8 份不够就跨多个 iTD),对照微帧 3072 字节的硬上限
  2. iTD 链进周期帧列表对应帧号的槽位,先把座位占了再使能——周期调度的带宽许可在挂链时结算,超九成直接拒绝,这是等时"保证到货"的物理来源
  3. 帧缓冲从 DMA 池分配,乒乓双缓冲:偶数帧写甲、奇数帧写乙,解码消费和采集生产永不踩脚——跟 SPSC 环是同一个纪律,只是容量缩到二

一个工程习惯顺带写死:摄像头插拔后座位要重排(设备可能换了档位),枚举顺序变化不影响找正结果——因为标定参数挂在"相机光轴"这个物理量上而不是 USB 总线拓扑上,重枚举只是重新谈一次格式。

四、MJPEG 的 ROI 解码:只解要用的那块

MJPEG 每帧就是一张完整的基线 JPEG(SOI 到 EOI),解码这个活在 Core1 跑(Core1 是服务层,图像处理是纯服务活,不碰实时侧)。零 Cargo 依赖的纪律在这兑现成一行决定:自己写一个基线 JPEG 子集解码器——只支持 UVC 摄像头实际输出的那一档(基线、4:2:0 或 4:2:2、8 位),渐进式 JPEG、算术编码这些分支一概不认,认出来就报错换档。解码器写小写死,比引入一个全功能解码库再担心它的许可证干净。

定位任务给解码器省了大半的活:ROI(感兴趣区域)解码。JPEG 的最小单位是 8×8 的 MCU 块,图像被划成 MCU 网格,霍夫曼比特流是变长码、没法随机跳转——所以解析必须从头顺序走,但走到 ROI 网格之外的 MCU,只需数着比特流把这块的码字消费掉,反量化、IDCT、上采样、颜色转换全免。找孔时 ROI 是孔周围那一圈,占整幅图的几十分之一,省下的正是大头。解码输出的灰度图喂给下一节的算法,Y 分量就是灰度——U/V 两路在 ROI 外连消费都省了。

内存的账顺手记:乒乓缓冲每路按最大帧 200KB 预算,解码后 ROI 灰度图几十 KB,全部在 Core1 的堆里出——ISR 禁分配的老纪律,图像这条线从头到尾没进过实时侧。

五、两笔标定账

标定是这门手艺里"老师傅的经验"变现的地方,账要一笔一笔算清楚。

第一笔:像素当量(µm/px)。光学放大后一个像素对应多少微米,决定视觉的物理分辨率。拿刻度尺或量块放在焦面上拍一张,已知长度 L(µm) 占了 N 个像素,µm/px = L/N。按视场 20mm、1280px 算,像素当量约 16µm/px;下一节的圆拟合能稳到 0.1 像素的亚像素精度,视觉定位的物理分辨率就是 1.6µm 一档——比碰的方式的几微米还细,够用且有余。这笔账装机标一次,镜头不动就不用重标。

第二笔:图像坐标到机床坐标。这里是整个设计的关键一步:相机装在上线架上,跟丝一起随 X/Y 动。这个安装决策把标定问题从"每拍一张都要解外参"降到"标一次长期有效"——因为要的只是相机光轴相对丝的偏移量:丝在机床坐标 (x₀, y₀) 时,图像中心对准的机床坐标是 (x₀+Δx, y₀+Δy),Δ 就是待标定的偏移。标法用回老家伙:让丝去碰一个标准孔的四壁取中心(碰的方式给视觉当了一次基准),然后移轴让这个孔进视场拍一张,圆心在图像里的位置结合像素当量,Δ 就解出来了。相机装得歪一点无妨——用机床走一段已知直线、看它在图像里的轨迹角度,旋转角一次标出,此后图像系的所有向量先转这一角再换算。

计算精度的纪律沿用模拟篇划定的边界:标定计算在 Core1 用 f32,算完的像素当量、偏移量、旋转角取整落进参数区(沿用参数篇的双副本世代提交),实时侧和运动层拿到的永远是整数 µm——浮点活在服务层算完就死,不越境。

1
2
3
4
5
6
/// 视觉标定参数(参数区,装机标定一次)
pub struct VisionGeom {
    pub um_per_px_q8: u32,      // 像素当量 ×256(16.4µm/px → 4198)
    pub axis_off_um: [i64; 2],  // 相机光轴相对丝的偏移(µm)
    pub rot_milli_deg: i32,     // 图像系相对机床系旋转角(毫度)
}

六、找孔找边的算法:小学几何加最小二乘

算法刻意朴素,跟伺服控制律挑简单律是同一个哲学。

找孔中心。穿丝孔在环形漫射 LED 照明下是个边界清晰的圆斑:ROI 灰度图先做单阈值分割(阈值不自动估计——车间光照是可控量,环形光电流固定、光圈手动环锁定,出厂标一个阈值写参数区,比每帧 Otsu 稳当);轮廓点取阈值穿越位置,每行左右两个穿越点直接进账;对全部边界点做最小二乘圆拟合,解一个三参数(cx、cy、r)线性方程组。拟合本身就是亚像素的——几十上百个边界点的统计平均把 ±0.5px 的量化噪声压到 0.1px 一档,这就是第五节 1.6µm 分辨率的来历。拟合完顺手验残差:最大残差超过阈值报警"这不是圆"(孔打歪了、有毛刺、或者拍到的是别的东西),算不出可信的圆就不给补偿,宁可退回碰的方式。

找边。沿垂直于边的方向取若干扫描线,每条线上找灰度梯度极值点,极值左右三个梯度值抛物线插值出亚像素位置,全部扫描线的极值点做最小二乘直线拟合,边就定了。工件基准边在图像里通常不是水平竖直的(装夹歪了几毫弧度),直线拟合把这歪角一起解出,报给操作工"装夹歪了 0.3°,要不要垫一下"——这类顺手的信息是数字化找正比老师傅目测多送的货。

光照。金属反光是这类应用的第一杀手,直射光在工件表面晃出一块镜面高光,阈值分割当场失效。环形 LED 加漫射罩从四周均匀打光,高光被摊薄;穿丝孔的孔壁是圆柱面漫反射,跟平面底色对比稳定。灯具电流进参数区,跟规准一个待遇——光路是测量链的一环,它漂了标定就白标

七、补偿落点:修零点,不动几何

算出来的偏差最后怎么进系统,这一步定错全盘皆输。口径一条:补偿修的是工件坐标系零点,插补几何一个字不动。段表在解析篇就是绝对 µm 的契约,找正的结果表现为工件零点偏移量(G54 一族的思路),插补器从头到尾不知道视觉存在——两级解耦,视觉错了撞不坏几何,几何改了不用重标视觉。

流程按法定工序走,界面对齐七屏体系:运行屏的"找正"工序位,操作工装夹后按键触发拍照(或按权限矩阵留出操作工级专用键),屏幕上叠显示十字丝与拟合圆,偏差值和"写入零点"按钮一起出——写入要人确认,跟对高度是法定工序同一个道理:自动算,人工认账。断丝续切的重定位走同一界面,快照恢复后加一步"拍基准孔",坐标信任闭环。

整条图像算法链(解码后灰度→中心/边→偏差)是纯函数,第七个开发机可测模块:golden 测试拿标准图集(理想圆、带毛刺圆、歪斜边、光照渐变)对拍中心坐标,精度断言卡在 0.1px;解码器的 golden 是"编码器输出→解码→逐字节比对"。至此这套系统的开发机可测模块排到七——解析器插补器锥度展开器直纹面生成器输入法引擎MES 协议栈、视觉定位——机床业务的核心逻辑全部不依赖真机就能回归。

总结

实战清单:需求收窄到三件活(找孔、找边、断丝重定位,不做测量不做外观不上深度学习);UVC 类驱动认类码 0x0E、MJPEG 1280×960@7.5fps(YUY2 同档 147Mbps 占总线三成,MJPEG 一个零头,分辨率优先帧率让路);iTD 挂周期帧列表占微帧座位(带宽九成上限在挂链时结算、乒乓双缓冲 DMA 池);基线 JPEG 子集解码器自写(只认 UVC 实际输出那一档,ROI 外 MCU 只消费码字不解块,Core1 堆全包);两笔标定账(像素当量 16µm/px 装机一次;相机随动安装把外参问题降为光轴-丝偏移,碰标准孔定基准,f32 算完取整落参数区);算法=阈值分割+最小二乘圆/直线拟合(亚像素 0.1px=1.6µm,残差超限拒出补偿,光照电流进参数区);补偿修工件零点不动段表(人工确认写入,断丝续切同界面闭环);视觉算法链=第七个开发机可测模块。跑通标志:摄像头枚举报 UVC 类码、SET_INTERFACE 后示波器量等时端点按微帧出包;拔插摄像头重枚举后找正结果不变;标准圆图 100 次重复拟合中心散差 <0.05px;实机碰孔四点取中与视觉找中心偏差 <5µm 且方向一致;故意换成带毛刺的孔,系统报"不是圆"拒写零点;MES 的 Samples 数据里能查到每次找正的偏差记录。

应用篇到此收官。下下一篇开始进硬件篇——下位机三块模块的电路与嵌入式实现,软件立的所有合同(七项寄存器表规准四字段RS-485 寄存器模型)该由硬件来履约了。中间隔着最后一篇软硬之间的桥:机床本体构成——工作液、走丝系统、导轮布置,系列里挂着号的知识黑洞。

参考链接