1
1个像素代表几微米 值越小,放大倍率越大即:imgResolution (μm/pixel) ↓ → 同物理尺寸对应更多像素 → 放大倍率 ↑ → 细节更精细。
2
Zoffset
元件级 zoffset → BaseZ
com_zoffset是每个元件所在 FOV 的真实 Z 高度,跟全局填的ZOffset做差:
差值(mm) ÷ 分辨率(mm/层) = 层偏移量然后用这个偏移修正算法BaseZ——从基准层往上或往下挪几个像素层,保证搜索起点对准实际位置。
就这么简单。
对,因为板子有翘曲,不同位置的元件实际 Z 高度不一样。
全局 ZOffset = 1.5 ← 用户填的,假设整板是平的 实际: 左边翘起来了 → 左边元件实际 Z = 1.8 → component_zoffset = 1.8 右边塌下去了 → 右边元件实际 Z = 1.2 → component_zoffset = 1.2如果全用全局 1.5 算 BaseZ,左边元件搜偏低了(找不着基准层),右边元件搜偏高了(也找不着)。
所以路径规划给每个元件算自己的 zoffset,一键配置时逐个换算成各自的 BaseZ,消除翘曲影响。
3
1. Shape(形状)
PCB 上一切可检测的图形元素,是系统的最小操作单元。
Shape = 图形(能画出来) + 检测数据(能算出来)- Board:基板,PCB 的物理边界
- PadCircle:检测窗,划定一个圆形/椭圆形检测区域
–
2. 坐标
同一位置有两套坐标表达:
| 坐标类型 | 含义 | 类比 |
|---|---|---|
| 物理坐标(StartX, EndX, mm) | PCB 上的真实位置 | 现实世界的位置 |
| 像素坐标(PixelStartX, PixelEndX, px) | 图像上的位置 | 照片上的位置 |
通过Matrix(变换矩阵)互相转换。
3. Status(状态)
每个 Shape 在检测过程中会经历一系列阶段,用位标志叠加表示:
待检测 → 扫描中 → 扫描完 → 重建中 → 重建完 → 检测中 → 结果(OK/NG)| 状态 | 含义 |
|---|---|
ReadyToExecute | 准备好了,等待执行 |
Scaning | 正在拍照扫描 |
Reconstructing | 正在 CT 算法重建 3D 图像 |
Detecting | 正在分析检测 |
ResultOK | 检测通过 ✅ |
ResultNG | 检测不通过 ❌ |
UI 根据 Status自动变色
4. CT(计算机断层扫描)
用 X 光从不同角度拍摄,重建出物体内部的 3D 图像。
三个关键参数:
| 参数 | 含义 | 单位 |
|---|---|---|
| imgResolution | 分辨率,每个像素代表多大 | mm/pixel |
| zoffset | Z 轴偏移,焦平面上下移动 | mm |
| 层数 | 重建多少层切片 | 层 |
imgResolution 越小 → 放大倍率越高 → 看得越精细 zoffset 正 → 焦平面上移;负 → 焦平面下移6. Drill(钻孔)
PCB 上需要检测的孔位,分两种:
| 类型 | 说明 |
|---|---|
| 通孔(Through Hole) | 从上面钻穿到下面 |
| 背钻(Backdrill) | 钻到某一层就停,不钻穿 |
背钻根据方向再分:上钻(从上面钻)、下钻(从下面钻)、上下都钻。
DrillStandard= 同一个钻孔规格{ 深度, 半径 },同类钻孔归为一组,共用检测算法。
概念关系总图
Board(基板,PCB 物理边界) │ ├─ Component(元件) │ └─ 一组相关的检测窗 │ ├─ PadCircle(检测窗) │ ├── 坐标(物理 ↔ 像素) │ ├── CT 参数(分辨率 + Z偏移) │ ├── Status(检测生命周期状态 → 驱动颜色) │ ├── AlgorithmViewShapes(子形状) │ └── 检测结果 │ ├─ DrillData(钻孔数据) │ └── 孔类型(通孔/背钻)+ 钻深 + 层厚 │ └─ Template(检测模板) └── 复用配置到同类 Shape一句话总结
Shape是基本单元,通过CT 重建(分辨率 + Z 偏移)获取内部图像,经历Status 生命周期(扫描→重建→检测→结果),UI 随状态)。
3
三个全局SemaphoreSlim,全部并发数 = 1:
_reconstructLock (1) → 重建:engine.setupResolver → 提交投影 → pollReconDone _extractGlobalLock (1) → 提取:CT 体数据从 C++ 引擎拉到内存 _detectGlobalLock (1) → 检测:ExecuteAlgoProcess 调 C++ 算法为什么都设成 1?
C++ 重建/提取/算法引擎底层不是线程安全的,多个 Job 同时调会崩。所以每个阶段同一时刻只允许一个 Job 在跑。
实际跑法:
扫图完了之后,10 个 Job 并行跑,但每到重建/提取/检测这仨环节,谁先抢到锁谁先进,剩下的await排队:
Job1 重建 ──┐ Job2 重建 ──┤ → _reconstructLock.WaitAsync() → 抢到的先跑,其他排队 Job3 重建 ──┘ ↓ Job1 提取 → Job2 提取 → Job3 提取 (_extractGlobalLock 排队) ↓ (保存体数据 和 检测 并行) Job1 检测 → Job2 检测 → Job3 检测 (_detectGlobalLock 排队)保存体数据不依赖 C++ 引擎,所以不和检测抢锁,直接并行跑。
跟同事一句话:C++ 引擎不线程安全,三个全局锁各管一个阶段,保证同一时刻只有一个 Job 在调引擎。