新增QFN引脚空洞率检测处理器及本地化资源
This commit is contained in:
@@ -2008,4 +2008,102 @@ Reprojection error: {1:F4} pixels</value>
|
||||
<value>Histogram — No data</value>
|
||||
<comment>ImageHistogramControl - Placeholder text when no image data</comment>
|
||||
</data>
|
||||
|
||||
<!-- QfnLeadPadVoidProcessor -->
|
||||
<data name="QfnLeadPadVoidProcessor_Name" xml:space="preserve">
|
||||
<value>QFN Lead Pad Void Detection</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Description" xml:space="preserve">
|
||||
<value>Automatically detect QFN lead pads and measure void rate per pad (two-step: locate pads → detect voids)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode" xml:space="preserve">
|
||||
<value>ROI Mode</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode_Desc" xml:space="preserve">
|
||||
<value>None: Full image; Polygon: Polygon ROI (select lead pad area)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize" xml:space="preserve">
|
||||
<value>Pad Blur Size</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize_Desc" xml:space="preserve">
|
||||
<value>Gaussian blur kernel size for pad detection (odd number)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow" xml:space="preserve">
|
||||
<value>Pad Threshold Low</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow_Desc" xml:space="preserve">
|
||||
<value>Lower gray threshold for pad segmentation (pads are dark regions)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh" xml:space="preserve">
|
||||
<value>Pad Threshold High</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>Upper gray threshold for pad segmentation</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel" xml:space="preserve">
|
||||
<value>Morph Kernel Size</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel_Desc" xml:space="preserve">
|
||||
<value>Closing kernel size to fill small holes inside pads</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea" xml:space="preserve">
|
||||
<value>Min Pad Area</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea_Desc" xml:space="preserve">
|
||||
<value>Minimum pixel area to be recognized as a lead pad (filter noise)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea" xml:space="preserve">
|
||||
<value>Max Pad Area</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea_Desc" xml:space="preserve">
|
||||
<value>Maximum pixel area for a lead pad (exclude thermal pad)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin" xml:space="preserve">
|
||||
<value>Min Aspect Ratio</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin_Desc" xml:space="preserve">
|
||||
<value>Minimum aspect ratio for lead pads (QFN pads are elongated, ratio > 1)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow" xml:space="preserve">
|
||||
<value>Void Threshold Low</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow_Desc" xml:space="preserve">
|
||||
<value>Lower gray threshold for void detection (voids are bright regions)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh" xml:space="preserve">
|
||||
<value>Void Threshold High</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>Upper gray threshold for void detection</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea" xml:space="preserve">
|
||||
<value>Min Void Area</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea_Desc" xml:space="preserve">
|
||||
<value>Areas smaller than this are treated as noise (pixels)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius" xml:space="preserve">
|
||||
<value>Void Merge Radius</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius_Desc" xml:space="preserve">
|
||||
<value>Dilation radius to merge adjacent voids (0 = no merge)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit" xml:space="preserve">
|
||||
<value>Void Rate Limit (%)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit_Desc" xml:space="preserve">
|
||||
<value>Max allowed void rate per lead pad (default 50%, ref IPC-7095)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea" xml:space="preserve">
|
||||
<value>Min Qualified Pad Area</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea_Desc" xml:space="preserve">
|
||||
<value>Pads with area below this value are marked as FAIL (insufficient solder)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness" xml:space="preserve">
|
||||
<value>Thickness</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness_Desc" xml:space="preserve">
|
||||
<value>Contour drawing line thickness</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2041,4 +2041,102 @@
|
||||
<value>直方图 — 暂无数据</value>
|
||||
<comment>ImageHistogramControl - 无图像输入时的提示文本 | Placeholder text when no image data</comment>
|
||||
</data>
|
||||
|
||||
<!-- QfnLeadPadVoidProcessor -->
|
||||
<data name="QfnLeadPadVoidProcessor_Name" xml:space="preserve">
|
||||
<value>QFN引脚空洞率检测</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Description" xml:space="preserve">
|
||||
<value>自动检测QFN引脚焊点并逐引脚测量空洞率(两步法:定位引脚 → 检测空洞)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode" xml:space="preserve">
|
||||
<value>ROI模式</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode_Desc" xml:space="preserve">
|
||||
<value>None: 全图检测; Polygon: 多边形ROI(框选引脚区域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize" xml:space="preserve">
|
||||
<value>引脚模糊核</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize_Desc" xml:space="preserve">
|
||||
<value>引脚定位时的高斯模糊核大小(奇数)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow" xml:space="preserve">
|
||||
<value>引脚阈值下限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow_Desc" xml:space="preserve">
|
||||
<value>引脚分割灰度下限(焊点为暗区域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh" xml:space="preserve">
|
||||
<value>引脚阈值上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>引脚分割灰度上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel" xml:space="preserve">
|
||||
<value>形态学核大小</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel_Desc" xml:space="preserve">
|
||||
<value>闭运算核大小,用于填充引脚内部小孔洞</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea" xml:space="preserve">
|
||||
<value>引脚最小面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea_Desc" xml:space="preserve">
|
||||
<value>识别为引脚焊点的最小像素面积(过滤噪声)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea" xml:space="preserve">
|
||||
<value>引脚最大面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea_Desc" xml:space="preserve">
|
||||
<value>识别为引脚焊点的最大像素面积(排除散热焊盘)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin" xml:space="preserve">
|
||||
<value>最小长宽比</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin_Desc" xml:space="preserve">
|
||||
<value>引脚最小长宽比(QFN引脚为长条形,长宽比>1)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow" xml:space="preserve">
|
||||
<value>空洞阈值下限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow_Desc" xml:space="preserve">
|
||||
<value>空洞检测灰度下限(空洞为亮区域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh" xml:space="preserve">
|
||||
<value>空洞阈值上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>空洞检测灰度上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea" xml:space="preserve">
|
||||
<value>最小空洞面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea_Desc" xml:space="preserve">
|
||||
<value>小于此面积的区域视为噪点忽略(像素)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius" xml:space="preserve">
|
||||
<value>空洞合并半径</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius_Desc" xml:space="preserve">
|
||||
<value>相邻空洞合并的膨胀半径(0=不合并)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit" xml:space="preserve">
|
||||
<value>空洞率限值(%)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit_Desc" xml:space="preserve">
|
||||
<value>单引脚最大允许空洞率(默认50%,参考IPC-7095)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea" xml:space="preserve">
|
||||
<value>引脚合格面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea_Desc" xml:space="preserve">
|
||||
<value>引脚面积低于此值判定为不合格(焊料不足)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness" xml:space="preserve">
|
||||
<value>线条粗细</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness_Desc" xml:space="preserve">
|
||||
<value>轮廓绘制线条粗细</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -2002,4 +2002,102 @@
|
||||
<value>直方图 — 暂无数据</value>
|
||||
<comment>ImageHistogramControl - 无图像输入时的提示文本 | Placeholder text when no image data</comment>
|
||||
</data>
|
||||
|
||||
<!-- QfnLeadPadVoidProcessor -->
|
||||
<data name="QfnLeadPadVoidProcessor_Name" xml:space="preserve">
|
||||
<value>QFN引脚空洞率检测</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Description" xml:space="preserve">
|
||||
<value>自动检测QFN引脚焊点并逐引脚测量空洞率(两步法:定位引脚 → 检测空洞)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode" xml:space="preserve">
|
||||
<value>ROI模式</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode_Desc" xml:space="preserve">
|
||||
<value>None: 全图检测; Polygon: 多边形ROI(框选引脚区域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize" xml:space="preserve">
|
||||
<value>引脚模糊核</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize_Desc" xml:space="preserve">
|
||||
<value>引脚定位时的高斯模糊核大小(奇数)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow" xml:space="preserve">
|
||||
<value>引脚阈值下限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow_Desc" xml:space="preserve">
|
||||
<value>引脚分割灰度下限(焊点为暗区域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh" xml:space="preserve">
|
||||
<value>引脚阈值上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>引脚分割灰度上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel" xml:space="preserve">
|
||||
<value>形态学核大小</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel_Desc" xml:space="preserve">
|
||||
<value>闭运算核大小,用于填充引脚内部小孔洞</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea" xml:space="preserve">
|
||||
<value>引脚最小面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea_Desc" xml:space="preserve">
|
||||
<value>识别为引脚焊点的最小像素面积(过滤噪声)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea" xml:space="preserve">
|
||||
<value>引脚最大面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea_Desc" xml:space="preserve">
|
||||
<value>识别为引脚焊点的最大像素面积(排除散热焊盘)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin" xml:space="preserve">
|
||||
<value>最小长宽比</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin_Desc" xml:space="preserve">
|
||||
<value>引脚最小长宽比(QFN引脚为长条形,长宽比>1)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow" xml:space="preserve">
|
||||
<value>空洞阈值下限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow_Desc" xml:space="preserve">
|
||||
<value>空洞检测灰度下限(空洞为亮区域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh" xml:space="preserve">
|
||||
<value>空洞阈值上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>空洞检测灰度上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea" xml:space="preserve">
|
||||
<value>最小空洞面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea_Desc" xml:space="preserve">
|
||||
<value>小于此面积的区域视为噪点忽略(像素)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius" xml:space="preserve">
|
||||
<value>空洞合并半径</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius_Desc" xml:space="preserve">
|
||||
<value>相邻空洞合并的膨胀半径(0=不合并)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit" xml:space="preserve">
|
||||
<value>空洞率限值(%)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit_Desc" xml:space="preserve">
|
||||
<value>单引脚最大允许空洞率(默认50%,参考IPC-7095)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea" xml:space="preserve">
|
||||
<value>引脚合格面积</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea_Desc" xml:space="preserve">
|
||||
<value>引脚面积低于此值判定为不合格(焊料不足)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness" xml:space="preserve">
|
||||
<value>线条粗细</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness_Desc" xml:space="preserve">
|
||||
<value>轮廓绘制线条粗细</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -1885,4 +1885,102 @@
|
||||
<value>直方圖 — 暫無資料</value>
|
||||
<comment>ImageHistogramControl - 無圖像輸入時的提示文字 | Placeholder text when no image data</comment>
|
||||
</data>
|
||||
|
||||
<!-- QfnLeadPadVoidProcessor -->
|
||||
<data name="QfnLeadPadVoidProcessor_Name" xml:space="preserve">
|
||||
<value>QFN引腳空洞率檢測</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Description" xml:space="preserve">
|
||||
<value>自動檢測QFN引腳焊點並逐引腳測量空洞率(兩步法:定位引腳 → 檢測空洞)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode" xml:space="preserve">
|
||||
<value>ROI模式</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_RoiMode_Desc" xml:space="preserve">
|
||||
<value>None: 全圖檢測; Polygon: 多邊形ROI(框選引腳區域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize" xml:space="preserve">
|
||||
<value>引腳模糊核</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadBlurSize_Desc" xml:space="preserve">
|
||||
<value>引腳定位時的高斯模糊核大小(奇數)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow" xml:space="preserve">
|
||||
<value>引腳閾值下限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdLow_Desc" xml:space="preserve">
|
||||
<value>引腳分割灰度下限(焊點為暗區域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh" xml:space="preserve">
|
||||
<value>引腳閾值上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>引腳分割灰度上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel" xml:space="preserve">
|
||||
<value>形態學核大小</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadMorphKernel_Desc" xml:space="preserve">
|
||||
<value>閉運算核大小,用於填充引腳內部小孔洞</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea" xml:space="preserve">
|
||||
<value>引腳最小面積</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinPadArea_Desc" xml:space="preserve">
|
||||
<value>識別為引腳焊點的最小像素面積(過濾噪聲)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea" xml:space="preserve">
|
||||
<value>引腳最大面積</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MaxPadArea_Desc" xml:space="preserve">
|
||||
<value>識別為引腳焊點的最大像素面積(排除散熱焊盤)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin" xml:space="preserve">
|
||||
<value>最小長寬比</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_PadAspectRatioMin_Desc" xml:space="preserve">
|
||||
<value>引腳最小長寬比(QFN引腳為長條形,長寬比>1)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow" xml:space="preserve">
|
||||
<value>空洞閾值下限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdLow_Desc" xml:space="preserve">
|
||||
<value>空洞檢測灰度下限(空洞為亮區域)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh" xml:space="preserve">
|
||||
<value>空洞閾值上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidThresholdHigh_Desc" xml:space="preserve">
|
||||
<value>空洞檢測灰度上限</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea" xml:space="preserve">
|
||||
<value>最小空洞面積</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinVoidArea_Desc" xml:space="preserve">
|
||||
<value>小於此面積的區域視為噪點忽略(像素)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius" xml:space="preserve">
|
||||
<value>空洞合併半徑</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidMergeRadius_Desc" xml:space="preserve">
|
||||
<value>相鄰空洞合併的膨脹半徑(0=不合併)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit" xml:space="preserve">
|
||||
<value>空洞率限值(%)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_VoidRateLimit_Desc" xml:space="preserve">
|
||||
<value>單引腳最大允許空洞率(預設50%,參考IPC-7095)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea" xml:space="preserve">
|
||||
<value>引腳合格面積</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_MinQualifiedPadArea_Desc" xml:space="preserve">
|
||||
<value>引腳面積低於此值判定為不合格(焊料不足)</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness" xml:space="preserve">
|
||||
<value>線條粗細</value>
|
||||
</data>
|
||||
<data name="QfnLeadPadVoidProcessor_Thickness_Desc" xml:space="preserve">
|
||||
<value>輪廓繪製線條粗細</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -0,0 +1,510 @@
|
||||
// ============================================================================
|
||||
// Copyright © 2026 Hexagon Technology Center GmbH. All Rights Reserved.
|
||||
// 文件名: QfnLeadPadVoidProcessor.cs
|
||||
// 描述: QFN 单引脚空洞率检测算子
|
||||
//
|
||||
// 处理流程:
|
||||
// 第一步 — 引脚定位: 高斯模糊 → 双阈值分割 → 形态学闭运算 → 轮廓检测
|
||||
// → 面积/长宽比过滤 → 排除散热焊盘 → 引脚排序
|
||||
// 第二步 — 空洞检测: 逐引脚掩码 → 双阈值分割 → 轮廓检测 → 面积过滤 → 空洞率计算
|
||||
//
|
||||
// 支持多边形ROI限定检测区域(框选引脚所在边),支持IPC-7095标准PASS/FAIL判定
|
||||
// 正片模式:焊点=暗区域,空洞=亮区域
|
||||
//
|
||||
// 作者: 李伟 wei.lw.li@hexagon.com
|
||||
// ============================================================================
|
||||
|
||||
using Emgu.CV;
|
||||
using Emgu.CV.CvEnum;
|
||||
using Emgu.CV.Structure;
|
||||
using Emgu.CV.Util;
|
||||
using XP.ImageProcessing.Core;
|
||||
using Serilog;
|
||||
using System.Drawing;
|
||||
|
||||
namespace XP.ImageProcessing.Processors;
|
||||
|
||||
public class QfnLeadPadVoidProcessor : ImageProcessorBase
|
||||
{
|
||||
private static readonly ILogger _logger = Log.ForContext<QfnLeadPadVoidProcessor>();
|
||||
|
||||
public QfnLeadPadVoidProcessor()
|
||||
{
|
||||
Name = LocalizationHelper.GetString("QfnLeadPadVoidProcessor_Name");
|
||||
Description = LocalizationHelper.GetString("QfnLeadPadVoidProcessor_Description");
|
||||
}
|
||||
|
||||
protected override void InitializeParameters()
|
||||
{
|
||||
// ── ROI限定区域 ──
|
||||
Parameters.Add("RoiMode", new ProcessorParameter(
|
||||
"RoiMode",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_RoiMode"),
|
||||
typeof(string), "None", null, null,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_RoiMode_Desc"),
|
||||
new string[] { "None", "Polygon" }));
|
||||
|
||||
// 多边形ROI点数和坐标(由UI注入,不可见,最多支持32个点)
|
||||
Parameters.Add("PolyCount", new ProcessorParameter("PolyCount", "PolyCount", typeof(int), 0, null, null, "") { IsVisible = false });
|
||||
for (int i = 0; i < 32; i++)
|
||||
{
|
||||
Parameters.Add($"PolyX{i}", new ProcessorParameter($"PolyX{i}", $"PolyX{i}", typeof(int), 0, null, null, "") { IsVisible = false });
|
||||
Parameters.Add($"PolyY{i}", new ProcessorParameter($"PolyY{i}", $"PolyY{i}", typeof(int), 0, null, null, "") { IsVisible = false });
|
||||
}
|
||||
|
||||
// ── 第一步:引脚定位参数 ──
|
||||
Parameters.Add("PadBlurSize", new ProcessorParameter(
|
||||
"PadBlurSize",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadBlurSize"),
|
||||
typeof(int), 5, 1, 31,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadBlurSize_Desc")));
|
||||
|
||||
Parameters.Add("PadThresholdLow", new ProcessorParameter(
|
||||
"PadThresholdLow",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadThresholdLow"),
|
||||
typeof(int), 0, 0, 255,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadThresholdLow_Desc")));
|
||||
|
||||
Parameters.Add("PadThresholdHigh", new ProcessorParameter(
|
||||
"PadThresholdHigh",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadThresholdHigh"),
|
||||
typeof(int), 120, 0, 255,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadThresholdHigh_Desc")));
|
||||
|
||||
Parameters.Add("PadMorphKernel", new ProcessorParameter(
|
||||
"PadMorphKernel",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadMorphKernel"),
|
||||
typeof(int), 5, 1, 31,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadMorphKernel_Desc")));
|
||||
|
||||
Parameters.Add("MinPadArea", new ProcessorParameter(
|
||||
"MinPadArea",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MinPadArea"),
|
||||
typeof(int), 200, 10, 1000000,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MinPadArea_Desc")));
|
||||
|
||||
Parameters.Add("MaxPadArea", new ProcessorParameter(
|
||||
"MaxPadArea",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MaxPadArea"),
|
||||
typeof(int), 100000, 100, 10000000,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MaxPadArea_Desc")));
|
||||
|
||||
Parameters.Add("PadAspectRatioMin", new ProcessorParameter(
|
||||
"PadAspectRatioMin",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadAspectRatioMin"),
|
||||
typeof(double), 1.2, 0.1, 20.0,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_PadAspectRatioMin_Desc")));
|
||||
|
||||
// ── 第二步:空洞检测参数 ──
|
||||
Parameters.Add("VoidThresholdLow", new ProcessorParameter(
|
||||
"VoidThresholdLow",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidThresholdLow"),
|
||||
typeof(int), 128, 0, 255,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidThresholdLow_Desc")));
|
||||
|
||||
Parameters.Add("VoidThresholdHigh", new ProcessorParameter(
|
||||
"VoidThresholdHigh",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidThresholdHigh"),
|
||||
typeof(int), 255, 0, 255,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidThresholdHigh_Desc")));
|
||||
|
||||
Parameters.Add("MinVoidArea", new ProcessorParameter(
|
||||
"MinVoidArea",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MinVoidArea"),
|
||||
typeof(int), 5, 1, 10000,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MinVoidArea_Desc")));
|
||||
|
||||
Parameters.Add("VoidMergeRadius", new ProcessorParameter(
|
||||
"VoidMergeRadius",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidMergeRadius"),
|
||||
typeof(int), 2, 0, 20,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidMergeRadius_Desc")));
|
||||
|
||||
Parameters.Add("VoidRateLimit", new ProcessorParameter(
|
||||
"VoidRateLimit",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidRateLimit"),
|
||||
typeof(double), 50.0, 0.0, 100.0,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_VoidRateLimit_Desc")));
|
||||
|
||||
Parameters.Add("MinQualifiedPadArea", new ProcessorParameter(
|
||||
"MinQualifiedPadArea",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MinQualifiedPadArea"),
|
||||
typeof(int), 1000, 0, 1000000,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_MinQualifiedPadArea_Desc")));
|
||||
|
||||
Parameters.Add("Thickness", new ProcessorParameter(
|
||||
"Thickness",
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_Thickness"),
|
||||
typeof(int), 2, 1, 10,
|
||||
LocalizationHelper.GetString("QfnLeadPadVoidProcessor_Thickness_Desc")));
|
||||
}
|
||||
|
||||
public override Image<Gray, byte> Process(Image<Gray, byte> inputImage)
|
||||
{
|
||||
// 读取参数
|
||||
string roiMode = GetParameter<string>("RoiMode");
|
||||
int padBlurSize = GetParameter<int>("PadBlurSize");
|
||||
int padThreshLow = GetParameter<int>("PadThresholdLow");
|
||||
int padThreshHigh = GetParameter<int>("PadThresholdHigh");
|
||||
int padMorphKernel = GetParameter<int>("PadMorphKernel");
|
||||
int minPadArea = GetParameter<int>("MinPadArea");
|
||||
int maxPadArea = GetParameter<int>("MaxPadArea");
|
||||
double padAspectRatioMin = GetParameter<double>("PadAspectRatioMin");
|
||||
int voidThreshLow = GetParameter<int>("VoidThresholdLow");
|
||||
int voidThreshHigh = GetParameter<int>("VoidThresholdHigh");
|
||||
int minVoidArea = GetParameter<int>("MinVoidArea");
|
||||
int voidMergeRadius = GetParameter<int>("VoidMergeRadius");
|
||||
double voidRateLimit = GetParameter<double>("VoidRateLimit");
|
||||
int minQualifiedPadArea = GetParameter<int>("MinQualifiedPadArea");
|
||||
int thickness = GetParameter<int>("Thickness");
|
||||
|
||||
// 确保模糊核为奇数
|
||||
if (padBlurSize % 2 == 0) padBlurSize++;
|
||||
if (padMorphKernel % 2 == 0) padMorphKernel++;
|
||||
|
||||
OutputData.Clear();
|
||||
int w = inputImage.Width, h = inputImage.Height;
|
||||
|
||||
// ── 构建ROI掩码 ──
|
||||
Image<Gray, byte>? roiMask = null;
|
||||
if (roiMode == "Polygon")
|
||||
{
|
||||
int polyCount = GetParameter<int>("PolyCount");
|
||||
if (polyCount >= 3)
|
||||
{
|
||||
var pts = new Point[polyCount];
|
||||
for (int i = 0; i < polyCount; i++)
|
||||
pts[i] = new Point(GetParameter<int>($"PolyX{i}"), GetParameter<int>($"PolyY{i}"));
|
||||
roiMask = new Image<Gray, byte>(w, h);
|
||||
using var vop = new VectorOfPoint(pts);
|
||||
using var vvop = new VectorOfVectorOfPoint(vop);
|
||||
CvInvoke.DrawContours(roiMask, vvop, 0, new MCvScalar(255), -1);
|
||||
_logger.Debug("QFN Lead ROI Polygon: {Count} points", polyCount);
|
||||
}
|
||||
}
|
||||
|
||||
OutputData["RoiMode"] = roiMode;
|
||||
OutputData["RoiMask"] = roiMask;
|
||||
|
||||
_logger.Debug("QfnLeadPadVoid: PadArea=[{Min},{Max}], Blur={Blur}, PadThresh=[{TLow},{THigh}], AspectMin={Asp}",
|
||||
minPadArea, maxPadArea, padBlurSize, padThreshLow, padThreshHigh, padAspectRatioMin);
|
||||
|
||||
// ================================================================
|
||||
// 第一步:自动检测QFN引脚焊点位置
|
||||
// ================================================================
|
||||
var leadPads = DetectLeadPads(inputImage, padBlurSize, padThreshLow, padThreshHigh,
|
||||
padMorphKernel, minPadArea, maxPadArea, padAspectRatioMin, roiMask);
|
||||
|
||||
_logger.Information("第一步完成: 检测到 {Count} 个QFN引脚焊点", leadPads.Count);
|
||||
|
||||
if (leadPads.Count == 0)
|
||||
{
|
||||
OutputData["QfnLeadResult"] = true;
|
||||
OutputData["LeadCount"] = 0;
|
||||
OutputData["LeadPads"] = leadPads;
|
||||
OutputData["VoidRate"] = 0.0;
|
||||
OutputData["VoidRateLimit"] = voidRateLimit;
|
||||
OutputData["Classification"] = "N/A";
|
||||
OutputData["ResultText"] = "No QFN lead pads detected";
|
||||
OutputData["Thickness"] = thickness;
|
||||
OutputData["TotalPadArea"] = 0;
|
||||
OutputData["TotalVoidArea"] = 0;
|
||||
OutputData["TotalVoidCount"] = 0;
|
||||
roiMask?.Dispose();
|
||||
return inputImage.Clone();
|
||||
}
|
||||
|
||||
// ================================================================
|
||||
// 第二步:在每个引脚焊点区域内检测空洞
|
||||
// ================================================================
|
||||
int totalPadArea = 0;
|
||||
int totalVoidArea = 0;
|
||||
int totalVoidCount = 0;
|
||||
|
||||
foreach (var pad in leadPads)
|
||||
{
|
||||
DetectVoidsInLeadPad(inputImage, pad, voidThreshLow, voidThreshHigh, minVoidArea, voidMergeRadius);
|
||||
totalPadArea += pad.PadArea;
|
||||
totalVoidArea += pad.VoidPixels;
|
||||
totalVoidCount += pad.Voids.Count;
|
||||
}
|
||||
|
||||
double overallVoidRate = totalPadArea > 0 ? (double)totalVoidArea / totalPadArea * 100.0 : 0;
|
||||
string classification = "PASS";
|
||||
|
||||
// 判定:空洞率超标或面积不足均为FAIL
|
||||
int failCount = 0;
|
||||
double maxSingleVoidRate = 0;
|
||||
foreach (var pad in leadPads)
|
||||
{
|
||||
if (pad.PadArea < minQualifiedPadArea)
|
||||
pad.Classification = "FAIL_AREA";
|
||||
else if (pad.VoidRate > voidRateLimit)
|
||||
pad.Classification = "FAIL";
|
||||
else
|
||||
pad.Classification = "PASS";
|
||||
|
||||
if (pad.Classification != "PASS") failCount++;
|
||||
if (pad.VoidRate > maxSingleVoidRate) maxSingleVoidRate = pad.VoidRate;
|
||||
}
|
||||
if (failCount > 0) classification = "FAIL";
|
||||
|
||||
_logger.Information("第二步完成: 总空洞率={VoidRate:F1}%, 最大单引脚={MaxRate:F1}%, 不合格={Fail}/{Total}, 判定={Class}",
|
||||
overallVoidRate, maxSingleVoidRate, failCount, leadPads.Count, classification);
|
||||
|
||||
// ── 输出数据 ──
|
||||
OutputData["QfnLeadResult"] = true;
|
||||
OutputData["LeadCount"] = leadPads.Count;
|
||||
OutputData["LeadPads"] = leadPads;
|
||||
OutputData["VoidRate"] = overallVoidRate;
|
||||
OutputData["MaxSingleVoidRate"] = maxSingleVoidRate;
|
||||
OutputData["VoidRateLimit"] = voidRateLimit;
|
||||
OutputData["TotalPadArea"] = totalPadArea;
|
||||
OutputData["TotalVoidArea"] = totalVoidArea;
|
||||
OutputData["TotalVoidCount"] = totalVoidCount;
|
||||
OutputData["FailCount"] = failCount;
|
||||
OutputData["Classification"] = classification;
|
||||
OutputData["Thickness"] = thickness;
|
||||
OutputData["ResultText"] = $"QFN Lead: {overallVoidRate:F1}% | {classification} | {leadPads.Count} pads | Fail: {failCount}";
|
||||
|
||||
roiMask?.Dispose();
|
||||
return inputImage.Clone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 第一步:自动检测QFN引脚焊点位置
|
||||
/// 使用双阈值分割 + 形态学 + 轮廓检测 + 面积/长宽比过滤
|
||||
/// QFN引脚在X-Ray正片中为暗色长条形区域
|
||||
/// </summary>
|
||||
private List<QfnLeadPadInfo> DetectLeadPads(
|
||||
Image<Gray, byte> input, int blurSize,
|
||||
int threshLow, int threshHigh, int morphKernel,
|
||||
int minArea, int maxArea, double aspectRatioMin,
|
||||
Image<Gray, byte>? roiMask)
|
||||
{
|
||||
var results = new List<QfnLeadPadInfo>();
|
||||
int w = input.Width, h = input.Height;
|
||||
|
||||
// 高斯模糊降噪
|
||||
var blurred = new Image<Gray, byte>(w, h);
|
||||
CvInvoke.GaussianBlur(input, blurred, new Size(blurSize, blurSize), 0);
|
||||
|
||||
// 双阈值分割(X-Ray正片:焊点=暗区域,灰度在[threshLow, threshHigh]范围内判为焊点)
|
||||
var binary = new Image<Gray, byte>(w, h);
|
||||
byte[,,] srcData = blurred.Data;
|
||||
byte[,,] dstData = binary.Data;
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
byte val = srcData[y, x, 0];
|
||||
dstData[y, x, 0] = (val >= threshLow && val <= threshHigh) ? (byte)255 : (byte)0;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果有ROI掩码,只保留ROI区域内的结果
|
||||
if (roiMask != null)
|
||||
{
|
||||
CvInvoke.BitwiseAnd(binary, roiMask, binary);
|
||||
}
|
||||
|
||||
// 形态学闭运算填充引脚内部小孔洞
|
||||
using var kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle,
|
||||
new Size(morphKernel, morphKernel), new Point(-1, -1));
|
||||
CvInvoke.MorphologyEx(binary, binary, MorphOp.Close, kernel, new Point(-1, -1), 2, BorderType.Default, new MCvScalar(0));
|
||||
|
||||
// 查找轮廓
|
||||
using var contours = new VectorOfVectorOfPoint();
|
||||
using var hierarchy = new Mat();
|
||||
CvInvoke.FindContours(binary, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
|
||||
|
||||
for (int i = 0; i < contours.Size; i++)
|
||||
{
|
||||
double area = CvInvoke.ContourArea(contours[i]);
|
||||
if (area < minArea || area > maxArea) continue;
|
||||
|
||||
// 需要至少5个点才能拟合椭圆/旋转矩形
|
||||
if (contours[i].Size < 5) continue;
|
||||
|
||||
// 最小外接旋转矩形
|
||||
var minRect = CvInvoke.MinAreaRect(contours[i]);
|
||||
float rectWidth = Math.Max(minRect.Size.Width, minRect.Size.Height);
|
||||
float rectHeight = Math.Min(minRect.Size.Width, minRect.Size.Height);
|
||||
|
||||
// 长宽比过滤:QFN引脚是长条形,长宽比应大于阈值
|
||||
if (rectHeight < 1) continue;
|
||||
double aspectRatio = rectWidth / rectHeight;
|
||||
if (aspectRatio < aspectRatioMin) continue;
|
||||
|
||||
var moments = CvInvoke.Moments(contours[i]);
|
||||
if (moments.M00 < 1) continue;
|
||||
|
||||
results.Add(new QfnLeadPadInfo
|
||||
{
|
||||
CenterX = moments.M10 / moments.M00,
|
||||
CenterY = moments.M01 / moments.M00,
|
||||
BoundingRotatedRect = minRect,
|
||||
ContourPoints = contours[i].ToArray(),
|
||||
PadArea = (int)area,
|
||||
AspectRatio = aspectRatio
|
||||
});
|
||||
}
|
||||
|
||||
// 按角度位置排序(从图像中心出发,逆时针排列)
|
||||
SortLeadPadsByPosition(results, w, h);
|
||||
|
||||
blurred.Dispose();
|
||||
binary.Dispose();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 按引脚在图像中的位置排序(从左上角开始逆时针)
|
||||
/// 先按所在边分组(上/右/下/左),再在每条边内按位置排序
|
||||
/// </summary>
|
||||
private void SortLeadPadsByPosition(List<QfnLeadPadInfo> pads, int imageWidth, int imageHeight)
|
||||
{
|
||||
if (pads.Count == 0) return;
|
||||
|
||||
double cx = imageWidth / 2.0;
|
||||
double cy = imageHeight / 2.0;
|
||||
|
||||
// 按角度排序(从正上方开始顺时针)
|
||||
// atan2 返回 [-π, π],调整为从正上方开始
|
||||
pads.Sort((a, b) =>
|
||||
{
|
||||
double angleA = Math.Atan2(a.CenterX - cx, -(a.CenterY - cy));
|
||||
double angleB = Math.Atan2(b.CenterX - cx, -(b.CenterY - cy));
|
||||
if (angleA < 0) angleA += 2 * Math.PI;
|
||||
if (angleB < 0) angleB += 2 * Math.PI;
|
||||
return angleA.CompareTo(angleB);
|
||||
});
|
||||
|
||||
// 重新编号
|
||||
for (int i = 0; i < pads.Count; i++)
|
||||
pads[i].Index = i + 1;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 第二步:在单个引脚焊点区域内检测空洞
|
||||
/// 使用引脚轮廓作为掩码,双阈值分割空洞区域
|
||||
/// </summary>
|
||||
private void DetectVoidsInLeadPad(
|
||||
Image<Gray, byte> input, QfnLeadPadInfo pad,
|
||||
int voidThreshLow, int voidThreshHigh, int minVoidArea, int mergeRadius)
|
||||
{
|
||||
int w = input.Width, h = input.Height;
|
||||
|
||||
// 创建该引脚的掩码
|
||||
var mask = new Image<Gray, byte>(w, h);
|
||||
using (var vop = new VectorOfPoint(pad.ContourPoints))
|
||||
using (var vvop = new VectorOfVectorOfPoint(vop))
|
||||
{
|
||||
CvInvoke.DrawContours(mask, vvop, 0, new MCvScalar(255), -1);
|
||||
}
|
||||
|
||||
int padPixels = CvInvoke.CountNonZero(mask);
|
||||
pad.PadArea = padPixels;
|
||||
|
||||
// 双阈值分割(正片模式:空洞=亮区域,灰度在[voidThreshLow, voidThreshHigh]范围内判为空洞)
|
||||
var voidImg = new Image<Gray, byte>(w, h);
|
||||
byte[,,] srcData = input.Data;
|
||||
byte[,,] dstData = voidImg.Data;
|
||||
byte[,,] maskData = mask.Data;
|
||||
|
||||
for (int y = 0; y < h; y++)
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
if (maskData[y, x, 0] > 0)
|
||||
{
|
||||
byte val = srcData[y, x, 0];
|
||||
dstData[y, x, 0] = (val >= voidThreshLow && val <= voidThreshHigh) ? (byte)255 : (byte)0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 形态学膨胀合并相邻空洞
|
||||
if (mergeRadius > 0)
|
||||
{
|
||||
int kernelSize = mergeRadius * 2 + 1;
|
||||
using var kernel = CvInvoke.GetStructuringElement(ElementShape.Ellipse,
|
||||
new Size(kernelSize, kernelSize), new Point(-1, -1));
|
||||
CvInvoke.Dilate(voidImg, voidImg, kernel, new Point(-1, -1), 1, BorderType.Default, new MCvScalar(0));
|
||||
// 与引脚掩码取交集,防止膨胀超出引脚区域
|
||||
CvInvoke.BitwiseAnd(voidImg, mask, voidImg);
|
||||
}
|
||||
|
||||
// 检测每个空洞的轮廓
|
||||
using var contours = new VectorOfVectorOfPoint();
|
||||
using var hierarchy = new Mat();
|
||||
CvInvoke.FindContours(voidImg, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
|
||||
|
||||
int filteredVoidArea = 0;
|
||||
for (int i = 0; i < contours.Size; i++)
|
||||
{
|
||||
double area = CvInvoke.ContourArea(contours[i]);
|
||||
if (area < minVoidArea) continue;
|
||||
|
||||
var moments = CvInvoke.Moments(contours[i]);
|
||||
if (moments.M00 < 1) continue;
|
||||
|
||||
filteredVoidArea += (int)Math.Round(area);
|
||||
pad.Voids.Add(new QfnLeadVoidInfo
|
||||
{
|
||||
Index = pad.Voids.Count + 1,
|
||||
CenterX = moments.M10 / moments.M00,
|
||||
CenterY = moments.M01 / moments.M00,
|
||||
Area = area,
|
||||
AreaPercent = padPixels > 0 ? area / padPixels * 100.0 : 0,
|
||||
BoundingBox = CvInvoke.BoundingRectangle(contours[i]),
|
||||
ContourPoints = contours[i].ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
// 空洞率基于过滤后的轮廓面积计算
|
||||
pad.VoidPixels = filteredVoidArea;
|
||||
pad.VoidRate = padPixels > 0 ? (double)filteredVoidArea / padPixels * 100.0 : 0;
|
||||
|
||||
// 按面积从大到小排序
|
||||
pad.Voids.Sort((a, b) => b.Area.CompareTo(a.Area));
|
||||
for (int i = 0; i < pad.Voids.Count; i++) pad.Voids[i].Index = i + 1;
|
||||
|
||||
mask.Dispose();
|
||||
voidImg.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个QFN引脚焊点信息
|
||||
/// </summary>
|
||||
public class QfnLeadPadInfo
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public double CenterX { get; set; }
|
||||
public double CenterY { get; set; }
|
||||
public RotatedRect BoundingRotatedRect { get; set; }
|
||||
public Point[] ContourPoints { get; set; } = Array.Empty<Point>();
|
||||
public int PadArea { get; set; }
|
||||
public double AspectRatio { get; set; }
|
||||
public int VoidPixels { get; set; }
|
||||
public double VoidRate { get; set; }
|
||||
public string Classification { get; set; } = "N/A";
|
||||
public List<QfnLeadVoidInfo> Voids { get; set; } = new();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个引脚内的空洞信息
|
||||
/// </summary>
|
||||
public class QfnLeadVoidInfo
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public double CenterX { get; set; }
|
||||
public double CenterY { get; set; }
|
||||
public double Area { get; set; }
|
||||
public double AreaPercent { get; set; }
|
||||
public Rectangle BoundingBox { get; set; }
|
||||
public Point[] ContourPoints { get; set; } = Array.Empty<Point>();
|
||||
}
|
||||
Reference in New Issue
Block a user