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