|
|
@@ -43,16 +43,18 @@ namespace AutoFocusTool.Calib
|
|
|
public int CenterScanExposure = 60;
|
|
|
|
|
|
// ── 行程限位(所有电机移动前钳到该区间)──
|
|
|
- /// <summary>水平电机行程下/上限脉冲(旧工程自检值 70000)。</summary>
|
|
|
- public int HMin = 0, HMax = 70000;
|
|
|
+ /// <summary>水平电机行程下/上限脉冲(实测16个well EEPROM位置达205800,留余量到220000)。</summary>
|
|
|
+ public int HMin = 0, HMax = 220000;
|
|
|
/// <summary>垂直电机行程下/上限脉冲(旧工程软上限 125000)。</summary>
|
|
|
public int ZMin = 0, ZMaxPulse = 125000;
|
|
|
|
|
|
- // ── 水平全行程粗扫定位 ──
|
|
|
- /// <summary>水平全行程粗扫起点/终点/步距。命中完整圆即停。</summary>
|
|
|
- public int HCoarseStart = 0;
|
|
|
- public int HCoarseEnd = 70000;
|
|
|
- public int HCoarseStep = 2000;
|
|
|
+ // ── 水平居中微调(以各well的EEPROM位置为中心做小范围Y居中)──
|
|
|
+ // 实测well间距~9000、EEPROM位置已准(移到位即|偏移|<5%),故半幅须远小于间距一半(4500),
|
|
|
+ // 只在小范围微调Y居中,绝不大范围扫描(否则会扫到相邻well)。
|
|
|
+ /// <summary>水平微调半幅(脉冲,EEPROM位置±range)。</summary>
|
|
|
+ public int HFineRange = 2000;
|
|
|
+ /// <summary>水平微调步数(±2000内9步→步距500)。</summary>
|
|
|
+ public int HFineSteps = 9;
|
|
|
|
|
|
// ── Z 全范围粗对焦(固定中心大窗口)──
|
|
|
/// <summary>Z 粗对焦固定中心(实测焦面集中区间 60000~120000 的中点)。</summary>
|
|
|
@@ -169,57 +171,6 @@ namespace AutoFocusTool.Calib
|
|
|
return (bestHPos, best);
|
|
|
}
|
|
|
|
|
|
- /// <summary>
|
|
|
- /// 水平全行程粗扫定位 + 局部密扫居中。不依赖 EEPROM 水平位置准确:
|
|
|
- /// ① 从 HCoarseStart 到 HCoarseEnd 按 HCoarseStep 扫,命中完整圆即停,记录该位置;
|
|
|
- /// ② 以命中点为中心做局部密扫(ScanForCenter)优化 Y 居中;
|
|
|
- /// ③ 全程未命中完整圆则取扫描中 |Y偏移| 最小且检出的位置;仍无返回 (-1,null)。
|
|
|
- /// </summary>
|
|
|
- (int bestHPos, WellCircle bestCircle) HCoarseLocate(int well)
|
|
|
- {
|
|
|
- _cam.SetExposure(CenterScanExposure);
|
|
|
-
|
|
|
- int hitHPos = -1; WellCircle hitCircle = null;
|
|
|
- int fallbackHPos = -1; WellCircle fallbackCircle = null;
|
|
|
- double fallbackScore = double.MaxValue;
|
|
|
-
|
|
|
- // ① 全行程粗扫,命中完整圆即停
|
|
|
- for (int hp = HCoarseStart; hp <= HCoarseEnd; hp += HCoarseStep)
|
|
|
- {
|
|
|
- int p = ClampH(hp);
|
|
|
- _motor.HorizontalMoveTo(p, ScanDelayMs);
|
|
|
- var b = Grab();
|
|
|
- var c = WellDetector.Detect(b, W, H);
|
|
|
- DebugSave?.Invoke(b, $"hcoarse_w{well}_hp{p}");
|
|
|
- Log?.Invoke(c.Found
|
|
|
- ? $" 粗扫水平{p}: Y偏移={c.OffsetYPct:F1}% 完整={c.Complete}"
|
|
|
- : $" 粗扫水平{p}: 未检出圆");
|
|
|
- OnStep?.Invoke($"水平粗扫 hp={p}", c, null);
|
|
|
- if (c.Found)
|
|
|
- {
|
|
|
- double score = Math.Abs(c.OffsetYPct) + (c.Complete ? 0 : 100);
|
|
|
- if (score < fallbackScore) { fallbackScore = score; fallbackHPos = p; fallbackCircle = c; }
|
|
|
- if (c.Complete) { hitHPos = p; hitCircle = c; break; }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 命中完整圆 → 局部密扫;否则用检出最优的降级点
|
|
|
- int center = hitHPos >= 0 ? hitHPos : fallbackHPos;
|
|
|
- if (center < 0)
|
|
|
- {
|
|
|
- Log?.Invoke($"[well{well}] ✗ 水平全行程未检出任何圆");
|
|
|
- return (-1, null);
|
|
|
- }
|
|
|
-
|
|
|
- // ② 以命中/降级点为中心局部密扫居中(范围取粗扫步距量级)
|
|
|
- int fineRange = HCoarseStep;
|
|
|
- var fine = ScanForCenter(well, center, fineRange, FineScanSteps, 800);
|
|
|
- if (fine.bestCircle != null) return (fine.bestHPos, fine.bestCircle);
|
|
|
-
|
|
|
- // ③ 局部密扫没检出 → 回退到粗扫命中/降级结果
|
|
|
- return (center, hitCircle ?? fallbackCircle);
|
|
|
- }
|
|
|
-
|
|
|
/// <summary>
|
|
|
/// 标定单个 well。eepromHPos=该well的EEPROM水平位置(扫描中心),eepromZ=Z焦准零点。
|
|
|
/// 返回标定结果。
|
|
|
@@ -245,14 +196,11 @@ namespace AutoFocusTool.Calib
|
|
|
|
|
|
// ── ② 旋转居中(此时画面清晰,圆可稳定检出;只优化Y偏移)──
|
|
|
// 转动培养皿让well在画面里上下(Y)移动;X的~5%固定偏移是相机/转盘硬件偏移,旋转修不了。
|
|
|
- Log?.Invoke($"[well{well}] ②水平全行程定位+居中...");
|
|
|
- var located = HCoarseLocate(well);
|
|
|
- if (located.bestHPos < 0)
|
|
|
- {
|
|
|
- Log?.Invoke($"[well{well}] ✗ 水平全行程未找到圆,跳过该well");
|
|
|
- return new WellCalib { Well = well, Note = "水平全程未检出圆" };
|
|
|
- }
|
|
|
- int bestHPos = located.bestHPos;
|
|
|
+ // 实测EEPROM位置已准(移到位即|偏移|<5%),故以eepromHPos为中心做小范围微调(半幅<well间距一半),
|
|
|
+ // 绝不大范围扫描(否则会扫到相邻well)。
|
|
|
+ Log?.Invoke($"[well{well}] ②以EEPROM位置({eepromHPos})为中心微调Y居中...");
|
|
|
+ var located = ScanForCenter(well, ClampH(eepromHPos), HFineRange, HFineSteps, 800);
|
|
|
+ int bestHPos = located.bestCircle != null ? located.bestHPos : eepromHPos;
|
|
|
WellCircle bestCircle = located.bestCircle;
|
|
|
r.HorizontalPulse = bestHPos;
|
|
|
r.CircleFound = bestCircle != null;
|