using HalconDotNet; using LB_VisionProcesses.Alogrithms.Halcon; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing.Imaging; using System.Linq; using System.Text; using System.Threading.Tasks; using static LB_VisionProcesses.Alogrithms.Halcon.HFindModelTool; using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip; namespace LB_VisionProcesses.Alogrithms.Halcon { [Process("Halcon2D单模板匹配工具", Category = "Halcon2D工具", Description = "创建单模板匹配工具")] public class HFindModelTool : TAlgorithm { public enum ModelType { 各向异形模板, 局部变形模板 }; public enum SubPixel { none, interpolation }; public enum Metric { use_polarity, ignore_local_polarity }; public HModel ModelID = new HModel(); public HFindModelTool() { strProcessClass = "LB_VisionProcesses.Alogrithms.Halcon.HFindModelTool"; strProcessName = "Halcon2D单模板匹配工具"; Params.Inputs.Add("ModelType", ModelType.各向异形模板); //AngleStart:搜索时的起始角度[需要转换为弧度] Params.Inputs.Add("AngleStart", -5.0); //AngleExtent:搜索时的角度范围,0表示无角度搜索[需要转换为弧度] Params.Inputs.Add("AngleExtent", 10.0); //AngleStep:角度步长--弧度[角度步长 >= 0和角度步长 <= pi / 16] Params.Inputs.Add("AngleStep", "auto"); Params.Inputs.Add("ScaleRMin", 0.9); Params.Inputs.Add("ScaleRMax", 1.1); Params.Inputs.Add("ScaleCMin", 0.9); Params.Inputs.Add("ScaleCMax", 1.1); //MinScore:被找到的模板最小分数 Params.Inputs.Add("MinScore", 0.5); //NumMatches:要找到的模板最多的实例数,0则找到所有可能的匹配 Params.Inputs.Add("NumMatches", 0); //MaxOverlap:允许找到的模型实例的最大重叠比例, 建议值:0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 Params.Inputs.Add("MaxOverlap", 0.2); //SubPixel:计算精度的设置 //'none' 不适用亚像素,最大误差为半个像素 //'interpolation' 差值得到亚像素精度 //'least_squares', 'least_squares_high', 'least_squares_very_high' //'max_deformation 1', 'max_deformation 2', 'max_deformation 3', 'max_deformation 4' Params.Inputs.Add("SubPixel", "none"); //NumLevels:搜索时金字塔的层级,0表示不使用金字塔 Params.Inputs.Add("NumLevels", 0); //Greediness:贪婪度,搜索启发式,一般都设为0.8,越高速度越快,容易出现找不到的情况 Params.Inputs.Add("Greediness", 0.8); Params.Inputs.Add("ResultType", ""); Params.Inputs.Add("MinCount", 0); Params.Inputs.Add("MaxCount", 9999); Params.Outputs.Add("CenterX", new List()); Params.Outputs.Add("CenterY", new List()); Params.Outputs.Add("Phi", new List()); Params.Outputs.Add("Score", new List()); Params.Outputs.Add("Count", 0); } private static readonly object lockObj = new object(); /// /// 运行算子 /// public override void TAlgorithmMain() { lock (lockObj) { #region 初始化变量 HTuple hv_CenterRow = new HTuple(); HTuple hv_CenterColumn = new HTuple(); HTuple hv_CenterPhi = new HTuple(); HTuple hv_Score = new HTuple(); #endregion try { if (InputImage == null) { Msg = "输入图片为空"; Result = false; return; } if (InputImage is Bitmap) { try { using (HImage hImage = new HImage()) { Rectangle rect = new Rectangle(0, 0, ((Bitmap)InputImage).Width, ((Bitmap)InputImage).Height); BitmapData srcBmpData = ((Bitmap)InputImage).LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb); hImage.GenImageInterleaved(srcBmpData.Scan0, "rgbx", ((Bitmap)InputImage).Width, ((Bitmap)InputImage).Height, 0, "byte", ((Bitmap)InputImage).Width, ((Bitmap)InputImage).Height, 0, 0, -1, 0); ((Bitmap)InputImage).UnlockBits(srcBmpData); ((Bitmap)InputImage).Dispose(); InputImage = hImage.Clone(); } } catch (Exception ex) { Msg = "转图出错:" + ex.Message; Result = false; return; } } if (!(InputImage is HObject)) { Msg = "输入图片格式不为HObject"; Result = false; return; } object DomainImage = null; #region 裁剪区域 if (!ReduceDomainImage(InputImage, ref DomainImage)) { Msg = "裁剪区域失败"; Result = false; return; } HObject hoDomainImage = DomainImage as HObject; HTuple hv_Channels = new HTuple(); //判断是否为灰度图 using (HDevDisposeHelper dh = new HDevDisposeHelper()) { try { HOperatorSet.CountChannels(hoDomainImage, out hv_Channels); if (hv_Channels.TupleInt() != 1) HOperatorSet.Rgb1ToGray(hoDomainImage, out hoDomainImage); //转换后再次检查是否为灰度图 HOperatorSet.CountChannels(hoDomainImage, out hv_Channels); if (hv_Channels.TupleInt() != 1) { HOperatorSet.Rgb1ToGray(hoDomainImage, out hoDomainImage); Msg = "输入图片不为灰度图"; Result = false; return; } } catch { Msg = "输入图片不为灰度图且转换失败"; Result = false; return; } } #endregion //判断是否有模板 if (ModelID.hvModel == null || ModelID.hvModel.Length == 0) { Msg = "未创建模板"; Result = false; return; } #region 算子逻辑 Record = new ObjectRecord(); HOperatorSet.GetImageSize(ModelID.hoImage, out HTuple ho_ModelWidth, out HTuple ho_ModelHeight); ModelType type = ModelID.Type; double AngleStart = Convert.ToDouble(Params.Inputs["AngleStart"]); double AngleExtent = Convert.ToDouble(Params.Inputs["AngleExtent"]); double MinScore = Convert.ToDouble(Params.Inputs["MinScore"]); int NumMatches = ProcessParams.ConvertToInt32(Params.Inputs["NumMatches"]); double MaxOverlap = Convert.ToDouble(Params.Inputs["MaxOverlap"]); string SubPixel = ProcessParams.ConvertToString(Params.Inputs["SubPixel"]); string NumLevels = ProcessParams.ConvertToString(Params.Inputs["NumLevels"]); double Greediness = Convert.ToDouble(Params.Inputs["Greediness"]); double ScaleRMin = Convert.ToDouble(Params.Inputs["ScaleRMin"]); double ScaleRMax = Convert.ToDouble(Params.Inputs["ScaleRMax"]); double ScaleCMin = Convert.ToDouble(Params.Inputs["ScaleCMin"]); double ScaleCMax = Convert.ToDouble(Params.Inputs["ScaleCMax"]); switch (type) { case ModelType.局部变形模板: //局部变形模板匹配 MaxTimeOut = 8000; #region 参数介绍 //* 参数1:输入图像 //* 参数2:模板句柄 //* 参数3:搜索时的起始角度 //* 参数4:搜索时的终止角度,必须与创建模板时的有交集 //* 参数5:被找到的模板最小分数--大于等于这个值才能被匹配 //* 默认值:0.5 建议值:0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 //* 典型值范围:0≤MinScore ≤ 1 //* 最小增量:0.01 建议增量:0.05 //* 参数6:要找到的模板最大实例数 //* 0 不限制 //* 参数7:要找到的模型实例的最大重叠比例 //* 默认值:0.5 建议值:0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 //* 典型值范围:0≤ MaxOverlap≤ 1 最小增量:0.01 建议增量:0.05 //* 0 表示不允许重叠 //* 参数8:计算精度的设置 //* 'none' 不适用亚像素,最大误差为半个像素 //* 'interpolation' 差值得到亚像素精度 //* 'least_squares', 'least_squares_high', 'least_squares_very_high' //* 'max_deformation 1', 'max_deformation 2', 'max_deformation 3', 'max_deformation 4' //* 'max_deformation 5', 'max_deformation 6' //* 参数9:搜索时金字塔的层级 //* 参数10:贪婪度,搜索启发式,一般都设为0.8,越高速度快,容易出现找不到的情况 //* 0≤ Greediness ≤ 1 //* 最后4个:输出匹配位置的行和列坐标、角度、得分 【中心坐标】 #endregion //0.5为最低分数阈值,后续通过MinScore过滤 HOperatorSet.FindLocalDeformableModel(hoDomainImage, out _, out _, out _, ModelID.hvModel , AngleStart / 180.0 * Math.PI, AngleExtent / 180.0 * Math.PI , ScaleRMin, ScaleRMax , ScaleCMin, ScaleCMax , 0.5, NumMatches, MaxOverlap , NumLevels == "auto" ? NumLevels : Convert.ToInt32(NumLevels) , Greediness, new HTuple(), new HTuple(), new HTuple() , out hv_Score, out hv_CenterRow, out hv_CenterColumn); break; case ModelType.各向异形模板: //形状模板匹配 #region 参数介绍 //* 参数1:输入图像 //* 参数2:模板句柄 //* 参数3:搜索时的起始角度 //* 参数4:搜索时的终止角度,必须与创建模板时的有交集 //* 参数5:被找到的模板最小分数--大于等于这个值才能被匹配 //* 默认值:0.5 建议值:0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 //* 典型值范围:0≤MinScore ≤ 1 //* 最小增量:0.01 建议增量:0.05 //* 参数6:要找到的模板最大实例数 //* 0 不限制 //* 参数7:要找到的模型实例的最大重叠比例 //* 默认值:0.5 建议值:0.0,0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0 //* 典型值范围:0≤ MaxOverlap≤ 1 最小增量:0.01 建议增量:0.05 //* 0 表示不允许重叠 //* 参数8:计算精度的设置 //* 'none' 不适用亚像素,最大误差为半个像素 //* 'interpolation' 差值得到亚像素精度 //* 'least_squares', 'least_squares_high', 'least_squares_very_high' //* 'max_deformation 1', 'max_deformation 2', 'max_deformation 3', 'max_deformation 4' //* 'max_deformation 5', 'max_deformation 6' //* 参数9:搜索时金字塔的层级 //* 参数10:贪婪度,搜索启发式,一般都设为0.8,越高速度快,容易出现找不到的情况 //* 0≤ Greediness ≤ 1 //* 最后4个:输出匹配位置的行和列坐标、角度、得分 [中心坐标] #endregion //0.5为最低分数阈值,后续通过MinScore过滤 HOperatorSet.FindAnisoShapeModel(hoDomainImage, ModelID.hvModel , AngleStart / 180.0 * Math.PI, AngleExtent / 180.0 * Math.PI , ScaleRMin, ScaleRMax , ScaleCMin, ScaleCMax , 0.5, NumMatches, MaxOverlap, SubPixel , NumLevels == "auto" ? NumLevels : Convert.ToInt32(NumLevels) , Greediness , out hv_CenterRow, out hv_CenterColumn , out hv_CenterPhi , out _, out _, out hv_Score); break; default: Msg = "不支持的模板"; Result = false; return; } #endregion #region 结果处理 List CenterX = new List(); List CenterY = new List(); List Phi = new List(); List Score = new List(); for (int i = 0; i < hv_CenterRow.Length; i++) { if (hv_Score[i].D < MinScore) continue; CenterX.Add(Math.Round(hv_CenterColumn[i].D, 3)); CenterY.Add(Math.Round(hv_CenterRow[i].D, 3)); if (i >= hv_CenterPhi.Length) Phi.Add(0); else Phi.Add(Math.Round(hv_CenterPhi[i].D, 3)); Score.Add(Math.Round(hv_Score[i].D, 3)); //HOperatorSet.GenRectangle2ContourXld(out HObject hRectangle, hv_CenterRow[i].D, hv_CenterColumn[i].D, hv_CenterPhi[i].D, ho_ModelWidth / 2, ho_ModelHeight / 2); HOperatorSet.GenRectangle2(out HObject hRectangle, CenterY[i], CenterX[i], Phi[i], ho_ModelWidth / 2, ho_ModelHeight / 2); ((ObjectRecord)Record).AddRecord(hRectangle); } Params.Outputs["CenterX"] = CenterX; Params.Outputs["CenterY"] = CenterY; Params.Outputs["Phi"] = Phi; Params.Outputs["Score"] = Score; Params.Outputs["Count"] = Score.Count; #endregion #region 生成OutputImage给后续处理 try { OutputImage = hoDomainImage; } catch (Exception ex) { Msg = "生成OutputImage失败,原因是:" + ex.ToString(); Result = false; return; } #endregion if (Msg == "运行超时") { Result = false; Record.ChangeAll2False(); return; } int MinCount = ProcessParams.ConvertToInt32(Params.Inputs["MinCount"]); int MaxCount = ProcessParams.ConvertToInt32(Params.Inputs["MaxCount"]); if (CenterX.Count < MinCount || CenterX.Count > MaxCount) { Msg = string.Format("结果个数超出范围({0},{1})", MinCount, MaxCount); Record.ChangeAll2False(); Result = false; return; } Msg = "运行成功"; Result = true; return; } catch (Exception ex) { Msg = "运行失败,原因是:" + ex.ToString().TrimEnd(); OutputImage = null; Result = false; return; } finally { if (!Result) { Params.Outputs["CenterX"] = 0; Params.Outputs["CenterY"] = 0; Params.Outputs["Phi"] = 0; Params.Outputs["Score"] = 0; Params.Outputs["Count"] = 0; } bCompleted = true; #region 内存释放 #endregion } } } public bool CreateModel(HObject Template, ModelType modelType = ModelType.各向异形模板, string NumLevels = "auto" , double AngleStart = -5, double AngleExtent = 10, string AngleStep = "auto" , double ScaleRMin = 0.9, double ScaleRMax = 1.1, string ScaleRStep = "auto" , double ScaleCMin = 1.1, double ScaleCMax = 0.9, string ScaleCStep = "auto" , string Optimization = "none", string Metric = "ignore_local_polarity" , string Contrast = "auto", int MinContrast = 10) { try { #region 参数介绍 //Template: : //reduce_domain后的模板图像 //NumLevels ,//金字塔的层数,可设为“auto”或0—10的整数 //AngleStart ,//模板旋转的起始角度 //AngleExtent ,//模板旋转角度范围, >=0 //AngleStep ,//旋转角度的步长, >=0 and <=pi/16 //Optimization ,//设置模板优化和模板创建方法 //Metric , //匹配方法设置 //Contrast ,//设置对比度 //MinContrast // 设置最小对比度 #endregion switch (modelType) { case ModelType.局部变形模板: HOperatorSet.CreateLocalDeformableModel(Template, NumLevels == "auto" ? NumLevels : Convert.ToInt16(NumLevels) , AngleStart, AngleExtent, AngleStep == "auto" ? AngleStep : Convert.ToDouble(AngleStep) , ScaleRMin, ScaleRMax, ScaleRStep == "auto" ? ScaleRStep : Convert.ToDouble(ScaleRStep) , ScaleCMin, ScaleCMax, ScaleCStep == "auto" ? ScaleCStep : Convert.ToDouble(ScaleCStep) , Optimization, Metric , Contrast, MinContrast , new HTuple(), new HTuple() , out ModelID.hvModel); ModelID.hoImage = Template.CopyObj(1, -1); ModelID.Type = ModelType.局部变形模板; return true; case ModelType.各向异形模板: default: HOperatorSet.CreateAnisoShapeModel(Template, NumLevels == "auto" ? NumLevels : Convert.ToInt16(NumLevels) , AngleStart, AngleExtent, AngleStep == "auto" ? AngleStep : Convert.ToDouble(AngleStep) , ScaleRMin, ScaleRMax, ScaleRStep == "auto" ? ScaleRStep : Convert.ToDouble(ScaleRStep) , ScaleCMin, ScaleCMax, ScaleCStep == "auto" ? ScaleCStep : Convert.ToDouble(ScaleCStep) , Optimization, Metric , Contrast, MinContrast, out ModelID.hvModel); ModelID.hoImage = Template.CopyObj(1, -1); ModelID.Type = ModelType.各向异形模板; return true; } } catch { return false; } } /// /// 加载算法 /// /// 完整路径带.json /// public override bool Load(string fullPath) { try { if (string.IsNullOrEmpty(fullPath)) return false; if (!fullPath.Contains(".json")) { Debug.WriteLine("文件路径不完整"); return false; } if (fullPath.StartsWith(".\\")) { // 判断原字符串长度是否大于等于2,避免越界 if (fullPath.Length >= 2) { // 替换开头两个字符 fullPath = Application.StartupPath + fullPath.Substring(2); Debug.WriteLine($"修改后的字符串: {fullPath}"); } } // 获取不带文件名的目录路径 string directoryPath = Path.GetDirectoryName(fullPath); strProcessName = Path.GetFileNameWithoutExtension(fullPath); // 修正真实路径,模板匹配工具其路径为一个文件夹,而不是一个文件 fullPath = directoryPath + "\\" + strProcessName + "\\" + strProcessName + ".json"; if (!File.Exists(fullPath)) { Debug.WriteLine("文件不存在创建空文件"); ModelID = new HModel(fullPath, ModelType.各向异形模板, strProcessName); Save(directoryPath); ModelID.Save(fullPath, ModelID.Type); return true; } string strJson = string.Empty; using (StreamReader streamReader = new StreamReader(fullPath, Encoding.UTF8)) { strJson = streamReader.ReadToEnd(); streamReader.Close(); } Params = JsonConvert.DeserializeObject(strJson); if (Params == null) return false; // 反序列化后修复数据 Params.FixDeserializedData(); strProcessName = Path.GetFileNameWithoutExtension(fullPath); if (!Enum.TryParse(Params.Inputs["ModelType"].ToString(), out ModelType modelType)) return false; if (!ModelID.Load(fullPath, modelType)) return false; return true; } catch { return false; } } /// /// 保存算法 /// /// 不带.json /// public override bool Save(string filePath) { try { // 修正真实路径,模板匹配工具其路径为一个文件夹,而不是一个文件 filePath += ("\\" + strProcessName); Params.Inputs.Add("ModelType", ModelID.Type.ToString()); if (!base.Save(filePath) || !ModelID.Save(filePath + "\\" + strProcessName + ".json", ModelID.Type)) return false; return true; } catch { return false; } } } public class HModel { public HModel(string modelName = "") { ModelName = modelName; } public HModel(string modelFullPath, ModelType modelType, string modelName) { ModelFullPath = modelFullPath; ModelName = modelName; Type = modelType; switch (modelType) { case ModelType.局部变形模板: Load(ModelFullPath, modelType); break; case ModelType.各向异形模板: default: Load(ModelFullPath, modelType); break; } } /// /// 模板路径 /// public string ModelFullPath = "C:\\MyVisionModel\\ModelName.temp"; /// /// 模板名称 /// public string ModelName { get { return Path.GetFileNameWithoutExtension(ModelFullPath); } set { // 获取文件的扩展名(包括点) string fileExtension = Path.GetExtension(ModelFullPath); // 获取文件所在的文件夹路径 string directoryPath = Path.GetDirectoryName(ModelFullPath); ModelFullPath = directoryPath + "\\" + value + fileExtension; } } /// /// Halcon模板句柄 /// public HTuple hvModel; /// /// 模板图片 /// public HObject hoImage; public HTuple Width { get { if (hoImage == null) return (HTuple)0; HOperatorSet.GetImageSize(hoImage, out HTuple ho_ModelWidth, out HTuple ho_ModelHeight); return ho_ModelWidth; } } public HTuple Height { get { if (hoImage == null) return (HTuple)0; HOperatorSet.GetImageSize(hoImage, out HTuple ho_ModelWidth, out HTuple ho_ModelHeight); return ho_ModelHeight; } } /// /// Halcon模板类型 /// public ModelType Type = ModelType.各向异形模板; /// /// 加载模板(带.json) /// /// 完整路径带.json /// public bool Load(string fullPath, ModelType modelType) { try { if (string.IsNullOrEmpty(fullPath)) return false; string filePath = Path.GetDirectoryName(fullPath); ModelName = Path.GetFileNameWithoutExtension(fullPath); ModelFullPath = filePath + "\\" + ModelName; ModelFullPath += ".model"; Type = modelType; switch (modelType) { case ModelType.局部变形模板: ModelFullPath += ".dfm"; if (File.Exists(ModelFullPath)) HOperatorSet.ReadDeformableModel(ModelFullPath, out hvModel); break; case ModelType.各向异形模板: default: if (File.Exists(ModelFullPath)) HOperatorSet.ReadShapeModel(ModelFullPath, out hvModel); break; } string ImageFileName = Path.GetFileNameWithoutExtension(fullPath); string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp"; if (File.Exists(ImageFullPath)) HOperatorSet.ReadImage(out hoImage, ImageFullPath); return true; } catch { Type = ModelType.各向异形模板; return false; } } /// /// 保存模板(路径带.model) /// /// 带.model /// public bool Save(string fullPath) { try { if (string.IsNullOrEmpty(fullPath)) return false; string filePath = Path.GetDirectoryName(fullPath); //判断文件夹是否存在 if (!Directory.Exists(filePath)) { try { Directory.CreateDirectory(filePath); } catch (Exception) { } } ModelName = Path.GetFileNameWithoutExtension(fullPath); // 使用 Path.GetExtension 提取扩展名 switch (Type) { case ModelType.局部变形模板: HOperatorSet.WriteDeformableModel(hvModel, fullPath); break; case ModelType.各向异形模板: default: HOperatorSet.WriteShapeModel(hvModel, fullPath); break; } string ImageFileName = Path.GetFileNameWithoutExtension(fullPath); string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp"; HOperatorSet.WriteImage(hoImage, "bmp", 0, ImageFullPath); return true; } catch { return false; } } /// /// 保存模板(路径带.json) /// /// 带.json /// public bool Save(string fullPath, ModelType modelType) { try { if (string.IsNullOrEmpty(fullPath)) return false; string filePath = Path.GetDirectoryName(fullPath); //判断文件夹是否存在 if (!Directory.Exists(filePath)) { try { Directory.CreateDirectory(filePath); } catch (Exception) { } } ModelName = Path.GetFileNameWithoutExtension(fullPath); ModelFullPath = filePath + "\\" + ModelName + ".model"; Type = modelType; switch (modelType) { case ModelType.局部变形模板: HOperatorSet.WriteDeformableModel(hvModel, ModelFullPath); break; case ModelType.各向异形模板: default: HOperatorSet.WriteShapeModel(hvModel, ModelFullPath); break; } string ImageFileName = Path.GetFileNameWithoutExtension(fullPath); string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp"; HOperatorSet.WriteImage(hoImage, "bmp", 0, ImageFullPath); return true; } catch { return false; } } } }