111
This commit is contained in:
		| @@ -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; | ||||
|  | ||||
|   | ||||
| @@ -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"); | ||||
|  | ||||
|             //训练阶段(相机3)9.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) | ||||
| @@ -107,12 +232,14 @@ namespace Check.Main.Camera | ||||
|                     // 当调用 Stop 时,会 CompleteAdding 队列,Take 会抛出此异常,是正常退出流程 | ||||
|                     break; | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 catch (Exception ex)  | ||||
|                 { | ||||
|                     ThreadSafeLogger.Log($"[ERROR] 相机 #{_cameraIndex} 处理线程异常: {ex.Message}"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|  | ||||
|         /// <summary> | ||||
|         /// 将 System.Drawing.Bitmap 安全地转换为 SkiaSharp.SKImage。 | ||||
|         /// </summary> | ||||
|   | ||||
| @@ -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; | ||||
|   | ||||
| @@ -179,7 +179,9 @@ namespace Check.Main.Camera | ||||
|                 } | ||||
|  | ||||
|                 // 默认设置为连续模式 | ||||
|                 SetContinuousMode(); | ||||
|                 //SetContinuousMode(); | ||||
|                 // 设置为硬触发模式(Line0) | ||||
|                 SetTriggerMode(false); | ||||
|  | ||||
|                 IsOpen = true; | ||||
|                 OnCameraMessage("相机打开成功。", 0); | ||||
|   | ||||
| @@ -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> | ||||
|  | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
							
								
								
									
										290
									
								
								Check.Main/Common/LogoMatcher.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								Check.Main/Common/LogoMatcher.cs
									
									
									
									
									
										Normal 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 = OK,false = 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); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										62
									
								
								Check.Main/Common/LogoTemplateTrainer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								Check.Main/Common/LogoTemplateTrainer.cs
									
									
									
									
									
										Normal 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++; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -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; | ||||
|   | ||||
| @@ -52,6 +52,7 @@ namespace Check.Main.Dispatch | ||||
|                 config = new ProcessConfig(); | ||||
|             } | ||||
|  | ||||
|              | ||||
|             CurrentProductName = productName; | ||||
|             CurrentConfig = config; | ||||
|  | ||||
| @@ -120,7 +121,7 @@ namespace Check.Main.Dispatch | ||||
|                     // 切换到列表中的第一个产品,或者如果列表为空,则创建一个默认产品 | ||||
|                     string nextProduct = ProductList.FirstOrDefault() ?? "DefaultProduct"; | ||||
|                     SwitchToProduct(nextProduct); | ||||
|                 } | ||||
|                 }  | ||||
|                 else | ||||
|                 { | ||||
|                     // 如果删除的不是当前产品,我们仍然需要触发一次事件, | ||||
|   | ||||
| @@ -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 = "生产统计" }; | ||||
|   | ||||
| @@ -127,5 +127,6 @@ namespace Check.Main.Infer | ||||
|         { | ||||
|             return this.MemberwiseClone(); // 浅克隆对于这个类足够了 | ||||
|         } | ||||
|  | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										150
									
								
								Check.Main/Process_Img.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								Check.Main/Process_Img.cs
									
									
									
									
									
										Normal 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; | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
							
								
								
									
										97
									
								
								Check.Main/UI/FormControlPanel.Designer.cs
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										97
									
								
								Check.Main/UI/FormControlPanel.Designer.cs
									
									
									
										generated
									
									
									
								
							| @@ -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); | ||||
|  | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -25,7 +25,7 @@ namespace Check.Main.UI | ||||
|         public FormControlPanel() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|  | ||||
|               | ||||
|             ConfigurationManager.OnConfigurationChanged += HandleConfigurationChanged; | ||||
|             UpdateUI(); | ||||
|  | ||||
| @@ -50,6 +50,8 @@ namespace Check.Main.UI | ||||
|                 })); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //点击“ 启动设备”  按钮 | ||||
|         private void btnStartDevice_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             if (_isDeviceReady)//_isDeviceRunning | ||||
| @@ -86,7 +88,7 @@ namespace Check.Main.UI | ||||
|                 ThreadSafeLogger.Log("用户点击“启动设备”,开始新的启动流程..."); | ||||
|  | ||||
|                 // 1. 从单一数据源获取完整的配置对象 | ||||
|                 var config = ConfigurationManager.GetCurrentConfig(); | ||||
|                 var config = ConfigurationManager.GetCurrentConfig();  | ||||
|                 // 2. 验证相机配置的有效性 | ||||
|                 if (config.CameraSettings == null || !config.CameraSettings.Any(c => c.IsEnabled)) | ||||
|                 { | ||||
| @@ -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(); | ||||
|                 } | ||||
|  | ||||
|                 // 开始统计 | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|   <!-- | ||||
|     Microsoft ResX Schema | ||||
|  | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|  | ||||
|     The primary goals of this format is to allow a simple XML format | ||||
|     that is mostly human readable. The generation and parsing of the | ||||
|     various data types are done through the TypeConverter classes | ||||
|     associated with the data types. | ||||
|      | ||||
|  | ||||
|     Example: | ||||
|      | ||||
|  | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
| @@ -26,36 +26,36 @@ | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|  | ||||
|     There are any number of "resheader" rows that contain simple | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|  | ||||
|     Each data row contains a name, and value. The row also contains a | ||||
|     type or mimetype. Type corresponds to a .NET class that support | ||||
|     text/value conversion through the TypeConverter architecture. | ||||
|     Classes that don't support this are serialized and stored with the | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|  | ||||
|     The mimetype is used for serialized objects, and tells the | ||||
|     ResXResourceReader how to depersist the object. This is currently not | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|  | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format | ||||
|     that the ResXResourceWriter will generate, however the reader can | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|     value   : The object must be serialized into a byte array | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
| @@ -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=">>btnStartDevice.Name" xml:space="preserve"> | ||||
|     <value>btnStartDevice</value> | ||||
|   </data> | ||||
|   <data name=">>btnStartDevice.Type" xml:space="preserve"> | ||||
|     <value>Sunny.UI.UIButton, SunnyUI, Culture=neutral, PublicKeyToken=27d7d2e821d97aeb</value> | ||||
|   </data> | ||||
|   <data name=">>btnStartDevice.Parent" xml:space="preserve"> | ||||
|     <value>uiTableLayoutPanel1</value> | ||||
|   </data> | ||||
|   <data name=">>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=">>btnStartCheck.Name" xml:space="preserve"> | ||||
|     <value>btnStartCheck</value> | ||||
|   </data> | ||||
|   <data name=">>btnStartCheck.Type" xml:space="preserve"> | ||||
|     <value>Sunny.UI.UIButton, SunnyUI, Culture=neutral, PublicKeyToken=27d7d2e821d97aeb</value> | ||||
|   </data> | ||||
|   <data name=">>btnStartCheck.Parent" xml:space="preserve"> | ||||
|     <value>uiTableLayoutPanel1</value> | ||||
|   </data> | ||||
|   <data name=">>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=">>uiTableLayoutPanel1.Name" xml:space="preserve"> | ||||
|     <value>uiTableLayoutPanel1</value> | ||||
|   </data> | ||||
|   <data name=">>uiTableLayoutPanel1.Type" xml:space="preserve"> | ||||
|     <value>Sunny.UI.UITableLayoutPanel, SunnyUI, Culture=neutral, PublicKeyToken=27d7d2e821d97aeb</value> | ||||
|   </data> | ||||
|   <data name=">>uiTableLayoutPanel1.Parent" xml:space="preserve"> | ||||
|     <value>$this</value> | ||||
|   </data> | ||||
|   <data name=">>uiTableLayoutPanel1.ZOrder" xml:space="preserve"> | ||||
|     <value>0</value> | ||||
|   </data> | ||||
|   <data name="uiTableLayoutPanel1.LayoutSettings" type="System.Windows.Forms.TableLayoutSettings, System.Windows.Forms"> | ||||
|     <value><?xml version="1.0" encoding="utf-16"?><TableLayoutSettings><Controls><Control Name="btnStartDevice" Row="1" RowSpan="1" Column="0" ColumnSpan="2" /><Control Name="btnStartCheck" Row="3" RowSpan="1" Column="0" ColumnSpan="2" /></Controls><Columns Styles="Percent,50,Percent,50" /><Rows Styles="Percent,11.38585,Percent,35.35353,Percent,7.575758,Percent,33.83838,Percent,11.38585" /></TableLayoutSettings></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=">>$this.Name" xml:space="preserve"> | ||||
|     <value>FormControlPanel</value> | ||||
|   </data> | ||||
|   <data name=">>$this.Type" xml:space="preserve"> | ||||
|     <value>WeifenLuo.WinFormsUI.Docking.DockContent, WeifenLuo.WinFormsUI.Docking, Culture=neutral, PublicKeyToken=5cded1a1a0a7b481</value> | ||||
|   </data> | ||||
| </root> | ||||
		Reference in New Issue
	
	Block a user