ZyClear 智澈
跨品牌工业相机客户端。厂商差异收进实现层(占 12.2% 代码),其余 87.8% 对品牌完全无感。
面向工业视觉产线,覆盖设备发现、接入、调参、配置持久化、实时成像、多机切换的完整闭环。
工业现场的多品牌 SDK 接口异构。传统写法每接一个品牌都要改遍上层——海康的判断写进参数面板、虚拟相机的判断写进图像显示,加第三家时满仓库改。这个项目用架构手段把厂商差异关进实现层:厂商相关代码只占 12.2%,其余 87.8% 对品牌完全无感。
| 规模 | 数字 |
|---|---|
| 核心 | 9,551 行 / 83 个源文件 / 30 个编译单元 |
| 全仓(含插件平台与 AI 助手层) | 11,289 行 / 94 个文件 / 49 个编译单元 |
| 单元测试 | 8 个测试目标,114 个用例 |
| 厂商相关代码占比 | 12.2%(按实现层目录行数反推) |
六层单向依赖
资源层 AppStyle(调色板 + QSS)│ Icon │ Resource(参数 Schema)
▲ 以 :/ 前缀编译进可执行文件,交互层只按名字引用
交互层 ControlWidget │ ParamWidget │ ViewWidget
└─ 模型层 CameraParamModel / CameraParamItem / Delegate
│ 仅凭序列号寻址,不出现任何厂商类型
门面层 CameraContext
多机映射 · 生命周期托管 · 当前会话状态
│
契约层 CameraInterface(17 个纯虚方法)
ZCCameraParam 统一参数模型 │ CameraImageQueue 缓冲队列 │ CameraError 统一错误码
▲ 实现契约(依赖倒置)
实现层 HikCamera(海康 MVS SDK)│ VirtualCamera(仿真)
CameraFactory:按厂商名登记「创建器 + 枚举器」
依赖只允许自上而下。唯一的反向关系是实现层对契约层的实现(依赖倒置),所以契约层不需要认识任何一个具体相机。
三条能当场验证的约束
架构文档里写的规则,如果没法验证,三个月后一定会烂掉。所以这三条都写成了可以立刻跑的检查:
# 1. 门面与契约层不得依赖任何具体适配器
grep -rn "HikCamera.h\|VirtualCamera.h" src/CameraInterface/ # 期望无输出
# 2. 面板之间不得互相持有引用(联动一律走事件总线与门面)
grep -rn "ControlWidget\*" src/ParamWidget/ src/ViewWidget/ # 期望无输出
# 3. 交互层不得出现厂商名判断
grep -rniE "hikrobot|\"Hik\"|MV_CC_" src/ControlWidget/ src/ParamWidget/ src/ViewWidget/ # 期望无输出
全仓厂商条件编译只有 2 处,且都落在工厂的注册与枚举。
参数:从代码资产变成配置资产
参数定义建模成「6 种类型 × 5 个业务分组 × 37 项 Schema」,解析与渲染彻底隔离,视图运行时动态装配。新增型号不编译、不发版,改 JSON 即可。
权限是这里最值得说的部分。设备的 GenICam 访问模式(RO / RW / WO / NI / NA)在适配器内被翻译成「有效 / 可读 / 可写」三态,逐项由设备实时上报、自动判定:
- 只读项在模型层就被拦下——视图连编辑控件都不会创建
- 不支持的项静默剔除
- 参数界面里没有任何硬编码的权限判断
图像链路
采集回调、有界缓冲队列、独立采集线程、界面渲染四段异步隔离,任何一段变慢都不阻塞其余部分。
SDK 回调线程 缓冲队列 采集线程 UI 线程
MV_CC 回调
│ 像素格式归一化(Mono / Bayer / YUV / BGR → Mono8 / RGB8)
▼
HikConvert2Mat
│ Put()
├─ 有空闲缓冲 → 原地 copyTo 复用内存
└─ 无空闲且队列满 → 丢弃最旧帧,回收其缓冲
▼
工作队列 ──→ Take() ─→ cvMat2QImage(深拷贝)
├─ 信号投递 → 图形视图渲染
└─ Recycle() 归还缓冲 ──┐
空闲缓冲池 ◄────────────────────────────────────────┘
- 保新弃旧:预览关心最新帧,队列满时丢最旧而非最新
- 缓冲循环:归还在消费者完成深拷贝之后才发生,保证同一时刻一块缓冲只有一个所有者
- 稳态零分配:分辨率与格式不变时
copyTo原地写入,堆分配只发生在首帧 - 超时保护:
Take带超时,超时返回错误码,不会把空帧当有效图像
像素格式覆盖 36 种 GigE Vision 格式(Mono / Bayer / YUV / BGR 各族),借 SDK 硬件转换通道统一归约为灰度与彩色两种形态,上层链路对像素格式彻底解耦,且转换失败不产出半成品帧。
运行时装插件
面板契约与注册表分离,主程序启动时扫描目录、以 QLibrary 装载动态库挂载面板,新增面板不重编主程序。
插件接口只导出纯 C 符号(extern "C"),规避 C++ 类没有稳定二进制布局导致的 ABI 断裂。注册表按唯一 ID 去重,同名注册被拒。挂载后走 QDockWidget 停靠体系:任意边停靠、可浮动成窗、关闭后能从「视图」菜单恢复,工具栏按钮随面板实际停靠位置自动重排。
附独立的示例插件工程可跑通整条链路。
接 AI:把设备能力自动工具化
接入一个 C++ Agent 框架,把相机全生命周期能力注册成 10 个 LLM 可调用工具(枚举 / 连接 / 读写参数 / 拉流 / 画面量化分析),工具带用途描述与参数 Schema,自然语言即可驱动。
几个面向 LLM 可靠性的处理:
参数写入前用设备上报的 min/max 与枚举可选值校验,超范围拒绝而不是静默钳位。 钳位会让模型以为写进去的就是它要的值,后续推理全建立在错前提上。
写后回读确认,把「请求值 / 实际生效值」并列回传。
工具失败以「可重试」语义回灌给模型自纠,步数上限兜底防调用不收敛。
工具回调经阻塞队列切回主线程串行操作设备——相机层不是线程安全的,切回主线程天然避免并发冲突,不用给整个相机层加锁。
工具提供者以四方法接口抽象,通用 AI 层不含任何相机知识,领域绑定收口在唯一的宿主适配层。
工程化
界面即代码:全仓零二进制窗体,布局骨架、弹性策略、主题钩子全量代码显式声明,消除生成代码不可读、不可评审、不可合并三个问题。
多环境自适应:海康 SDK 缺失时适配器整体不参与编译,程序退化为纯虚拟相机形态仍可构建可运行——CI 就运行在这个降级配置下,照样跑通全部测试。
虚拟设备同构仿真:与真机共用同一套契约和同一个图像缓冲队列,开发、调试、演示、压测、持续集成全程无需真机。
依赖探测双路径:OpenCV 先走官方 Config 包,找不到再回退到手工指定头文件与库;Debug / Release 自动选对应库文件。