This commit is contained in:
2025-10-20 14:47:17 +08:00
parent 2e46747ba9
commit 546b894e6b
16 changed files with 917 additions and 141 deletions

View File

@@ -24,34 +24,32 @@ namespace Check.Main.Camera
/// </summary>
public static class CameraManager
{
// 活动的相机实例字典,键为相机名称
//1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。
// 活动的相机实例字典(键值对),键 (string):使用相机的名称,值 (HikvisionCamera):存储实际的相机实例,{ get; } 创建了一个只读的公共属性
public static Dictionary<string, HikvisionCamera> ActiveCameras { get; } = new Dictionary<string, HikvisionCamera>();
// 相机对应的图像显示窗口字典
//public static Dictionary<string, FormImageDisplay> CameraDisplays { get; } = new Dictionary<string, FormImageDisplay>();
public static Dictionary<string, FormImageDisplay> OriginalImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();
public static Dictionary<string, FormImageDisplay> ResultImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();
public static Dictionary<string, FormImageDisplay> OriginalImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//原始图像窗口
public static Dictionary<string, FormImageDisplay> ResultImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//结果图像窗口
// --- 多相机同步逻辑 ---
// 产品检测队列
//2、多相机同步逻辑
// 【队列】一个产品需要多台相机拍完,才算完整。
private static readonly Queue<ProductResult> ProductQueue = new Queue<ProductResult>();
// 队列锁,保证线程安全
// 【(队列)锁】保证队列在多线程安全
private static readonly object QueueLock = new object();
// 当前启用的相机数量,用于判断产品是否检测完毕
private static int EnabledCameraCount = 0;
//public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted;
// public static bool IsDetectionRunning { get; private set; } = false;
// 产品ID计数器
// 产品ID【计数器】
private static long _productCounter = 0;
// 用于同步计数器访问的锁虽然long的自增是原子操作但为清晰和未来扩展使用锁是好习惯
private static readonly object _counterLock = new object();
// --- 新增:硬触发模拟器 ---
// 3、--- 新增:硬触发模拟器 ---
private static readonly System.Timers.Timer _hardwareTriggerSimulator;
/// <summary>
/// 获取或设置模拟硬触发的间隔时间(毫秒)。
/// </summary>
@@ -221,12 +219,12 @@ namespace Check.Main.Camera
// }
//}
//********************初始化和启动流程*******************
/// <summary>
/// 准备所有相机硬件、UI窗口和后台处理器但不开始采集。
/// 这是“启动设备”的第一阶段。
/// </summary>
public static void PrepareAll(ProcessConfig config, FrmMain mainForm)
public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型
{
// 1. 清理旧资源和UI
mainForm.ClearStatusStrip();
@@ -238,7 +236,7 @@ namespace Check.Main.Camera
YoloModelManager.Initialize(config.ModelSettings);
DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
// 3. 创建相机硬件实例和UI窗口
// 3. 创建相机硬件实例和UI窗口------
var deviceList = new HikvisionCamera().FindDevices();
if (deviceList.Count == 0)
{
@@ -246,7 +244,7 @@ namespace Check.Main.Camera
return;
}
foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))
foreach (var setting in config.CameraSettings.Where(s => s.IsEnabled))//打开相机 → 设置触发模式 → 绑定事件(图像采集、检测完成)。
{
var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex };
cam.TriggerMode = setting.TriggerMode;
@@ -293,6 +291,7 @@ namespace Check.Main.Camera
/// <summary>
/// 根据配置列表初始化或更新所有相机
/// 类似 PrepareAll但更侧重于更新配置会检查物理设备数量不足时报 warning。
/// </summary>
public static void Initialize(ProcessConfig config, FrmMain mainForm)
{
@@ -398,7 +397,7 @@ namespace Check.Main.Camera
}
/// <summary>
/// 定时器触发事件。
/// 定时器触发事件。(定时器 OnHardwareTriggerTimerElapsed 会遍历相机,对处于软件触发模式的相机执行 SoftwareTrigger()。)
/// </summary>
private static void OnHardwareTriggerTimerElapsed(object sender, ElapsedEventArgs e)
{
@@ -490,7 +489,9 @@ namespace Check.Main.Camera
//}
// Shutdown 方法也简化
/// <summary>
/// 停止所有相机 + 关闭窗口 + 清空字典 + 关闭检测协调器 + 销毁模型
/// </summary>
public static void Shutdown()
{
// 1. 停止硬件和模拟器
@@ -618,7 +619,7 @@ namespace Check.Main.Camera
// }
//}
// 图像回调方法现在极其简单
//【相机回调】
private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp)
{
Bitmap bmpForDisplay = null;
@@ -626,16 +627,16 @@ namespace Check.Main.Camera
try
{
// 1. 为“显示”和“处理”创建两个完全独立的深克隆副本
// 1. 深度克隆 Bitmap → 分成 显示副本 和 处理副本
bmpForDisplay = DeepCloneBitmap(bmp, "Display");
bmpForProcessing = DeepCloneBitmap(bmp, "Processing");
}
finally
{
// 2.无论克隆成功与否都必须立即释放事件传递过来的原始bmp防止泄漏。
bmp?.Dispose();
bmp?.Dispose();//空条件运算符 ?.-----在访问对象成员前检查对象是否为 null如果 bmp 不是 null则正常调用 Dispose() 方法,替代传统的 if (bmp != null) 检查
}
// 分支 A: 将用于显示副本发送到对应的UI窗口
// 分支 A: 显示副本 → 交给 FormImageDisplay.UpdateImage()。
if (bmpForDisplay != null && OriginalImageDisplays.TryGetValue(sender.Name, out var displayWindow))
{
// displayWindow.UpdateImage 会处理线程安全问题
@@ -646,7 +647,7 @@ namespace Check.Main.Camera
// 如果没有对应的显示窗口,或克隆失败,必须释放为显示创建的副本
bmpForDisplay?.Dispose();
}
// 分支 B: 将用于处理副本发送到检测协调器的后台队列
// 分支 B: 处理副本 → 交给 DetectionCoordinator.EnqueueImage() 进行检测。
if (bmpForProcessing != null)
{
// bmpForProcessing 的所有权在这里被转移给了协调器
@@ -673,11 +674,11 @@ namespace Check.Main.Camera
// 2. 找到此相机的结果显示窗口
if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay))
{
var bmp = ConvertSKImageToBitmap(e.ResultImage);
if (bmp != null)
//var bmp = ConvertSKImageToBitmap(e.ResultImage);
if (e.ResultImage != null)
{
// UpdateImage 会负责克隆并显示,所以这里传递 bmp 即可
resultDisplay.UpdateImage(bmp);
resultDisplay.UpdateImage(e.ResultImage);
}
}
else
@@ -691,7 +692,7 @@ namespace Check.Main.Camera
/// <summary>
/// 【将 SkiaSharp.SKImage 安全地转换为 System.Drawing.Bitmap。
/// </summary>
private static Bitmap ConvertSKImageToBitmap(SKImage skImage)
public static Bitmap ConvertSKImageToBitmap(SKImage skImage)
{
if (skImage == null) return null;

View File

@@ -1,5 +1,6 @@
using Check.Main.Common;
using Check.Main.Infer;
using HalconTemplateMatch;
using SkiaSharp;
using System;
using System.Collections.Concurrent;
@@ -10,7 +11,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using YoloDotNet.Extensions;
using OpenCvSharp;
namespace Check.Main.Camera
{
public class CameraProcessor : IDisposable
@@ -52,6 +53,9 @@ namespace Check.Main.Camera
_imageQueue.Add(new ImageData(_imageCounter, _cameraIndex, bmp));
}
/// <summary>
/// 图像处理主循环
/// </summary>
private void ProcessQueue()
{
// 从模型管理器获取此线程专属的YOLO模型
@@ -61,6 +65,36 @@ namespace Check.Main.Camera
ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型处理线程已中止。");
return; // 如果没有模型,此线程无法工作
}
//训练阶段相机2
var trainer = new LogoTemplateTrainer();
trainer.TrainAndSaveTemplates(
new List<string>
{
@"D:\HalconTemplateMatch\train2\logo1.bmp",
@"D:\HalconTemplateMatch\train2\logo2.bmp",
@"D:\HalconTemplateMatch\train2\logo3.bmp"
},
@"D:\HalconTemplateMatch\model_2");
//训练阶段相机39.25修改!!
trainer.TrainAndSaveTemplates(
new List<string>
{
@"D:\HalconTemplateMatch\train3\3C_1.bmp",
@"D:\HalconTemplateMatch\train3\3C_2.bmp",
@"D:\HalconTemplateMatch\train3\3C_3.bmp"
},
@"D:\HalconTemplateMatch\model_3");
if (trainer == null)
{
ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 未加载模板,处理线程已中止。");
return;
}
while (_isRunning)
{
try
@@ -69,19 +103,15 @@ namespace Check.Main.Camera
ImageData data = _imageQueue.Take();
using (data)
{
//SKImage resultSkImage = null; // 用于存储最终绘制好结果的图像
string result = "";
using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期
{
if (skImage == null) continue;
var predictions = yoloModel.RunObjectDetection(skImage);
// 模拟模型处理
//Thread.Sleep(50); // 模拟耗时
//bool isOk = new Random().NextDouble() > 0.1;
//string result = isOk ? "OK" : "NG";
result = predictions.Any() ? "NG" : "OK";
string result = predictions.Any() ? "NG" : "OK";
ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},检测到 {predictions.Count} 个目标,结果: {result}");
// 将处理结果交给协调器进行组装
DetectionCoordinator.AssembleProduct(data, result);
@@ -90,16 +120,111 @@ namespace Check.Main.Camera
using (var resultSkImage = skImage.Draw(predictions))
{
// 4. 触发事件,将绘制好的 resultSkImage 传递出去
Bitmap bitmap = CameraManager.ConvertSKImageToBitmap(resultSkImage);
// 所有权在这里被转移
OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs(
_cameraIndex,
data.ProductId,
resultSkImage
bitmap
));
}
}
}
//***********************************使用Halcon模板匹配进行检测****************************************************
if (data.Image == null) continue;
// 统一定义预测结果
var matcher = new LogoMatcher();
//9.25(增加一根据不同的相机编号调用不同的模型!)
string filepath = "";
if (_cameraIndex == 2)
{
matcher.LoadTemplates(@"D:\HalconTemplateMatch\model_2");
filepath = "D:\\HalconTemplateMatch\\train2";
}
else if (_cameraIndex == 3)
{
matcher.LoadTemplates(@"D:\HalconTemplateMatch\model_3");
filepath = "D:\\HalconTemplateMatch\\train3";
}
////原bool返回的处理
//bool found = matcher.FindLogo(data.Image);
//string result = found ? "OK":"NG";
//double返回的处理
double score = matcher.FindLogo(data.Image);
//Mat cam = ProcessImg.BitmapToMat(data.Image);
//score= ProcessImg.ProcessImagesInFolder(filepath,cam);
//string result = (score > 0.5) ? "OK" : "NG";
ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},结果: {result},得分: {score}");
// 将处理结果交给协调器进行组装
DetectionCoordinator.AssembleProduct(data, result);
//给PLC的M90、M91写值10.10
if (FrmMain.PlcClient != null && FrmMain.PlcClient.IsConnected)
{
if (result == "OK")
{
//吹气到合格框
FrmMain.PlcClient.WriteAsync("M90", 1).Wait(); // 写入M90为1
//Thread.Sleep(100);
FrmMain.PlcClient.WriteAsync("M90", 0).Wait(); // 写入M90为1
//var a = FrmMain.PlcClient.ReadAsync("M90");
//Console.WriteLine(a);
//FrmMain.PlcClient.WriteAsync("M91", 0).Wait();
// 延时复位
Task.Run(async () =>
{
//await Task.Delay(300); // 延时300毫秒可根据实际气动时间调整
//await FrmMain.PlcClient.WriteAsync("M90", 0);
});
}
else
{
//吹气到不合格框
//FrmMain.PlcClient.WriteAsync("M90", 0).Wait();
FrmMain.PlcClient.WriteAsync("M91", 1).Wait();// 写入M91为1
FrmMain.PlcClient.WriteAsync("M91", 0).Wait(); // 写入M90为1
//var a = FrmMain.PlcClient.ReadAsync("M90");
//Console.WriteLine(a);
// 延时复位
Task.Run(async () =>
{
//await Task.Delay(300);
//await FrmMain.PlcClient.WriteAsync("M91", 0);
});
}
}
else
{
ThreadSafeLogger.Log("[PLC] 未连接,跳过写入。");
}
// ③ 外部订阅事件
OnProcessingCompleted?.Invoke(
this,
new ProcessingCompletedEventArgs
(
_cameraIndex,
data.ProductId,
data.Image // 原图传出去
)
);
}
}
catch (InvalidOperationException)
@@ -113,6 +238,8 @@ namespace Check.Main.Camera
}
}
}
/// <summary>
/// 将 System.Drawing.Bitmap 安全地转换为 SkiaSharp.SKImage。
/// </summary>

View File

@@ -30,7 +30,7 @@ namespace Check.Main.Camera
}
/// <summary>
/// 相机配置信息类用于PropertyGrid显示和编辑
/// 相机配置信息类CameraSettings用于PropertyGrid显示和编辑(****核心!****)
/// </summary>
public class CameraSettings : INotifyPropertyChanged, ICloneable
{
@@ -39,8 +39,8 @@ namespace Check.Main.Camera
private int _cameraIndex = 0;
private string _name = "Camera-1";
private string _ipAddress = "192.168.1.100";
private string _ipDeviceAddress = "192.168.1.101";
private string _ipAddress = "169.254.51.253";
private string _ipDeviceAddress = "169.254.51.45";
private TriggerModeType _triggerMode = TriggerModeType.Continuous;
private bool _isEnabled = true;
private CheckType _checkType = CheckType.DeepLearning;

View File

@@ -179,7 +179,9 @@ namespace Check.Main.Camera
}
// 默认设置为连续模式
SetContinuousMode();
//SetContinuousMode();
// 设置为硬触发模式Line0
SetTriggerMode(false);
IsOpen = true;
OnCameraMessage("相机打开成功。", 0);

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWindowsForms>true</UseWindowsForms>
@@ -10,11 +10,14 @@
<PropertyGroup>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="DockPanelSuite" Version="3.1.1" />
<PackageReference Include="DockPanelSuite.ThemeVS2015" Version="3.1.1" />
<PackageReference Include="HalconDotNet" Version="19.11.0" />
<PackageReference Include="HslCommunication" Version="7.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NPOI" Version="2.7.4" />
<PackageReference Include="OpenCvSharp4" Version="4.10.0.20241108" />
@@ -26,11 +29,17 @@
</ItemGroup>
<ItemGroup>
<Reference Include="halcondotnet">
<HintPath>C:\Program Files\MVTec\HALCON-10.0\bin\dotnet35\halcondotnet.dll</HintPath>
</Reference>
<Reference Include="hdevenginedotnet">
<HintPath>C:\Program Files\MVTec\HALCON-10.0\bin\dotnet35\hdevenginedotnet.dll</HintPath>
</Reference>
<Reference Include="HslCommunication">
<HintPath>..\..\..\HslCommunication-master\HslCommunication.dll</HintPath>
</Reference>
<Reference Include="MvCameraControl.Net">
<HintPath>C:\Program Files (x86)\MVS\Development\DotNet\win64\MvCameraControl.Net.dll</HintPath>
<HintPath>..\..\..\..\XKRS2025\XKRS2025\CheckDevice\Check.Main\bin\Debug\MvCameraControl.Net.dll</HintPath>
</Reference>
</ItemGroup>

View File

@@ -33,6 +33,8 @@ namespace Check.Main.Common
_slaveId = slaveId;
}
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;//10.11添加
public async Task ConnectAsync()
{
await _tcpClient.ConnectAsync(_ip, _port);

View File

@@ -0,0 +1,290 @@
//using HalconDotNet;
//using System;
//using System.Collections.Generic;
//using System.Drawing.Imaging;
//using System.IO;
//namespace HalconTemplateMatch
//{
// public class LogoMatcher
// {
// private List<HTuple> modelHandles = new List<HTuple>();
// /// <summary>
// /// 从文件加载多个模板
// /// </summary>
// public void LoadTemplates(string dir)
// {
// foreach (var file in Directory.GetFiles(dir, "*.shm"))
// {
// HTuple modelID;
// HOperatorSet.ReadShapeModel(file, out modelID);
// modelHandles.Add(modelID);
// Console.WriteLine($"加载模板: {file}");
// }
// }
// /// <summary>
// /// 在测试图像中查找 Logo
// /// </summary>
// /// <returns>true = OKfalse = NG</returns>
// public bool FindLogo(string testImagePath)
// {
// HObject ho_TestImage;
// HOperatorSet.ReadImage(out ho_TestImage, testImagePath);
// HOperatorSet.Rgb1ToGray(ho_TestImage, out ho_TestImage);
// foreach (var modelID in modelHandles)
// {
// HOperatorSet.FindShapeModel(
// ho_TestImage,
// modelID,
// new HTuple(0).TupleRad(),
// new HTuple(360).TupleRad(),
// 0.5, // 最低分数
// 1, // 最大匹配数
// 0.5, // 重叠度
// "least_squares",
// 0,
// 0.9,
// out HTuple row,
// out HTuple col,
// out HTuple angle,
// out HTuple score);
// if (score.Length > 0 && score[0].D > 0.5)
// {
// Console.WriteLine($"找到 Logo: Row={row[0]}, Col={col[0]}, Score={score[0]}");
// return true; // 找到即返回成功
// }
// }
// return false; // 没找到
// }
// /// <summary>
// /// 重载FindLogo函数double返回
// /// </summary>
// /// <param name="bmp"></param>
// /// <returns></returns>
// public double FindLogo(Bitmap bmp)
// {
// // Bitmap 转 HObject
// HObject ho_TestImage;
// Bitmap2HObject(bmp, out ho_TestImage);
// HOperatorSet.Rgb1ToGray(ho_TestImage, out ho_TestImage);
// double bestScore = -1;
// foreach (var modelID in modelHandles)
// {
// HOperatorSet.FindShapeModel(
// ho_TestImage,
// modelID,
// new HTuple(0).TupleRad(),
// new HTuple(360).TupleRad(),
// 0.5, // 最低分数
// 1, // 最大匹配数
// 0.5, // 重叠度
// "least_squares",
// 0,
// 0.9,
// out HTuple row,
// out HTuple col,
// out HTuple angle,
// out HTuple score);
// if (score.Length > 0 && score[0].D > bestScore)
// {
// bestScore = score[0].D;
// }
// }
// ho_TestImage.Dispose();
// return bestScore; // -1 = 没找到
// }
// /// <summary>
// /// Bitmap 转 Halcon HObject
// /// </summary>
// private void Bitmap2HObject(Bitmap bmp, out HObject hobj)
// {
// HOperatorSet.GenEmptyObj(out hobj);
// Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
// BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
// try
// {
// HOperatorSet.GenImageInterleaved(
// out hobj,
// bmpData.Scan0,
// "bgr", // Bitmap 默认是 BGR
// bmp.Width,
// bmp.Height,
// 0,
// "byte",
// bmp.Width,
// bmp.Height,
// 0,
// 0,
// -1,
// 0
// );
// }
// finally
// {
// bmp.UnlockBits(bmpData);
// }
// }
// }
//}
using HalconDotNet;
using NPOI.OpenXmlFormats.Vml;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
namespace HalconTemplateMatch
{
public class LogoMatcher
{
private readonly List<HTuple> modelHandles = new List<HTuple>();
/// <summary>
/// 从指定目录加载所有 .shm 模板文件
/// </summary>
public void LoadTemplates(string dir)
{
try
{
string fullPath = Path.GetFullPath(dir);
if (!Directory.Exists(fullPath))
{
Console.WriteLine($"[警告] 模型目录不存在: {fullPath}");
return;
}
string[] modelFiles = Directory.GetFiles(fullPath, "*.shm", SearchOption.TopDirectoryOnly);
if (modelFiles.Length == 0)
{
Console.WriteLine($"[警告] 模型目录中没有任何 .shm 文件: {fullPath}");
return;
}
foreach (var file in modelFiles)
{
try
{
HTuple modelID;
HOperatorSet.ReadShapeModel(file, out modelID);
modelHandles.Add(modelID);
Console.WriteLine($"[加载成功] 模板: {file}");
}
catch (HOperatorException ex)
{
Console.WriteLine($"[错误] 无法加载模板 {file}: {ex.Message}");
}
}
if (modelHandles.Count == 0)
{
Console.WriteLine($"[警告] 没有成功加载任何模板文件。");
}
}
catch (Exception ex)
{
Console.WriteLine($"[异常] 加载模板目录出错: {ex.Message}");
}
}
/// <summary>
/// 匹配并返回最高得分double返回
/// </summary>
public double FindLogo(Bitmap bmp)
{
if (modelHandles.Count == 0)
{
Console.WriteLine("[警告] 尚未加载任何模板。");
return -1;
}
// Bitmap 转 Halcon 对象
HObject ho_TestImage;
Bitmap2HObject(bmp, out ho_TestImage);
HOperatorSet.Rgb1ToGray(ho_TestImage, out ho_TestImage);
double bestScore = -1;
foreach (var modelID in modelHandles)
{
try
{
HOperatorSet.FindScaledShapeModel(
ho_TestImage,
modelID,
new HTuple(0).TupleRad(),
new HTuple(360).TupleRad(),
0.8, 1.2,
0.5, 1, 0.5,
"least_squares_high",
0, 0.9,
out HTuple row, out HTuple col, out HTuple angle, out HTuple scale, out HTuple score
);
if (score.Length > 0 && score[0].D > bestScore)
bestScore = score[0].D;
}
catch (HOperatorException ex)
{
Console.WriteLine($"[错误] 模板匹配失败: {ex.Message}");
}
}
ho_TestImage.Dispose();
return bestScore;
}
/// <summary>
/// Bitmap 转 Halcon HObject
/// </summary>
private void Bitmap2HObject(Bitmap bmp, out HObject hobj)
{
HOperatorSet.GenEmptyObj(out hobj);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
try
{
HOperatorSet.GenImageInterleaved(
out hobj,
bmpData.Scan0,
"bgr",
bmp.Width,
bmp.Height,
0,
"byte",
bmp.Width,
bmp.Height,
0,
0,
-1,
0);
}
finally
{
bmp.UnlockBits(bmpData);
}
}
}
}

View File

@@ -0,0 +1,62 @@
using HalconDotNet;
using System;
using System.Collections.Generic;
using System.IO;
namespace HalconTemplateMatch
{
public class LogoTemplateTrainer
{
/// <summary>
/// 从多张样本图像生成模板并保存到文件
/// </summary>
/// <param name="imagePaths">训练图片路径集合</param>
/// <param name="saveDir">模板保存目录</param>
public void TrainAndSaveTemplates(List<string> imagePaths, string saveDir)
{
if (!Directory.Exists(saveDir))
Directory.CreateDirectory(saveDir);
int index = 0;
foreach (var path in imagePaths)
{
HObject ho_Image;
HOperatorSet.ReadImage(out ho_Image, path);
// 转灰度
HOperatorSet.Rgb1ToGray(ho_Image, out HObject ho_Gray);
// 二值化
HOperatorSet.Threshold(ho_Gray, out HObject ho_Region, 128, 255);
// 提取连通域
HOperatorSet.Connection(ho_Region, out HObject ho_Connected);
HOperatorSet.SelectShapeStd(ho_Connected, out HObject ho_Selected, "max_area", 70);
// ROI 约束
HOperatorSet.ReduceDomain(ho_Gray, ho_Selected, out HObject ho_ROI);
// 创建形状模板
HTuple modelID;
HOperatorSet.CreateShapeModel(
ho_ROI,
"auto",
new HTuple(0).TupleRad(),
new HTuple(360).TupleRad(),
"auto",
"auto",
"use_polarity",
"auto",
"auto",
out modelID);
// 保存模板到文件
string modelFile = Path.Combine(saveDir, $"logo_model_{index}.shm");
HOperatorSet.WriteShapeModel(modelID, modelFile);
Console.WriteLine($"训练完成并保存模板: {modelFile}");
index++;
}
}
}
}

View File

@@ -11,9 +11,9 @@ namespace Check.Main.Common
{
public int CameraIndex { get; }
public long ProductId { get; }
public SKImage ResultImage { get; } // 【修改】携带已绘制好结果的SKImage
public Bitmap ResultImage { get; } // 原来是SKImage ResultImage
public ProcessingCompletedEventArgs(int cameraIndex, long productId, SKImage resultImage)
public ProcessingCompletedEventArgs(int cameraIndex, long productId, Bitmap resultImage)//原来是SKImage resultImage
{
CameraIndex = cameraIndex;
ProductId = productId;

View File

@@ -52,6 +52,7 @@ namespace Check.Main.Dispatch
config = new ProcessConfig();
}
CurrentProductName = productName;
CurrentConfig = config;

View File

@@ -19,6 +19,8 @@ namespace Check.Main
public partial class FrmMain : Form
{
// private FrmCamConfig _formCameraConfig;
public static EasyPlcClient PlcClient;//定义全局PLC对象 --- 10.10添加①
private FrmConfig _frmConfig;
private FrmLog _formLog;
private ThemeBase _theme = new VS2015BlueTheme(); // 外观主题
@@ -40,11 +42,28 @@ namespace Check.Main
_deserializeDockContent = new DeserializeDockContent(GetContentFromPersistString);
}
private void FrmMain_Load(object sender, EventArgs e)
//添加 PLC 初始化方法 --- 10.10添加②
private async Task InitPlcConnection()
{
try
{
PlcClient = new EasyPlcClient("192.168.1.88", 502);
await PlcClient.ConnectAsync();
ThreadSafeLogger.Log("[PLC] 已成功连接到汇川PLC");
}
catch (Exception ex)
{
ThreadSafeLogger.Log($"[PLC] 连接失败: {ex.Message}");
}
}
private async void FrmMain_Load(object sender, EventArgs e)//添加了一个async原来是private void FrmMain_Load-----10.10修改
{
// 初始化PLC连接-----10.10添加
await InitPlcConnection();
EasyPlcClient easyPlcClient = new EasyPlcClient("127.0.0.1", 502, 1);
easyPlcClient.ConnectAsync();
_frmConfig = new FrmConfig { Text = "主程序配置" };
_formLog = new FrmLog { Text = "运行日志" };
_formStatistics = new FormStatistics { Text = "生产统计" };

View File

@@ -127,5 +127,6 @@ namespace Check.Main.Infer
{
return this.MemberwiseClone(); // 浅克隆对于这个类足够了
}
}
}

150
Check.Main/Process_Img.cs Normal file
View File

@@ -0,0 +1,150 @@
using OpenCvSharp;
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Drawing.Imaging;
using OpenCvSharp.Extensions;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;
public class ProcessImg
{
// 对单个图像进行模板匹配
public static (double score, Rect? coords) MatchTemplate(Mat img, string templatePath)
{
// 确保图像和模板文件存在
// 读取图像和模板
var template = Cv2.ImRead(templatePath, ImreadModes.Color);
// 创建一个模板匹配的结果矩阵
var result = new Mat();
Cv2.MatchTemplate(img, template, result, TemplateMatchModes.CCoeffNormed);
// 查找最大匹配值
Cv2.MinMaxLoc(result, out _, out var maxVal, out _, out var maxLoc);
// 如果找到的最大匹配值大于阈值
double threshold = 0.3; // 可以根据需要调整阈值
if (maxVal >= threshold)
{
// 计算匹配的坐标
var topLeft = maxLoc;
var rect = new Rect(topLeft.X, topLeft.Y, template.Width, template.Height);
return (maxVal, rect);
}
return (0, null);
}
// 遍历文件夹中的所有图像文件进行模板匹配,并找到最佳得分图像
public static Double ProcessImagesInFolder(string folderPath, Mat img)
{
// 获取所有图像文件
var imageFiles = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly)
.Where(file => file.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
file.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
file.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase))
.ToList();
// 确保输出文件夹存在
// 线程数量
int numThreads = 5;
// 用于存储每个图像的得分和坐标
var bestMatch = new ConcurrentBag<(string imagePath, double score, Rect? coords)>();
DateTime startTime = DateTime.Now;
Stopwatch sw = new Stopwatch();
sw.Start();
Parallel.ForEach(imageFiles, new ParallelOptions { MaxDegreeOfParallelism = numThreads }, picPath =>
{
DateTime threadStartTime = DateTime.Now;
var (score, coords) = MatchTemplate(img, picPath);
bestMatch.Add((picPath, score, coords));
DateTime threadEndTime = DateTime.Now;
TimeSpan threadElapsed = threadEndTime - threadStartTime;
Console.WriteLine($"线程处理 {picPath} 耗时: {threadElapsed.TotalMilliseconds}ms");
});
sw.Stop();
TimeSpan totalElapsed = sw.Elapsed;
Console.WriteLine($"处理完成,耗时: {totalElapsed.TotalSeconds}秒");
// 查找最佳得分
var best = bestMatch.OrderByDescending(m => m.score).FirstOrDefault();
return best.score;
//if (best.coords.HasValue)
//{
// Rect rect = best.coords.Value;
// return rect;
//}
//else
//{
// return new Rect(0, 0, 0, 0);
//}
}
public static Mat BitmapToMat(Bitmap bitmap)
{
if (bitmap == null)
{
throw new ArgumentException("Bitmap is null");
}
// 根据 Bitmap 的宽度、高度和像素格式创建一个与其相对应的 Mat
Mat mat = new Mat(bitmap.Height, bitmap.Width, MatType.CV_8UC3); // 假设 Bitmap 是 24-bit RGB
// 锁定 Bitmap 的内存区域以直接访问它的内存
BitmapData bitmapData = bitmap.LockBits(
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadOnly,
bitmap.PixelFormat
);
try
{
// 使用直接内存拷贝将 Bitmap 的数据拷贝到 Mat
unsafe
{
byte* srcData = (byte*)bitmapData.Scan0;
byte* dstData = (byte*)mat.DataPointer;
int stride = bitmapData.Stride;
int width = bitmap.Width * 3; // 24bpp 3个字节一个像素
int height = bitmap.Height;
for (int y = 0; y < height; y++)
{
Buffer.MemoryCopy(srcData + y * stride, dstData + y * mat.Step(), width, width);
}
}
}
finally
{
bitmap.UnlockBits(bitmapData);
}
return mat;
}
}

View File

@@ -28,79 +28,54 @@
/// </summary>
private void InitializeComponent()
{
this.uiTableLayoutPanel1 = new Sunny.UI.UITableLayoutPanel();
this.btnStartDevice = new Sunny.UI.UIButton();
this.btnStartCheck = new Sunny.UI.UIButton();
this.uiTableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormControlPanel));
uiTableLayoutPanel1 = new Sunny.UI.UITableLayoutPanel();
btnStartDevice = new Sunny.UI.UIButton();
btnStartCheck = new Sunny.UI.UIButton();
uiTableLayoutPanel1.SuspendLayout();
SuspendLayout();
//
// uiTableLayoutPanel1
//
this.uiTableLayoutPanel1.ColumnCount = 2;
this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
this.uiTableLayoutPanel1.Controls.Add(this.btnStartDevice, 0, 1);
this.uiTableLayoutPanel1.Controls.Add(this.btnStartCheck, 0, 3);
this.uiTableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.uiTableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
this.uiTableLayoutPanel1.Name = "uiTableLayoutPanel1";
this.uiTableLayoutPanel1.RowCount = 5;
this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.38585F));
this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 35.35353F));
this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 7.575758F));
this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.83838F));
this.uiTableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 11.38585F));
this.uiTableLayoutPanel1.Size = new System.Drawing.Size(230, 198);
this.uiTableLayoutPanel1.TabIndex = 0;
this.uiTableLayoutPanel1.TagString = null;
resources.ApplyResources(uiTableLayoutPanel1, "uiTableLayoutPanel1");
uiTableLayoutPanel1.Controls.Add(btnStartDevice, 0, 1);
uiTableLayoutPanel1.Controls.Add(btnStartCheck, 0, 3);
uiTableLayoutPanel1.Name = "uiTableLayoutPanel1";
uiTableLayoutPanel1.TagString = null;
//
// btnStartDevice
//
this.uiTableLayoutPanel1.SetColumnSpan(this.btnStartDevice, 2);
this.btnStartDevice.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnStartDevice.Dock = System.Windows.Forms.DockStyle.Fill;
this.btnStartDevice.FillPressColor = System.Drawing.Color.LimeGreen;
this.btnStartDevice.FillSelectedColor = System.Drawing.Color.LimeGreen;
this.btnStartDevice.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnStartDevice.Location = new System.Drawing.Point(3, 25);
this.btnStartDevice.MinimumSize = new System.Drawing.Size(1, 1);
this.btnStartDevice.Name = "btnStartDevice";
this.btnStartDevice.Size = new System.Drawing.Size(224, 64);
this.btnStartDevice.TabIndex = 0;
this.btnStartDevice.Text = "启动设备";
this.btnStartDevice.TipsFont = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnStartDevice.Click += new System.EventHandler(this.btnStartDevice_Click);
uiTableLayoutPanel1.SetColumnSpan(btnStartDevice, 2);
btnStartDevice.Cursor = Cursors.Hand;
resources.ApplyResources(btnStartDevice, "btnStartDevice");
btnStartDevice.FillPressColor = Color.LimeGreen;
btnStartDevice.FillSelectedColor = Color.LimeGreen;
btnStartDevice.Name = "btnStartDevice";
btnStartDevice.TipsFont = new Font("宋体", 9F, FontStyle.Regular, GraphicsUnit.Point, 134);
btnStartDevice.Click += btnStartDevice_Click;
//
// btnStartCheck
//
this.uiTableLayoutPanel1.SetColumnSpan(this.btnStartCheck, 2);
this.btnStartCheck.Cursor = System.Windows.Forms.Cursors.Hand;
this.btnStartCheck.Dock = System.Windows.Forms.DockStyle.Fill;
this.btnStartCheck.FillPressColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0)))));
this.btnStartCheck.FillSelectedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0)))));
this.btnStartCheck.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnStartCheck.Location = new System.Drawing.Point(3, 110);
this.btnStartCheck.MinimumSize = new System.Drawing.Size(1, 1);
this.btnStartCheck.Name = "btnStartCheck";
this.btnStartCheck.Size = new System.Drawing.Size(224, 61);
this.btnStartCheck.TabIndex = 1;
this.btnStartCheck.Text = "开始检测";
this.btnStartCheck.TipsFont = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.btnStartCheck.Click += new System.EventHandler(this.btnStartCheck_Click);
uiTableLayoutPanel1.SetColumnSpan(btnStartCheck, 2);
btnStartCheck.Cursor = Cursors.Hand;
resources.ApplyResources(btnStartCheck, "btnStartCheck");
btnStartCheck.FillPressColor = Color.FromArgb(0, 192, 0);
btnStartCheck.FillSelectedColor = Color.FromArgb(0, 192, 0);
btnStartCheck.Name = "btnStartCheck";
btnStartCheck.TipsFont = new Font("宋体", 9F, FontStyle.Regular, GraphicsUnit.Point, 134);
btnStartCheck.Click += btnStartCheck_Click;
//
// FormControlPanel
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(230, 198);
this.ControlBox = false;
this.Controls.Add(this.uiTableLayoutPanel1);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FormControlPanel";
this.Text = "启动管理";
this.uiTableLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
resources.ApplyResources(this, "$this");
AutoScaleMode = AutoScaleMode.Font;
ControlBox = false;
Controls.Add(uiTableLayoutPanel1);
MaximizeBox = false;
MinimizeBox = false;
Name = "FormControlPanel";
uiTableLayoutPanel1.ResumeLayout(false);
ResumeLayout(false);
}

View File

@@ -50,6 +50,8 @@ namespace Check.Main.UI
}));
}
}
//点击“ 启动设备” 按钮
private void btnStartDevice_Click(object sender, EventArgs e)
{
if (_isDeviceReady)//_isDeviceRunning
@@ -124,6 +126,7 @@ namespace Check.Main.UI
UpdateUI();//UpdateDeviceButtonUI();
}
//点击 “开始检测” 按钮
private void btnStartCheck_Click(object sender, EventArgs e)
{
if (_isDetecting)
@@ -131,7 +134,7 @@ namespace Check.Main.UI
// --- 停止检测 ---
ThreadSafeLogger.Log("用户点击“停止检测”,暂停数据流...");
// 停止硬触发模拟器
CameraManager.StopHardwareTriggerSimulator();
//CameraManager.StopHardwareTriggerSimulator();
// 停止相机采集
CameraManager.StopAll();
@@ -150,10 +153,11 @@ namespace Check.Main.UI
// 启动硬触发模拟器(如果需要)
var config = ConfigurationManager.GetCurrentConfig();
if (config.CameraSettings.Any(c => c.IsEnabled && c.TriggerMode == TriggerModeType.Software))
if (config.CameraSettings.Any(c => c.IsEnabled && c.TriggerMode == TriggerModeType.Hardware))
{
CameraManager.TriggerInterval = 100;
CameraManager.StartHardwareTriggerSimulator();
ThreadSafeLogger.Log("相机设置为硬件触发模式,将由 PLC 输出脉冲信号控制拍照。");
//CameraManager.TriggerInterval = 100;
// CameraManager.StartHardwareTriggerSimulator();
}
// 开始统计

View File

@@ -117,4 +117,137 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="mscorlib" name="mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="uiTableLayoutPanel1.ColumnCount" type="System.Int32, mscorlib">
<value>2</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="btnStartDevice.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="btnStartDevice.Font" type="System.Drawing.Font, System.Drawing">
<value>宋体, 12pt</value>
</data>
<data name="btnStartDevice.Location" type="System.Drawing.Point, System.Drawing">
<value>6, 51</value>
</data>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="btnStartDevice.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>6, 6, 6, 6</value>
</data>
<data name="btnStartDevice.MinimumSize" type="System.Drawing.Size, System.Drawing">
<value>2, 2</value>
</data>
<data name="btnStartDevice.Size" type="System.Drawing.Size, System.Drawing">
<value>410, 128</value>
</data>
<data name="btnStartDevice.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="btnStartDevice.Text" xml:space="preserve">
<value>启动设备</value>
</data>
<data name="&gt;&gt;btnStartDevice.Name" xml:space="preserve">
<value>btnStartDevice</value>
</data>
<data name="&gt;&gt;btnStartDevice.Type" xml:space="preserve">
<value>Sunny.UI.UIButton, SunnyUI, Culture=neutral, PublicKeyToken=27d7d2e821d97aeb</value>
</data>
<data name="&gt;&gt;btnStartDevice.Parent" xml:space="preserve">
<value>uiTableLayoutPanel1</value>
</data>
<data name="&gt;&gt;btnStartDevice.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="btnStartCheck.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="btnStartCheck.Font" type="System.Drawing.Font, System.Drawing">
<value>宋体, 12pt</value>
</data>
<data name="btnStartCheck.Location" type="System.Drawing.Point, System.Drawing">
<value>6, 221</value>
</data>
<data name="btnStartCheck.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>6, 6, 6, 6</value>
</data>
<data name="btnStartCheck.MinimumSize" type="System.Drawing.Size, System.Drawing">
<value>2, 2</value>
</data>
<data name="btnStartCheck.Size" type="System.Drawing.Size, System.Drawing">
<value>410, 122</value>
</data>
<data name="btnStartCheck.TabIndex" type="System.Int32, mscorlib">
<value>1</value>
</data>
<data name="btnStartCheck.Text" xml:space="preserve">
<value>开始检测</value>
</data>
<data name="&gt;&gt;btnStartCheck.Name" xml:space="preserve">
<value>btnStartCheck</value>
</data>
<data name="&gt;&gt;btnStartCheck.Type" xml:space="preserve">
<value>Sunny.UI.UIButton, SunnyUI, Culture=neutral, PublicKeyToken=27d7d2e821d97aeb</value>
</data>
<data name="&gt;&gt;btnStartCheck.Parent" xml:space="preserve">
<value>uiTableLayoutPanel1</value>
</data>
<data name="&gt;&gt;btnStartCheck.ZOrder" xml:space="preserve">
<value>1</value>
</data>
<data name="uiTableLayoutPanel1.Dock" type="System.Windows.Forms.DockStyle, System.Windows.Forms">
<value>Fill</value>
</data>
<data name="uiTableLayoutPanel1.Location" type="System.Drawing.Point, System.Drawing">
<value>0, 0</value>
</data>
<data name="uiTableLayoutPanel1.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>6, 6, 6, 6</value>
</data>
<data name="uiTableLayoutPanel1.RowCount" type="System.Int32, mscorlib">
<value>5</value>
</data>
<data name="uiTableLayoutPanel1.Size" type="System.Drawing.Size, System.Drawing">
<value>422, 396</value>
</data>
<data name="uiTableLayoutPanel1.TabIndex" type="System.Int32, mscorlib">
<value>0</value>
</data>
<data name="&gt;&gt;uiTableLayoutPanel1.Name" xml:space="preserve">
<value>uiTableLayoutPanel1</value>
</data>
<data name="&gt;&gt;uiTableLayoutPanel1.Type" xml:space="preserve">
<value>Sunny.UI.UITableLayoutPanel, SunnyUI, Culture=neutral, PublicKeyToken=27d7d2e821d97aeb</value>
</data>
<data name="&gt;&gt;uiTableLayoutPanel1.Parent" xml:space="preserve">
<value>$this</value>
</data>
<data name="&gt;&gt;uiTableLayoutPanel1.ZOrder" xml:space="preserve">
<value>0</value>
</data>
<data name="uiTableLayoutPanel1.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms">
<value>&lt;?xml version="1.0" encoding="utf-16"?&gt;&lt;TableLayoutSettings&gt;&lt;Controls&gt;&lt;Control Name="btnStartDevice" Row="1" RowSpan="1" Column="0" ColumnSpan="2" /&gt;&lt;Control Name="btnStartCheck" Row="3" RowSpan="1" Column="0" ColumnSpan="2" /&gt;&lt;/Controls&gt;&lt;Columns Styles="Percent,50,Percent,50" /&gt;&lt;Rows Styles="Percent,11.38585,Percent,35.35353,Percent,7.575758,Percent,33.83838,Percent,11.38585" /&gt;&lt;/TableLayoutSettings&gt;</value>
</data>
<metadata name="$this.Localizable" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<data name="$this.AutoScaleDimensions" type="System.Drawing.SizeF, System.Drawing">
<value>11, 24</value>
</data>
<data name="$this.ClientSize" type="System.Drawing.Size, System.Drawing">
<value>422, 396</value>
</data>
<data name="$this.Margin" type="System.Windows.Forms.Padding, System.Windows.Forms">
<value>6, 6, 6, 6</value>
</data>
<data name="$this.Text" xml:space="preserve">
<value>启动管理</value>
</data>
<data name="&gt;&gt;$this.Name" xml:space="preserve">
<value>FormControlPanel</value>
</data>
<data name="&gt;&gt;$this.Type" xml:space="preserve">
<value>WeifenLuo.WinFormsUI.Docking.DockContent, WeifenLuo.WinFormsUI.Docking, Culture=neutral, PublicKeyToken=5cded1a1a0a7b481</value>
</data>
</root>