为了更好地去学习WPF+Halcon,我决定去报个班学一下。原因无非是想换个工作。相关的教学视频来源于下方的Up主的提供的教程。这里只做笔记分享,想要源码或者教学视频可以和他联系一下。
简单的模板匹配代码
* 读取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# 代码导出,详情看这个网址
这里使用.net core 8.0新建项目
可以通过安装目录的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();
}
}
}
如果你上个代码已经跑成功了,说明你已经能成功导入Halcon代码并进行运行,接下来我们就要开始学习WPF如何导入Halcon
我不想使用Prism框架,我想用HandyControl替换Material Design UI框架。所以我前端时间尝试了一下代码的书写。
将程序修改为控制台程序
由于WPF比较复杂,这里我就不展开说明了。具体代码可以看我的Gitee仓库
<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>
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();//设置该异常已察觉(这样处理后就不会引起程序崩溃)
}
}
}
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();
}
}
}
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);
}
}
}
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开发
<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>
<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>
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组件的使用中去。