记录日期:2026-06-12 来源:阅读
C:\claudeFile\TL\源码下两套工程后的只读分析结论。 用途:把"怎么控相机 / 怎么控马达 / 怎么控光源 + 物理参数 + 安全限位"一次说清,供你核对,确认后据此写第一步手动测试程序。 配套:主文档相机自动对焦项目-总方案.md(需求与算法逻辑看那份,本份只讲源码能落地的硬接口)。 标注规则:【确证】=代码里直接读到;【推测】=据命名/引用推断,需真机或反编译确认。
| 工程 | 位置 | 作用 | 对我们的价值 |
|---|---|---|---|
ivf_tl_control_2.0 |
完整下位机控制系统(多项目解决方案) | 现役:控温、拍照、换气、对焦上传 | 主参考:相机、串口马达、LED、对焦循环全在这 |
aivfo_ccd |
一个小 WPF 工程,含独立 Camera.cs |
像是相机调试/试验程序 | 相机操作更全(SetExp、BitmapImage、Raw 访问),适合直接抄来做测试程序的相机层 |
结论:测试程序以 ivf_tl_control_2.0 的健壮相机框架为骨架,补 aivfo_ccd 的便捷方法。
"MVC2000",USB2.0 工业相机,约 200 万像素。using Microview;(见 aivfo_ccd\Util\Camera.cs:1)。mvcapi.dll(核心)+ mvavi.dll(录像),P/Invoke 封装在 ivf_tl_CameraHelper\MVCAPI.cs。new Camera(index, 1600, 1200, exposure)
→ Init() ⇒ MV_Usb2Init("MVC2000", out index, ref capInfo, out hImager)
→ SetOpMode() ⇒ MV_Usb2SetOpMode(hImager, nMode, false)
nMode:0 = 拍照(单帧)模式,1 = 实时图像模式(aivfo_ccd\Util\Camera.cs:282-285 注释明确)。IntPtr hImager 贯穿后续所有调用。UnInit() ⇒ MV_Usb2Uninit(ref hImager)。GetRgbData() ⇒ MV_Usb2GetRgbData(hImager, ref capInfo, pDest)。SourceBuffer → byte[],长度 width*height*3,24bpp RGB(BGR 排列)。aivfo_ccd 版取图后做了 RotateNoneFlipY 垂直翻转(Camera.cs:185-208)。显示/保存/算清晰度时要统一处理,否则 ROI 位置会上下错。GetRawData() → RawToRgb()(MV_Usb2ConvertRawToRgb),用于需要 Raw 的场合。CapInfoStruct.Exposure(int),单位 100µs(默认 400 → 40ms)。
SetPartOfCapInfo(int Exposure)(ivf_tl 版)/ SetExp(int newExp)(aivfo_ccd 版,最简洁)⇒ MV_Usb2SetPartOfCapInfo。CapInfoStruct.Gain 是 byte[3],分别是 红 / 绿 / 蓝 三通道增益(不是单一全局 gain),取值 0–255,默认约 R25 G14 B25。Usb2Start(控件句柄, x, y, w, h) ⇒ MV_Usb2Start(...);停 Usb2Stop;暂停 MV_Usb2PausePreview。MV_Usb2SetFrameCallBack(hImager, 委托, lpUser)。MV_Usb2GetFrameRate。GetRgbData() 抓帧即可;要给人看实时画面再开 Usb2Start 或回调。SaveBmpPic(path) 存 BMP;SaveJPG 实为原始字节直写(非真 JPEG,注意)。System.IO.Ports.SerialPort 事件驱动收数(Channel.cs:84-109)。Commander.cs:873-884 注释):| 字节 | [0] | [1] | [2] | [3] | [4..n-2] | [n-1] |
|---|---|---|---|---|---|---|
| 含义 | 帧头 0x5E |
命令码 CMD | 序号 0x00 |
整帧长 LTH | 辅助码+参数 | 校验 CRC |
Commander.CreateORC Commander.cs:15-41)。Commander.CustomProtocolLength Commander.cs:43-89),接收端按固定长度从环形缓冲区取,不靠帧尾分包。Channel.cs:186-188)。只有 2 个步进马达(不是 X+Y+Z 三轴):
1。单轴,把样品/well 定位到水平位置。→ 对应方案里的"XY 皿孔定位",但实际是单轴水平。2。⚠️ 与方案文档的差异:方案里写"XY 两轴居中",源码里水平只有一个轴。皿孔居中是否真的只有单轴、还是另有机构,需你真机确认。
辅助码 = 高半字节(轴) | 低半字节(动作):正转0 / 反转1 / 脱机2 / 复位3 / 绝对运动4。
Z 轴(垂直/对焦)——写程序重点:
| 动作 | 辅助码 | Commander 方法 | ComBin 封装(阻塞等待) |
|---|---|---|---|
| 复位(回零) | 0x23 | CreateVerticalMotorResetCommand (:998) | VerticalMotorResetWait (ComBin.cs:509) |
| 正转(相对) | 0x20 | CreateVerticalMotorForwardCommand (:1009) | — |
| 反转(相对) | 0x21 | CreateVerticalMotorBackwardCommand (:1080) | — |
| 绝对运动 | 0x24 | CreateVerticalMotorAbsoluteMovementCommand (:1045) | VerticalMotorAbsoluteWait (ComBin.cs:535) |
| 读位置 | — | CreateReadVerticalMotorCommand(CMD=0x18,辅助0x02,:187) | ReadVerticalMotorWait (ComBin.cs:493) |
水平电机: 同结构,绝对运动 HorizontalMotorAbsoluteWait(ComBin.cs:457),读位置 ReadHorizontalMotorWait(ComBin.cs:477)。
Pulse/Position。Commander.cs:1056-1069)。House.verticalMotorSpacePulse(每层脉冲间隔,House.cs:86);未配置时硬编码默认 128 脉冲/层(HouseBin.cs:1125)。TLSetting.verticalMotorPulseMax(TLSetting.cs:259,测试值见 StartMain.cs:106 = 125000)。对焦循环里多处 if (currentVer > verticalMotorPulseMax) break; 做软限位(HouseBin.cs:1128/1266/1442)。CreateReadEEPROMvertMtStartPulse Commander.cs:530)。开环 + 固定延时 + 事后回读,没有闭环到位反馈:
WaitOne),但回复只代表"下位机收到指令",不代表机械停稳。超时 30s 重发,最多 3 次,失败重开串口。Thread.Sleep(motorDelay) 等运动完成。motorDelay = TLSetting.motorDelay(电机到位延时 ms,TLSetting.cs:253)。ReadVerticalMotorWait 回读实际位置与目标比对,不一致记错误日志(HouseBin.cs:1537/1610)。Analysiser.ParseCurrentMotor(Analysiser.cs:110-117):回复字节[4..7]拼 int32,失败返回 -1。对自动对焦的直接影响:Z 每移一层,必须等
motorDelay稳定后再抓图,否则拍到运动模糊。motorDelay的真机取值需标定。
(你补充的环节)曝光校正除了相机曝光+增益,还有第三个旋钮:补光 LED。
CreateOpenLEDCommand = 5E 09 00 07 00 01 6F(Commander.cs:107)→ OpenLEDWait(ComBin.cs:655)CreateCloseLEDCommand = 5E 09 00 07 00 00 6E(Commander.cs:117)→ CloseLEDWait(ComBin.cs:670)OpenLEDWait,结束后 CloseLEDWait(HouseBin.cs:1070/1139/1407/1452)。CreateReadEEPROMLightNum Commander.cs:807 → ReadEEPROMLightNumWait ComBin.cs:822),没找到动态"设置亮度"的串口命令。【确证:只读未写】
结论:曝光校正三个可调量的现状
| 旋钮 | 能否程序调 | 接口 |
|---|---|---|
| 相机曝光时间 | ✅ 可调 | SetExp/SetPartOfCapInfo,单位100µs |
| 相机 RGB 增益 | ✅ 可调 | CapInfoStruct.Gain[3],0–255 |
| LED 亮度 | ❌ 现仅开关+读取 | 需硬件加"写亮度"命令才可调 |
| LED 开关 | ✅ 可调 | OpenLEDWait/CloseLEDWait |
现役 C# 客户端本身不计算"哪一层最清晰"。它只做:沿 Z 逐层移动 → 每层抓图 → 存盘 → HTTP 上传服务器 → 轮询向服务器索要"最清晰层"的相对位置。
HouseBin.cs:1106-1137;单孔:HouseBin.cs:1244-1281。逻辑相同:
House.autoFocusNumber;对焦起点 verticalMotorPosition + 层间距 * i(层间距默认 128 脉冲);verticalMotorPulseMax 则 break;VerticalMotorAbsoluteWait 移动 → Autofocus() 抓图存盘上传。HouseBin.GetScore(...)(HouseBin.cs:1734)→ 外部 DLL Project2.dll 的 GetImageScoreAndSaveImage(AivfoHelper.cs:34,导出序号 #5)。GetScore 除定义外零调用 —— 是死代码 / 调试遗留。Project2.dll 里,底层应是 OpenCV),但现役流程没拿它选层。Project2.dll(依赖 opencv_world3416.dll)或查服务器才能确证。leftOffset / bottomOffset(抠图裁剪偏移,HouseWellSettingDB.cs)。| DLL | 作用 | 依据 |
|---|---|---|
mvcapi.dll |
相机 SDK | 【确证】MVCAPI.cs 全部 P/Invoke |
mvavi.dll |
录像 | 【确证】 |
Project2.dll(newccd) |
当前图像处理:抠图#1 / 打分#5 / 存图#7 | 【确证】AivfoHelper.cs |
opencv_world3416.dll |
Project2 的底层 CV | 【推测】 |
CellProcessorDll.dll/CellCultureDllMulti64.dll/opencv_world342.dll(ccd) |
旧图像处理链路 | 【推测】已被 newccd 取代,无 C# 引用 |
MVBayerDec.dll/MVParm.dll |
Bayer 解码 / 相机参数 | 【推测】仅打包 |
新程序是独立小工程,不动现役系统。各能力的现成接口:
| 能力 | 直接复用 | 备注 |
|---|---|---|
| 连相机 | Camera.Init() + SetOpMode(0) |
抄 aivfo_ccd\Util\Camera.cs |
| 调曝光 | SetExp(int) 单位100µs |
|
| 调增益 | CapInfoStruct.Gain[0..2] 0–255 |
RGB 三通道 |
| 抓帧 | GetRgbData() + SourceBuffer |
注意 FlipY 翻转 |
| 实时预览 | Usb2Start(句柄,...) 或帧回调 |
可视化界面用 |
| 开串口/电机 | ComBin + Commander(COM口、9600) |
|
| Z 绝对移动 | VerticalMotorAbsoluteWait(pulse, motorDelay) |
移完等 motorDelay 再抓图 |
| 读 Z 位置 | ReadVerticalMotorWait |
事后校验 |
| 水平移动 | HorizontalMotorAbsoluteWait |
皿孔定位 |
| LED 开关 | OpenLEDWait / CloseLEDWait |
对焦前开、后关 |
| 清晰度算分 | 自己写(Tenengrad/Laplacian,OpenCvSharp 或纯 C#) | 不依赖服务器、不依赖 Project2.dll,方案要求本地可解释 |
最小闭环(手动): 连相机 → 开 LED → 实时预览 + 显示当前帧清晰度分 → 手动按钮调曝光/增益、手动 Z±/水平± → 移动后回读位置 → 关 LED。打通这条就完成"第一步"。
确认 1/2/3/5/7 即可让我开始写第一步手动测试程序;4/6 可在联调时标定。