机器视觉开发教程——封装Halcon通用模板匹配工具【含免费教程源码】
目录
- 引言
- 前期准备
- 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模板匹配工具
相关文章:
机器视觉开发教程——封装Halcon通用模板匹配工具【含免费教程源码】
目录 引言前期准备Step1 设计可序列化的输入输出集合【不支持多线程】Step2 设计程序框架1、抽象层【IProcess】2、父类【HAlgorithm】3、子类【HFindModelTool】 Step3 设计UI结果展示 引言 通过仿照VisionPro软件二次开发Halcon的模板匹配工具,便于在客户端软件中…...
Android 中 ConstrantLayout 与 RelativeLayout 区别
ConstraintLayout 和 RelativeLayout 都是 Android 开发中常用的布局容器,它们都可以用于构建复杂的用户界面,但在功能、性能、使用方式等方面存在一些区别,下面为你详细介绍: 1. 布局原理 RelativeLayout:RelativeL…...
【3DMAX室内设计】2D转3D平面图插件2Dto3D使用方法
【一键筑梦】革新性2Dto3D插件,轻松实现2D平面图向3D空间的华丽蜕变。这款专为3DMAX室内设计师设计的神器,集一键式墙体、门、窗自动生成功能于一身,能够将2D图形无缝转化为3D网格对象(3D平面图、鸟瞰图),一…...
vscode 查看3d
目录 1. vscode-3d-preview obj查看ok 2. vscode-obj-viewer 没找到这个插件: 3. 3D Viewer for Vscode 查看obj失败 1. vscode-3d-preview obj查看ok 可以查看obj 显示过程:开始是绿屏,过了1到2秒,后来就正常看了。 2. vsc…...
自动驾驶---不依赖地图的大模型轨迹预测
1 前言 早期传统自动驾驶方案通常依赖高精地图(HD Map)提供道路结构、车道线、交通规则等信息,可参考博客《自动驾驶---方案从有图迈进无图》,本质上还是存在问题: 数据依赖性高:地图构建成本昂贵…...
perl初试
我手头有一个脚本,用于从blastp序列比对的结果文件中,进行文本处理, 获取序列比对最优的hit记录 #!/usr/bin/perl -w use strict;my ($blast_out) ARGV; my $usage "This script is to get the best hit from blast output file wit…...
VS Code C++ 开发环境配置
VS Code 是当前非常流行的开发工具. 本文讲述如何配置 VS Code 作为 C开发环境. 本文将按照如下步骤来介绍如何配置 VS Code 作为 C开发环境. 安装编译器安装插件配置工作区 第一个步骤的具体操作会因为系统不同或者方案不同而有不同的选择. 环境要求 首先需要立即 VS Code…...
Web Snapshot 网页截图 模块代码详解
本文将详细解析 Web Snapshot 模块的实现原理和关键代码。这个模块主要用于捕获网页完整截图,特别优化了对动态加载内容的处理。 1. 模块概述 snapshot.py 是一个功能完整的网页截图工具,它使用 Selenium 和 Chrome WebDriver 来模拟真实浏览器行为&am…...
Java TCP 通信:实现简单的 Echo 服务器与客户端
TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输层协议。与 UDP 不同,TCP 保证了数据的顺序、可靠性和完整性,适用于需要可靠传输的应用场景,如文件传输、网页浏览等。本文将基于 Java 实现一个简单的…...
Windows 10 下 SIBR Core (i.e. 3DGS SIBR Viewers) 的编译
本文针对在 Windows 10 上从源码编译安装3DGS (3D Gaussian Splatting)的Viewers 即SIBR Core及外部依赖库extlibs(预编译的版本直接在页面https://sibr.gitlabpages.inria.fr/download.html下载) ,参考SIBR 的官方网站…...
JavaWeb-HttpServletRequest请求域接口
文章目录 HttpServletRequest请求域接口HttpServletRequest请求域接口简介关于请求域和应用域的区别 请求域接口中的相关方法获取前端请求参数(getParameter系列方法)存储请求域名参数(Attribute系列方法)获取客户端的相关地址信息获取项目的根路径 关于转发和重定向的细致剖析…...
【C++】switch 语句编译报错:error: jump to case label
/home/share/mcrockit_3588/prj_linux/../source/rkvpss.cpp: In member function ‘virtual u32 CRkVpss::Control(u32, void*, u32)’: /home/share/mcrockit_3588/prj_linux/../source/rkvpss.cpp:242:8: error: jump to case label242 | case emRkComCmd_DBG_SaveInput:|…...
防火墙虚拟系统实验
拓扑图 需求一 安全策略要求: 1、只存在一个公网IP地址,公司内网所有部门都需要借用同一个接口访问外网 2、财务部禁止访问Internet,研发部门只有部分员工可以访问Internet,行政部门全部可以访问互联网 3、为三个部门的虚拟系统分…...
点云滤波方法:特点、作用及使用场景
点云滤波是点云数据预处理的重要步骤,目的是去除噪声点、离群点等异常数据,平滑点云或提取特定频段特征,为后续的特征提取、配准、曲面重建、可视化等高阶应用打下良好基础。以下是点云中几种常见滤波方法的特点、作用及使用场景:…...
Gradle 配置 Lombok 项目并发布到私有 Maven 仓库的完整指南
Gradle 配置 Lombok 项目并发布到私有 Maven 仓库的完整指南 在 Java 项目开发中,使用 Lombok 可以极大地减少样板代码(如 getter/setter 方法、构造器等),提高开发效率。然而,当使用 Gradle 构建工具并将项目发布到私…...
ArcGIS Pro 基于基站数据生成基站扇区地图
在当今数字化的时代,地理信息系统(GIS)在各个领域都发挥着至关重要的作用。 ArcGIS Pro作为一款功能强大的GIS软件,为用户提供了丰富的工具和功能,使得数据处理、地图制作和空间分析变得更加高效和便捷。 本文将为您…...
【Python · Pytorch】Conda介绍 DGL-cuda安装
本文仅涉及DGL库介绍与cuda配置,不包含神经网络及其训练测试。 起因:博主电脑安装了 CUDA 12.4 版本,但DGL疑似没有版本支持该CUDA版本。随即想到可利用Conda创建CUDA12.1版本的虚拟环境。 1. Conda环境 1.1 Conda环境简介 Conda࿱…...
Spring AI:开启Java开发的智能新时代
目录 一、引言二、什么是 Spring AI2.1 Spring AI 的背景2.2 Spring AI 的目标 三、Spring AI 的核心组件3.1 数据处理3.2 模型训练3.3 模型部署3.4 模型监控 四、Spring AI 的核心功能4.1 支持的模型提供商与类型4.2 便携 API 与同步、流式 API 选项4.3 将 AI 模型输出映射到 …...
leetcode:2965. 找出缺失和重复的数字(python3解法)
难度:简单 给你一个下标从 0 开始的二维整数矩阵 grid,大小为 n * n ,其中的值在 [1, n2] 范围内。除了 a 出现 两次,b 缺失 之外,每个整数都 恰好出现一次 。 任务是找出重复的数字a 和缺失的数字 b 。 返回一个下标从…...
Android U 分屏——SystemUI侧处理
WMShell相关的dump命令 手机分屏启动应用后运行命令:adb shell dumpsys activity service SystemUIService WMShell 我们可以找到其中分屏的部分,如下图所示: 分屏的组成 简图 分屏是由上分屏(SideStage)、下分屏(MainStage)以及分割线组…...
面试基础---MySQL 事务隔离级别与 MVCC 深度解析
MySQL 事务隔离级别与 MVCC 深度解析:原理、实践与源码分析 引言 在高并发的互联网应用中,数据库事务的隔离级别是保证数据一致性和并发性能的关键。MySQL 通过多版本并发控制(MVCC)机制实现了不同的事务隔离级别。本文将深入探…...
第十二届蓝桥杯大学A组java省赛答案整理
货物摆放 题目描述 小蓝有一个超大的仓库,可以摆放很多货物。 现在,小蓝有 nn 箱货物要摆放在仓库,每箱货物都是规则的正方体。小蓝规定了长、宽、高三个互相垂直的方向,每箱货物的边都必须严格平行于长、宽、高。 小蓝希望所…...
浅浅初识AI、AI大模型、AGI
前记:这里只是简单了解,后面有时间会专门来扩展和深入。 当前,人工智能(AI)及其细分领域(如AI算法工程师、自然语言处理NLP、通用人工智能AGI)的就业前景呈现高速增长态势,市场需求…...
flink集成tidb cdc
Flink TiDB CDC 详解 1. TiDB CDC 简介 1.1 TiDB CDC 的核心概念 TiDB CDC 是 TiDB 提供的变更数据捕获工具,能够实时捕获 TiDB 集群中的数据变更(如 INSERT、UPDATE、DELETE 操作),并将这些变更以事件流的形式输出。TiDB CDC 的…...
【flutter】TextField输入框工具栏文本为英文解决(不用安装插件版本
输入框长按选项菜单复制、粘贴、剪切、全选部分默认为英文,对于只需要对此部分做中文本地化,不需要考虑其他语言及全局本地化的项目,可以直接自定义一个本地化代理方法进行覆盖,不需要额外下载插件 // 自定义本地化代理 class _C…...
推荐1款OCR的扫描仪软件,无需安装,打开即用!
聊一聊 现在日常办公,很多时候还是需要扫描仪配合。 很多时候需要将文件搜索成PDF再传输。 今天给大家分享一款OCR扫描仪软件。 软件介绍 OCR的扫描仪软件 支持扫描仪共享。 支持WIA、TWAIN、SANE和ESCL驱动程序。 还可以批量多扫描仪配置扫描,支持…...
SpringBoot为什么默认使用CGLIB?
大家好,我是锋哥。今天分享关于【SpringBoot为什么默认使用CGLIB?】面试题。希望对大家有帮助; SpringBoot为什么默认使用CGLIB? 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Spring Boot 默认使用 CGLIB(Code Generation Li…...
去除HTML有序列表(ol)编号的多种解决方案
以下是去除HTML有序列表(ol)编号的多种解决方案: <!DOCTYPE html> <html> <head> <style> /* 基础方案:完全移除编号 */ ol.no-number {list-style-type: none; /* 移除默认编号 */padding-left: 0; /* 移除默认缩进 */…...
神经网络|(十三)|SOM神经网络
【1】引言 前序已经对神经网络有了基础认识,今天先学习SOM神经网络。 前序学习文章链接包括且不限于: 神经网络|(十一)|神经元和神经网络-CSDN博客 神经网络|(十二)|常见激活函数-CSDN博客 【2】SOM神经网络 SOM神经网络是一种结构比较简单、但是理…...
IP协议、DNS协议、DHCP协议、Telent协议的记忆总结
首先记忆一下几个协议的端口号 HTTP:超文本传输协议 80 HTTPS:安全传输协议 443 DHCP:动态主机配置协议 67/68 DNS:域名解析协议 53 FTP:文件传输协议 20/21 TFTP:简单文件传输协议 69 TELENT:远…...
