图片:





要求:
高级【F01】
1.检测每个药水瓶的液位,用红线标识出来;
2.同一套程序适用于素材目录所有图片;
答题要点及评分标准:
根据图片内容判定
试题解析:
1.每张图片至少标示出8个药瓶的液位,每个药瓶正确标示+2.5分;
2.共5个素材图片,根据每个图片运行结果,累计以上分数;
3.总分100分;
VisionPro方案:


工具参数:
CogPMAlignTool工具

CogIPOneImageTool工具


CogFindLineTool工具


高级脚本完整代码:
cs
#region namespace imports
using System;
using System.Collections;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using Cognex.VisionPro;
using Cognex.VisionPro.ToolBlock;
using Cognex.VisionPro3D;
using Cognex.VisionPro.PMAlign;
using Cognex.VisionPro.ImageProcessing;
using Cognex.VisionPro.Caliper;
#endregion
public class CogToolBlockAdvancedScript : CogToolBlockAdvancedScriptBase
{
#region Private Member Variables
private Cognex.VisionPro.ToolBlock.CogToolBlock mToolBlock;
CogGraphicCollection gc = new CogGraphicCollection();
#endregion
/// <summary>
/// Called when the parent tool is run.
/// Add code here to customize or replace the normal run behavior.
/// </summary>
/// <param name="message">Sets the Message in the tool's RunStatus.</param>
/// <param name="result">Sets the Result in the tool's RunStatus</param>
/// <returns>True if the tool should run normally,
/// False if GroupRun customizes run behavior</returns>
public override bool GroupRun(ref string message, ref CogToolResultConstants result)
{
// To let the execution stop in this script when a debugger is attached, uncomment the following lines.
// #if DEBUG
// if (System.Diagnostics.Debugger.IsAttached) System.Diagnostics.Debugger.Break();
// #endif
// Run each tool using the RunTool function
foreach(ICogTool tool in mToolBlock.Tools)
mToolBlock.RunTool(tool, ref message, ref result);
gc.Clear();
CogPMAlignTool pma = mToolBlock.Tools[0] as CogPMAlignTool;
CogFindLineTool Line1 = mToolBlock.Tools[4] as CogFindLineTool;
CogImage8Grey image1 = mToolBlock.Inputs["yy"].Value as CogImage8Grey;
//CogDistancePointLineTool distance1 = mToolBlock.Tools[3] as CogDistancePointLineTool;
//CogMath math1 = new CogMath();
/*
foreach(CogPMAlignResult p in pma.Results)
{
//Line1.RunParams.ExpectedLineSegment.StartX =
Line1.RunParams.ExpectedLineSegment.StartX = p.GetPose().TranslationX - 8;
Line1.RunParams.ExpectedLineSegment.StartY = p.GetPose().TranslationY;
Line1.RunParams.ExpectedLineSegment.EndX = p.GetPose().TranslationX +8;
Line1.RunParams.ExpectedLineSegment.EndY = p.GetPose().TranslationY;
Line1.RunParams.ExpectedLineSegment.SelectedColor = CogColorConstants.Green;
Line1.Run();
CogFindLineResults lineResults = Line1.Results;
CogLine foundLine = lineResults.GetLine();
//CogDistancePointLineTool distance1 = new CogDistancePointLineTool();
double pX = p.GetPose().TranslationX;
double pY = p.GetPose().TranslationY;
double footX, footY;
double distance1 = CogMath.DistancePointLine(
pX,
pY,
foundLine,
image1, //传入工具块输入图像
out footX,
out footY
);
CogLineSegment line2 = new CogLineSegment();
line2.Color = CogColorConstants.Red;
line2.StartX = Line1.Results.GetLineSegment().MidpointX - 28;
line2.StartY = Line1.Results.GetLineSegment().MidpointY;
line2.EndX = Line1.Results.GetLineSegment().MidpointX + 28;
line2.EndY = Line1.Results.GetLineSegment().MidpointY;
gc.Add(line2);
}
*/
// 设置距离公差,按需修改!
double tolerance = 30;
foreach(CogPMAlignResult p in pma.Results)
{
double pX = p.GetPose().TranslationX;
double pY = p.GetPose().TranslationY;
// 动态更新找线ROI
Line1.RunParams.ExpectedLineSegment.StartX = pX - 18;
Line1.RunParams.ExpectedLineSegment.StartY = pY;
Line1.RunParams.ExpectedLineSegment.EndX = pX + 18;
Line1.RunParams.ExpectedLineSegment.EndY = pY;
Line1.RunParams.ExpectedLineSegment.SelectedColor = CogColorConstants.Green;
Line1.Run();
CogFindLineResults lineResults = Line1.Results;
// ✅判空,防止找不到线崩溃
if (lineResults != null && lineResults.Count > 0)
{
//CogFindLineResult lineResult = lineResults;
CogLine foundLine = lineResults.GetLine();
CogLineSegment foundSeg = lineResults.GetLineSegment();
double footX, footY;
double distance1 = CogMath.DistancePointLine(
pX,
pY,
foundLine,
image1,
out footX,
out footY
);
CogLineSegment line2 = new CogLineSegment();
// 根据距离设置颜色
if (distance1 < tolerance)
{
line2.Color = CogColorConstants.Red; // NG 红色
}
else
{
line2.Color = CogColorConstants.Green; // OK 绿色
}
line2.StartX = foundSeg.MidpointX - 28;
line2.StartY = foundSeg.MidpointY;
line2.EndX = foundSeg.MidpointX + 28;
line2.EndY = foundSeg.MidpointY;
gc.Add(line2);
//【可选】绘制蓝色垂线(PM点→垂足,方便调试)
CogLineSegment perpendicular = new CogLineSegment();
perpendicular.Color = CogColorConstants.Blue;
perpendicular.StartX = pX;
perpendicular.StartY = pY;
perpendicular.EndX = footX;
perpendicular.EndY = footY;
gc.Add(perpendicular);
//【可选】绘制距离文本
CogGraphicLabel txt = new CogGraphicLabel();
txt.Text = "Dist:" +distance1.ToString("F3");
txt.X = foundSeg.MidpointX;
txt.Y = foundSeg.MidpointY - 10;
txt.Color = line2.Color;
gc.Add(txt);
}
else
{
// 本次找线失败,可以画黄色提示标记
CogGraphicLabel txtFail = new CogGraphicLabel();
txtFail.Text = "LineNotFound";
txtFail.X = pX;
txtFail.Y = pY;
txtFail.Color = CogColorConstants.Yellow;
gc.Add(txtFail);
}
}
return false;
}
#region When the Current Run Record is Created
/// <summary>
/// Called when the current record may have changed and is being reconstructed
/// </summary>
/// <param name="currentRecord">
/// The new currentRecord is available to be initialized or customized.</param>
public override void ModifyCurrentRunRecord(Cognex.VisionPro.ICogRecord currentRecord)
{
}
#endregion
#region When the Last Run Record is Created
/// <summary>
/// Called when the last run record may have changed and is being reconstructed
/// </summary>
/// <param name="lastRecord">
/// The new last run record is available to be initialized or customized.</param>
public override void ModifyLastRunRecord(Cognex.VisionPro.ICogRecord lastRecord)
{
foreach(ICogGraphic g in gc)
{
mToolBlock.AddGraphicToRunRecord(g,lastRecord,"CogIPOneImageTool2.OutputImage","");
}
}
#endregion
#region When the Script is Initialized
/// <summary>
/// Perform any initialization required by your script here
/// </summary>
/// <param name="host">The host tool</param>
public override void Initialize(Cognex.VisionPro.ToolGroup.CogToolGroup host)
{
// DO NOT REMOVE - Call the base class implementation first - DO NOT REMOVE
base.Initialize(host);
// Store a local copy of the script host
this.mToolBlock = ((Cognex.VisionPro.ToolBlock.CogToolBlock)(host));
}
#endregion
}
结果:



代码整体概述
这是一段 VisionPro ToolBlock 高级脚本(C#),典型应用场景:
模板匹配(PMAlign)多点定位 → 每一个模板结果位置动态移动【找线工具 CogFindLineTool】的 ROI → 抓取直线 → 计算模板点到直线的垂直距离 → 根据距离公差 OK/NG 上色、叠加绘图(线段、垂线、文字),最后把图形挂载到图像输出记录上。 核心:在 GroupRun 里自定义串行逻辑,循环处理多个 PM 匹配结果,动态驱动子工具运行 + 自定义绘图
重要特点:return false;代表接管运行流程,不再走 ToolBlock 默认依次执行子工具的逻辑。
一、核心框架知识点(ToolBlock 高级脚本固定结构)
1. 基类与重写函数
public class CogToolBlockAdvancedScript : CogToolBlockAdvancedScriptBase
VisionPro 高级脚本固定继承这个基类,4 个关键重写方法:
表格
| 方法 | 作用 | 本代码要点 |
|---|---|---|
Initialize(host) |
脚本初始化,只加载一次 | 把 host 强转为CogToolBlock mToolBlock,保存工具块引用;必须先调用 base.Initialize (host) |
GroupRun(ref message, ref result) |
✅ 运行主逻辑,每次工具块 Run 都会执行 | 自定义运行流程;return true:让工具块自动依次跑里面所有子工具;return false:自己全权控制子工具运行 |
ModifyCurrentRunRecord |
当前记录构建时回调 | 本代码空实现 |
ModifyLastRunRecord |
上次运行记录构建回调 | 把绘图集合 gc 里的图形添加到 RunRecord,在图像上显示 |
重点区分:
GroupRun:计算逻辑、跑子工具、算距离、生成 GraphicModifyLastRunRecord:只负责把图形挂到图像输出上,不能在这里跑工具、做计算 ❌ 大坑:不要在 ModifyLastRunRecord 里面调用 Tool.Run (),记录回调不是运行流程!
2. ToolBlock 内部对象访问两种方式
// 方式1:按索引取子工具(代码在用)
CogPMAlignTool pma = mToolBlock.Tools[0] as CogPMAlignTool;
CogFindLineTool Line1 = mToolBlock.Tools[4] as CogFindLineTool;
// 方式2:按名称取(更稳定,推荐,避免拖动工具改变索引后代码崩)
// mToolBlock.Tools["PMAlignTool1"]
// 读取输入端子
CogImage8Grey image1 = mToolBlock.Inputs["yy"].Value as CogImage8Grey;
风险点:索引取工具,一旦在 ToolBlock 里调换工具顺序,脚本直接空引用崩溃。
二、GroupRun 运行流程知识点
1. 子工具运行:RunTool
foreach(ICogTool tool in mToolBlock.Tools)
mToolBlock.RunTool(tool, ref message, ref result);
RunTool:ToolBlock 脚本里标准调用子工具运行的 API,会填充子工具自己的 Results- 这段代码先把所有子工具跑一遍;然后脚本内部又单独循环多次调用
Line1.Run()
⚠️ 这里是一个容易踩坑点: Line1 被外层先 Run 一次,之后在 PM 结果循环里反复覆盖 Run,Line1 的结果会被不断覆盖,只保留最后一次找线结果。 如果后续其他工具依赖 Line1 输出,会出问题;本脚本只是在脚本内部即时拿结果绘图,所以暂时可用。
2. CogPMAlignTool 多结果遍历
foreach(CogPMAlignResult p in pma.Results)
{
double pX = p.GetPose().TranslationX;
double pY = p.GetPose().TranslationY;
}
PMAlign.Results是匹配到的全部模板实例集合,支持多目标p.GetPose():CogTransform2D,包含 X/Y 平移、角度、缩放TranslationX / TranslationY:模板原点坐标(像素坐标系)
3. 动态修改 CogFindLineTool ROI(核心业务逻辑)
Line1.RunParams.ExpectedLineSegment.StartX = pX - 18;
Line1.RunParams.ExpectedLineSegment.StartY = pY;
Line1.RunParams.ExpectedLineSegment.EndX = pX + 18;
Line1.RunParams.ExpectedLineSegment.EndY = pY;
Line1.RunParams.ExpectedLineSegment.SelectedColor = CogColorConstants.Green;
Line1.Run();
ExpectedLineSegment:就是找线工具的搜索 ROI 线段框- 跟着每一个 PM 匹配点,实时平移找线窗口,实现跟随式找线
- 调用
Line1.Run()单独执行找线,不依赖工具块自动调度
区分:
RunParams:运行参数(ROI、阈值、边缘极性等,可改)Results:运行输出结果(只读)
4. CogFindLineTool 结果判空与取值
if (lineResults != null && lineResults.Count > 0)
{
CogLine foundLine = lineResults.GetLine();
CogLineSegment foundSeg = lineResults.GetLineSegment();
}
GetLine():无限长几何直线(用于数学距离计算)GetLineSegment():找到的线段(有起止点、中点,用于绘图)- 判空是必须的:产品偏移、无边缘时找线会无结果,不判断直接
GetLine()会抛异常崩溃
5. CogMath.DistancePointLine 点到直线距离 API
double distance1 = CogMath.DistancePointLine(
pX, pY,
foundLine,
image1,
out footX, out footY
);
重点:
- 第二个参数是 CogLine(无限直线),不是 CogLineSegment!如果传线段,函数语义就变成点到线段最短距离,和业务不符;本代码写法是正确的
footX,footY:垂足坐标,用来绘制垂线- 返回值:垂直距离(像素单位)
- image 参数:图像对象,用于坐标系上下文,一般直接传入输入图即可
业务逻辑:距离 < tolerance → NG(红),否则 OK(绿)
三、VisionPro 矢量绘图 Graphic 体系知识点
CogGraphicCollection gc = new CogGraphicCollection();
gc.Clear();
gc.Add(xxx);
常用 Graphic 类型:
表格
| 图形类 | 用途 |
|---|---|
| CogLineSegment | 线段(有起止点) |
| CogGraphicLabel | 文字标签,显示数值、报错文本 |
| CogCircle、CogCrosshair | 十字、圆标记(本代码没用到) |
属性:
Color = CogColorConstants.Red/Green/Blue/Yellow枚举颜色X/Y:标签锚点坐标
绘图是先全部放进内存集合 gc ,等到
ModifyLastRunRecord统一挂载,而不是 Run 的时候直接画。
挂载图形到图像输出(ModifyLastRunRecord)
mToolBlock.AddGraphicToRunRecord(g,lastRecord,"CogIPOneImageTool2.OutputImage","");
参数解析:
- graphic 对象
- lastRecord:运行记录
- 图像 Record 路径字符串 :
工具名.输出端子名
这是高频踩坑点:名字写错、工具改名后字符串不会自动更新 → 图形不显示!
- 第四个参数:图层名称,留空默认图层
原理:VisionPro 的图像和绘图都保存在 ICogRecord 树里面,不是直接贴在图片 Bitmap 上;Record 可以保存、回放、在 Display 控件上加载显示。
四、代码里存在的缺陷 & 坑点(重点面试 / 调试知识点)
- 按索引取子工具、图像路径硬编码字符串
Tools[0]、Tools[4]、"CogIPOneImageTool2.OutputImage",重构工具块极易失效。✅ 推荐改成按名称访问。 - Line1 反复 Run,结果被覆盖 循环多次调用 Line1.Run (),Line1 自身的 Result 只保留最后一次;如果后续有其他子工具依赖 Line1 输出,会逻辑错误。 ✅ 改进方案:脚本内部不要复用同一个 CogFindLineTool 多次 Run,要么在脚本里 new 临时 CogFindLineTool,或者把找线逻辑改成脚本内 CogCaliper 实现。
- 没有输出距离、OK/NG 结果到 ToolBlock 输出端子 现在只绘图,没有把每一个距离、判定结果输出到 Outputs,外部程序(C# 上位机)拿不到数据,只能看画面。 ✅ 补充:
mToolBlock.Outputs["Dist"].Value = distance1;可以输出单个 / 数组结果。 - 没有结果计数:匹配到多少个、找线失败多少个 缺少统计量,不方便做统计监控。
- 没有异常捕获 try-catch 图像为空、PM 匹配异常等场景会直接让 ToolBlock Run 报错。
- 公差
tolerance写死在脚本常量,不方便产线调试 ✅ 优化:做成 ToolBlock 输入端子,外部界面直接改公差,不用改脚本重编译。
五、业务流程图总结
ToolBlock.Run()
↓
GroupRun 开始
↓
先批量Run所有子工具
↓
拿到PMAlign结果集合
↓
循环每一个匹配点Pose
├─ 动态移动FindLine的ROI窗口到当前点
├─ 执行FindLine.Run()
├─ 找到线:计算点到直线距离、判定OK/NG、绘制线段+垂线+距离文字
└─ 找不到线:绘制黄色提示文字
↓
全部Graphic存入gc集合
↓
return false; 结束GroupRun(不再执行默认流程)
↓
回调 ModifyLastRunRecord
↓
把gc里所有图形挂载到指定图像输出的Record上
↓
VisionPro Display 显示叠加绘图
六、高频面试提问点(基于这份代码)
- GroupRun return true 和 false 的区别?
- ModifyLastRunRecord 和 GroupRun 的分工?能不能在 ModifyLastRunRecord 里面跑工具?
- CogMath.DistancePointLine 传 CogLine 和 CogLineSegment 有什么区别?
- 为什么图形要放到 CogGraphicCollection,在 ModifyLastRunRecord 统一添加?
- PM 多目标循环里复用同一个 FindLineTool 反复 Run 有什么隐患?
- AddGraphicToRunRecord 第三个字符串参数是什么作用,写错会怎么样?