通用视觉工具模块-位置修正位置修正的作用过程核心将仿射变换结果赋值于ModuleBase基类属性中其他任意模块插件都可以使用Plugin.Coordinate 坐标补正插件 — 完整解析所属分类: 005坐标标定路径:02Plugins\005坐标标定\Plugin.Coordinate继承基类:JGTechVision.Core.ModuleBase依赖: Halcon 20.11 VM.Halcon 封装层显示名称: 坐标补正核心文件:CoordinateViewModel.cs(241行)1. 一句话概述Plugin.Coordinate 是整个 JGTechVision 平台的位置修正引擎。它通过计算模板匹配的示教基准位置与实际运行位置之间的 2D 仿射变换矩阵HomMat2D/HomMat2D_Inverse并利用ModuleBase.GetHomMat2D()将矩阵自动传播到下游所有测量/检测工具实现整个流程链的自动位置跟随修正。2. 项目文件结构Plugin.Coordinate/ ├── Plugin.Coordinate.csproj ├── ViewModels/ │ └── CoordinateViewModel.cs # 唯一核心文件 (241行) ├── Views/ │ ├── CoordinateView.xaml # 配置界面 (9304字节) │ └── CoordinateView.xaml.cs # View code-behind ├── Assets/Images/Tool/ │ └── Coordinate.png # 工具箱图标 └── Properties/3. 代码设计 — 极简但影响深远3.1 完整源码结构 (241行)CoordinateViewModel : ModuleBase │ ├── 枚举: eLinkCommand { InputImageLink, XLink, YLink, DegLink } │ ├── ★ ExeModule() # 核心算法 (21行) ├── GetModeCoord() # 向前查找模板匹配模块 (7行) ├── SetDefaultLink() # 自动推断默认链接 │ ├── 属性: │ ├── InputImageLinkText # 输入图像链接 │ ├── XLinkText / YLinkText # X/Y 偏移量链接 (LinkVarModel) │ ├── DegLinkText # 角度链接 │ └── ShowCoordinate # 显示坐标轴开关 │ ├── 命令: │ ├── LinkCommand # 变量链接命令 │ ├── ExecuteCommand # 手动触发执行 │ ├── ConfirmCommand # 确认关闭窗口 │ └── Loaded() # 窗口加载时初始化 Halcon 控件 │ └── OnVarChanged() # 变量链接变化回调3.2 核心算法 —ExeModule()逐行解析publicoverrideboolExeModule(){// (1) 哨兵模式: 如果是结束标记节点,直接返回OKif(ModuleParam.ModuleName.StartsWith(坐标补正结束)){ChangeModuleRunStatus(eRunStatus.OK);returntrue;}Stopwatch.Restart();try{// (2) 向前查找最近的上游模板匹配模块,获取其 ModeCoord (示教基准坐标)GetModeCoord();// (3) 从变量链接中读取当前匹配的实际坐标 (X, Y, 角度)MathCoord.Xdouble.Parse(GetLinkValue(XLinkText).ToString());MathCoord.Ydouble.Parse(GetLinkValue(YLinkText).ToString());HTupleradnewHTuple(double.Parse(GetLinkValue(DegLinkText).ToString()));MathCoord.Phirad.TupleRad();// 角度转弧度// (4) ★ 关键: 用 Halcon 算子计算两个坐标之间的 2D 仿射变换矩阵// HomMat2D: 模板坐标 → 实际坐标 (前向变换)HOperatorSet.VectorAngleToRigid(ModeCoord.Y,ModeCoord.X,ModeCoord.Phi,// 源: 模板基准坐标MathCoord.Y,MathCoord.X,MathCoord.Phi,// 目标: 实际匹配坐标outHomMat2D);// (5) ★ 关键: 计算逆矩阵// HomMat2D_Inverse: 实际坐标 → 模板坐标 (反向变换)HOperatorSet.VectorAngleToRigid(MathCoord.Y,MathCoord.X,MathCoord.Phi,ModeCoord.Y,ModeCoord.X,ModeCoord.Phi,outHomMat2D_Inverse);ChangeModuleRunStatus(eRunStatus.OK);returntrue;}catch(Exceptionex){...}}3.3MakeModeCoord()— 模板基准坐标获取privatevoidGetModeCoord(){intindexPrj.ModuleList.IndexOf(this);for(intiindex-1;i0;i--)// 向前遍历{if(Prj.ModuleList[i].ModuleParam.ModuleName.StartsWith(模板匹配)){ModeCoordPrj.ModuleList[i].ModeCoord;// 直接拿走模板模块的基准坐标return;}}}4. UI 界面设计 (980×680)┌──────────────────────────────────────────────────┐ │ [左侧参数区 370px] │ │ │ │ │ │ ┌─ 图像链接 ─────────────┐│ │ │ │ 输入图像: [链接文本框] ││ [Halcon 图像窗口] │ │ └────────────────────────┘│ (WinForm 宿主) │ │ │ │ │ ┌─ 坐标参数 ─────────────┐│ │ │ │ 原点X: [链接文本框] ││ ← 自动/手动选择 │ │ │ 原点Y: [链接文本框] ││ 模板匹配模块的 │ │ │ 角度 : [链接文本框] ││ 输出 X/Y/Deg 作为 │ │ └────────────────────────┘│ 偏移来源 │ │ │ │ │ ┌─ 显示设置 ─────────────┐│ │ │ │ ☑ 显示坐标轴 ││ │ │ └────────────────────────┘│ │ ├──────────────────────────────┴────────────────────┤ │ 耗时: XXms 状态: OK/NG [执行] [取消] [确认] │ └──────────────────────────────────────────────────┘5. 位置修正机制 — 最核心部分5.1 矩阵自动传播机制:ModuleBase.GetHomMat2D()这是整个平台位置修正的核心传播器定义在 [ModuleBase.cs](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/01Main/VM.Start/Core/ModuleBase.cs:245)publicvirtualvoidGetHomMat2D(){intindexPrj.ModuleList.IndexOf(this);intvalue0;for(intiindex-1;i0;i--){// 遇到坐标补正结束: 嵌套深度1 (缩小作用域)if(Prj.ModuleList[i].ModuleParam.ModuleName.StartsWith(坐标补正结束))value1;// 遇到补正开始: 嵌套深度-1if(Prj.ModuleList[i].ModuleParam.ModuleName.StartsWith(补正开始)){value-1;if(value0)// 找到匹配层级的开始标记{HomMat2DPrj.ModuleList[i].HomMat2D;// ★ 复制前向矩阵HomMat2D_InversePrj.ModuleList[i].HomMat2D_Inverse;// ★ 复制反向矩阵return;}}}}设计精妙之处:支持嵌套补正通过value计数器追踪开始/结束标记的嵌套深度每个模块在执行前调用GetHomMat2D()自动获取其作用域内的补正矩阵如果流程中没有补正模块HomMat2D为空 → 下游模块走无修正分支5.2 修正流程中的补正开始与坐标补正结束这两个特殊命名的模块节点构成了补正作用域流程中的模块布局: ┌─────────────────────────────────────────────┐ │ 模板匹配 → 产生 ModeCoord (基准坐标) │ │ ↓ │ │ 【补正开始】 → Coordinate.ExeModule() │ ← 这里计算 HomMat2D │ 计算 HomMat2D 矩阵 │ │ ↓ │ │ 测量圆/测量线 → GetHomMat2D() ← 获取矩阵 │ ← 作用域内模块自动获取 │ 创建点 → GetHomMat2D() │ │ Blob分析 → GetHomMat2D() │ │ ↓ │ │ 【坐标补正结束】→ 哨兵节点,直接返回 OK │ ← 作用域结束 └─────────────────────────────────────────────┘5.3 双矩阵的语义矩阵Halcon 算子方向用途HomMat2DVectorAngleToRigid(模板→实际)模板坐标 → 实际坐标运行时将模板中的测量位置变换到实际匹配位置HomMat2D_InverseVectorAngleToRigid(实际→模板)实际坐标 → 模板坐标示教时把实际选中的 ROI 点还原到模板坐标系存储6. 受影响的模块全景 — 谁在用它6.1 各模块使用 HomMat2D 的方式汇总测量圆 — [MeasureCircle](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/02Plugins/002检测识别/Plugin.MeasureCircle/ViewModels/MeasureCircleViewModel.cs:71)示教阶段 (DisenableAffine2dtrue): HomMat2D_Inverse → 将临时圆 TempCircle 还原为模板圆 InitCircle 存储 InitCircle (模板坐标系的圆参数) 运行阶段: HomMat2D → InitCircle 变换为 TranCircle 用 TranCircle (实际位置) 执行圆检测关键代码// 示教: 实际 → 模板Aff.Affine2d(HomMat2D_Inverse,TempCircle,InitCircle);// 运行: 模板 → 实际Aff.Affine2d(HomMat2D,InitCircle,TranCircle);测量线 — [MeasureLine](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/02Plugins/002检测识别/Plugin.MeasureLine/ViewModels/MeasureLineViewModel.cs:78)完全相同的模式: HomMat2D_Inverse → TempLine → InitLine (示教还原) HomMat2D → InitLine → TranLine (运行变换)创建点 — [CreatePoints](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/02Plugins/002检测识别/Plugin.CreatePoints/ViewModels/CreatePointsViewModel.cs:79)示教阶段: HomMat2D_Inverse → 反向变换单个点: Aff.Affine2d(HomMat2D_Inverse, TempPoints[Index].PointX, TempPoints[Index].PointY, out X, out Y) Points[Index] (X, Y) ← 存储为模板坐标 运行阶段: HomMat2D → 前向变换所有点: for each Points[i]: Aff.Affine2d(HomMat2D, Points[i].PointX, Points[i].PointY, out X, out Y) TranPoints[i] (X, Y)创建 ROI — [CreateROI](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/02Plugins/002检测识别/Plugin.CreateROI/ViewModels/CreateROIViewModel.cs:112)对 Halcon Region 做仿射变换 (比点变换更复杂): HomMat2D → OutRegion.AffineTransRegion(new HHomMat2D(HomMat2D)) HomMat2D → finalRegion.AffineTransRegion(new HHomMat2D(HomMat2D)) 涂抹/擦除时也需考虑 HomMat2D: 绘图时: finalRegion_Temp finalRegion.AffineTransRegion(HomMat2D) // 模板→实际显示 结束时: finalRegion finalRegion_Temp.AffineTransRegion(HomMat2D_Inverse) // 实际→模板存储Blob 分析 — [Blob](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/02Plugins/001图像处理/Plugin.Blob/ViewModels/BlobViewModel.cs:130)对搜索区域做仿射变换: HomMat2D → ReduceRegion.AffineTransRegion(new HHomMat2D(HomMat2D)) 如果存在遮罩: ReduceRegion ReduceRegion.Difference(maskRegion)模板匹配 — [Plugin.Matching](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/02Plugins/002检测识别/Plugin.Matching/ViewModels/MatchingViewModel.cs)使用 HomMat2D 被动影响: - 匹配结果的轮廓叠加显示使用了 HomMat2D 做仿射变换 - SetDefaultLink() 时向前查找模板匹配模块获取 HomMat2D偏移计算 — [CalculateOffset](/D:/JGTechVision/GJTechVisionV1.0.0/01_Sourse - 0604-2/VM/02Plugins/005坐标标定/Plugin.CalculateOffset/ViewModels/CalculateOffsetViewModel.cs)读取 HomMat2D (从九点标定 NPointCal 获取): HomMat2D Prj.ModuleList[i].HomMat2D; // 直接取上游补正模块的矩阵 将模板坐标和匹配坐标都映射到物理坐标系: HOperatorSet.AffineTransPoint2d(Hommat2DTrans, ModeCoord.X, ModeCoord.Y, out hv_RealXRef, out hv_RealYRef); HOperatorSet.AffineTransPoint2d(Hommat2DTrans, MathCoord.X, MathCoord.Y, out hv_RealFindX, out hv_RealFindY); 计算物理偏移量: OffsetX -(hv_RealXRef - hv_RealFindX) // 物理 X 偏移 OffsetY -(hv_RealYRef - hv_RealFindY) // 物理 Y 偏移 OffsetR ModeCoord.Phi - MathCoord.Phi // 角度偏差7. 完整数据流 — 一个典型工作流程┌──────────────┐ │ (1) 示教阶段 │ └──────┬───────┘ │ ▼ 相机采集 → 模板匹配(创建模板) → 获得 ModeCoord (X₀, Y₀, θ₀) │ ↓ │ 存入 Coordinate.ModuleBase.ModeCoord │ ▼ 用户拖拽 ROI 选取测量位置 → TempCircle/TempLine │ ▼ 【补正开始】→ Coordinate.ExeModule() ← 但此时 ModeCoord MathCoord HomMat2D 恒等矩阵 (无偏移) │ ▼ 测量工具调用 GetHomMat2D() HomMat2D_Inverse → TempCircle → InitCircle (恒等变换,存储模板坐标) │ ▼ 保存 Project → InitCircle 序列化到文件 ┌──────────────┐ │ (2) 运行阶段 │ └──────┬───────┘ │ ▼ 相机采集 → 模板匹配(运行) → MathCoord (X₁, Y₁, θ₁) ≠ ModeCoord │ ↓ │ 链接到 Coordinate.XLinkText/YLinkText/DegLinkText │ ▼ 【补正开始】→ Coordinate.ExeModule() VectorAngleToRigid(X₀,Y₀,θ₀ → X₁,Y₁,θ₁) │ ├── HomMat2D 模板→实际的变换矩阵 └── HomMat2D_Inverse 实际→模板的变换矩阵 │ ▼ 测量工具调用 GetHomMat2D() ← 获取上述矩阵 │ ▼ InitCircle → (HomMat2D前向变换) → TranCircle ← 在正确位置测量! │ ▼ 执行测量 → 输出结果关键配套:SetDefaultLink()的自动连接publicoverridevoidSetDefaultLink(){// 1. 自动连接最近的模板匹配模块的 HomMat2Dfor(intiindex-1;i0;i--)if(Prj.ModuleList[i].ModuleParam.ModuleName.StartsWith(模板匹配)){HomMat2DPrj.ModuleList[i].HomMat2D;XLinkText.Text${Prj.ModuleList[i].ModuleParam.ModuleName}.X;YLinkText.Text${Prj.ModuleList[i].ModuleParam.ModuleName}.Y;DegLinkText.Text${Prj.ModuleList[i].ModuleParam.ModuleName}.Deg;return;}}这使得用户添加坐标补正模块时系统自动查找上游的模板匹配模块并连接其 X/Y/Deg 输出。8. 与其他标定模块的关系┌──────────────────────────────────────────────────────────────┐ │ 坐标标定体系 │ │ │ │ Plugin.NPointCal ────────→ 九点标定 │ │ (像素坐标 → 机械坐标) 输出: HomMat2D_NPoints │ │ │ 存入: ModuleBase.HomMat2D_NPoints │ │ │ │ │ ├──→ Plugin.CoordinateAffine │ │ │ (坐标仿射变换) │ │ │ 读取 HomMat2D_NPoints │ │ │ 将机械坐标点映射到另一机械坐标系 │ │ │ │ │ └──→ Plugin.CalculateOffset │ │ (偏移计算) │ │ 读取 HomMat2D_NPoints │ │ Coordinate 补正结果 │ │ → 计算物理世界的实际偏移量 │ │ │ │ Plugin.Coordinate ────────→ 坐标补正 ★ (本文档) │ │ (模板偏移修正) 输出: HomMat2D / HomMat2D_Inverse │ │ │ 存入: ModuleBase.HomMat2D │ │ │ │ │ ├──→ Plugin.MeasureCircle (测量位置修正) │ │ ├──→ Plugin.MeasureLine (测量位置修正) │ │ ├──→ Plugin.CreatePoints (点位修正) │ │ ├──→ Plugin.CreateROI (ROI区域修正) │ │ ├──→ Plugin.Blob (搜索区域修正) │ │ └──→ Plugin.Matching (轮廓显示修正) │ │ │ │ Plugin.CaliAssistant ─────→ 标定助手 (交互式标定) │ │ │ └──────────────────────────────────────────────────────────────┘9. 设计亮点极简实现, 影响深远: 核心逻辑仅 7 行数学运算但通过基类传播机制影响了 7 个下游模块嵌套作用域设计: 补正开始/结束标记 value 计数器支持任意深度的嵌套补正区域双矩阵策略:HomMat2D(模板→实际) 用于运行态,HomMat2D_Inverse(实际→模板) 用于示教态自然分离了两个阶段的需求自动默认链接:SetDefaultLink()自动查找上游模板匹配模块大幅降低用户配置成本哨兵节点模式: 以ModuleName.StartsWith(坐标补正结束)直接返回 OK轻量实现作用域结束标记与九点标定解耦:HomMat2D(模板偏移修正) 和HomMat2D_NPoints(像素→物理映射) 独立存储于ModuleBase下游插件可以分别获取也可以组合使用文档说明: 本文档通过对Plugin.Coordinate源码及全项目HomMat2D引用链的静态分析生成。所有引用的文件路径和行号基于当前版本 (2026-06-07)。