通用视觉工具模块-图像分类Halcon-实现
一 UI设计


<vb:ModuleViewBase x:Class="Plugin.ClassificationH.ClassifyView"
xmlns:vb="clr-namespace:JGTechVision.Core;assembly=JGTechVision"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:vm="clr-namespace:Plugin.ClassificationH.ViewModels"
xmlns:WinFormHost="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
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:Plugin.ClassificationH"
mc:Ignorable="d"
Title="图像分类-H"
Height="680" Width="980">
<vb:ModuleViewBase.Resources>
<ResourceDictionary Source="/JGTechVision;component/Assets/Collection.xaml"/>
</vb:ModuleViewBase.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="55"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="370"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TabControl Style="{StaticResource VerTabControl}">
<TabControl.Resources>
<Style TargetType="TabItem" BasedOn="{StaticResource VerTabItem}"/>
</TabControl.Resources>
<TabItem Header="基本参数">
<Grid x:Name="gd">
<Grid.RowDefinitions>
<RowDefinition Height="80"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Grid.Row="0">
<GroupBox Header="图像索引">
<StackPanel HorizontalAlignment="Center">
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="索引:" VerticalAlignment="Center" Width="60"/>
<TextBox Text="{Binding nImageIndex.Text,UpdateSourceTrigger=PropertyChanged}"
mah:TextBoxHelper.Value="{Binding nImageIndex.Value}"
Style="{StaticResource LinkTextBox}"
mah:TextBoxHelper.LinkButtonCommand="{Binding LinkCommand,Mode=OneWay}"
mah:TextBoxHelper.LinkButtonCommandParameter="{x:Static vm:eLinkCommand.nImageIndex}"
mah:TextBoxHelper.Watermark="int"
VerticalAlignment="Center" HorizontalAlignment="Center" Width="150" Margin="5 0"/>
</StackPanel>
</StackPanel>
</GroupBox>
</Border>
<Border Grid.Row="1">
<GroupBox Header="图像链接">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="AUTO"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="5 0">
<Button Content="" Width="30" Height="26" FontFamily="{DynamicResource iconfont}" FontSize="18"
ToolTip="添加" Command="{Binding DataOperateCommand}" CommandParameter="Add"/>
<Button Content="" Width="30" Height="26" FontFamily="{DynamicResource iconfont}" FontSize="18"
Margin="5 0"
ToolTip="删除" Command="{Binding DataOperateCommand}" CommandParameter="Delete"/>
</StackPanel>
<DataGrid Grid.Row="1" AutoGenerateColumns="False" x:Name="dg"
EnableColumnVirtualization="True"
EnableRowVirtualization="True"
VirtualizingPanel.IsVirtualizing="True"
ItemsSource="{Binding ImageParam}"
FontSize="12"
RowHeaderWidth="20"
SelectionUnit="FullRow"
HeadersVisibility="Column"
GridLinesVisibility="All"
CanUserAddRows="False"
SelectedIndex="{Binding nSelectIndex}"
>
<DataGrid.Columns>
<DataGridTextColumn Header="索引" Binding="{Binding Index}" CanUserSort="True" IsReadOnly="True"/>
<DataGridTemplateColumn Header="图像链接" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding InputImage.Text,UpdateSourceTrigger=PropertyChanged}"
IsReadOnly="True"
BorderThickness="0 0 0 0"
mah:TextBoxHelper.Value="{Binding InputImage.Value}"
Style="{StaticResource LinkTextBox}"
mah:TextBoxHelper.LinkButtonCommand="{Binding LinkCommand,Mode=OneWay}"
mah:TextBoxHelper.LinkButtonCommandParameter="{x:Static vm:eLinkCommand.InputImageLink}"
VerticalAlignment="Center" HorizontalAlignment="Center" Background="Transparent" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</GroupBox>
</Border>
</Grid>
</TabItem>
<TabItem Header="显示设置">
<Grid x:Name="gd1">
<GroupBox Header="图像参数">
<StackPanel>
<CheckBox Content="流程结束时显示结果图像" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10 10 0 0"
IsChecked="{Binding ShowResultRoi}"/>
<StackPanel Orientation="Horizontal" Margin="10">
<TextBlock Text="显示窗体" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" BorderThickness="0 0 0 1" Width="200" Margin="5 0"
SelectedIndex="{Binding DispViewID}" ItemsSource="{Binding WindowInfoList}"/>
</StackPanel>
<CheckBox Content="显示图像" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10 10 0 0" IsChecked="{Binding ShowImage}"/>
<CheckBox Content="显示OK日志" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10 10 0 0" IsChecked="{Binding ShowOkLog}"/>
<CheckBox Content="显示NG日志" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="10 10 0 0" IsChecked="{Binding ShowNgLog}"/>
</StackPanel>
</GroupBox>
</Grid>
</TabItem>
</TabControl>
<!—分割线—>
<GridSplitter Grid.Column="1"/>
<!—显示窗口—>
<WinFormHost:WindowsFormsHost Margin="5" Grid.Column="2" x:Name="winFormHost">
</WinFormHost:WindowsFormsHost>
</Grid>
<Border Grid.Row="1" BorderBrush="White" BorderThickness="0 0.5 0 0">
<DockPanel LastChildFill="True">
<StackPanel Margin="10 0 0 0" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock>
<Run Text="耗时:"/>
<Run Text="{Binding ModuleParam.ElapsedTime}"/>
<Run Text="ms"/>
</TextBlock>
<TextBlock>
<Run Text="状态:"/>
<Run Text="{Binding ModuleParam.Status,Converter={StaticResource StatusConverter}}"/>
</TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<Button x:Name="btnExecute" Content="执行" HorizontalAlignment="Right" Margin="10 0" Command="{Binding ExecuteCommand}"/>
<Button x:Name="btnCancel" Content="取消" HorizontalAlignment="Right" Margin="10 0" Click="btnCancel_Click"/>
<Button x:Name="btnConfirm" Content="确认" HorizontalAlignment="Right" Margin="10 0" Command="{Binding ConfirmCommand}"
Background="#FFFF2000" BorderBrush="#FFFF2000"/>
</StackPanel>
</DockPanel>
</Border>
</Grid>
</vb:ModuleViewBase>
using JGTechVision.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace Plugin.ClassificationH
{
/// <summary>
/// ClassifyView.xaml 的交互逻辑
/// </summary>
public partial class ClassifyView : ModuleViewBase
{
public ClassifyView()
{
InitializeComponent();
}
/// <summary>
/// 取消按钮
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
}
}
二 ViewModel
using EventMgrLib;
using HalconDotNet;
using Helper;
using JGTechVision.Attributes;
using JGTechVision.Common;
using JGTechVision.Common.Enums;
using JGTechVision.Common.Helper;
using JGTechVision.Common.Provide;
using JGTechVision.Core;
using JGTechVision.Events;
using JGTechVision.Models;
using JGTechVision.Services;
using JGTechVision.ViewModels;
using JGTechVision.Views.Dock;
using Plugin.ClassificationH.Models;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VM.Halcon;
using VM.Halcon.Config;
namespace Plugin.ClassificationH.ViewModels
{
#region enum
public enum eLinkCommand
{
nImageIndex,
InputImageLink,
}
#endregion
[Serializable]
public class ImageParams : NotifyPropertyBase
{
public int Index { get; set; }
private LinkVarModel _InputImage = new LinkVarModel();
public LinkVarModel InputImage
{
get { return _InputImage; }
set { _InputImage = value; RaisePropertyChanged(); }
}
public CommandBase LinkCommand { get; set; }
}
[Category("深度学习")]
[DisplayName("图像分类")]
[ModuleImageName("ClassifyH")]
[Serializable]
public class ClassifyViewModel : ModuleBase
{
// 分类模型
HTuple Classify_DLModelHandle = new HTuple();
/// <summary>
/// 分类任务参数
/// </summary>
ClassifyTaskModel classifyTaskModel = new ClassifyTaskModel();
/// <summary>
/// 图像分类
/// </summary>
/// <param name="ho_Image"></param>
/// <param name="ho_ImagePreprocessedByte"></param>
/// <param name="hv_DLModelHandle"></param>
/// <param name="hv_ClassName"></param>
/// <param name="hv_Score"></param>
/// <param name="hv_ClassId"></param>
public void ClassifyTask(HObject ho_Image, out HObject ho_ImagePreprocessedByte,
HTuple hv_DLModelHandle, out HTuple hv_ClassName, out HTuple hv_Score, out HTuple hv_ClassId)
{
// Stack for temporary objects
HObject[] OTemp = new HObject[20];
// Local iconic variables
HObject ho_ImagePreprocessed;
// Local control variables
HTuple hv_ImageDimensions = new HTuple(), hv_DLSample = new HTuple();
HTuple hv_DLResult = new HTuple(), hv_Confidences = new HTuple();
HTuple hv_PredictClasses = new HTuple(), hv_Ids = new HTuple();
// Initialize local and output iconic variables
HOperatorSet.GenEmptyObj(out ho_ImagePreprocessedByte);
HOperatorSet.GenEmptyObj(out ho_ImagePreprocessed);
hv_ClassName = new HTuple();
hv_Score = new HTuple();
hv_ClassId = new HTuple();
//获取图像输入维度
hv_ImageDimensions.Dispose();
HOperatorSet.GetDlModelParam(hv_DLModelHandle, "image_dimensions", out hv_ImageDimensions);
//缩放图像
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
ho_ImagePreprocessedByte.Dispose();
HOperatorSet.ZoomImageSize(ho_Image, out ho_ImagePreprocessedByte, hv_ImageDimensions.TupleSelect(
0), hv_ImageDimensions.TupleSelect(1), "constant");
}
//转换实数类型
ho_ImagePreprocessed.Dispose();
HOperatorSet.ConvertImageType(ho_ImagePreprocessedByte, out ho_ImagePreprocessed,
"real");
//归一化:对图像像素值进行线性变换,将原本 [0, 255] 范围的灰度值转换到 [-127, 128]
{
HObject ExpTmpOutVar_0;
HOperatorSet.ScaleImage(ho_ImagePreprocessed, out ExpTmpOutVar_0, 1, –127);
ho_ImagePreprocessed.Dispose();
ho_ImagePreprocessed = ExpTmpOutVar_0;
}
//** 5.创建图像输入采集对象字典
hv_DLSample.Dispose();
HOperatorSet.CreateDict(out hv_DLSample);
//图像输入到字典中
HOperatorSet.SetDictObject(ho_ImagePreprocessed, hv_DLSample, "image");
//** 7. 核心:AI模型推理
hv_DLResult.Dispose();
HOperatorSet.ApplyDlModel(hv_DLModelHandle, hv_DLSample, new HTuple(), out hv_DLResult);
//** 8.解析预测结果
//获取预测结果的置信度(相似度)
hv_Confidences.Dispose();
HOperatorSet.GetDictTuple(hv_DLResult, "classification_confidences", out hv_Confidences);
//获取输出类别
hv_PredictClasses.Dispose();
HOperatorSet.GetDictTuple(hv_DLResult, "classification_class_names", out hv_PredictClasses);
//获取输出ID
hv_Ids.Dispose();
HOperatorSet.GetDictTuple(hv_DLResult, "classification_class_ids", out hv_Ids);
//类别名称
hv_ClassName.Dispose();
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
hv_ClassName = hv_PredictClasses.TupleSelect(
0);
}
//类别分数
hv_Score.Dispose();
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
hv_Score = hv_Confidences.TupleSelect(
0);
}
//类别Id
hv_ClassId.Dispose();
using (HDevDisposeHelper dh = new HDevDisposeHelper())
{
hv_ClassId = hv_Ids.TupleSelect(
0);
}
ho_ImagePreprocessed.Dispose();
hv_ImageDimensions.Dispose();
hv_DLSample.Dispose();
hv_DLResult.Dispose();
hv_Confidences.Dispose();
hv_PredictClasses.Dispose();
hv_Ids.Dispose();
return;
}
#region Prop属性
/// <summary>
/// 获取图像索引,默认为0
/// </summary>
private LinkVarModel _nImageIndex = new LinkVarModel() { Text = "0" };
/// <summary>
/// 图像索引
/// </summary>
public LinkVarModel nImageIndex
{
get { return _nImageIndex; }
set { _nImageIndex = value; RaisePropertyChanged(); }
}
/// <summary>
/// 显示窗口信息集合
/// </summary>
private List<string> _WindowInfoList;
public List<string> WindowInfoList
{
get
{
_WindowInfoList = new List<string>();
// 获取显示窗口数量
int count = Convert.ToInt32(Solution.Ins.ViewMode);
for (int i = 1; i <= count + 1; i++)
{
string windowInfo = "图像窗口" + i.ToString();
_WindowInfoList.Add(windowInfo);
}
return _WindowInfoList;
}
set { _WindowInfoList = value; RaisePropertyChanged(); }
}
/// <summary>
/// 图像参数集合
/// </summary>
private ObservableCollection<ImageParams> _ImageParam = new ObservableCollection<ImageParams>();
/// <summary>
/// 定义图像参数
/// </summary>
public ObservableCollection<ImageParams> ImageParam
{
get { return _ImageParam; }
set { _ImageParam = value; RaisePropertyChanged(); }
}
/// <summary>
/// 被选中索引
/// </summary>
private int _nSelectIndex;
public int nSelectIndex
{
get { return _nSelectIndex; }
set { Set(ref _nSelectIndex, value); }
}
/// <summary>显示结果区域</summary>
private bool _ShowResultRoi = true;
public bool ShowResultRoi
{
get { return _ShowResultRoi; }
set { Set(ref _ShowResultRoi, value); }
}
private bool _ShowImage = true;
/// <summary>
/// 覆盖图像
/// </summary>
public bool ShowImage
{
get { return _ShowImage; }
set
{
Set(ref _ShowImage, value);
}
}
private bool _ShowOkLog;
/// <summary>
/// 显示OK日志
/// </summary>
public bool ShowOkLog
{
get { return _ShowOkLog; }
set
{
Set(ref _ShowOkLog, value);
}
}
private bool _ShowNgLog;
/// <summary>
/// 显示NG日志
/// </summary>
public bool ShowNgLog
{
get { return _ShowNgLog; }
set
{
Set(ref _ShowNgLog, value);
}
}
#endregion
#region Command命令
/// <summary>
/// 加载函数,初始化操作
/// </summary>
public override void Loaded()
{
base.Loaded();
if (File.Exists(@"D:\\\\模型推理参数\\\\ClassifyTaskModel.xml"))
{
classifyTaskModel = XmlSerializerHelper.ReadXML(@"D:\\\\模型推理参数\\\\ClassifyTaskModel.xml", typeof(ClassifyTaskModel)) as ClassifyTaskModel;
}
else
{
classifyTaskModel = new ClassifyTaskModel();
}
/// 读取模型
HOperatorSet.ReadDlModel(classifyTaskModel.ModelPath, out Classify_DLModelHandle);
//** 2.设置参数
//空间换时间 gpu显存有要求
HOperatorSet.SetDlModelParam(Classify_DLModelHandle, "batch_size", classifyTaskModel.batch_size);
//** 设置GPU
HOperatorSet.SetDlModelParam(Classify_DLModelHandle, "gpu", classifyTaskModel.gpu);
// 转ClassifyView
var view = ModuleView as ClassifyView;
if (view != null)
{
ClosedView = true;
if (view.mWindowH == null)
{
view.mWindowH = new VMHWindowControl();
view.winFormHost.Child = view.mWindowH;
}
// 二次打开复原操作
if (DispImage != null && DispImage.IsInitialized())
{
// ShowHRoi();
}
}
}
/// <summary>
/// 执行命令
/// </summary>
[NonSerialized]
private CommandBase _ExecuteCommand;
public CommandBase ExecuteCommand
{
get
{
if (_ExecuteCommand == null)
{
_ExecuteCommand = new CommandBase((obj) =>
{
ExeModule();
});
}
return _ExecuteCommand;
}
}
/// <summary>
/// 确认
/// </summary>
[NonSerialized]
private CommandBase _ConfirmCommand;
public CommandBase ConfirmCommand
{
get
{
if (_ConfirmCommand == null)
{
_ConfirmCommand = new CommandBase((obj) =>
{
var view = this.ModuleView as ClassifyView;
// 关闭界面
if (view != null)
{
view.Close();
}
});
}
return _ConfirmCommand;
}
}
/// <summary>
/// 参数变化回调函数
/// </summary>
/// <param name="obj"></param>
private void OnVarChanged(VarChangedEventParamModel obj)
{
switch (obj.SendName.Split(',')[1])
{
case "nImageIndex":
nImageIndex.Text = obj.LinkName;
break;
case "InputImageLink":
ImageParam[nSelectIndex].InputImage.Text = obj.LinkName;
break;
default:
break;
}
}
/// <summary>
/// 链接命令
/// </summary>
[NonSerialized]
private CommandBase _LinkCommand;
public CommandBase LinkCommand
{
get
{
if (_LinkCommand == null)
{
//以GUID+类名作为筛选器
EventMgr.Ins.GetEvent<VarChangedEvent>().Subscribe(OnVarChanged, o => o.SendName.StartsWith($"{ModuleGuid}"));
_LinkCommand = new CommandBase((obj) =>
{
eLinkCommand linkCommand = (eLinkCommand)obj;
switch (linkCommand)
{
// 根据图像索引获取图像
case eLinkCommand.nImageIndex:
CommonMethods.GetModuleList(ModuleParam, VarLinkViewModel.Ins.Modules, "int");
EventMgr.Ins.GetEvent<OpenVarLinkViewEvent>().Publish($"{ModuleGuid},nImageIndex");
break;
// 根据模块输出图像获取图像
case eLinkCommand.InputImageLink:
CommonMethods.GetModuleList(ModuleParam, VarLinkViewModel.Ins.Modules, "HImage");
EventMgr.Ins.GetEvent<OpenVarLinkViewEvent>().Publish($"{ModuleGuid},InputImageLink");
break;
default:
break;
}
// 调用执行模块
ExeModule();
});
}
return _LinkCommand;
}
}
/// <summary>
/// 数据操作命令
/// </summary>
[NonSerialized]
private CommandBase _DataOperateCommand;
public CommandBase DataOperateCommand
{
get
{
if (_DataOperateCommand == null)
{
_DataOperateCommand = new CommandBase((obj) =>
{
switch (obj)
{
case "Add":
ImageParam.Add(new ImageParams()
{
Index = ImageParam.Count,
LinkCommand = LinkCommand
});
break;
case "Delete":
if (nSelectIndex < 0) return;
ImageParam.RemoveAt(nSelectIndex);
break;
default:
break;
}
});
}
return _DataOperateCommand;
}
}
#endregion
/// <summary>
/// 执行模块
/// </summary>
/// <returns></returns>
public override bool ExeModule()
{
// 重置计时器
Stopwatch.Restart();
try
{
// 判断选择图像索引和
if (nImageIndex == null || ImageParam.Count <= 0)
{
ChangeModuleRunStatus(eRunStatus.NG);
return false;
}
bool bImage = false;
int nIndex = Convert.ToInt32(GetLinkValue(nImageIndex));
for (int i = 0; i < ImageParam.Count; i++)
{
if (nIndex == ImageParam[i].Index && ImageParam[i].InputImage.Text != "")
{
GetDispImage(ImageParam[i].InputImage.Text, true);
// 取图
bImage = true;
}
}
if (DispImage == null || !DispImage.IsInitialized() || bImage == false)
{
ChangeModuleRunStatus(eRunStatus.NG);
return false;
}
// ShowHRoi();
// 取图
VarModel var = Prj.GetParamByName(ImageParam[0].InputImage.Text);
object image = var.Value;
// 赋值显示图像
if (image is RImage)
{
ClassifyTask((RImage)image, out HObject ho_ImagePreprocessedByte,
Classify_DLModelHandle, out HTuple hv_ClassName, out HTuple hv_Score, out HTuple hv_ClassId);
var view = ModuleView as ClassifyView;
if (view != null && view.mWindowH != null)
{
var handle = view.mWindowH.hControl.HalconWindow;
string text = hv_ClassName.TupleSelect(0).S; // HTuple 转 string
ShowTool.SetFont(handle, 20, "false", "false");
ShowTool.SetMsg(handle, text, "image", 30, 30, "green", "false");
}
}
ChangeModuleRunStatus(eRunStatus.OK);
return true;
}
catch (Exception ex)
{
ChangeModuleRunStatus(eRunStatus.NG);
Logger.GetExceptionMsg(ex);
return false;
}
}
/// <summary>
/// 添加输出参数
/// </summary>
public override void AddOutputParams()
{
if (DispImage == null)
DispImage = new RImage();
if (!DispImage.IsInitialized())
DispImage.GenEmptyObj();
base.AddOutputParams();
AddOutputParam("图像", "HImage", DispImage);
}
#region Method
private void ShowHRoi()
{
var view = ModuleView as ClassifyView;
VMHWindowControl mWindowH;
if (view == null || view.IsClosed)
{
mWindowH = ViewDic.GetView(DispImage.DispViewID);
if (ShowImage)
{
mWindowH.ClearWindow();
mWindowH.WindowH.fitImage_InitFlag = true;
mWindowH.Image = new HImage(DispImage);
}
}
else
{
CommonMethods.UIBeginInvoke(() =>
{
view.mWindowH.ClearWindow();
view.mWindowH.WindowH.fitImage_InitFlag = true;
view.mWindowH.Image = new HImage(DispImage);
});
}
}
#endregion
}
}
三 ClassifyTaskModel
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Plugin.ClassificationH.Models
{
public class ClassifyTaskModel
{
[Category("图像分类任务参数"), Description("推理图像数量")]
public int batch_size { get; set; } = 1;
[Category("图像分类任务参数"), Description("GPU索引")]
public int gpu { get; set; } = 0;
[Category("图像分类任务参数"), Description("模型路径")]
public string ModelPath { get; set; } = "";
}
}

![[特殊字符]DeepSeek‑Harness(DSH)小白保姆教程-171主机测评](https://www.171host.com/wp-content/uploads/2026/08/20260816085112-6a817a009aabf-220x150.png)
