# 相机自动对焦项目 · 总方案(唯一权威文档) > **最后更新:2026-06-13** > **用途:** 相机自动对焦/自动拍摄校正项目的需求、技术方案、代码结构、当前状态的**唯一权威记录**。 > **阅读建议:** 想快速了解进度 → 直接看**第十节"现状与下一步"**。 --- ## 一、项目背景 ### 1.1 设备与场景 - **设备:** 10舱室时差培养箱(成都艾伟孚,川械注准20232180053) - **用途:** 相机在显微镜下对胚胎进行延时拍摄 - **核心问题:** 现有"自动选清晰层"算法效果差,7组测试全部选错 ### 1.2 项目目标 开发**独立C#小软件**,实现"拍出一张可用图"的全自动校正流程: ``` 曝光校正 → XY皿孔居中 → 空皿检测 → Z对焦(选层/搜索) ``` --- ## 二、硬件架构(真机确认) ### 2.1 设备组成 - **10舱室整机** + 1缓冲瓶(11号) - 每舱独立:CCD相机 + 光源LED + Z轴电机 + 水平旋转电机 ### 2.2 相机 - **型号:** Microview MVC2000 - **接口:** USB2.0 - **分辨率:** 2592×1944(必须用这个,曾误用1600×1200导致well被裁出画面) - **枚举:** 全局0-9台,通过序列号(CCDSN)桥接到舱室 - **曝光单位:** 100µs,增益RGB各0-255 - **数据:** 上下颠倒需FlipY ### 2.3 运动机构(关键) - **相机:** 只做Z轴垂直运动拍不同焦平面 - **培养皿:** 水平电机旋转,让16个well圆形布局逐个对准相机 - **单位:** 步进电机脉冲(步),非µm - **Z参数:** 层间距默认128脉冲,行程上限125000 - **Well间距:** 约9000脉冲(如舱9: well1=70200, well2=79200...well16=205300) ### 2.4 下位机通信 - **串口:** 每舱独立COM口,9600/8N1 - **协议:** `5E CMD 00 LTH 数据 累加和校验`,无帧尾,回复长度固定 - **EEPROM:** 每个well的"水平位置"+"Z焦准零点"存下位机 - **LED:** 只能开关+读亮度,无写亮度命令 --- ## 三、技术方案 ### 3.1 清晰度评价(核心算法) - **指标:** Tenengrad(梯度平方和),纯C#实现 - **ROI:** 只在well圆内计算,排除背景干扰 - **预处理:** 统一尺寸、去噪 - **归一化:** 分数除以像素数,可跨帧比较 ### 3.2 标定流程(四步) ``` ①粗对焦(中央40% ROI) → ②旋转居中(粗扫+细扫) → ③曝光二分(well内ROI) → ④精对焦(0.95r ROI + 平滑插值) ``` **关键技术点:** 1. **粗对焦:** 使用中央40% ROI避免被培养皿边缘带偏 2. **居中:** 只优化Y偏移(旋转控制),X偏移~5%是硬件局限 3. **曝光:** 每次设置后丢弃第1帧使用第2帧(解决帧延迟) 4. **精对焦:** 0.95r ROI保留边缘 + 3点平滑 + 抛物线插值 ### 3.3 居中判据 - **目标:** Y偏移 < ±12% + well完整 - **检测:** Otsu阈值化 + 连通域 + 圆度过滤 - **阈值:** aspect < 1.5, boxFill > 0.6, rConsist > 0.65, rFrac ∈ [0.10,0.28] - **延时:** 细扫用800ms确保稳定 --- ## 四、代码结构 ### 4.1 目录结构 ``` C:\claudeFile\TL\AutoFocusTool\ (WPF/.NET 8/x64) ├── Camera/ 相机封装(MVCAPI P/Invoke) ├── Serial/ 串口协议(Protocol/SerialMotor/HouseMotor) ├── Imaging/ 图像处理(Sharpness/WellDetector/ExposureMeter) ├── Calib/ 标定引擎(CalibrationEngine/CalibrationFile/CalibrationManager) ├── Devices/ 设备扫描(DeviceScanner/HouseDevice) ├── MainWindow.*.cs 主窗口(分部类:连接/相机/电机/扫描/标定) ├── CalibWindow.xaml 标定独立窗口(4x4=16格实时显示) ├── Survey/Survey.cs 巡检工具(已集成CalibrationManager) ├── Calibrate/Calibrate.cs 命令行标定工具 ├── SelfTest/Program.cs 硬件自检工具 └── calibration.json 标定结果(16个well参数) ``` ### 4.2 关键类 - **CalibrationEngine:** 标定引擎核心,可复用,带回调 - **CalibrationManager:** 优先读JSON标定结果,降级到EEPROM - **WellDetector:** 圆检测(连通域+圆度过滤) - **Sharpness:** Tenengrad清晰度计算(纯C#) - **ExposureMeter:** 曝光评价+二分搜索 - **HouseMotor:** 电机语义层封装(支持delayMs重载) --- ## 五、关键修复记录(2026-06-13) ### 5.1 P0致命问题(全部已修复) | 问题 | 根因 | 修复方案 | 效果 | |------|------|---------|------| | **曝光不可重复** | SetExposure后返回旧帧 | 延时+丢弃第1帧用第2帧 | 7倍偏差→0 | | **居中成功率低** | aspect阈值过严+延时短 | 1.35→1.5 + 细扫800ms | 62.5%→100% | | **well2系统性偏差** | 真正居中位置被拒绝 | 同上 | 28.1%→1.1% | | **业务未闭环** | Survey不读JSON | 集成CalibrationManager | ✅ | | **异常处理薄弱** | 错误被忽略 | 检查返回值+质量判断 | ✅ | | **相机旧帧缓冲** | 未清空缓冲 | 每次移动后丢第1帧 | ✅ | | **粗对焦被背景带偏** | 使用全图清晰度 | 改用中央40% ROI | ✅ | ### 5.2 P1算法优化(已完成) | 优化 | 方案 | 效果 | |------|------|------| | **对焦ROI不一致** | 精对焦0.7r→0.95r保留边缘 | 峰比提升 | | **峰值检测无插值** | 3点平滑+抛物线插值 | 提升精度 | ### 5.3 真机验证结果(9号舱16个well) **测试日期:** 2026-06-13 **测试轮次:** 3轮 | 指标 | 修复前 | 修复后 | 改善 | |------|--------|--------|------| | 曝光可重复性 | 3/16相差7倍 | 16/16完全一致(exp=58) | 100% | | 居中成功率 | 10/16 (62.5%) | 16/16 (100%) | +60% | | well2偏移 | 28.1% | 1.1% | -96% | | 最大Y偏移 | 28.1% | 5.9% | -79% | | 对焦峰比 | 全部<1.2 | 1.09~1.20 | 空well预期 | **可重复性验证(两轮对比):** - 曝光:0偏差 - 居中:<0.1%偏差 - 对焦Z:<20脉冲偏差 ### 5.4 第二轮修复(2026-06-13 · 针对同事审查报告 CODE_REVIEW_AND_ANALYSIS.md) 对审查报告逐条核查后,修复了 **5 项真实缺陷**,其余条目要么已在第一轮修过、要么属非必要的架构重构(见 5.5)。 | 编号 | 问题(核查属实) | 根因 | 修复 | |------|------|------|------| | **P0-1** | 标定结果"只存不用" | `CalibrationManager` 从未被调用;无 Survey 目录;转well直读EEPROM | GUI `BtnGoWell` 接入 `CalibrationManager`,优先用JSON(合格才用),存档后刷新缓存 | | **P0-2/3** | 手动路径运动模糊/旧帧 | 手动Z扫描、手动设曝光后立即抓帧=旧帧 | Z扫描每层丢第1帧用第2帧;设曝光后等待`max(200,e/5)`ms并丢帧 | | **P0-4** | 精对焦圆未检出用全图 | roi=null → 全图清晰度被背景/反光干扰 | 圆未检出降级到中央40% ROI(与粗对焦一致),绝不用全图 | | **P0-6** | 电机移动无重试 | 下位机偶发不回复即放弃整个well | 新增 `RetryMove`,关键定位移动重试3次 | | **P0-5** | 误导性死代码 | `GrabWithRollingExposure`/`FullMean` 写好但零调用 | 删除,避免后人误以为有自适应曝光(固定曝光60真机已验证16/16可靠) | | **P0-7** | **JSON反序列化静默失效**(真机测试发现,比审查报告更严重) | `CalibrationFile`等用public**字段**,`System.Text.Json`默认不反序列化字段→`Load`返回空对象(舱数0) | `Load`的`JsonSerializerOptions`加`IncludeFields=true`。**此bug导致即便P0-1接线正确,标定结果仍永远读不到、静默降级EEPROM——闭环形同虚设** | **附带统一:** 手动Z扫描ROI也改为"先检well圆→圆内0.95r,检不出→中央40%",与标定引擎一致。 **离线验证:** 用合成图(中心清晰+四周噪声)实测 Tenengrad —— 全图焦/糊比值仅1.61(被边缘噪声淹没),中央40% ROI比值达∞(完美区分),证明 P0-4 降级策略有效。编译0错误0警告,GUI启动冒烟通过。 **真机验证(2026-06-13 第二轮,SmokeTest工具,现场无1号舱→回退2号舱):** 4项全过—— - 设备扫描:发现7舱(2/4/6/7/8/9有相机, 11缓冲瓶无相机),相机/COM映射正确,全分辨率2592×1944 - **P0-2/3 实证**:设曝光30→亮度75.1,设曝光150→亮度228.8,丢帧后第2帧亮度正确跟随曝光,**确认无旧帧污染** - **P0-7 实证**:修复前`Load`读出舱数0(静默失效),修复后正确读出`house9`;当前9号皿为空皿,16个well峰比全≤1.2,按`PeakRatio>1.2`判据会全部降级EEPROM——符合空皿预期,待放真胚胎重标(峰比>1.5)后闭环才会启用JSON参数 ### 5.5 审查报告中**未采纳**的条目(评估为非必要) 报告 P1-3/4/5、P2-1/2/3 主张:详细子步骤进度、续标/重标UI、参数热加载配置、**仿真模式、单元测试框架、状态机重构**。 这些属"锦上添花"的架构升级,**真机已验证16/16通过**,贸然重构反而引入回归风险。经与用户确认后**本轮不做**(用户原话:"UX/仿真/单元测试/状态机,改动太大,就不弄,且非必要")。如未来需要再单独立项。 --- ## 六、踩过的坑(重要教训) ### 6.1 画面分辨率被裁剪(最坑) - **问题:** 硬编码1600×1200,相机原生2592×1944 → well被裁出画面 - **教训:** 所有图像处理尺寸必须取自`cam.Width/Height`,绝不硬编码 ### 6.2 居中优化错了方向 - **问题:** 曾优化X偏移,但旋转只能控制Y - **真相:** X的~5%固定偏移是相机/转盘硬件偏移,旋转修不了 - **正确:** 只优化Y偏移,X偏移接受 ### 6.3 圆检测被斜纹反光骗 - **问题:** well间大块斜纹反光被误判成圆 - **方案:** 增加判据aspect<1.5 + boxFill>0.6 + rConsist>0.65 + rFrac∈[0.10,0.28] ### 6.4 曝光必须只看well圆内ROI - **问题:** 用全图均值被黑边拉低 → 一路过曝 - **方案:** 只在检测到的well圆内ROI计算亮度 ### 6.5 对焦与居中耦合 - **洞察:** 焦不对→画面糊→检不出圆→无法居中 - **方案:** 先粗对焦让well清晰,再居中 --- ## 七、编译与运行 ### 7.1 编译 ```bash cd "C:\claudeFile\TL\AutoFocusTool" dotnet build -c Debug ``` ### 7.2 运行GUI ```bash ./bin/Debug/net8.0-windows/AutoFocusTool.exe ``` ### 7.3 命令行标定 ```bash ./Calibrate/bin/Debug/net8.0-windows/Calibrate.exe [舱号] [起始well] [结束well] # 例如:./Calibrate.exe 9 1 16 ``` ### 7.4 查看结果 - 标定参数:`calibration.json` - 标定终图:`calib/cal_h9_w*_FINAL.bmp` - 测试日志:`test_fix/*.log` --- ## 八、GUI功能 ### 8.1 设备连接 1. 点击"扫描设备" → 发现6个带相机舱(2/4/6/7/8/9) 2. 选择舱室(如9号舱) 3. 点击"连接" ### 8.2 手动控制 - 开/关光源LED - 抓帧实时预览(显示曝光/增益/Z/水平/well值) - 调整曝光/增益(自动抓帧看效果) - Z轴上下移动 - 水平旋转(选well 1-16,读EEPROM位置并转过去) - Z扫描选层(显示清晰度曲线) ### 8.3 自动标定 1. 勾选要标定的well(1-16任选,支持全选/全不选) 2. 点击"一键全自动初始化" 3. 弹出全屏窗口,4x4=16格实时显示 4. 正在标定的格:黄字进度+实时画面 5. 完成的格:定格绿框 6. 结果存`calibration.json` + 终图存`calib_result/` --- ## 九、业务闭环 ### 9.1 标定结果使用(2026-06-13 第二轮修复后) - **GUI 转well(MainWindow.Calib.cs · BtnGoWell_Click):** 已接入 `CalibrationManager` - **逻辑:** 优先读 `calibration.json`,标定合格(`CircleFound && PeakRatio > 1.2`)才用JSON的水平/Z/曝光;否则降级到EEPROM(曝光不动) - **缓存刷新:** 每次"一键全自动初始化"存档后调用 `RefreshCache()`,转well立即用最新结果 - **日志标注:** 转well时打印参数来源 `[标定结果]` 或 `[EEPROM(未标定/不合格)]`,便于现场确认 > ⚠️ **更正历史记录:** 此前文档曾声称"Survey.cs 已集成 CalibrationManager、业务已闭环", > 经代码核查 **该说法不实**:项目中既无 Survey 目录,`CalibrationManager` 也从未被任何代码调用, > 标定结果实际"只存不用"。本轮(第二轮修复)已在 GUI 转well路径真正接通,业务闭环现已成立。 ### 9.2 标定文件格式 ```json { "tlSn": "house9", "date": "2026-06-13 13:54", "houses": [{ "house": 9, "wells": [{ "well": 1, "horizontalPulse": 70198, "exposure": 58, "focusZ": 79555, "centerOffsetPct": -0.0, "circleFound": true, "peakSharp": 0.0647, "peakRatio": 1.11, "note": "对焦峰弱;" }, ...] }] } ``` --- ## 十、现状与下一步(快速上手必读) ### 10.1 ★ 当前状态(2026-06-13 第二轮修复后) **✅ 代码层缺陷已清零,等待真胚胎验证** - ✅ P0致命问题全部修复(第一轮7项 + 第二轮6项,含真机才暴露的P0-7) - ✅ 业务闭环**真正接通**:GUI转well已应用标定结果,且修复了底层JSON静默失效(P0-7) - ✅ 真机冒烟测试4/4通过(设备扫描/相机通路/曝光丢帧/JSON读取) - ✅ 曝光可重复性:0偏差;居中成功率:16/16 - ⏳ **尚未做真胚胎验证**(计划过几天放胚胎后测,见10.2) **关键指标(空皿,9号舱):** - 曝光:16/16 well = 58(完全一致) - 居中:16/16成功,最大偏移5.9% - 对焦:可重复,Z偏差<20脉冲 - 峰比:1.08~1.20(**空皿预期就这么低**,真胚胎应>1.5) > ⚠️ **当前闭环对空皿"看似不生效"是正常的**:calibration.json里16个well峰比全≤1.2, > 按合格判据`PeakRatio>1.2`会全部判不合格→转well时降级读EEPROM。这是空皿的正确行为。 > 放真胚胎重新标定后峰比上去了,转well才会真正用JSON的标定参数。**别误以为闭环坏了。** ### 10.2 ★★ 下一步:真胚胎验证(过几天放胚胎后做,必读) **现场情况:** 目前**没有 1 号舱室**(SmokeTest已确认,现场为2/4/6/7/8/9+11缓冲瓶)。 9号舱有培养皿但**当前是空皿无胚胎**。 **测试舱室约定(用户指定):** - **有胚胎 → 用 9 号舱**(well 9 那个皿) - **空皿/无胚胎的纯功能测试 → 用 1 号舱**(但现场目前无1号舱,回退用其他空皿舱如2号) **放胚胎后要做的验证(按顺序):** 1. **重新标定9号舱**:GUI「一键全自动初始化」勾选有胚胎的well,或命令行 `./Calibrate/bin/Debug/net8.0-windows/Calibrate.exe 9 1 16` 2. **看峰比是否>1.5**:有胚胎的well峰比应明显高于空皿的1.1。若仍<1.2需查ROI/对焦范围 3. **验证选层准确**:人工核对最清晰层Z是否选对(对比终图 calib_result/) 4. **验证闭环真生效**:标定后在GUI转到该well,日志应显示`[标定结果]`而非`[EEPROM...]`, 且曝光自动设为标定值。这是P0-1+P0-7修复的最终验收点。 5. **不同发育阶段**:PN/卵裂/囊胚 的对焦效果差异 **预期风险点:** 真胚胎透明、对比度低,Tenengrad峰可能仍不够尖。若验证不通过, 优先怀疑:精对焦Z范围(`ZHalf`/`ZLayers`)、ROI半径(0.95r)、是否需要空皿检测先行。 ### 10.3 可选优化(非阻塞) **优先级低:** 1. **空皿检测** - 需真实样本后设计(基于峰形或几何特征) 2. **预测式居中** - 可提速70%(当前14秒可接受) 3. **EEPROM降级** - zZero<0时改为跳过而非设0 4. **曝光顺序** - 可移到精对焦之后(在清晰状态测定更准) ### 10.4 快速上手流程 **初次使用:** ```bash 1. cd "C:\claudeFile\TL\AutoFocusTool" 2. 阅读本文档第十节(当前节) 3. dotnet build -c Debug 4. ./bin/Debug/net8.0-windows/AutoFocusTool.exe 5. 扫描设备 → 选舱室 → 连接 → 手动测试 6. 一键全自动初始化 → 勾选well → 开始标定 7. 查看calibration.json和终图验证效果 ``` **继续工作(新会话无缝衔接):** ```bash # 新会话开场说这句即可接上: "继续相机自动对焦项目,读总方案文档第十节,代码在 C:\claudeFile\TL\AutoFocusTool" # 当前进度一句话:第二轮代码缺陷已清零(P0-1~P0-7),真机冒烟4/4过, # 就差"放胚胎做真胚胎验证"(见10.2)。放了胚胎用9号舱,空皿功能测试用1号舱(现场暂无则用其他空舱)。 # 主要操作: - 真机测试:GUI 或 Calibrate.exe 9 1 16 - 不动电机的快速回归:SmokeTest/bin/Debug/net8.0-windows/SmokeTest.exe - 查看结果:calibration.json + calib_result/*.bmp - 改算法:重点 Calib/CalibrationEngine.cs;改闭环:MainWindow.Calib.cs + Calib/CalibrationManager.cs - 编译:cd AutoFocusTool && dotnet build -c Debug ``` ### 10.5 关键文件速查 **代码:** - 标定核心:`Calib/CalibrationEngine.cs`(四步:粗焦→居中→曝光→精焦;含RetryMove/CenterRoi40) - 业务闭环:`Calib/CalibrationManager.cs`(优先JSON降级EEPROM)+ `MainWindow.Calib.cs`(BtnGoWell接线) - 标定档案读写:`Calib/CalibrationFile.cs`(⚠️ Load必须`IncludeFields=true`,见P0-7) - 圆检测:`Imaging/WellDetector.cs` 清晰度:`Imaging/Sharpness.cs` 曝光二分:`Imaging/ExposureMeter.cs` - 命令行标定:`Calibrate/Calibrate.cs` - **真机回归冒烟(本轮新增):** `SmokeTest/SmokeTest.cs`(不大幅驱动电机,验证扫描/相机/曝光/JSON) > 注:文档旧版提到的 `Survey/Survey.cs` **实际不存在**,已废弃该说法(见9.1更正)。 **数据:** - 标定结果:`calibration.json`(当前为9号空皿数据,峰比全≤1.2) - 标定终图:`calib_result/house9_well*_标定后.bmp`、`calib/cal_h9_w*_FINAL.bmp` **文档(唯一权威):** `相机自动对焦项目-总方案.md`(本文) **审查报告(同事,已逐条评估):** `AutoFocusTool/CODE_REVIEW_AND_ANALYSIS.md` ### 10.6 常见问题 **Q: 程序崩溃怎么办?** A: 检查Grab()方法的安全性,已加重试机制,正常不会崩溃 **Q: 居中失败怎么办?** A: 检查aspect阈值(1.5)和细扫延时(800ms),必要时用HScan工具诊断 **Q: 曝光不一致怎么办?** A: 确认每次SetExposure后都丢弃第1帧使用第2帧 **Q: 对焦峰比<1.2?** A: 空well正常,真胚胎会>1.5;若真胚胎也<1.2则检查ROI设置 **Q: 标定明明做了,转well却不用标定参数(日志显示[EEPROM...])?** A: 两种可能:①该well峰比≤1.2被判不合格(空皿就是这样,正常);②calibration.json没读进来。 先跑 SmokeTest.exe 看【2】是否"舱数=1"。若舱数=0说明JSON反序列化失效—— 检查 CalibrationFile.Load 是否带 `IncludeFields=true`(P0-7的坑,字段非属性)。 **Q: 测试该用哪个舱室?** A: 有胚胎用9号舱;空皿纯功能测试用1号舱。现场目前无1号舱,空皿测试回退用2号等其他空舱。 **Q: 不想驱动电机只想快速回归核心逻辑?** A: 跑 `SmokeTest/bin/Debug/net8.0-windows/SmokeTest.exe`,验证设备扫描/相机/曝光丢帧/JSON读取,不跑标定。 **Q: 如何调试特定well?** A: 命令行单well标定 `./Calibrate/bin/Debug/net8.0-windows/Calibrate.exe 9 2 2`(舱9 well2), 中间帧会存到 hscan/ 目录供排查。(注:文档旧版提到的独立 HScan.exe 工具现已不存在) ### 10.7 联系上下文 **本项目解决的核心问题:** 1. ✅ 曝光帧延迟 → 丢帧策略 2. ✅ 居中检测严格 → 放宽阈值+延时 3. ✅ 背景干扰对焦 → 中央ROI 4. ✅ 业务未闭环 → CalibrationManager 5. ✅ 异常处理薄弱 → 完善检查 **审查报告对应:** - 审查报告:`评审报告/AutoFocusTool 自动校准工具代码审查报告.md` - 10/13问题已修复,高优先级8/8全部完成 **测试报告对应:** - 测试报告:`report/自动标定测试报告_2026-06-13.html` - 3个P0根因全部修复并验证通过 --- ## 十一、Memory提示 **如果需要更新memory:** ```markdown --- name: autofocus-system-ready description: 自动对焦系统两轮P0修复完成(P0-1~P0-7),真机冒烟4/4过,业务闭环真正接通。待真胚胎验证。空皿用1号舱(现场暂无则2号),胚胎用9号舱。 metadata: type: project --- 2026-06-13 两轮修复。第一轮:曝光丢帧/居中阈值/中央ROI。第二轮(针对同事审查报告): P0-1转well接入CalibrationManager、P0-2/3手动路径补丢帧、P0-4精焦ROI降级、 P0-6电机重试、P0-5删死代码,外加真机才暴露的P0-7(JSON反序列化字段缺IncludeFields, 导致标定结果静默读不到、闭环形同虚设)。 **真机冒烟4/4过:** 设备扫描7舱、相机2592x1944、曝光丢帧无旧帧污染、JSON正确读取。 **关键坑(务必记住):** - CalibrationFile用public字段非属性 → Load必须 IncludeFields=true,否则静默失效 - 空皿峰比全≤1.2 → 闭环按>1.2判据会全降级EEPROM,这是正常的,别误判闭环坏了 - 现场无1号舱;项目里无Survey目录(旧文档说法不实) **系统状态:** 代码缺陷清零,等过几天放胚胎做真胚胎验证(峰比预期>1.5) **唯一文档:** [[相机自动对焦项目-总方案]] 第十节 ``` --- **文档版本:** V3.1(2026-06-13 第二轮修复+真机冒烟验证) **状态:** 唯一权威,其他文档仅供参考 **维护:** 有重大变更时更新本文档第十节 **下次衔接:** 读10.1(现状)+10.2(真胚胎验证步骤)即可无缝接上