当前位置: 首页 > news >正文

C#/Halcon:简单介绍在AOI设备软件中的应用

前言

C#目前在国内的工业场景中还是有一定的应用的,使用的框架有Winform、WPF,很多会结合Halcon、OpenCV做一些偏视觉类的应用。目前据我所经历的在半导体封装设备、AOI设备中使用都挺多的。

今天跟大家简单分享一下C#/Halcon在AOI设备软件中的应用。

AOI(Automated Optical Inspection,自动光学检测)是一种利用计算机视觉技术和光学设备对产品进行自动化检测的技术。

看起来AOI算是一种技术,那AOI设备就是就是使用这种技术的一套设备,看起来就像下面这样:

在这个设备上有很多硬件,比如相机、光源等等,AOI设备软件就是用来控制这些硬件,执行检测操作等等部署在这个设备上的软件。

现在我们大概了解了什么是AOI/AOI设备/AOI设备软件了。

现在就需要知道我们拿AOI这个技术用来干嘛,AOI一般可以用来检测产品的缺陷。这个技术又是如何检测的呢?接下来我们通过一个简单的例子了解这个过程。

简单的检测过程

比如我们有一个标准的东西,比如说在设计时就是这样设计的,还有一个实际的东西,也就是最终生产出来的东西。

比如现在这个标准的东西长这样:

然后实际的东西长这样:

我们可以很直观的看到矩形的一角与设计图纸不一样,不一样的地方大概率就是出问题的地方。那么使用AOI进行检测的第一个步骤就是先把设计图纸与实物图对齐,因为实际上它们大概率坐标系不同。

现在在读取了这两张图片之后,先让它们的坐标系变得不一样。

* 1. 读取图像 read_image (Test, 'F:/Users/user/Desktop/Halcon脚本/333.png') read_image (Test2, 'F:/Users/user/Desktop/Halcon脚本/444.png') * 2. 将设计图纸缩小2倍,模拟图纸与实物尺寸不一致 zoom_image_factor (Test, TestScaled, 0.5, 0.5, 'constant')

然后现在我们就要想怎么样让这两张图对齐?

这时候就需要用到仿射变换矩阵了,它是个什么东西呢?

先来过一下它的定义:

仿射变换矩阵是一种线性代数工具,用于描述二维/三维空间中的仿射变换。仿射变换是保持直线平行性和直线比例的几何变换——即变换后,直线仍为直线,平行线仍平行,但长度和角度可能改变。

可以理解为就是平面对平面的变换,也就是在这张图上的某个点使用这个矩阵之后就会变换到另一张图上的对应点。

那么现在的问题来到了怎么计算得到这个仿射变换矩阵呢?

对AOI熟悉的朋友可能会经常听到定位点这个词,我们只要找到这两张图片上3个对应点就可以计算了。而具体如何计算要根据实际情况,在这个例子中通过对图像进行网格拆分然后寻找封闭轮廓选择圆度比较高的来当定位点。

* 3. 对设计图纸查找封闭轮廓 edges_sub_pix (TestScaled, Edges1, 'canny', 1.5, 10, 20) select_shape_xld (Edges1, ClosedEdges1, 'contlength', 'and', 50, 99999) * 4. 对实物图查找封闭轮廓 edges_sub_pix (Test2, Edges2, 'canny', 1.5, 10, 20) select_shape_xld (Edges2, ClosedEdges2, 'contlength', 'and', 50, 99999)

edges_sub_pix从图像中提取亚像素级精度的边缘轮廓。

select_shape_xld根据轮廓的形状特征对 XLD 轮廓进行筛选,只保留满足条件的轮廓。

现在设计图纸变成了这样:

实物图变成了这样:

接下来选点这一步简单了解一下就行,实际上要根据实际情况进行判断,很难找到全部适用的情况。

* ======================================== * 5 & 6. 从两幅图中选定位点(2×3网格,设计图优先,实物图就近匹配) * ======================================== get_image_size (TestScaled, Width1, Height1) get_image_size (Test2, Width2, Height2) count_obj (ClosedEdges1, NumEdges1) count_obj (ClosedEdges2, NumEdges2) GridRows := 2 GridCols := 3 CellHeight1 := Height1 / GridRows CellWidth1 := Width1 / GridCols CellHeight2 := Height2 / GridRows CellWidth2 := Width2 / GridCols MinCircularity := 0.6 RowDesign := [] ColDesign := [] RowReal := [] ColReal := [] for gr := 0 to GridRows - 1 by 1 for gc := 0 to GridCols - 1 by 1 * ============================================ * 第一步:在设计图该网格中找圆度最高的特征点 * ============================================ RowMin1 := gr * CellHeight1 ColMin1 := gc * CellWidth1 RowMax1 := (gr + 1) * CellHeight1 ColMax1 := (gc + 1) * CellWidth1 BestCircularity1 := 0 BestRow1 := -1 BestCol1 := -1 for i := 1 to NumEdges1 by 1 select_obj (ClosedEdges1, EdgeSelected, i) area_center_xld (EdgeSelected, Area, Row, Column, PointOrder) if (Row >= RowMin1 and Row < RowMax1 and Column >= ColMin1 and Column < ColMax1) gen_region_contour_xld (EdgeSelected, Region, 'filled') circularity (Region, Circularity) if (Circularity > BestCircularity1 and Circularity >= MinCircularity) BestCircularity1 := Circularity BestRow1 := Row BestCol1 := Column endif endif endfor * 设计图该网格没找到,跳过 if (BestRow1 < 0) continue endif * ============================================ * 第二步:推算实物图中的预期位置 * 设计图点在网格内的相对比例 → 映射到实物图网格 * ============================================ RelRow := (BestRow1 - RowMin1) / CellHeight1 RelCol := (BestCol1 - ColMin1) / CellWidth1 RowMin2 := gr * CellHeight2 ColMin2 := gc * CellWidth2 RowMax2 := (gr + 1) * CellHeight2 ColMax2 := (gc + 1) * CellWidth2 ExpectedRow2 := RowMin2 + RelRow * CellHeight2 ExpectedCol2 := ColMin2 + RelCol * CellWidth2 * ============================================ * 第三步:在实物图该网格中找圆度>0.6且距离预期位置最近的特征点 * ============================================ BestDist2 := 999999 BestRow2 := -1 BestCol2 := -1 for i := 1 to NumEdges2 by 1 select_obj (ClosedEdges2, EdgeSelected, i) area_center_xld (EdgeSelected, Area, Row, Column, PointOrder) if (Row >= RowMin2 and Row < RowMax2 and Column >= ColMin2 and Column < ColMax2) gen_region_contour_xld (EdgeSelected, Region, 'filled') circularity (Region, Circularity) if (Circularity >= MinCircularity) Dist := sqrt((Row - ExpectedRow2) * (Row - ExpectedRow2) + (Column - ExpectedCol2) * (Column - ExpectedCol2)) if (Dist < BestDist2) BestDist2 := Dist BestRow2 := Row BestCol2 := Column endif endif endif endfor * ============================================ * 第四步:两幅图都找到才添加点对 * ============================================ if (BestRow1 >= 0 and BestRow2 >= 0) RowDesign := [RowDesign, BestRow1] ColDesign := [ColDesign, BestCol1] RowReal := [RowReal, BestRow2] ColReal := [ColReal, BestCol2] endif endfor endfor

反正通过这一步我们获取到了设计图与实物图上对应的一组点,现在可以通过这些点来计算仿射变换矩阵了。

* 7. 检查定位点数量是否足够(仿射变换至少需要3对点) NumPoints := |RowDesign| if (NumPoints < 3) disp_message (3600, '定位点不足(需要至少3对,当前' + NumPoints + '对),无法计算变换矩阵', 'window', 12, 12, 'red', 'true') stop () endif * 8. 计算仿射变换矩阵(从设计图纸坐标 → 实物图坐标) vector_to_hom_mat2d (RowDesign, ColDesign, RowReal, ColReal, HomMat2D)

然后就可以使用仿射变换矩阵将设计图纸与实物图对齐,或者使用逆矩阵实物图与设计图对齐,反正现在可以实现两者的对齐了。

* 9. 将设计图纸通过仿射变换对齐到实物图 affine_trans_image (TestScaled, TestAligned, HomMat2D, 'constant', 'true')

可以看下我们的定位点选的情况怎么样。

* 10. 在设计图上绘制定位点十字标记 gen_cross_contour_xld (Cross1, RowDesign, ColDesign, 15, 0.785398) dev_display (TestScaled) dev_display (Cross1) * 11. 在实物图上绘制定位点十字标记 gen_cross_contour_xld (Cross2, RowReal, ColReal, 15, 0.785398) dev_display (Test2) dev_display (Cross2)

设计图定位点坐标:

实物图定位点坐标:

只要我们定位点选的好,仿射变换矩阵一般效果就不错。

现在得到了仿射变换矩阵,现在就可以开始检测了,今天介绍一下最简单的检测,实际的检测肯定没有这么简单。

* ======================================== * 13. 缺陷检测:对齐后的设计图与实物图做差 + 轮廓掩膜排除 * ======================================== * 13.1 裁剪对齐后的设计图,使其与实物图尺寸一致 affine_trans_point_2d (HomMat2D, 0, 0, OffsetRow, OffsetCol) get_image_size (Test2, W2, H2) crop_part (TestAligned, TestAlignedCrop, OffsetRow, OffsetCol, W2, H2) * 13.2 将两幅图转为灰度 rgb1_to_gray (TestAlignedCrop, GrayDesign) rgb1_to_gray (Test2, GrayReal) * 13.3 计算绝对差值 abs_diff_image (GrayReal, GrayDesign, DiffImage, 1) * ======================================== * 13.4 提取设计图中的正常轮廓区域(作为掩膜) * ======================================== * 对设计图做边缘检测 + 膨胀,生成轮廓掩膜 edges_sub_pix (TestAlignedCrop, DesignEdges, 'canny', 1.5, 10, 20) gen_region_contour_xld (DesignEdges, DesignEdgeRegion, 'margin') * 膨胀轮廓区域(覆盖轮廓线宽 + 配准误差范围) dilation_circle (DesignEdgeRegion, DesignEdgeDilated, 5.0) * 合并所有轮廓为一个掩膜 union1 (DesignEdgeDilated, ContourMask) * ======================================== * 13.5 差分结果中排除正常轮廓区域 * ======================================== * 阈值分割提取差异 threshold (DiffImage, DiffRegion, 30, 255) * 从差异区域中减去轮廓掩膜 difference (DiffRegion, ContourMask, DefectCandidates) * ======================================== * 13.6 形态学处理 + 面积过滤 * ======================================== * 填充小孔洞 fill_up (DefectCandidates, DefectFilled) * 连通域分割 connection (DefectFilled, DefectConnected) * 去除小噪声 + 面积过滤 select_shape (DefectConnected, DefectRegions, 'area', 'and', 200, 5000) * ======================================== * 13.7 显示结果(用圆标记缺陷位置) * ======================================== * 显示实物图 dev_display (Test2) * 显示被排除的轮廓区域(绿色,半透明) dev_set_draw ('margin') dev_set_line_width (1) dev_set_color ('green') dev_display (DesignEdgeDilated) * 用圆标记缺陷位置 dev_set_draw ('margin') dev_set_line_width (2) dev_set_color ('red') * 计算每个缺陷的等效圆半径,用于绘制 smallest_circle (DefectRegions, DefectCenterRows, DefectCenterCols, DefectRadii) * 绘制圆标记 gen_circle_contour_xld (DefectCircles, DefectCenterRows, DefectCenterCols, DefectRadii*1.5, 0, 6.28318, 'positive', 1) dev_display (DefectCircles) * 在圆心处画十字 gen_cross_contour_xld (DefectCrosses, DefectCenterRows, DefectCenterCols, 15, 0.785398) dev_set_color ('yellow') dev_display (DefectCrosses)

我们来看这一个过程,首先是灰度化之后做差。

然后我们发现直接做差并不全是缺陷区域,也有一些是正常的轮廓,然后就把轮廓当掩膜。

减去掩膜区域之后就会得到真正的缺陷位置。

然后我们再标注出这个缺陷位置。

这只是简化的例子,实际上可能还要再做一些操作,找到缺陷之后还需要判断是什么缺陷,也要结合深度学习去训练模型去做一些过滤之类的。

当我们有Halcon脚本之后再转化为C#问题就不大了,现在有AI了一般交给AI就可以了,篇幅有限,转化为WPF应用后面再进行分享。

以上是AOI检测流程的一个简单分享与自己的学习记录,希望对你有所帮助。

http://www.jsqmd.com/news/875526/

相关文章:

  • 基于图元随机游走的网络嵌入:提升同质性与下游任务性能
  • 量子机器学习采样加速:热力学视角下的双向量子制冷器
  • 量子机器学习在消费电子异常检测中的应用与实战解析
  • Claude Code-入门篇-Claude-Code基础与环境配置
  • 为Claude Code配置Taotoken后端,告别封号与Token不足困扰
  • AI Agent安全治理框架缺失导致客户数据泄露?(Gartner 2024新评估模型首次落地解读)
  • 图数据管理与图机器学习:双向赋能的技术融合与实战解析
  • 含光热电站的冷、热、电综合能源系统优化调度【节点网络】附Matlab代码
  • 【芯片测试】:7. Action 与 Operating Sequence
  • 新手避坑指南:在Ubuntu 22.04上从零搭建Plexe-SUMO自动驾驶仿真环境
  • 年薪50万必备技能:.NET云原生架构实战,3分钟部署全球可用的微服务
  • GE 和 Runtime:不是上下游,是协同决策
  • Midjourney --style raw + 调色板协同失效?3步诊断流程+4类硬件级色彩配置冲突解决方案
  • 反应坐标映射:非马尔可夫开放量子系统的高效模拟方法
  • B物理反常的全局拟合:有效场论与机器学习解析新物理信号
  • 神经材质:NeRF之后,下一代数字内容的“皮肤”革命
  • Harness Engineering:麻绳还是马绳
  • SVM在频繁模式挖掘中的应用:从高维稀疏数据中提取判别性关联规则
  • Leslie矩阵建模:从种群动力学到捕食竞争与机器学习拟合
  • 从《原神》到《黑神话》都在用的AI Agent中间件:轻量级推理框架v0.9.3内部测试版首次泄露(仅限前500名开发者)
  • 别急着重启!深入理解Ubuntu 22.04的needrestart:守护进程、库文件与系统更新背后的原理
  • Telnet与SSH协议安全本质对比:从明文传输到公钥认证
  • 神经阴影:当AI学会“画影子”,实时渲染的下一个突破口
  • KNO标度律与粒子多重数:从QCD喷注结构到夸克-胶子鉴别的理论推导
  • 从语义网到神经符号系统:知识图谱与LLM融合实战指南
  • 为什么你的MJ图总像“老胶片过曝”?揭秘ISO模拟算法缺陷,5种降颗粒参数组合实测对比(含LUT映射表)
  • Spark Transformer:稀疏激活优化与计算效率提升
  • 别再手动处理表格了!用PyQt6的QTableWidget自定义右键菜单,5分钟搞定复制粘贴与格式设置
  • 基于共享潜在空间的贝叶斯优化:解决异构算法超参数联合选择难题
  • ml_edm:基于成本敏感的时间序列早期分类Python工具包详解