前言
为了更好地去学习WPF+Halcon,我决定去报个班学一下。原因无非是想换个工作。相关的教学视频来源于下方的Up主的提供的教程。这里只做笔记分享,想要源码或者教学视频可以和他联系一下。
相关链接
微软系列技术教程 WPF 年度公益课程
Halcon开发 CSDN博客专栏
个人学习的Gitee 项目地址仓库
项目专栏
WPF+Halcon实战项目
运行环境
- .net core 8.0
- visual studio 2022
- halcon HDevelop 20.11
- windows 11
匹配图片
7.Halcon代码导出
简单的模板匹配代码
* 读取Resource的文件
read_image (Image, '../resource/1.png')
dev_open_window_fit_image (Image, 0, 0, -1, -1, WindowHandle)
dev_display (Image)* 截取ROI
draw_rectangle1 (WindowHandle, Row1, Column1, Row2, Column2)
gen_rectangle1 (Rectangle, Row1, Column1, Row2, Column2)
reduce_domain (Image, Rectangle, ImageReduced)* 生成匹配模板
create_shape_model (ImageReduced, 'auto', -0.39, 0.79, 'auto', 'auto', 'use_polarity', 'auto', 'auto', ModelID)
* 导出匹配模板
write_shape_model (ModelID, 'output.sha')find_shape_model (ImageReduced, ModelID, -0.39, 0.79, 0.5, 1, 0.5, 'least_squares', 0, 0.9, Row, Column, Angle, Score)
导出C#代码
我之前做过C# 代码导出,详情看这个网址
Halcon WPF 开发学习笔记(2):Halcon导出c#脚本和WPF初步开发
控制台测试
这里使用.net core 8.0新建项目
导入halcon.dll
可以通过安装目录的dll导入程序
也可以直接在Nuget上面搜索halcon
using HalconDotNet;namespace ConsoleApp1
{internal class Program{static void Main(string[] args){HObject img;HOperatorSet.GenEmptyObj(out img);//读取图片,这里填你的图片的位置HOperatorSet.ReadImage(out img, "Resources\\1.png");//读取形状匹配模板,路径选择你的文件路径HTuple modelId = new HTuple();HOperatorSet.ReadShapeModel("Resources\\output.shm", out modelId);//匹配Image中的结果HTuple hv_WindowHandle = new HTuple(), hv_Row1 = new HTuple();HTuple hv_Column1 = new HTuple(), hv_Row2 = new HTuple();HTuple hv_Column2 = new HTuple(), hv_ModelID = new HTuple();HTuple hv_Row = new HTuple(), hv_Column = new HTuple();HTuple hv_Angle = new HTuple(), hv_Score = new HTuple();//hv_Row.Dispose(); hv_Column.Dispose(); hv_Angle.Dispose(); hv_Score.Dispose();HOperatorSet.FindShapeModel(img, modelId, -0.39, 0.79, 0.5, 1,0.5, "least_squares", 0, 0.9, out hv_Row, out hv_Column, out hv_Angle, out hv_Score);//输出匹配结果Console.WriteLine($"分数:{hv_Score},Row坐标:{hv_Row},Col坐标:{hv_Column},角度:{hv_Angle}");Console.WriteLine("程序运行完毕!");Console.ReadKey();}}
}
运行报错:
运行结果:
7.WPF导入Halcon
如果你上个代码已经跑成功了,说明你已经能成功导入Halcon代码并进行运行,接下来我们就要开始学习WPF如何导入Halcon
新建WPF程序
我不想使用Prism框架,我想用HandyControl替换Material Design UI框架。所以我前端时间尝试了一下代码的书写。
将程序修改为控制台程序
WPF仿网易云搭建笔记(7):HandyControl重构
Halcon WPF 开发学习笔记(3):WPF+Halcon初步开发
WPF 消息日志打印帮助类:HandyControl+NLog+彩色控制台打印+全局异常捕捉
WPF-UI HandyControl 控件简单实战+IconPacks矢量图导入
由于WPF比较复杂,这里我就不展开说明了。具体代码可以看我的Gitee仓库
个人学习的Gitee 项目地址仓库
Nuget目录
项目目录
相关代码
app.xaml:导入HandyControl Style主题
<Application x:Class="WpfApp1.App"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:local="clr-namespace:WpfApp1"StartupUri="MainWindow.xaml"><Application.Resources><ResourceDictionary><!--导入HandyControl Style主题--><ResourceDictionary.MergedDictionaries><ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml" /><ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml" /></ResourceDictionary.MergedDictionaries></ResourceDictionary></Application.Resources>
</Application>
App.xaml.cs:全局异常捕捉
using System.Configuration;
using System.Data;
using System.Text;
using System.Windows;
using System.Windows.Threading;
using WpfApp1.Utils;namespace WpfApp1
{/// <summary>/// Interaction logic for App.xaml/// </summary>public partial class App : Application{public App(){//首先注册开始和退出事件this.Startup += new StartupEventHandler(App_Startup);this.Exit += new ExitEventHandler(App_Exit);}void App_Startup(object sender, StartupEventArgs e){//UI线程未捕获异常处理事件this.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);//Task线程内未捕获异常处理事件TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;//非UI线程未捕获异常处理事件AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);}void App_Exit(object sender, ExitEventArgs e){//程序退出时需要处理的业务}void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e){try{e.Handled = true; //把 Handled 属性设为true,表示此异常已处理,程序可以继续运行,不会强制退出 MsgHelper.Error("UI线程异常:" + e.Exception.Message);//MessageBox.Show("UI线程异常:" + e.Exception.Message);}catch (Exception ex){//此时程序出现严重异常,将强制结束退出//MessageBox.Show("UI线程发生致命错误!");MsgHelper.FatalGlobal("UI线程发生致命错误!"+ex.ToString());}}void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e){StringBuilder sbEx = new StringBuilder();if (e.IsTerminating){sbEx.Append("非UI线程发生致命错误");}sbEx.Append("非UI线程异常:");if (e.ExceptionObject is Exception){sbEx.Append(((Exception)e.ExceptionObject).Message);}else{sbEx.Append(e.ExceptionObject);}//MessageBox.Show(sbEx.ToString());MsgHelper.Error(sbEx.ToString());}void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e){//task线程内未处理捕获MsgHelper.Error("Task线程异常:" + e.Exception.Message);//MessageBox.Show("Task线程异常:" + e.Exception.Message);e.SetObserved();//设置该异常已察觉(这样处理后就不会引起程序崩溃)}}}
NlogHelper:日志打印
using NLog.Config;
using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp1.Utils
{public static class NLogHelper{private static Logger logger;static NLogHelper(){LogManager.Configuration = new XmlLoggingConfiguration(string.Format("{0}/NLog.config", AppDomain.CurrentDomain.BaseDirectory.ToString()));logger = NLog.LogManager.GetCurrentClassLogger();}public static void Debug(string msg){Console.WriteLine(msg);logger.Debug(msg);}public static void Info(string msg){ConsoleWirte(msg,ConsoleColor.Green);logger.Info(msg);}public static void Error(string msg){ConsoleWirte(msg, ConsoleColor.Red);logger.Error(msg);}public static void Warning(string msg){ConsoleWirte(msg, ConsoleColor.Yellow);logger.Warn(msg);}public static void ConsoleWirte(string msg,ConsoleColor color){Console.ForegroundColor = color;Console.WriteLine(msg);Console.ResetColor();}}
}
MsgHelper:HandyControl+NLog组合
using HandyControl.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace WpfApp1.Utils
{public static class MsgHelper{public enum MsgType { Info, Warn, Error, Success, Fatal }/// <summary>/// 打印消息/// </summary>/// <param name="msg"></param>/// <param name="type"></param>/// <param name="isGlobal"></param>public static void ShowMsg(string msg, MsgType type, bool isGlobal = false){if (isGlobal){switch (type){case MsgType.Info:NLogHelper.Info(msg);Growl.InfoGlobal(msg);break;case MsgType.Warn:NLogHelper.Warning(msg);Growl.WarningGlobal(msg);break;case MsgType.Error:NLogHelper.Error(msg);Growl.ErrorGlobal(msg);break;case MsgType.Success:NLogHelper.Info(msg);Growl.SuccessGlobal(msg);break;case MsgType.Fatal:NLogHelper.Error(msg);Growl.FatalGlobal(msg);break;}}else{switch (type){case MsgType.Info:NLogHelper.Info(msg);Growl.Info(msg);break;case MsgType.Warn:NLogHelper.Warning(msg);Growl.Warning(msg);break;case MsgType.Error:NLogHelper.Error(msg);Growl.Error(msg);break;case MsgType.Success:NLogHelper.Info(msg);Growl.Success(msg);break;case MsgType.Fatal:NLogHelper.Error(msg);Growl.Fatal(msg);break;}}}/// <summary>/// 询问回调/// </summary>/// <param name="msg"></param>/// <param name="callback"></param>/// <param name="isGlobal"></param>public static void Ask(string msg, Action<bool> callback, bool isGlobal = false){NLogHelper.Info(msg);if (isGlobal){Growl.AskGlobal(msg, isConfrimed =>{callback(isConfrimed);return true;});}else{Growl.Ask(msg, isConfrimed =>{callback(isConfrimed);return true;});}}/// <summary>/// 强制清空全部消息/// </summary>public static void CleanAll(){Growl.Clear();Growl.ClearGlobal();}public static void Info(string msg){NLogHelper.Info(msg);Growl.Info(msg);}public static void Warning(string msg){NLogHelper.Warning(msg);Growl.Warning(msg);}public static void Error(string msg) {NLogHelper.Error(msg);Growl.Error(msg);}public static void Fatal(string msg) {NLogHelper.Error(msg);Growl.Fatal(msg);}public static void Success(string msg) {NLogHelper.Info(msg);Growl.Success(msg);}public static void InfoGlobal(string msg){NLogHelper.Info(msg);Growl.InfoGlobal(msg);}public static void WarningGlobal(string msg){NLogHelper.Warning(msg);Growl.WarningGlobal(msg);}public static void ErrorGlobal(string msg){NLogHelper.Error(msg);Growl.ErrorGlobal(msg);}public static void FatalGlobal(string msg){NLogHelper.Error(msg);Growl.FatalGlobal(msg);}public static void SuccessGlobal(string msg){NLogHelper.Info(msg);Growl.SuccessGlobal(msg);}}
}
IconPacksExtesion:IconPacks Material Design 矢量Icon扩展
using MahApps.Metro.IconPacks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Markup;
using System.Windows.Media;namespace WpfApp1.ViewModels
{public class IconPacksExtesion : PackIconGeometryExtension<PackIconMaterialKind>{protected override IDictionary<PackIconMaterialKind, string> DataIndex => PackIconMaterialDataFactory.DataIndex.Value;public IconPacksExtesion() { }public IconPacksExtesion(PackIconMaterialKind kind) : base(kind) { }}public abstract class PackIconGeometryExtension<TKind> : MarkupExtension where TKind : Enum{public TKind Kind { get; set; }protected abstract IDictionary<TKind, string> DataIndex { get; }protected PackIconGeometryExtension() { }protected PackIconGeometryExtension(TKind kind) => Kind = kind;public override object ProvideValue(IServiceProvider serviceProvider) => Geometry.Parse(DataIndex[Kind]);}
}
注意:这里已经默认你很了解WPF程序项目。已经搭建好了WPF框架。而且本项目不会使用Prism,而是使用原生的WPF MVVM开发
程序生成时将整个Resources文件复制到Debug文件夹中
Visual Studio C# 项目生成时复制项目资源目录到生成目录
初始界面测试
MainView
<Window x:Class="WpfApp1.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:WpfApp1"xmlns:Views="clr-namespace:WpfApp1.Views"xmlns:ViewModels="clr-namespace:WpfApp1.ViewModels"xmlns:hc="https://handyorg.github.io/handycontrol"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Window.DataContext><ViewModels:MainViewModel /></Window.DataContext><Grid><hc:TabControl Style="{StaticResource TabControlInLine}"><hc:TabItem Header="Halcon测试界面"><Views:HalconView /></hc:TabItem><!--存放你其它的页面--></hc:TabControl></Grid>
</Window>
HalconView.xaml
<UserControl x:Class="WpfApp1.Views.HalconView"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:local="clr-namespace:WpfApp1.Views"mc:Ignorable="d"xmlns:ViewModels="clr-namespace:WpfApp1.ViewModels"xmlns:hc="https://handyorg.github.io/handycontrol"xmlns:halcon="clr-namespace:HalconDotNet;assembly=halcondotnet"d:DesignHeight="450"d:DesignWidth="800"><UserControl.DataContext><ViewModels:HalconViewModel /></UserControl.DataContext><UserControl.Resources><Style TargetType="Button"x:Key="MyButton"BasedOn="{StaticResource {x:Type Button}}"><Setter Property="Margin"Value="5" /></Style></UserControl.Resources><DockPanel><StackPanel Orientation="Horizontal"DockPanel.Dock="Top"><StackPanel.Resources><Style TargetType="Button"BasedOn="{StaticResource MyButton}" /></StackPanel.Resources><Button Content="读取图片"hc:IconElement.Geometry="{ViewModels:IconPacksExtesion Kind=ImagePlus}" Command="{Binding ReadImgBtn}" /><Button Content="定位结果"hc:IconElement.Geometry="{ViewModels:IconPacksExtesion Kind=ImageMarkerOutline}" Command="{Binding LocateBtn}"/><Button Content="生成矩形"hc:IconElement.Geometry="{ViewModels:IconPacksExtesion Kind=ShapeRectanglePlus}" Command="{Binding InitRectangleBtn}"/><Button Content="生成图片"hc:IconElement.Geometry="{ViewModels:IconPacksExtesion Kind=StickerPlus}" Command="{Binding InitImgBtn}"/></StackPanel><halcon:HSmartWindowControlWPF /></DockPanel>
</UserControl>
HalconViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WpfApp1.Utils;namespace WpfApp1.ViewModels
{public class HalconViewModel : ObservableObject{public RelayCommand ReadImgBtn { get; set; }public RelayCommand LocateBtn { get; set; }public RelayCommand InitRectangleBtn { get; set; }public RelayCommand InitImgBtn { get; set; }public HalconViewModel() {ReadImgBtn = new RelayCommand(() =>{MsgHelper.Info("读取图片");});LocateBtn = new RelayCommand(() => {MsgHelper.Info("显示定位结果");});InitRectangleBtn = new RelayCommand(() => {MsgHelper.Info("生成矩形");});InitImgBtn = new RelayCommand(() => {MsgHelper.Info("生成图片");});}}
}
测试运行结果
总结
下一章我们将重点转移到WPF 的Halcon组件的使用中去。