网站模板库免费lol关键词查询
目录
- 引言
- 前期准备
- Step1 设计可序列化的输入输出集合【不支持多线程】
- Step2 设计程序框架
- 1、抽象层【IProcess】
- 2、父类【HAlgorithm】
- 3、子类【HFindModelTool】
- Step3 设计UI
- 结果展示
引言
通过仿照VisionPro软件二次开发Halcon的模板匹配工具,便于在客户端软件中调试,可以保存到本地导入复用,后续开发软件也可快速调用。
前期准备
安装Halcon【教程的版本为19.05】
Step1 设计可序列化的输入输出集合【不支持多线程】
设计可序列化的输入输出集合
1、可通过名称索引,也可以通过序号索引【示例:Inputs[“ABC”]或Inputs[0]】
2、可任意添加和移除【示例:Inputs.Add(“123”,0),Inputs.Remove(0)】
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml.Linq;namespace MyProcesses.Alogrithms
{[Serializable]public class AlogrithmCollections<T> : ICollection<AlogrithmCollectionItem<T>>{private List<AlogrithmCollectionItem<T>> _items = new List<AlogrithmCollectionItem<T>>();// 通过名称查找public AlogrithmCollectionItem<T> Get(string name){var item = _items.FirstOrDefault(item => item.Name.Equals(name, StringComparison.OrdinalIgnoreCase));if (item == null)return default;return item;}// 通过索引查找public AlogrithmCollectionItem<T> Get(int index){var item = _items.FirstOrDefault(item => item.Index == index);if (item == null)return default;return item;}// ICollection<NamedItem<T>> 实现public int Count => _items.Count;public bool IsReadOnly => false;void ICollection<AlogrithmCollectionItem<T>>.Add(AlogrithmCollectionItem<T> item){var existingItem = Get(item.Name);if (existingItem != null){// 如果已存在,更新现有项的值existingItem = item;}else{// 如果不存在,创建新项并分配索引_items.Add(item);}// 更新后自动排序SortItems();}// 添加项时为新项分配一个索引public void Add(string name, T value){var existingItem = Get(name);if (existingItem != null){// 如果已存在,更新现有项的值existingItem.Value = value;}else{// 如果不存在,创建新项并分配索引var newItem = new AlogrithmCollectionItem<T>(name, _items.Count > 0 ? _items.Max(i => i.Index) + 1 : 0, value);_items.Add(newItem);}// 更新后自动排序SortItems();}// 按 Index 排序集合private void SortItems(){_items = _items.OrderBy(item => item.Index).ToList();}// 通过索引器实现基于序号的访问public T this[int index]{get{if (index < 0 || index >= _items.Count){return default;}return _items[index].Value;}set{if (index < 0 || index >= _items.Count){return;}_items[index].Value = value;}}public T this[string name]{get{var existingItem = Get(name);if (existingItem != null){// 如果已存在,更新现有项的值return existingItem.Value;}return default;}set{var item = _items.FirstOrDefault(i => i.Name == name);if (item != null){item.Value = value;}}}public void Clear(){_items.Clear();_items.TrimExcess(); // 将容量缩减到实际元素数量}public bool Contains(AlogrithmCollectionItem<T> item){return _items.Contains(item);}public bool Contains(string name){var item = _items.FirstOrDefault(i => i.Name == name);if (item != null)return true;return false;}public void CopyTo(AlogrithmCollectionItem<T>[] array, int arrayIndex){_items.CopyTo(array, arrayIndex);}public bool Remove(AlogrithmCollectionItem<T> item){var removed = _items.Remove(item);if (removed){// 移除元素后,调整后续元素的索引UpdateIndexes();}return removed;}public IEnumerator<AlogrithmCollectionItem<T>> GetEnumerator(){return _items.GetEnumerator();}IEnumerator IEnumerable.GetEnumerator(){return _items.GetEnumerator();}// 通过名称删除public bool Remove(string name){var namedItem = Get(name);if (namedItem != null){return Remove(namedItem);}return false;}// 通过索引删除public bool Remove(int index){var namedItem = Get(index);if (namedItem != null){return Remove(namedItem);}return false;}// 删除项后,调整其他项的索引private void UpdateIndexes(){for (int i = 0; i < _items.Count; i++){_items[i].Index = i;}}}public class AlogrithmCollectionItem<T>{public string Name { get; set; }public int Index { get; set; }public T Value { get; set; } // 存储的对象public AlogrithmCollectionItem(string name, int index, T value){Name = name;Index = index;Value = value;}public override string ToString(){return $"{Name} (Index: {Index}, Value: {Value})";}}
}
Step2 设计程序框架
1、抽象层【IProcess】
dicProcesses为可用于实例化的类全称,后续可以通过反射直接实例化该类,这些类都是继承于HAlgorithm
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;namespace MyProcesses
{public abstract class IProcess : IDisposable, ICloneable{/// <summary>/// 流程集合/// </summary>public static Dictionary<string, string> dicProcesses = new Dictionary<string, string>{{ "相机取图", "MyCameras.BaseCamera" },{ "图片转换工具", "MyProcesses.Alogrithms.Halcon.BitmapConvertToHoImageTool" },{ "斑点工具", "MyProcesses.Alogrithms.Halcon.HBlobTool" },{ "模板匹配工具", "MyProcesses.Alogrithms.Halcon.HFindModelTool" },{ "延时", "MyProcesses.Processes.DelayTime" }};/// <summary>/// 运行日志/// </summary>public string strMsg = "运行成功";/// <summary>/// 运行结果/// </summary>public bool bResult = true;/// <summary>/// 运行时间/// </summary>public double RunTime = 0;/// <summary>/// 允许运行时间/// </summary>public double MaxTimeOut = 1000;/// <summary>/// 运行完成标记/// </summary>protected bool bCompleted = false;/// <summary>/// 工具名称/// </summary>public string strProcessName = string.Empty;/// <summary>/// 工具名称/// </summary>public string strProcessClass = "MyProcesses.IProcess";/// <summary>/// 运行参数/// </summary>public ProcessCollections<object> ProcessCollections = new ProcessCollections<object>();public void InitRunParams(){bResult = false;bCompleted = false;strMsg = "";}public abstract bool Run();public abstract bool Load(string fullPath);public abstract bool Save(string fullPath);public static Assembly GetExecutingAssembly(){return Assembly.GetExecutingAssembly();}public abstract void Dispose();public abstract object Clone();}
}
2、父类【HAlgorithm】
父类重载了抽象类的成员函数,后续拓展Halcon算法类只需要继承父类和实例化父类即可
using HalconDotNet;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Collections.Specialized.BitVector32;
using System.Data.Common;
using OpenCvSharp;
using System.Runtime.InteropServices;
using System.Drawing.Imaging;
using System.Drawing;
using Newtonsoft.Json;
using MyControls;
using System.Net;namespace MyProcesses.Alogrithms.Halcon
{public class HAlgorithm : IProcess{/// <summary>/// 输入图片/// </summary>public object InputImage;/// <summary>/// 输出图片/// </summary>public object OutputImage;/// <summary>/// 运行图片/// </summary>public HObject hoDomainImage;/// <summary>/// 结果图片/// </summary>public HObject hoRecordImage;/// <summary>/// 算法参数/// </summary>public AlogrithmParams Params = new AlogrithmParams();public bool ReduceDomainImage(){if (InputImage == null){strMsg = "输入图片为空";bResult = false;return false;}if (!(InputImage is HObject)){strMsg = "输入图片不是HObject,未经过转换";bResult = false;return false;}lock (InputImage){try{switch (Params.ROI?.GetType().Name){case "HRectangle2":HOperatorSet.GenRectangle2(out HObject hRectangle2, (HTuple)((HRectangle2)Params.ROI).Row, (HTuple)((HRectangle2)Params.ROI).Column, (HTuple)((HRectangle2)Params.ROI).Phi, (HTuple)((HRectangle2)Params.ROI).SemiLength1, (HTuple)((HRectangle2)Params.ROI).SemiLength2);HOperatorSet.ReduceDomain((HObject)InputImage, hRectangle2, out hoDomainImage);break;case "HCircle":HOperatorSet.GenCircle(out HObject hCircle, (HTuple)((HCircle)Params.ROI).Row, (HTuple)((HCircle)Params.ROI).Column, (HTuple)((HCircle)Params.ROI).Radius);HOperatorSet.ReduceDomain((HObject)InputImage, hCircle, out hoDomainImage);break;case "ROI":default:hoDomainImage = ((HObject)InputImage)?.CopyObj(1, -1);break;}return true;}catch{strMsg = "裁剪区域失败";bResult = false;return false;}}}public override void Dispose(){if (InputImage != null){if (InputImage is HObject)((HObject)InputImage).Dispose();}if (hoDomainImage != null)hoDomainImage.Dispose();if (hoRecordImage != null)hoRecordImage.Dispose();//this.Dispose();}public override object Clone(){return MemberwiseClone();}public override bool Run(){DateTime StartTime = DateTime.Now;InitRunParams();// 创建并启动任务Task.Factory.StartNew(() => { HAlgorithmMain(); });while ((DateTime.Now - StartTime).TotalMilliseconds <= MaxTimeOut){if (bCompleted)return bResult;}strMsg = "运行超时";return false;}/// <summary>/// 算子逻辑/// </summary>public virtual void HAlgorithmMain(){bCompleted = true;Console.WriteLine("任务完成");}/// <summary>/// 加载算法/// </summary>/// <param name="fullPath">完整路径带.json</param>/// <returns></returns>public override bool Load(string fullPath = ""){try{if (string.IsNullOrEmpty(fullPath))return false;if (!fullPath.Contains(".json")){Console.WriteLine("文件路径不完整");return false;}if (!File.Exists(fullPath)){Console.WriteLine("文件不存在创建空文件");// 获取不带文件名的目录路径string directoryPath = Path.GetDirectoryName(fullPath);strProcessName = Path.GetFileNameWithoutExtension(fullPath);Save(directoryPath);return true;}string strJson = string.Empty;using (StreamReader streamReader = new StreamReader(fullPath, Encoding.UTF8)){strJson = streamReader.ReadToEnd();streamReader.Close();}Params = JsonConvert.DeserializeObject<AlogrithmParams>(strJson);if (Params == null)return false;return true;}catch { return false; }}/// <summary>/// 保存算法/// </summary>/// <param name="filePath">不带.json</param>/// <returns></returns>public override bool Save(string filePath = ""){try{if (string.IsNullOrEmpty(filePath))return false;string strJson = string.Empty;strJson = JsonConvert.SerializeObject(Params);JsonFormatting(ref strJson);Params = JsonConvert.DeserializeObject<AlogrithmParams>(strJson);//判断文件夹是否存在,防呆输入为文件名称if (!Directory.Exists(filePath)){try{Directory.CreateDirectory(filePath);}catch (Exception){ }}File.WriteAllText(filePath + "//" + strProcessName + ".json", strJson, Encoding.UTF8);return true;}catch { return false; }}#region 自定义的算法public static int JsonFormatting(ref string strJson){try{JsonSerializer jsonSerializer = new JsonSerializer();JsonReader reader = new JsonTextReader(new StringReader(strJson));object obj = jsonSerializer.Deserialize(reader);if (obj == null)return 0;StringWriter stringWriter = new StringWriter();JsonTextWriter jsonTextWriter1 = new JsonTextWriter(stringWriter);jsonTextWriter1.Formatting = Formatting.Indented;jsonTextWriter1.Indentation = 4;jsonTextWriter1.IndentChar = ' ';JsonTextWriter jsonTextWriter2 = jsonTextWriter1;jsonSerializer.Serialize(jsonTextWriter2, obj);strJson = stringWriter.ToString();return 1;}catch (Exception ex){return -1;}}public static void Bitmap2HObject(Bitmap bmp, out HObject image){try{if (bmp == null){image = null;return;}BitmapData srcBmpData;switch (bmp.PixelFormat){case PixelFormat.Format24bppRgb:srcBmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);HOperatorSet.GenImageInterleaved(out image, srcBmpData.Scan0, "bgr", bmp.Width, bmp.Height, 0, "byte", 0, 0, 0, 0, -1, 0);bmp.UnlockBits(srcBmpData);break;default:srcBmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);HOperatorSet.GenImage1(out image, "byte", bmp.Width, bmp.Height, srcBmpData.Scan0);bmp.UnlockBits(srcBmpData);break;}}catch (Exception ex){image = null;}}/// <summary>/// 显示符号/// </summary>/// <param name="hv_WindowHandle"></param>/// <param name="hv_String"></param>/// <param name="hv_CoordSystem"></param>/// <param name="hv_Row"></param>/// <param name="hv_Column"></param>/// <param name="hv_Color"></param>/// <param name="hv_Box"></param>public static void disp_message(HTuple hv_WindowHandle, HTuple hv_String, HTuple hv_CoordSystem, HTuple hv_Row, HTuple hv_Column, HTuple hv_Color, HTuple hv_Box){// Local iconic variables // Local control variables HTuple hv_GenParamName = new HTuple(), hv_GenParamValue = new HTuple();HTuple hv_Color_COPY_INP_TMP = new HTuple(hv_Color);HTuple hv_Column_COPY_INP_TMP = new HTuple(hv_Column);HTuple hv_CoordSystem_COPY_INP_TMP = new HTuple(hv_CoordSystem);HTuple hv_Row_COPY_INP_TMP = new HTuple(hv_Row);try{if ((int)new HTuple(hv_Row_COPY_INP_TMP.TupleEqual(new HTuple())).TupleOr(new HTuple(hv_Column_COPY_INP_TMP.TupleEqual(new HTuple()))) != 0){hv_Color_COPY_INP_TMP.Dispose();hv_Column_COPY_INP_TMP.Dispose();hv_CoordSystem_COPY_INP_TMP.Dispose();hv_Row_COPY_INP_TMP.Dispose();hv_GenParamName.Dispose();hv_GenParamValue.Dispose();return;}if ((int)new HTuple(hv_Row_COPY_INP_TMP.TupleEqual(-1)) != 0){hv_Row_COPY_INP_TMP.Dispose();hv_Row_COPY_INP_TMP = 12;}if ((int)new HTuple(hv_Column_COPY_INP_TMP.TupleEqual(-1)) != 0){hv_Column_COPY_INP_TMP.Dispose();hv_Column_COPY_INP_TMP = 12;}////Convert the parameter Box to generic parameters.hv_GenParamName.Dispose();hv_GenParamName = new HTuple();hv_GenParamValue.Dispose();hv_GenParamValue = new HTuple();if ((int)new HTuple(new HTuple(hv_Box.TupleLength()).TupleGreater(0)) != 0){if ((int)new HTuple(hv_Box.TupleSelect(0).TupleEqual("false")) != 0){//Display no boxusing (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamName = hv_GenParamName.TupleConcat("box");hv_GenParamName.Dispose();hv_GenParamName = ExpTmpLocalVar_GenParamName;}}using (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamValue = hv_GenParamValue.TupleConcat("false");hv_GenParamValue.Dispose();hv_GenParamValue = ExpTmpLocalVar_GenParamValue;}}}else if ((int)new HTuple(hv_Box.TupleSelect(0).TupleNotEqual("true")) != 0){//Set a color other than the default.using (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamName = hv_GenParamName.TupleConcat("box_color");hv_GenParamName.Dispose();hv_GenParamName = ExpTmpLocalVar_GenParamName;}}using (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamValue = hv_GenParamValue.TupleConcat(hv_Box.TupleSelect(0));hv_GenParamValue.Dispose();hv_GenParamValue = ExpTmpLocalVar_GenParamValue;}}}}if ((int)new HTuple(new HTuple(hv_Box.TupleLength()).TupleGreater(1)) != 0){if ((int)new HTuple(hv_Box.TupleSelect(1).TupleEqual("false")) != 0){//Display no shadow.using (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamName = hv_GenParamName.TupleConcat("shadow");hv_GenParamName.Dispose();hv_GenParamName = ExpTmpLocalVar_GenParamName;}}using (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamValue = hv_GenParamValue.TupleConcat("false");hv_GenParamValue.Dispose();hv_GenParamValue = ExpTmpLocalVar_GenParamValue;}}}else if ((int)new HTuple(hv_Box.TupleSelect(1).TupleNotEqual("true")) != 0){//Set a shadow color other than the default.using (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamName = hv_GenParamName.TupleConcat("shadow_color");hv_GenParamName.Dispose();hv_GenParamName = ExpTmpLocalVar_GenParamName;}}using (HDevDisposeHelper dh = new HDevDisposeHelper()){{HTupleExpTmpLocalVar_GenParamValue = hv_GenParamValue.TupleConcat(hv_Box.TupleSelect(1));hv_GenParamValue.Dispose();hv_GenParamValue = ExpTmpLocalVar_GenParamValue;}}}}//Restore default CoordSystem behavior.if ((int)new HTuple(hv_CoordSystem_COPY_INP_TMP.TupleNotEqual("window")) != 0){hv_CoordSystem_COPY_INP_TMP.Dispose();hv_CoordSystem_COPY_INP_TMP = "image";}//if ((int)new HTuple(hv_Color_COPY_INP_TMP.TupleEqual("")) != 0){//disp_text does not accept an empty string for Color.hv_Color_COPY_INP_TMP.Dispose();hv_Color_COPY_INP_TMP = new HTuple();}////HOperatorSet.DispText(hv_WindowHandle, hv_String, hv_CoordSystem_COPY_INP_TMP,// hv_Row_COPY_INP_TMP, hv_Column_COPY_INP_TMP, hv_Color_COPY_INP_TMP, hv_GenParamName,hv_GenParamValue);HOperatorSet.SetTposition(hv_WindowHandle, hv_Row_COPY_INP_TMP, hv_Column_COPY_INP_TMP);HOperatorSet.WriteString(hv_WindowHandle, hv_String);hv_Color_COPY_INP_TMP.Dispose();hv_Column_COPY_INP_TMP.Dispose();hv_CoordSystem_COPY_INP_TMP.Dispose();hv_Row_COPY_INP_TMP.Dispose();hv_GenParamName.Dispose();hv_GenParamValue.Dispose();return;}catch (HalconException HDevExpDefaultException){hv_Color_COPY_INP_TMP.Dispose();hv_Column_COPY_INP_TMP.Dispose();hv_CoordSystem_COPY_INP_TMP.Dispose();hv_Row_COPY_INP_TMP.Dispose();hv_GenParamName.Dispose();hv_GenParamValue.Dispose();throw HDevExpDefaultException;}}public static void SetColor(HTuple hv_WindowHandle){HOperatorSet.SetColor(hv_WindowHandle, GetRandomColor());}/// <summary>/// 设置颜色("dark olive green")/// </summary>/// <param name="hv_WindowHandle"></param>/// <param name="color"></param>public static void SetColor(HTuple hv_WindowHandle, string color){HOperatorSet.SetColor(hv_WindowHandle, color);}/// <summary>/// 生成Halcon随机颜色/// </summary>/// <returns></returns>public static string GetRandomColor(){// 获取当前时间的毫秒数作为种子int seed = DateTime.Now.Millisecond;// 使用种子创建 Random 实例Random random = new Random(seed);// 生成随机数int randomNumber = random.Next(0, 18);// 延时随机时间变更随机种子Thread.Sleep(randomNumber);string[] strsColors = new string[]{"red", "green","blue", "cyan", "magenta","yellow", "dim gray", "gray","light gray", "medium slate blue", "coral", "slate blue","spring green", "orange red", "orange", "dark olive green","pink", "forest green", "cadet blue"};if (randomNumber <= strsColors.Length)return strsColors[randomNumber];elsereturn strsColors[0];}/// <summary>/// 计算两点的距离/// </summary>/// <param name="startPoint"></param>/// <param name="endPoint"></param>/// <returns></returns>public static double GetDistanceP2P(HPoint startPoint, HPoint endPoint){try{return Math.Sqrt(Math.Pow(startPoint.X - endPoint.X, 2) + Math.Pow(startPoint.Y - endPoint.Y, 2));}catch { return 9994; }}public static double GetDistanceP2P(System.Drawing.Point startPoint, System.Drawing.Point endPoint){return GetDistanceP2P(new HPoint(startPoint), new HPoint(endPoint));}public static double DistanceP2P(double startX, double startY, double endX, double endY){return GetDistanceP2P(new HPoint(startX, startY), new HPoint(endX, endY));}/// <summary>/// 获取两点的中点/// </summary>/// <param name="startPoint"></param>/// <param name="endPoint"></param>/// <returns></returns>public static HPoint GetMidPoint(HPoint startPoint, HPoint endPoint){return new HPoint((startPoint.X + endPoint.X) / 2, (startPoint.Y + endPoint.Y) / 2);}public static OpenCvSharp.Point GetMidPoint(OpenCvSharp.Point startPoint, OpenCvSharp.Point endPoint){return new OpenCvSharp.Point((startPoint.X + endPoint.X) / 2, (startPoint.Y + endPoint.Y) / 2);}/// <summary>/// 判断点是否在线段上/// </summary>/// <param name="point"></param>/// <param name="segment"></param>/// <returns></returns>public static bool IsPointOnSegment(HPoint pt, HSegment segment, double tolerance = 1e-3){// 计算直线方程的系数double A = segment.EndY - segment.StartX;double B = segment.StartX - segment.EndX;double C = segment.EndX * segment.StartY - segment.StartX * segment.EndY;// 计算点到直线的距离double distance = Math.Abs(A * pt.X + B * pt.Y + C) / Math.Sqrt(A * A + B * B);// 允许一个很小的误差if (distance < tolerance){// 判断点是否在直线段范围内if (Math.Min(segment.StartX, segment.EndX) <= pt.X && pt.X <= Math.Max(segment.StartX, segment.EndX)&& Math.Min(segment.StartY, segment.EndY) <= pt.Y && pt.Y <= Math.Max(segment.StartY, segment.EndY)){return true;}}return false;}public static bool IsPointOnSegment(double px, double py, double x1, double y1, double x2, double y2){return IsPointOnSegment(new HPoint(px, py), new HSegment(x1, y1, x2, y2));}/// <summary>/// 判断点是否在矩形边附近/// </summary>/// <param name="pt"></param>/// <param name="rect"></param>/// <param name="tolerance"></param>/// <returns></returns>static bool IsPointNearRectangleSilde(OpenCvSharp.Point pt, Rectangle rect, double tolerance = 100){try{// 如果点的 X 坐标等于矩形的左边 (rect.Left) 或右边 (rect.Right),并且 Y 坐标在矩形的上下边界之间,那么点在矩形的垂直边界上。// 如果点的 Y 坐标等于矩形的上边(rect.Top) 或下边(rect.Bottom),并且 X 坐标在矩形的左右边界之间,那么点在矩形的水平边界上。return (Math.Abs(pt.X - rect.Left) <= tolerance || Math.Abs(pt.X - rect.Right) <= tolerance) && pt.Y >= rect.Top + tolerance && pt.Y <= rect.Bottom - tolerance|| (Math.Abs(pt.Y - rect.Top) <= tolerance || Math.Abs(pt.Y - rect.Bottom) <= tolerance) && pt.X >= rect.Left - tolerance && pt.X <= rect.Right + tolerance;}catch { return false; }}public static bool IsPointNearRectangleSilde(HPoint pt, HRectangle2 rect, double tolerance = 100){return IsPointNearRectangleSilde(new OpenCvSharp.Point((int)pt.X, (int)pt.Y), new Rectangle((int)rect.CenterX, (int)rect.CenterY, (int)rect.Width, (int)rect.Height), tolerance);}/// <summary>/// 判断点是否在点附近/// </summary>/// <param name="pt1"></param>/// <param name="pt2"></param>/// <param name="tolerance"></param>/// <returns></returns>public static bool IsPointNearPoint(HPoint pt1, HPoint pt2, double tolerance = 100){if (GetDistanceP2P(pt1, pt2) <= tolerance)return true;return false;}public static bool IsPointNearPoint(System.Drawing.Point pt1, System.Drawing.Point pt2, double tolerance = 100){if (GetDistanceP2P(pt1, pt2) <= tolerance)return true;return false;}public static bool IsPointNearPoint(double x1, double y1, double x2, double y2, int tolerance = 100){return IsPointNearPoint(new HPoint(x1, y1), new HPoint(x2, y2), tolerance);}/// <summary>/// 判断点是否在矩形角上/// </summary>/// <param name="pt"></param>/// <param name="rect"></param>/// <param name="corner"></param>/// <param name="tolerance"></param>/// <returns></returns>public static bool IsPointNearRectangleCorner(System.Drawing.Point pt, Rectangle rect, out string corner, double tolerance = 10){try{//按顺时针去匹配角位System.Drawing.Point LeftTopPoint = new System.Drawing.Point(rect.Left, rect.Top);if (IsPointNearPoint(LeftTopPoint, pt, tolerance)){corner = "LeftTop";return true;}System.Drawing.Point RightTopPoint = new System.Drawing.Point(rect.Right, rect.Top);if (IsPointNearPoint(RightTopPoint, pt, tolerance)){corner = "RightTop";return true;}System.Drawing.Point RightBtmPoint = new System.Drawing.Point(rect.Right, rect.Bottom);if (IsPointNearPoint(RightBtmPoint, pt, tolerance)){corner = "RightBtm";return true;}System.Drawing.Point LeftBtmPoint = new System.Drawing.Point(rect.Left, rect.Bottom);if (IsPointNearPoint(LeftBtmPoint, pt, tolerance)){corner = "LeftBtm";return true;}corner = "";return false;}catch { corner = ""; return false; }}public static bool IsPointNearRectangleCorner(HPoint pt, HRectangle2 rect, out string corner, double tolerance = 10){try{//按顺时针去匹配角位var rectCorners = rect.Corners;HPoint LeftTopPoint = rectCorners[0];if (IsPointNearPoint(LeftTopPoint, pt, tolerance)){corner = "LeftTop";return true;}HPoint RightTopPoint = rectCorners[1];if (IsPointNearPoint(RightTopPoint, pt, tolerance)){corner = "RightTop";return true;}HPoint RightBtmPoint = rectCorners[2];if (IsPointNearPoint(RightBtmPoint, pt, tolerance)){corner = "RightBtm";return true;}HPoint LeftBtmPoint = rectCorners[3];if (IsPointNearPoint(LeftBtmPoint, pt, tolerance)){corner = "LeftBtm";return true;}corner = "";return false;}catch { corner = ""; return false; }}#endregion}
}
3、子类【HFindModelTool】
后续只需要通过CreateModel,Save,Load,Run等封装好的函数既可以运行模板匹配工具
using HalconDotNet;
using MyProcesses.Alogrithms.Halcon;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip;namespace MyProcesses.Alogrithms.Halcon
{public class HFindModelTool : HAlgorithm{public HModel ModelID = new HModel();public HFindModelTool(){strProcessClass = "MyProcesses.Alogrithms.Halcon.HFindModelTool";strProcessName = "模板匹配工具";//ModelType:模板类型Params.Inputs.Add("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.0Params.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", 1);//Params.Inputs.Add("GenParamName", new HTuple());//Params.Inputs.Add("GenParamValue", new HTuple());Params.Inputs.Add("MinCount", 0);Params.Inputs.Add("MaxCount", 9999);Params.Outputs.Add("CenterX", new List<double>());Params.Outputs.Add("CenterY", new List<double>());Params.Outputs.Add("Angle", new List<double>());Params.Outputs.Add("Score", new List<double>());Params.Outputs.Add("Count", 0);}/// <summary>/// 运行算子/// </summary>public override void HAlgorithmMain(){#region 初始化变量HTuple hv_CenterRow = new HTuple();HTuple hv_CenterColumn = new HTuple();HTuple hv_CenterPhi = new HTuple();HTuple hv_Score = new HTuple();//创建虚拟HWindow用于显示图片【需要释放】HWindowControl hWindowControl = new HWindowControl();#endregiontry{if (InputImage == null){strMsg = "输入图片为空";bResult = false;return;}#region 裁剪区域if (!ReduceDomainImage()){strMsg = "裁剪区域失败";bResult = false;return;}#endregion//判断是否有模板if (ModelID.hvModel == null || ModelID.hvModel.Length == 0){strMsg = "未创建模板";bResult = false;return;}#region 算子逻辑HOperatorSet.Rgb1ToGray(hoDomainImage, out hoDomainImage);//判断是否为灰度图using (HDevDisposeHelper dh = new HDevDisposeHelper()){HOperatorSet.CountChannels(hoDomainImage, out HTuple hv_Channels);if (hv_Channels.TupleInt() != 1){strMsg = "输入图片不为灰度图";bResult = false;return;}}string type = ModelID.Type.ToString();double AngleStart = (double)Params.Inputs["AngleStart"];double AngleExtent = (double)Params.Inputs["AngleExtent"];double MinScore = (double)Params.Inputs["MinScore"];int NumMatches = AlogrithmParams.ConvertToInt32(Params.Inputs["NumMatches"]);double MaxOverlap = (double)Params.Inputs["MaxOverlap"];string SubPixel = AlogrithmParams.ConvertToString(Params.Inputs["SubPixel"]);int NumLevels = AlogrithmParams.ConvertToInt32(Params.Inputs["NumLevels"]);double Greediness = (double)Params.Inputs["Greediness"];switch (type){case "ShapeModel"://形状模板匹配#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个:输出匹配位置的行和列坐标、角度、得分 【中心坐标】#endregionHOperatorSet.FindShapeModel(hoDomainImage, ModelID.hvModel, (HTuple)(AngleStart / 180.0 * Math.PI), (HTuple)(AngleExtent / 180.0 * Math.PI), (HTuple)MinScore, (HTuple)NumMatches, (HTuple)MaxOverlap, (HTuple)SubPixel, (HTuple)NumLevels, (HTuple)Greediness, out hv_CenterRow, out hv_CenterColumn, out hv_CenterPhi, out hv_Score);break;case "LocalDeformableModel":break;default:strMsg = "未创建模板";bResult = false;return;}#endregion#region 结果处理OutputImage = hoDomainImage;List<double> CenterX = new List<double>();List<double> CenterY = new List<double>();List<double> Angle = new List<double>();List<double> Score = new List<double>();for (int i = 0; i < hv_CenterRow.Length; i++){CenterX.Add(Math.Round(hv_CenterColumn[i].D, 3));CenterY.Add(Math.Round(hv_CenterRow[i].D, 3));Angle.Add(Math.Round(hv_CenterPhi[i].D / Math.PI * 180, 3));Score.Add(Math.Round(hv_Score[i].D, 3));}Params.Outputs["CenterX"] = CenterX;Params.Outputs["CenterY"] = CenterY;Params.Outputs["Angle"] = Angle;Params.Outputs["Score"] = Score;Params.Outputs["Count"] = CenterX.Count;#endregion#region 生成RecordImage便于分析和显示HOperatorSet.GetImageSize(hoDomainImage, out HTuple ho_ImageWidth, out HTuple ho_ImageHeight);HOperatorSet.SetPart(hWindowControl.HalconWindow, 0, 0, ho_ImageHeight - 1, ho_ImageWidth - 1);HOperatorSet.ClearWindow(hWindowControl.HalconWindow);HOperatorSet.DispObj(hoDomainImage, hWindowControl.HalconWindow);for (int i = 0; i < hv_CenterRow.Length; i++){SetColor(hWindowControl.HalconWindow, "dark olive green");HOperatorSet.GenCrossContourXld(out HObject hCross, hv_CenterRow[i].D, hv_CenterColumn[i].D, 300, 0);HOperatorSet.DispObj(hCross, hWindowControl.HalconWindow);//HOperatorSet.GenCircle(out HObject hPoint, hv_CenterRow[i].D, hv_CenterColumn[i].D, 30);//HOperatorSet.DispObj(hPoint, hWindowControl.HalconWindow);}//生成RecordImagetry{HOperatorSet.DumpWindowImage(out hoRecordImage, hWindowControl.HalconWindow);}catch (Exception ex){strMsg = "生成RecordImage失败,原因是:" + ex.ToString();bResult = false;return;}#endregionif (strMsg == "运行超时"){bResult = false;return;}int MinCount = AlogrithmParams.ConvertToInt32(Params.Inputs["MinCount"]);int MaxCount = AlogrithmParams.ConvertToInt32(Params.Inputs["MaxCount"]);if (CenterX.Count < MinCount || CenterX.Count > MaxCount){strMsg = string.Format("结果个数超出范围({0},{1})", MinCount, MaxCount);bResult = false;return;}strMsg = "运行成功";bResult = true;return;}catch (Exception ex){strMsg = "运行失败,原因是:" + ex.ToString().TrimEnd();HOperatorSet.GenEmptyObj(out hoRecordImage);bResult = false;return;}finally{bCompleted = true;#region 内存释放hWindowControl.Dispose();hoDomainImage.Dispose();#endregion}}public bool CreateModel(HObject Template, HTuple NumLevels, HTuple AngleStart, HTuple AngleExtent, HTuple AngleStep, HTuple Optimization, HTuple Metric, HTuple Contrast, HTuple MinContrast, ModelType modelType){try{switch (modelType){case ModelType.ShapeModel://形状模板匹配#region 参数介绍//Template: : //reduce_domain后的模板图像//NumLevels ,//金字塔的层数,可设为“auto”或0—10的整数//AngleStart ,//模板旋转的起始角度//AngleExtent ,//模板旋转角度范围, >=0//AngleStep ,//旋转角度的步长, >=0 and <=pi/16//Optimization ,//设置模板优化和模板创建方法//Metric , //匹配方法设置//Contrast ,//设置对比度//MinContrast // 设置最小对比度#endregionHOperatorSet.CreateShapeModel(Template, NumLevels, AngleStart, AngleExtent, AngleStep, Optimization, Metric, Contrast, MinContrast, out ModelID.hvModel);ModelID.hoImage = Template.CopyObj(1, -1);return true;case ModelType.LocalDeformableModel:default:return false;}}catch{return false;}}/// <summary>/// 加载算法/// </summary>/// <param name="fullPath">完整路径带.json</param>/// <returns></returns>public override bool Load(string fullPath){try{if (string.IsNullOrEmpty(fullPath))return false;if (!fullPath.Contains(".json")){Console.WriteLine("文件路径不完整");return false;}if (fullPath.StartsWith(".\\")){// 判断原字符串长度是否大于等于2,避免越界if (fullPath.Length >= 2){// 替换开头两个字符fullPath = Application.StartupPath + fullPath.Substring(2);Console.WriteLine($"修改后的字符串: {fullPath}");}}if (!File.Exists(fullPath)){Console.WriteLine("文件不存在创建空文件");// 获取不带文件名的目录路径string directoryPath = Path.GetDirectoryName(fullPath);strProcessName = Path.GetFileNameWithoutExtension(fullPath);ModelID = new HModel(fullPath, ModelType.ShapeModel);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<AlogrithmParams>(strJson);if (Params == null)return false;if (!ModelID.Load(fullPath, Params.Inputs["ModelType"].ToString()))return false;return true;}catch { return false; }}/// <summary>/// 保存算法/// </summary>/// <param name="filePath">不带.json</param>/// <returns></returns>public override bool Save(string filePath){try{base.Save(filePath);//if (string.IsNullOrEmpty(filePath))// return false;//string strJson = string.Empty;//strJson = JsonConvert.SerializeObject(Params);//JsonFormatting(ref strJson);判断文件夹是否存在,防呆输入为文件名称//if (!Directory.Exists(filePath))//{// try// {// Directory.CreateDirectory(filePath);// }// catch (Exception)// { }//}//File.WriteAllText(filePath + "//" + strProcessName + ".json", strJson, Encoding.UTF8);ModelID.Save(filePath + "//" + strProcessName + ".json", ModelID.Type);return true;}catch { return false; }}}public enum ModelType { None, ShapeModel, LocalDeformableModel };public class HModel{public HModel(string modelName = "") { ModelName = modelName; }public HModel(string modelFullPath, string modelType){ModelFullPath = modelFullPath;switch (modelType){case "形状":Type = ModelType.ShapeModel; break;case "可形变":Type = ModelType.LocalDeformableModel; break;default:Type = ModelType.None; break;}Load(ModelFullPath, modelType);}public HModel(string modelFullPath, ModelType modelType){ModelFullPath = modelFullPath;switch (modelType){case ModelType.ShapeModel:Type = ModelType.ShapeModel;Load(ModelFullPath, "形状");break;case ModelType.LocalDeformableModel:Type = ModelType.LocalDeformableModel;Load(ModelFullPath, "可形变");break;default:Type = ModelType.None; break;}}/// <summary>/// 模板路径/// </summary>public string ModelFullPath = "C:\\MyVisionModel\\ModelName.none";/// <summary>/// 模板名称/// </summary>public string ModelName = string.Empty;/// <summary>/// Halcon模板句柄/// </summary>public HTuple hvModel;/// <summary>/// 模板图片/// </summary>public HObject hoImage;/// <summary>/// Halcon模板类型/// </summary>public ModelType Type = ModelType.ShapeModel;/// <summary>/// 加载模板(带.spm)/// </summary>/// <param name="fullPath">完整路径带.spm</param>/// <returns></returns>public bool Load(string fullPath){try{if (string.IsNullOrEmpty(fullPath))return false;string filePath = Path.GetFullPath(fullPath);ModelFullPath = fullPath;ModelName = Path.GetFileNameWithoutExtension(fullPath);// 使用 Path.GetExtension 提取扩展名string extension = Path.GetExtension(filePath);switch (extension){case ".spm":if (File.Exists(ModelFullPath))HOperatorSet.ReadShapeModel(ModelFullPath, out hvModel);Type = ModelType.ShapeModel;break;case ".dfm":if (File.Exists(ModelFullPath))HOperatorSet.ReadDeformableModel(ModelFullPath, out hvModel);Type = ModelType.LocalDeformableModel;break;default:hvModel = new HTuple();Type = ModelType.None;return false;}string ImageFileName = Path.GetFileNameWithoutExtension(fullPath);string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp";if (File.Exists(ImageFullPath))HOperatorSet.ReadImage(out hoImage, ImageFullPath);return true;}catch { hvModel = new HTuple(); Type = ModelType.None; return false; }}/// <summary>/// 加载模板(带.json)/// </summary>/// <param name="fullPath">完整路径带.json</param>/// <returns></returns>public bool Load(string fullPath, string modelType){try{if (string.IsNullOrEmpty(fullPath))return false;string filePath = Path.GetDirectoryName(fullPath);ModelName = Path.GetFileNameWithoutExtension(fullPath);ModelFullPath = filePath + "\\" + ModelName;switch (modelType.ToString()){case "形状":ModelFullPath += ".spm";if (File.Exists(ModelFullPath))HOperatorSet.ReadShapeModel(ModelFullPath, out hvModel);Type = ModelType.ShapeModel;break;case "可形变":ModelFullPath += ".dfm";if (File.Exists(ModelFullPath))HOperatorSet.ReadDeformableModel(ModelFullPath, out hvModel);Type = ModelType.LocalDeformableModel;break;default:Type = ModelType.None;return false;}string ImageFileName = Path.GetFileNameWithoutExtension(fullPath);string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp";if (File.Exists(ImageFullPath))HOperatorSet.ReadImage(out hoImage, ImageFullPath);return true;}catch { Type = ModelType.None; return false; }}/// <summary>/// 保存模板(路径带.spm)/// </summary>/// <param name="fullPath">带.spm</param>/// <returns></returns>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 提取扩展名string extension = Path.GetExtension(filePath);switch (extension){case ".spm":HOperatorSet.WriteShapeModel(hvModel, fullPath);Type = ModelType.ShapeModel;break;case ".dfm":HOperatorSet.WriteDeformableModel(hvModel, fullPath);Type = ModelType.LocalDeformableModel;break;default:hvModel = new HTuple();HOperatorSet.WriteDeformableModel(hvModel, fullPath);Type = ModelType.None;break;}string ImageFileName = Path.GetFileNameWithoutExtension(fullPath);string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp";HOperatorSet.WriteImage(hoImage, "bmp", 0, ImageFullPath);return true;}catch { return false; }}/// <summary>/// 保存模板(路径带.json)/// </summary>/// <param name="fullPath">带.json</param>/// <returns></returns>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;switch (modelType.ToString()){case "ShapeModel":ModelFullPath += ".spm";HOperatorSet.WriteShapeModel(hvModel, ModelFullPath);Type = ModelType.ShapeModel;break;case "LocalDeformableModel":ModelFullPath += ".spm";HOperatorSet.WriteDeformableModel(hvModel, ModelFullPath);Type = ModelType.LocalDeformableModel;break;default:hvModel = new HTuple();Type = ModelType.None;break;}string ImageFileName = Path.GetFileNameWithoutExtension(fullPath);string ImageFullPath = filePath + "\\" + ImageFileName + ".bmp";HOperatorSet.WriteImage(hoImage, "bmp", 0, ImageFullPath);return true;}catch { return false; }}}
}
Step3 设计UI
UI设计读者可自行完成 这里展示的是用WinForm实现的
结果展示
Halcon模板匹配工具