简介:这份资源是面向机器视觉初学者与工业检测开发者的VisionPro案例合集,围绕实际产线中的识别、测量与统计需求,提供可直接参考的工程实例。内容覆盖零件尺寸测量与显示、硬币统计、骰子点数统计、零件孔位数量统计、零件瑕疵检测、啤酒盖瑕疵检测、书签类型检测、手机电池正反面识别、车牌识别、陶瓷釉面瑕疵检测标注、地面裂痕检测标注、杂志页排序、药物胶囊瑕疵检测等十余个典型场景,兼顾尺寸测量、缺陷检测与目标计数三类任务。压缩包共930个文件,约65.75MB,以jpg、bmp、png等图像样本为主,配合cs源码、vpp视觉程序、resx资源、csproj工程与少量dll、exe,便于直接打开运行与二次修改。目前已有127人学习下载,适合希望借助现成案例理解VisionPro工具链、快速搭建检测流程并积累项目经验的读者。
1. 从一枚硬币到一颗骰子:VisionPro 案例里最值得先跑通的三件事
很多人第一次打开 VisionPro 的 QuickBuild,面对满屏的工具面板是懵的——CogFindCircle、CogCaliper、CogBlob、CogPMAlign 到底先用哪个?我当初也一样,直到把「零件尺寸测量与显示、硬币统计、骰子点数统计」这三个案例从头到尾跑了一遍,才算真正摸到这套视觉软件的脾气。这三个案例看着简单,其实覆盖了工业视觉最核心的三类任务:几何尺寸测量(卡尺找边、圆拟合、亚像素精度)、数量统计(Blob 连通域分析、面积筛选)、特征分类(模式匹配、斑点计数)。它们不是玩具,而是产线上每天都在跑的活。如果你正在学 VisionPro,或者想用康耐视这套工具做二次开发,这三个案例是最好的练手起点——工具链完整、结果可验证、参数调优的边界清晰。下面我按自己踩过的顺序,把每个案例的落地路径拆开讲。
2. 零件尺寸测量与显示:从卡尺到亚像素的完整链路
2.1 为什么尺寸测量要先做标定,而不是直接拉卡尺
新手最容易犯的错,是打开一张图就拖 CogCaliper 去量边长,量完发现数值和卡尺对不上。问题出在像素当量没标定。VisionPro 里所有几何工具输出的都是像素单位,要转成毫米必须经过标定。常见做法有两种:用 CogCalibCheckerboard 做棋盘格标定,或者用已知尺寸的标准件做单轴标定。前者精度高、能校正镜头畸变,后者快但只适合视野小、畸变可忽略的场景。
我一般会先用棋盘格跑一次完整标定,拿到PixelSize(每像素多少毫米)和畸变参数,再把这个标定文件挂到后续所有测量工具上。标定板的格子尺寸要选得和视野匹配——视野 50mm 就用 2mm 格子,视野 200mm 就用 10mm 格子,格子太小在图像里糊成一片,角点检测会飘。
# VisionPro 标定流程(QuickBuild 操作路径) # 1. 打开 CogCalibCheckerboard 工具 # 2. 加载棋盘格图像,设置棋盘格行列数(如 7x7) # 3. 设置格子物理尺寸(如 2.0 mm) # 4. 运行标定,查看 RMS 误差,一般要求 < 0.1 pixel # 5. 保存 .cal 文件,后续工具通过 Calibration 属性挂载标定完成后,RMS 误差是第一个要看的指标。如果大于 0.2 pixel,说明棋盘格图像质量有问题——可能是光照不均、格子模糊、或者棋盘格没铺满视野。这时候别急着往下做,先重新拍图。
2.2 CogCaliper 找边的三个必调参数
CogCaliper 是尺寸测量的主力工具,它的原理是在一条搜索线上做灰度投影,找梯度最大的位置作为边缘。听起来简单,但三个参数不调好,结果能差出十几个像素。
第一个是EdgeThreshold(边缘阈值)。它决定多大的梯度才算边缘。设太高,弱边缘直接漏掉;设太低,噪声全被当成边缘。我一般先看图像的灰度剖面图,找到真实边缘的梯度值,然后取它的 60%~70% 作为阈值。
第二个是FilterHalfWidth(滤波半宽)。这个参数控制投影时的平滑窗口。宽度越大,抗噪越好,但边缘定位越模糊。对于干净的背光图像,设 2~3 就行;对于表面有纹理的金属件,得设到 5~7。
第三个是ContrastThreshold(对比度阈值)。它过滤掉对比度不足的边缘对。比如你找的是黑白交界,对比度至少得设到 10 以上,否则划痕也会被当成边。
# VisionPro 二次开发中通过 CogCaliper 测量尺寸的典型代码 from Cognex.VisionPro import CogCaliper, CogCaliperEdgeModeConstants caliper = CogCaliper() caliper.EdgeMode = CogCaliperEdgeModeConstants.Pair caliper.EdgeThreshold = 15 # 边缘梯度阈值,根据灰度剖面调整 caliper.FilterHalfWidth = 3 # 滤波半宽,噪声大时增大 caliper.ContrastThreshold = 10 # 对比度阈值,过滤弱边缘 caliper.RunParams.ExpectedEdgeCount = 2 # 期望找到一对边 # 在搜索线上运行,返回边缘对的距离(像素) result = caliper.Execute(image) if result: distance_px = result.Edges[1].Position - result.Edges[0].Position distance_mm = distance_px * pixel_size # pixel_size 来自标定 print(f"测量尺寸: {distance_mm:.3f} mm")这段代码里ExpectedEdgeCount设成 2 表示找一对边(比如零件的左右边界)。如果实际边缘数不对,工具会报错而不是给错误结果,这一点比很多开源库靠谱。pixel_size就是标定得到的像素当量,单位 mm/pixel。
2.3 尺寸显示:把测量结果叠回图像的正确姿势
测出数值只是第一步,产线上需要把结果实时显示在图像上,操作员才能判断。VisionPro 的 CogGraphicCollection 可以把卡尺线、边缘点、尺寸标注叠到原图上。常见做法是创建一个CogCaliperGraphics对象,设置好颜色和线宽,然后挂到显示控件的StaticGraphics属性上。
显示时有个细节:标注文字的位置要避开零件本体,否则会遮挡关键区域。我一般把文字放在卡尺线的延长线上,偏移 20~30 像素。另外,合格品用绿色、超差用红色,这个颜色逻辑要在代码里根据公差判断后动态设置,不能写死。
# 尺寸结果显示与颜色标注 from Cognex.VisionPro import CogGraphicCollection, CogCaliperGraphics graphics = CogGraphicCollection() caliper_graphics = CogCaliperGraphics(caliper) caliper_graphics.Color = CogColorConstants.Green if in_tolerance else CogColorConstants.Red caliper_graphics.LineWidthInScreenPixels = 2 graphics.Add(caliper_graphics) # 添加文字标注 text = CogTextGraphic() text.Text = f"{distance_mm:.3f} mm" text.Color = caliper_graphics.Color text.X = result.Edges[1].Position + 30 # 偏移避免遮挡 text.Y = result.Edges[1].PositionY graphics.Add(text) display.StaticGraphics.Add(graphics, "Measurement")这里in_tolerance是根据公差带算出来的布尔值。公差带通常来自图纸,比如 10±0.05mm,那上下限就是 9.95 和 10.05。判断逻辑要放在测量之后、显示之前,顺序不能乱。
3. 硬币统计:Blob 分析里那些不写出来就会踩的坑
3.1 硬币计数的本质是连通域分割,不是模板匹配
很多人一听「硬币统计」就想用 CogPMAlign 做模板匹配,一个硬币一个硬币去对。这在硬币数量少、姿态固定的时候能跑,但一旦硬币有重叠、有旋转,匹配就崩了。正确的做法是用CogBlob做连通域分析——把图像二值化后,每个硬币就是一个独立的连通区域,数区域个数就是数硬币。
Blob 分析的核心是阈值分割。硬币和背景的对比度要足够大,通常用背光照明,硬币是黑的、背景是亮的,或者反过来。如果对比度不够,二值化后硬币会粘连在一起,这时候就得先做形态学腐蚀把粘连断开,或者用分水岭算法。
# 硬币统计的 Blob 分析流程 from Cognex.VisionPro import CogBlob, CogBlobThresholdModeConstants blob = CogBlob() blob.ThresholdMode = CogBlobThresholdModeConstants.HardFixedThreshold blob.Threshold = 128 # 二值化阈值,根据直方图选双峰之间的谷底 blob.Polarity = CogBlobPolarityConstants.DarkBlobs # 硬币是暗的 blob.AreaThresholdLow = 500 # 最小面积,过滤噪声 blob.AreaThresholdHigh = 50000 # 最大面积,过滤背景 blob.RunParams.Area = True # 启用面积计算 result = blob.Execute(image) coin_count = result.Blobs.Count print(f"硬币数量: {coin_count}")Threshold这个值不能拍脑袋定。我一般会先跑一次 CogHistogram,看灰度直方图,找双峰之间的谷底作为阈值。如果直方图只有一个峰,说明光照太差,得重新打光。AreaThresholdLow设成 500 是经验值,对应大概直径 25 像素的硬币,实际要根据视野和硬币大小调整。
3.2 面积筛选与圆度筛选:把游戏币和真硬币分开
实际产线上,硬币堆里可能混着游戏币、垫圈、甚至碎屑。光靠面积筛选不够,还得加圆度(Circularity)判断。圆度的定义是4π × 面积 / 周长²,完美圆形是 1,越接近 1 越圆。硬币的圆度一般在 0.85 以上,游戏币可能只有 0.6。
# 圆度筛选,排除非圆形物体 for blob_item in result.Blobs: area = blob_item.Area perimeter = blob_item.Perimeter circularity = 4 * 3.14159 * area / (perimeter * perimeter) if circularity > 0.85: valid_coins.append(blob_item) else: rejected_items.append(blob_item) print(f"有效硬币: {len(valid_coins)}, 排除: {len(rejected_items)}")这里Perimeter是 Blob 的周长,VisionPro 会自动算。圆度阈值 0.85 不是绝对的,如果硬币有磨损、边缘不齐,可以降到 0.8。但降太低会把游戏币放进来,这个平衡点得根据实际样本调。
3.3 重叠硬币的分割:腐蚀与分水岭的取舍
硬币重叠是统计里最头疼的情况。两个硬币叠在一起,Blob 会当成一个区域,数量就少了一个。常见做法是先做形态学腐蚀,把粘连处断开,再统计。腐蚀的次数取决于粘连程度——粘得紧就多腐蚀几次,但腐蚀太多小硬币会消失。
# 形态学腐蚀断开粘连硬币 from Cognex.VisionPro import CogMorphology, CogMorphologyOperatorConstants morph = CogMorphology() morph.Operator = CogMorphologyOperatorConstants.Erode morph.KernelSizeX = 3 # 腐蚀核大小,粘连严重时增大到 5 morph.KernelSizeY = 3 morph.RunParams.Iterations = 2 # 腐蚀次数 eroded_image = morph.Execute(image) # 对 eroded_image 再做 Blob 分析腐蚀核大小和迭代次数是联动的。核 3x3 迭代 2 次,相当于腐蚀掉 2 个像素的边界。如果硬币直径 50 像素,腐蚀 2 次还能保留大部分面积;如果直径只有 20 像素,腐蚀 2 次就没了。所以小硬币场景要慎用腐蚀,改用分水岭或者距离变换。
注意:腐蚀之后统计出的面积会比实际小,如果后续还要用面积做筛选,得把腐蚀掉的量补回来,或者直接用腐蚀前的面积。
4. 骰子点数统计:从斑点计数到模式匹配的选型对比
4.1 骰子点数的两种解法:Blob 计数 vs 模板匹配
骰子点数和硬币统计看着像,其实不一样。硬币是数「有几个物体」,骰子是数「一个物体上有几个点」。解法有两条路:一是用 Blob 找出每个点,数点的个数;二是用 CogPMAlign 匹配 1~6 点的模板,看哪个模板得分最高。
Blob 计数的优点是快、参数少,缺点是骰子点小、对比度低的时候容易漏。模板匹配的优点是鲁棒,缺点是每个面都要建模板,而且骰子旋转角度大时匹配得分会掉。我一般先用 Blob 试,如果点数识别率低于 95%,再上模板匹配。
# 骰子点数 Blob 计数 blob = CogBlob() blob.ThresholdMode = CogBlobThresholdModeConstants.HardFixedThreshold blob.Threshold = 100 blob.Polarity = CogBlobPolarityConstants.DarkBlobs # 骰子点是暗的 blob.AreaThresholdLow = 20 # 点很小,面积下限要低 blob.AreaThresholdHigh = 500 blob.RunParams.Area = True result = blob.Execute(dice_image) dot_count = result.Blobs.Count print(f"骰子点数: {dot_count}")AreaThresholdLow设 20 是因为骰子点在高分辨率图像里可能只有几十个像素。设太高会漏点,设太低会把噪点算进去。这个值最好用 CogHistogram 看一下点的面积分布再定。
4.2 模板匹配建库:1 到 6 点的模板怎么拍
如果决定用模板匹配,建库是关键。每个点数(1~6)至少要拍 5~10 张不同角度、不同光照的样本,用 CogPMAlign 训练。训练时要注意掩膜的设置——把骰子本体以外的区域屏蔽掉,只保留点所在的区域,这样匹配时不会被背景干扰。
# CogPMAlign 模板训练 from Cognex.VisionPro import CogPMAlign, CogPMAlignPattern pattern = CogPMAlignPattern() pattern.TrainImage = dice_image pattern.Mask = dot_region_mask # 只保留点区域 pattern.AngleStart = -180 # 允许全角度旋转 pattern.AngleEnd = 180 pattern.ScaleStart = 0.9 # 允许 ±10% 缩放 pattern.ScaleEnd = 1.1 pmalign = CogPMAlign() pmalign.Pattern = pattern pmalign.RunParams.AcceptThreshold = 0.7 # 匹配得分阈值 pmalign.RunParams.NumberToFind = 1 result = pmalign.Execute(test_image) if result: print(f"匹配到点数: {result.Item(0).Score:.2f}")AcceptThreshold设 0.7 是起点,实际要根据误判率调。如果漏检多就降到 0.6,如果误检多就升到 0.8。AngleStart和AngleEnd设 -180 到 180 表示允许任意旋转,但计算量会大,如果骰子姿态固定可以缩小范围。
4.3 点数识别的后处理:排除非骰子区域
实际图像里不可能只有骰子,还有桌面、手指、阴影。这些区域也可能被 Blob 或模板匹配误判。后处理要做两件事:一是位置约束,只统计骰子所在 ROI 内的结果;二是尺寸约束,点的面积和间距要在合理范围内。
# 后处理:位置与尺寸约束 valid_dots = [] for blob_item in result.Blobs: cx = blob_item.CenterOfMassX cy = blob_item.CenterOfMassY # 只保留 ROI 内的点 if roi_x_min < cx < roi_x_max and roi_y_min < cy < roi_y_max: # 点的面积要在合理范围 if 20 < blob_item.Area < 500: valid_dots.append(blob_item) dot_count = len(valid_dots)ROI 的坐标来自骰子检测的结果——先用一个粗定位工具(比如 Blob 找最大区域)确定骰子位置,再在这个位置周围画 ROI。这个两步走的策略比全图搜索稳得多。
5. 避坑与排查:这三个案例里最容易翻车的五个地方
5.1 标定文件没挂上,测量值全是像素
现象:CogCaliper 测出来的数值和实际尺寸差了一个数量级,比如实际 10mm 测出来 500。
原因:标定文件没挂到工具上,或者挂错了。VisionPro 里每个几何工具都有Calibration属性,不设置的话默认输出像素值。
解决:检查工具的Calibration属性是否指向正确的.cal文件。可以在 QuickBuild 里右键工具 → Properties → Calibration 确认。二次开发时要在代码里显式赋值:caliper.Calibration = calib。
5.2 Blob 阈值设太高,小硬币直接消失
现象:硬币统计结果比实际少,小面额硬币一个都数不到。
原因:Threshold设太高,小硬币的灰度值没超过阈值,二值化后被当成背景。
解决:用 CogHistogram 看灰度分布,找双峰之间的谷底。如果小硬币和大硬币灰度差异大,考虑用自适应阈值或者分区域设不同阈值。VisionPro 的 CogBlob 支持RelativeThreshold模式,可以基于局部对比度分割。
5.3 骰子点粘连,Blob 数出来少一个点
现象:6 点的骰子只数出 5 个点,或者 4 点变 3 点。
原因:两个点靠得太近,二值化后连成一个区域。
解决:先做形态学开运算(先腐蚀后膨胀)断开粘连,再统计。或者降低AreaThresholdHigh,把粘连的大区域排除掉,但这样会漏掉正常的大点。更好的做法是用距离变换 + 分水岭,但 VisionPro 里没有现成的分水岭工具,得用 CogMorphology 组合实现。
5.4 模板匹配得分普遍偏低,找不到骰子
现象:CogPMAlign 的匹配得分都在 0.5 以下,AcceptThreshold设 0.7 时一个都匹配不上。
原因:模板训练时的光照条件和实际图像差异太大,或者掩膜没设好,背景干扰了匹配。
解决:重新拍训练样本,确保光照和实际产线一致。检查掩膜是否覆盖了非点区域。如果骰子有反光,考虑用偏振片或者调整光源角度。AcceptThreshold可以先降到 0.5 看能不能匹配上,再逐步提高。
5.5 显示图形闪烁,操作员看不清
现象:测量结果在界面上闪烁,或者刷新时旧图形没清掉,叠了一堆。
原因:每次运行都往StaticGraphics里 Add 新图形,但没有清空旧的。
解决:在每次运行前调用display.StaticGraphics.Clear(),或者用display.StaticGraphics.Add(graphics, "Measurement")的第二个参数作为 key,同名 key 会覆盖旧图形。二次开发时要注意线程安全,UI 刷新和工具运行要在同一个线程或者加锁。
6. 进阶技巧:用 C# 联合 VisionPro 做硬触发采集与实时统计
6.1 硬触发采集的配置要点
产线上不可能手动点「运行」,得靠传感器硬触发。VisionPro 支持通过 CogFrameGrabber 的触发输入引脚接收硬触发信号。配置时要注意三个参数:触发沿(上升沿还是下降沿)、去抖时间(防止机械抖动误触发)、超时时间(触发后多久没图像就报错)。
// C# 中配置硬触发采集 CogFrameGrabberGigE frameGrabber = new CogFrameGrabberGigE(); frameGrabber.TriggerMode = CogFrameGrabberTriggerModeConstants.Hardware; frameGrabber.TriggerEdge = CogFrameGrabberTriggerEdgeConstants.RisingEdge; frameGrabber.TriggerDebounceTime = 100; // 去抖 100 微秒 frameGrabber.TriggerTimeout = 5000; // 超时 5 秒 frameGrabber.Start();TriggerDebounceTime设 100 微秒是经验值,如果传感器信号干净可以设更小。TriggerTimeout设 5000 毫秒,超过这个时间没收到图像就抛异常,避免程序卡死。
6.2 实时统计的线程模型
硬触发采集是异步的,图像回调在采集线程里执行,而 UI 更新在主线程。如果直接在回调里更新 UI,会报跨线程异常。常见做法是用Invoke或者BeginInvoke把结果抛回主线程。
// 采集回调中更新 UI private void OnImageAcquired(object sender, CogFrameGrabberImageEventArgs e) { CogImage8Grey image = e.Image as CogImage8Grey; // 在采集线程里跑工具 CogBlobResult blobResult = blob.Execute(image); int count = blobResult.Blobs.Count; // 抛回主线程更新 UI this.BeginInvoke(new Action(() => { lblCount.Text = $"硬币数量: {count}"; display.Image = image; display.StaticGraphics.Clear(); display.StaticGraphics.Add(blobResult.CreateGraphics(), "Blob"); })); }BeginInvoke是异步的,不会阻塞采集线程。如果统计逻辑复杂,可以在回调里只做工具运行,把结果存到队列里,另开一个线程做 UI 更新。这样采集和显示解耦,不会因为 UI 卡顿丢帧。
6.3 统计结果的持久化与追溯
产线上统计结果要存数据库,方便追溯。我一般用 SQLite 存每条记录:时间戳、图像路径、测量值、统计数量、判定结果。写入时用事务批量提交,避免频繁 IO。
-- 统计结果表结构 CREATE TABLE inspection_record ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, image_path TEXT, coin_count INTEGER, dice_value INTEGER, dimension_mm REAL, result TEXT -- 'PASS' 或 'FAIL' );result字段根据公差和统计规则算出来。比如硬币数量必须在 10~12 之间,骰子点数必须和预期一致,尺寸必须在公差带内。这些规则写在配置文件里,换产品时只改配置不改代码。
6.4 我踩过的最大的坑:忘记释放 CogImage
VisionPro 的 CogImage 是托管对象,但底层有非托管内存。如果采集频率高、图像不释放,内存会一直涨,跑几个小时就崩。血泪经验是:每处理完一帧,显式调用image.Dispose(),或者用using包起来。另外,CogGraphicCollection里的图形对象也要在清空后释放,否则同样泄漏。
using (CogImage8Grey image = e.Image as CogImage8Grey) { // 处理图像 var result = blob.Execute(image); // 处理完自动释放 }这个using写法我后来在所有采集回调里都加上了,内存曲线立刻平稳。如果你也在做 VisionPro 二次开发,这条习惯能省掉很多半夜排查内存泄漏的时间。
希望帮到你。
本文还有配套的精品资源,点击获取