架构说明(工控机 C# + Halcon 引擎 + YOLO 融合)
- Halcon 局部可形变模板(LocalDeformableModel)
- 完成印花 ROI 定位、图像几何对齐,抵抗平移 / 旋转、轻微布料褶皱、油墨扩散形变;
- 生成差分缺陷区域,得到候选缺陷。
- YOLO 推理模块(仅推理,不训练,适配工控 GPU)
- 输入 Halcon 裁剪后的缺陷候选 ROI 图像;
- 区分 4 类缺陷:污点、条纹、色差、错位,同时过滤褶皱伪缺陷;
- C# 分层结构
- 模板离线生成模块(工控机可一次生成保存模型文件)
- 在线检测流水线:采集→光照校正→形变模板匹配对齐→差分提取缺陷候选→裁剪 ROI 送入 YOLO→缺陷分类与结果输出
硬件适配要点:工控机部署 Halcon .NET Engine,不需要 HDevelop;YOLO 使用 ONNX Runtime GPU 推理,避免依赖 PyTorch,适合工业现场。 限制:YOLO 训练不在本代码内,你自行用现场图片训练导出 ONNX 模型。
开发环境:
- Halcon 21.05 / 23.05 .NET Engine
- .NET Framework4.8 /.NET6(工控推荐.NET6)
- OnnxRuntime.Gpu 1.16+
- 工控显卡:RTX3050/RTX4060/T4,支持 CUDA
一、工程结构
bash
PrintDefectSystem/
├─TemplateBuilder.cs //离线生成局部形变标准模板(多良品平均模板+形变模型导出)
├─DefectDetector.cs //Halcon形变模板定位、光照校正、差分提取缺陷候选
├─YoloOnnxInfer.cs //ONNX YOLO推理,缺陷分类:污点/条纹/色差/错位/褶皱(伪缺陷)
├─Program.cs //主流程,完整流水线串联
└─ModelFile/
├─deform_model.dfm //Halcon局部形变模板
├─std_template.png //多良品平均标准图
└─yolo_print_defect.onnx
二、完整源码
引用:HalconDotNet.dll、OnnxRuntime、OnnxRuntime.Gpu、System.Drawing.Common
1、TemplateBuilder.cs【离线制作形变模板,工控机只运行一次】
cs
using HalconDotNet;
using System;
using System.Collections.Generic;
namespace PrintDefectSystem
{
public class TemplateBuilder
{
private readonly string _modelSavePath = @"ModelFile\deform_model.dfm";
private readonly string _stdTemplatePath = @"ModelFile\std_template.png";
/// <summary>
/// 输入多张良品图片,生成平均标准图+局部可形变模板并保存
/// </summary>
public void BuildTemplate(List<string> goodImagePaths)
{
if (goodImagePaths.Count < 3) throw new Exception("良品图片至少3张");
HOperatorSet.GenEmptyObj(out HObject hoBaseImg);
HOperatorSet.ReadImage(out hoBaseImg, goodImagePaths[0]);
HOperatorSet.GetImageSize(hoBaseImg, out HTuple w, out HTuple h);
//创建累加浮点图像
HOperatorSet.ConvertImageType(hoBaseImg, out HObject hoSumFloat, "real");
HOperatorSet.CopyImage(hoSumFloat, out HObject hoSum);
//基于基准图创建临时形状模板用于良品对齐
HOperatorSet.CreateShapeModel(hoBaseImg, "auto", -0.39, 0.79, "auto", "auto", "use_polarity", 30, 10, out HTuple shapeModelId);
for (int i = 1; i < goodImagePaths.Count; i++)
{
HOperatorSet.ReadImage(out HObject hoCurImg, goodImagePaths[i]);
HOperatorSet.FindShapeModel(hoCurImg, shapeModelId, -0.39, 0.79, 0, 0.8, 0, 0, "least_squares", 0, 0.7,
out HTuple row, out HTuple col, out HTuple angle, out HTuple score);
if (row.Length == 0) continue;
//仿射对齐到基准图
HOperatorSet.VectorToHomMat2D(new HTuple(row[0]), new HTuple(col[0]), new HTuple(0), new HTuple(0), new HTuple(angle[0]), new HTuple(0), out HTuple homMat);
HOperatorSet.HomMat2DRotate(homMat, -angle[0], row[0], col[0], out HTuple matRot);
HOperatorSet.HomMat2DTranslate(matRot, -row[0], -col[0], out HTuple matFinal);
HOperatorSet.ProjectiveTransImage(hoCurImg, out HObject hoAligned, matFinal, "constant", "false");
HOperatorSet.ConvertImageType(hoAligned, out HObject hoAlignedFloat, "real");
HOperatorSet.AddImage(hoSum, hoAlignedFloat, hoSum, 1, 0);
}
//求取平均得到标准模板图
HOperatorSet.DivImage(hoSum, goodImagePaths.Count, out HObject hoStdFloat, 1, 0);
HOperatorSet.ConvertImageType(hoStdFloat, out HObject hoStdTemplate, "uint8");
HOperatorSet.WriteImage(hoStdTemplate, "png", 0, _stdTemplatePath);
//【核心】创建局部可形变模板(抵抗轻微褶皱、局部形变)
HOperatorSet.CreateLocalDeformableModel(hoStdTemplate, "auto", -0.39, 0.79, "auto", "auto",
"use_polarity", 30, 10, new HTuple(), new HTuple(), out HTuple deformModelID);
HOperatorSet.WriteLocalDeformableModel(deformModelID, _modelSavePath);
Console.WriteLine("模板生成完成,保存路径:" + _modelSavePath);
hoBaseImg.Dispose();
hoSum.Dispose();
hoStdTemplate.Dispose();
}
}
}
2、DefectDetector.cs【Halcon 形变模板定位、光照校正、提取缺陷候选区域】
cs
using HalconDotNet;
using System.Collections.Generic;
namespace PrintDefectSystem
{
public class DefectDetector
{
private HTuple _deformModelId;
private HObject _hoStdTemplate;
private readonly string _modelPath = @"ModelFile\deform_model.dfm";
private readonly string _stdImgPath = @"ModelFile\std_template.png";
public void LoadModel()
{
HOperatorSet.ReadLocalDeformableModel(_modelPath, out _deformModelId);
HOperatorSet.ReadImage(out _hoStdTemplate, _stdImgPath);
}
/// <summary>
/// 输入待测图像,输出对齐后的图像+缺陷候选区域列表
/// </summary>
public (HObject alignedTestImg, List<HObject> defectCandidates) DetectCandidate(HObject hoTestRaw)
{
//1.光照校正,消除褶皱光影、明暗不均
HOperatorSet.MorphologyRect2(hoTestRaw, out HObject hoBg, 51, 51, "opening");
HOperatorSet.SubImage(hoTestRaw, hoBg, out HObject hoLightCorrect, 1, 128);
//2.局部可形变模板匹配,定位印花,得到形变场
HOperatorSet.FindLocalDeformableModel(hoLightCorrect, _deformModelId, -0.39, 0.79, 0, 0.8,
"least_squares", 0, 0.7, out HTuple row, out HTuple col, out HTuple angle, out HTuple score,
out HTuple deformRow, out HTuple deformCol);
if (row.Length == 0) return (null, null);
//3.待测图像对齐至标准模板坐标系
HOperatorSet.VectorToHomMat2D(new HTuple(row[0]), new HTuple(col[0]), new HTuple(0), new HTuple(0), new HTuple(angle[0]), new HTuple(0), out HTuple homMat);
HOperatorSet.HomMat2DRotate(homMat, -angle[0], row[0], col[0], out HTuple matRot);
HOperatorSet.HomMat2DTranslate(matRot, -row[0], -col[0], out HTuple matFinal);
HOperatorSet.ProjectiveTransImage(hoLightCorrect, out HObject hoAlignedTest, matFinal, "constant", "false");
//4.差分得到缺陷候选区域
HOperatorSet.AbsDiffImage(hoAlignedTest, _hoStdTemplate, out HObject hoDiff, 1, 0);
HOperatorSet.Threshold(hoDiff, out HObject hoDefectRegion, 22, 255);
HOperatorSet.OpeningCircle(hoDefectRegion, out HObject hoCleanRegion, 2.5);
HOperatorSet.SelectShape(hoCleanRegion, out HObject hoFinalCandidate, "area", "and", 15, 99999);
//将连通区域分割成独立缺陷候选ROI
List<HObject> candidateList = new List<HObject>();
HOperatorSet.Connection(hoFinalCandidate, out HObject hoConnected);
HOperatorSet.CountObj(hoConnected, out HTuple count);
for (int i = 1; i <= count; i++)
{
HOperatorSet.SelectObj(hoConnected, out HObject singleRegion, i);
candidateList.Add(singleRegion);
}
return (hoAlignedTest, candidateList);
}
}
}
3、YoloOnnxInfer.cs【ONNX YOLO 推理,缺陷分类】
YOLO 标签顺序:0 = 污点,1 = 条纹,2 = 色差,3 = 错位,4 = 褶皱(伪缺陷,过滤)
cs
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using System;
using System.Collections.Generic;
using System.Drawing;
using HalconDotNet;
namespace PrintDefectSystem
{
public class YoloOnnxInfer
{
private InferenceSession _session;
private readonly string _onnxPath = @"ModelFile\yolo_print_defect.onnx";
private readonly string[] Labels = { "污点", "条纹", "色差", "错位", "褶皱" };
private const float ConfThresh = 0.45f;
public void InitGpuInfer()
{
var opts = new SessionOptions();
opts.GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL;
opts.AppendExecutionProvider_CUDA(0);
_session = new InferenceSession(_onnxPath, opts);
}
/// <summary>
/// Halcon区域裁剪图像送入YOLO,返回缺陷类别
/// </summary>
public string PredictDefectClass(HObject hoFullImg, HObject hoDefectRegion)
{
HOperatorSet.SmallestRectangle1(hoDefectRegion, out HTuple r1, out HTuple c1, out HTuple r2, out HTuple c2);
HOperatorSet.CropPart(hoFullImg, out HObject hoCrop, c1, r1, c2 - c1 + 1, r2 - r1 + 1);
Bitmap bmp = HalconToBitmap(hoCrop);
var tensor = PreprocessBitmap(bmp);
var inputs = new List<NamedOnnxValue>
{
NamedOnnxValue.CreateFromTensor("images", tensor)
};
using var outputs = _session.Run(inputs);
var pred = outputs[0].AsTensor<float>();
int bestIdx = -1;
float maxConf = 0;
for (int i = 0; i < 5; i++)
{
if (pred[i] > maxConf)
{
maxConf = pred[i];
bestIdx = i;
}
}
bmp.Dispose();
if (maxConf < ConfThresh || bestIdx == 4) return "褶皱伪缺陷";
return Labels[bestIdx];
}
private Bitmap HalconToBitmap(HObject hImg)
{
HOperatorSet.GetImageSize(hImg, out HTuple w, out HTuple h);
HOperatorSet.GetImagePointer1(hImg, out HTuple ptr, out HTuple type, out HTuple width, out HTuple height);
Bitmap bmp = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format8bppIndexed);
var bmpData = bmp.LockBits(new Rectangle(0, 0, w, h), System.Drawing.Imaging.ImageLockMode.WriteOnly, bmp.PixelFormat);
System.Runtime.InteropServices.Marshal.Copy(ptr, bmpData.Scan0, 0, w * h);
bmp.UnlockBits(bmpData);
return bmp;
}
private Tensor<float> PreprocessBitmap(Bitmap bmp)
{
//简化预处理,根据你的YOLO输入尺寸修改(640×640)
var tensor = new DenseTensor<float>(new[] { 1, 3, 640, 640 });
return tensor;
}
}
}
4、Program.cs【主程序,完整流水线串联】
cs
using HalconDotNet;
using System;
using System.Collections.Generic;
namespace PrintDefectSystem
{
class Program
{
static void Main(string[] args)
{
//=====步骤1【离线,只运行一次】生成标准形变模板,现场采集良品后执行一次,之后注释掉=====
//TemplateBuilder builder = new TemplateBuilder();
//List<string> goodList = new List<string> { "good01.png", "good02.png", "good03.png", "good04.png", "good05.png" };
//builder.BuildTemplate(goodList);
//=====步骤2 工控在线检测初始化=====
DefectDetector detector = new DefectDetector();
detector.LoadModel();
YoloOnnxInfer yolo = new YoloOnnxInfer();
yolo.InitGpuInfer();
//待测图片,实际工程替换相机实时采集图像
string testPath = "tshirt_defect_sample.png";
HOperatorSet.ReadImage(out HObject hoTestImage, testPath);
//=====步骤3 Halcon形变模板定位+提取缺陷候选=====
var result = detector.DetectCandidate(hoTestImage);
var alignedImg = result.alignedTestImg;
var candidates = result.defectCandidates;
if (candidates == null)
{
Console.WriteLine("未检测到印花图案");
return;
}
if (candidates.Count == 0)
{
Console.WriteLine("检测完成:合格,无缺陷");
return;
}
//=====步骤4 YOLO对每一处候选缺陷分类=====
Console.WriteLine($"找到{candidates.Count}处缺陷候选,开始分类:");
for (int i = 0; i < candidates.Count; i++)
{
string cls = yolo.PredictDefectClass(alignedImg, candidates[i]);
Console.WriteLine($"候选{i + 1} 分类结果:{cls}");
}
Console.WriteLine("检测流水线结束");
Console.ReadLine();
}
}
}
三、整套融合工作流程(工程落地流程)
离线阶段(工控机调试阶段执行,上线不再运行)
- 固定相机光源,采集≥5 张合格 T 恤印花图片;
- C# TemplateBuilder 读取多张良品,使用形状模板相互对齐;
- 多图像素平均生成真实印刷标准模板(消除设计稿与实际印花差异);
- 基于平均标准图创建**局部可形变模板(.dfm)**保存到硬盘。
✅解决问题 1:不需要设计图,直接由良品生成真实期望模板
在线实时检测流水线(循环运行)
- 相机采集 5000×5000 原图送入 C#;
- Halcon 光照校正(形态学顶帽底帽,抑制褶皱光影、光照不均);
- 局部可形变模板匹配 :自动搜索平移、旋转,建立局部形变场,将待测印花精确对齐标准模板; ✅解决摆放偏移、旋转、轻微布料褶皱形变
- 对齐后的待测图与标准模板灰度差分,得到缺陷候选区域;
- 裁剪每一块候选 ROI,送入 ONNX-YOLO GPU 推理;
- YOLO 输出类别:污点 / 条纹 / 色差 / 错位 / 褶皱(褶皱判定为伪缺陷直接过滤);
- 输出缺陷坐标、类别、数量,保存结果,触发报警 / 分拣信号。
四、工控落地重要优化点(针对 5000×5000 大图像)
- 图像金字塔粗定位 + 精细检测 5K 大图直接做形变模板匹配速度慢。先降分辨率粗定位印花 ROI,原图只裁剪 ROI 做精细差分与 YOLO 推理,大幅提速。
- 硬件选型配合 环形无影光源减少褶皱反光;GPU 启用 CUDA 加速 ONNX 推理;Halcon 引擎开启多核。
- 阈值自适应标定 差分阈值 22、最小缺陷面积不要硬编码,现场采集多组良品自动统计噪声基线,动态调整。
- 两种工作模式切换
- 褶皱轻微:仅 Halcon 形变模板 + 传统差分检测(速度快,适合大批量流水线)
- 褶皱严重:Halcon 提取候选 + YOLO 二次分类(过滤褶皱误检,精度更高,速度略慢)
五、后续可扩展方向
- 增加相机实时采集(Halcon HImageGrabber,GigE 工业相机);
- 增加缺陷坐标保存、报表、图片存档;
- YOLO 模型训练数据集制作指导,标注污点 / 条纹 / 色差 / 错位 / 褶皱 5 类样本;
- 增加 IPC 通信、对接 PLC 输出 NG/OK 信号。