111
This commit is contained in:
@@ -24,34 +24,32 @@ namespace Check.Main.Camera
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CameraManager
|
public static class CameraManager
|
||||||
{
|
{
|
||||||
// 活动的相机实例字典,键为相机名称
|
//1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。
|
||||||
|
// 活动的相机实例字典(键值对),键 (string):使用相机的名称,值 (HikvisionCamera):存储实际的相机实例,{ get; } 创建了一个只读的公共属性
|
||||||
public static Dictionary<string, HikvisionCamera> ActiveCameras { get; } = new Dictionary<string, HikvisionCamera>();
|
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> CameraDisplays { get; } = new Dictionary<string, FormImageDisplay>();
|
||||||
|
|
||||||
public static Dictionary<string, FormImageDisplay> OriginalImageDisplays { 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> ResultImageDisplays { get; } = new Dictionary<string, FormImageDisplay>();//结果图像窗口
|
||||||
|
|
||||||
// --- 多相机同步逻辑 ---
|
//2、多相机同步逻辑
|
||||||
// 产品检测队列
|
// 【队列】一个产品需要多台相机拍完,才算完整。
|
||||||
private static readonly Queue<ProductResult> ProductQueue = new Queue<ProductResult>();
|
private static readonly Queue<ProductResult> ProductQueue = new Queue<ProductResult>();
|
||||||
// 队列锁,保证线程安全
|
// 【(队列)锁】保证队列在多线程下安全
|
||||||
private static readonly object QueueLock = new object();
|
private static readonly object QueueLock = new object();
|
||||||
// 当前启用的相机数量,用于判断产品是否检测完毕
|
// 当前启用的相机数量,用于判断产品是否检测完毕
|
||||||
private static int EnabledCameraCount = 0;
|
private static int EnabledCameraCount = 0;
|
||||||
|
|
||||||
//public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted;
|
//public static event EventHandler<DetectionResultEventArgs> OnDetectionCompleted;
|
||||||
// public static bool IsDetectionRunning { get; private set; } = false;
|
// public static bool IsDetectionRunning { get; private set; } = false;
|
||||||
|
// 产品ID【计数器】
|
||||||
// 产品ID计数器
|
|
||||||
private static long _productCounter = 0;
|
private static long _productCounter = 0;
|
||||||
// 用于同步计数器访问的锁,虽然long的自增是原子操作,但为清晰和未来扩展,使用锁是好习惯
|
// 用于同步计数器访问的锁,虽然long的自增是原子操作,但为清晰和未来扩展,使用锁是好习惯
|
||||||
private static readonly object _counterLock = new object();
|
private static readonly object _counterLock = new object();
|
||||||
|
|
||||||
// --- 新增:硬触发模拟器 ---
|
// 3、--- 新增:硬触发模拟器 ---
|
||||||
private static readonly System.Timers.Timer _hardwareTriggerSimulator;
|
private static readonly System.Timers.Timer _hardwareTriggerSimulator;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取或设置模拟硬触发的间隔时间(毫秒)。
|
/// 获取或设置模拟硬触发的间隔时间(毫秒)。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -221,12 +219,12 @@ namespace Check.Main.Camera
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
//********************初始化和启动流程*******************
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。
|
/// 准备所有相机硬件、UI窗口和后台处理器,但不开始采集。
|
||||||
/// 这是“启动设备”的第一阶段。
|
/// 这是“启动设备”的第一阶段。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void PrepareAll(ProcessConfig config, FrmMain mainForm)
|
public static void PrepareAll(ProcessConfig config, FrmMain mainForm)//①准备所有相机和模型
|
||||||
{
|
{
|
||||||
// 1. 清理旧资源和UI
|
// 1. 清理旧资源和UI
|
||||||
mainForm.ClearStatusStrip();
|
mainForm.ClearStatusStrip();
|
||||||
@@ -238,7 +236,7 @@ namespace Check.Main.Camera
|
|||||||
YoloModelManager.Initialize(config.ModelSettings);
|
YoloModelManager.Initialize(config.ModelSettings);
|
||||||
DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
|
DetectionCoordinator.Initialize(config.CameraSettings, config.ModelSettings);
|
||||||
|
|
||||||
// 3. 创建相机硬件实例和UI窗口
|
// 3. 创建相机硬件实例和UI窗口------
|
||||||
var deviceList = new HikvisionCamera().FindDevices();
|
var deviceList = new HikvisionCamera().FindDevices();
|
||||||
if (deviceList.Count == 0)
|
if (deviceList.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -246,7 +244,7 @@ namespace Check.Main.Camera
|
|||||||
return;
|
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 };
|
var cam = new HikvisionCamera { Name = setting.Name, CameraIndex = setting.CameraIndex };
|
||||||
cam.TriggerMode = setting.TriggerMode;
|
cam.TriggerMode = setting.TriggerMode;
|
||||||
@@ -293,6 +291,7 @@ namespace Check.Main.Camera
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 根据配置列表初始化或更新所有相机
|
/// 根据配置列表初始化或更新所有相机
|
||||||
|
/// 类似 PrepareAll,但更侧重于更新配置,会检查物理设备数量,不足时报 warning。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static void Initialize(ProcessConfig config, FrmMain mainForm)
|
public static void Initialize(ProcessConfig config, FrmMain mainForm)
|
||||||
{
|
{
|
||||||
@@ -398,7 +397,7 @@ namespace Check.Main.Camera
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 定时器触发事件。
|
/// 定时器触发事件。(定时器 OnHardwareTriggerTimerElapsed 会遍历相机,对处于软件触发模式的相机执行 SoftwareTrigger()。)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void OnHardwareTriggerTimerElapsed(object sender, ElapsedEventArgs e)
|
private static void OnHardwareTriggerTimerElapsed(object sender, ElapsedEventArgs e)
|
||||||
{
|
{
|
||||||
@@ -490,7 +489,9 @@ namespace Check.Main.Camera
|
|||||||
|
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// Shutdown 方法也简化
|
/// <summary>
|
||||||
|
/// 停止所有相机 + 关闭窗口 + 清空字典 + 关闭检测协调器 + 销毁模型
|
||||||
|
/// </summary>
|
||||||
public static void Shutdown()
|
public static void Shutdown()
|
||||||
{
|
{
|
||||||
// 1. 停止硬件和模拟器
|
// 1. 停止硬件和模拟器
|
||||||
@@ -618,7 +619,7 @@ namespace Check.Main.Camera
|
|||||||
// }
|
// }
|
||||||
//}
|
//}
|
||||||
|
|
||||||
// 图像回调方法现在极其简单
|
//【相机回调】
|
||||||
private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp)
|
private static void OnCameraImageAcquired(HikvisionCamera sender, Bitmap bmp)
|
||||||
{
|
{
|
||||||
Bitmap bmpForDisplay = null;
|
Bitmap bmpForDisplay = null;
|
||||||
@@ -626,16 +627,16 @@ namespace Check.Main.Camera
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 1. 为“显示”和“处理”创建两个完全独立的深克隆副本
|
// 1. 深度克隆 Bitmap → 分成 显示副本 和 处理副本。
|
||||||
bmpForDisplay = DeepCloneBitmap(bmp, "Display");
|
bmpForDisplay = DeepCloneBitmap(bmp, "Display");
|
||||||
bmpForProcessing = DeepCloneBitmap(bmp, "Processing");
|
bmpForProcessing = DeepCloneBitmap(bmp, "Processing");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// 2.无论克隆成功与否,都必须立即释放事件传递过来的原始bmp,防止泄漏。
|
// 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))
|
if (bmpForDisplay != null && OriginalImageDisplays.TryGetValue(sender.Name, out var displayWindow))
|
||||||
{
|
{
|
||||||
// displayWindow.UpdateImage 会处理线程安全问题
|
// displayWindow.UpdateImage 会处理线程安全问题
|
||||||
@@ -646,7 +647,7 @@ namespace Check.Main.Camera
|
|||||||
// 如果没有对应的显示窗口,或克隆失败,必须释放为显示创建的副本
|
// 如果没有对应的显示窗口,或克隆失败,必须释放为显示创建的副本
|
||||||
bmpForDisplay?.Dispose();
|
bmpForDisplay?.Dispose();
|
||||||
}
|
}
|
||||||
// 分支 B: 将用于处理的副本发送到检测协调器的后台队列
|
// 分支 B: 处理副本 → 交给 DetectionCoordinator.EnqueueImage() 进行检测。
|
||||||
if (bmpForProcessing != null)
|
if (bmpForProcessing != null)
|
||||||
{
|
{
|
||||||
// bmpForProcessing 的所有权在这里被转移给了协调器
|
// bmpForProcessing 的所有权在这里被转移给了协调器
|
||||||
@@ -673,11 +674,11 @@ namespace Check.Main.Camera
|
|||||||
// 2. 找到此相机的结果显示窗口
|
// 2. 找到此相机的结果显示窗口
|
||||||
if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay))
|
if (ResultImageDisplays.TryGetValue(cameraEntry.Key, out var resultDisplay))
|
||||||
{
|
{
|
||||||
var bmp = ConvertSKImageToBitmap(e.ResultImage);
|
//var bmp = ConvertSKImageToBitmap(e.ResultImage);
|
||||||
if (bmp != null)
|
if (e.ResultImage != null)
|
||||||
{
|
{
|
||||||
// UpdateImage 会负责克隆并显示,所以这里传递 bmp 即可
|
// UpdateImage 会负责克隆并显示,所以这里传递 bmp 即可
|
||||||
resultDisplay.UpdateImage(bmp);
|
resultDisplay.UpdateImage(e.ResultImage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -691,7 +692,7 @@ namespace Check.Main.Camera
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 【将 SkiaSharp.SKImage 安全地转换为 System.Drawing.Bitmap。
|
/// 【将 SkiaSharp.SKImage 安全地转换为 System.Drawing.Bitmap。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static Bitmap ConvertSKImageToBitmap(SKImage skImage)
|
public static Bitmap ConvertSKImageToBitmap(SKImage skImage)
|
||||||
{
|
{
|
||||||
if (skImage == null) return null;
|
if (skImage == null) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Check.Main.Common;
|
using Check.Main.Common;
|
||||||
using Check.Main.Infer;
|
using Check.Main.Infer;
|
||||||
|
using HalconTemplateMatch;
|
||||||
using SkiaSharp;
|
using SkiaSharp;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -10,7 +11,7 @@ using System.Runtime.InteropServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using YoloDotNet.Extensions;
|
using YoloDotNet.Extensions;
|
||||||
|
using OpenCvSharp;
|
||||||
namespace Check.Main.Camera
|
namespace Check.Main.Camera
|
||||||
{
|
{
|
||||||
public class CameraProcessor : IDisposable
|
public class CameraProcessor : IDisposable
|
||||||
@@ -52,6 +53,9 @@ namespace Check.Main.Camera
|
|||||||
_imageQueue.Add(new ImageData(_imageCounter, _cameraIndex, bmp));
|
_imageQueue.Add(new ImageData(_imageCounter, _cameraIndex, bmp));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 图像处理主循环
|
||||||
|
/// </summary>
|
||||||
private void ProcessQueue()
|
private void ProcessQueue()
|
||||||
{
|
{
|
||||||
// 从模型管理器获取此线程专属的YOLO模型
|
// 从模型管理器获取此线程专属的YOLO模型
|
||||||
@@ -61,6 +65,36 @@ namespace Check.Main.Camera
|
|||||||
ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型,处理线程已中止。");
|
ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型,处理线程已中止。");
|
||||||
return; // 如果没有模型,此线程无法工作
|
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)
|
while (_isRunning)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -69,19 +103,15 @@ namespace Check.Main.Camera
|
|||||||
ImageData data = _imageQueue.Take();
|
ImageData data = _imageQueue.Take();
|
||||||
using (data)
|
using (data)
|
||||||
{
|
{
|
||||||
//SKImage resultSkImage = null; // 用于存储最终绘制好结果的图像
|
string result = "";
|
||||||
|
|
||||||
using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期
|
using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期
|
||||||
{
|
{
|
||||||
if (skImage == null) continue;
|
if (skImage == null) continue;
|
||||||
var predictions = yoloModel.RunObjectDetection(skImage);
|
var predictions = yoloModel.RunObjectDetection(skImage);
|
||||||
// 模拟模型处理
|
result = predictions.Any() ? "NG" : "OK";
|
||||||
//Thread.Sleep(50); // 模拟耗时
|
|
||||||
//bool isOk = new Random().NextDouble() > 0.1;
|
|
||||||
//string result = isOk ? "OK" : "NG";
|
|
||||||
|
|
||||||
string result = predictions.Any() ? "NG" : "OK";
|
|
||||||
ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},检测到 {predictions.Count} 个目标,结果: {result}");
|
ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},检测到 {predictions.Count} 个目标,结果: {result}");
|
||||||
|
|
||||||
// 将处理结果交给协调器进行组装
|
// 将处理结果交给协调器进行组装
|
||||||
DetectionCoordinator.AssembleProduct(data, result);
|
DetectionCoordinator.AssembleProduct(data, result);
|
||||||
|
|
||||||
@@ -90,16 +120,111 @@ namespace Check.Main.Camera
|
|||||||
using (var resultSkImage = skImage.Draw(predictions))
|
using (var resultSkImage = skImage.Draw(predictions))
|
||||||
{
|
{
|
||||||
// 4. 触发事件,将绘制好的 resultSkImage 传递出去
|
// 4. 触发事件,将绘制好的 resultSkImage 传递出去
|
||||||
|
|
||||||
|
Bitmap bitmap = CameraManager.ConvertSKImageToBitmap(resultSkImage);
|
||||||
// 所有权在这里被转移
|
// 所有权在这里被转移
|
||||||
OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs(
|
OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs(
|
||||||
_cameraIndex,
|
_cameraIndex,
|
||||||
data.ProductId,
|
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)
|
catch (InvalidOperationException)
|
||||||
@@ -107,12 +232,14 @@ namespace Check.Main.Camera
|
|||||||
// 当调用 Stop 时,会 CompleteAdding 队列,Take 会抛出此异常,是正常退出流程
|
// 当调用 Stop 时,会 CompleteAdding 队列,Take 会抛出此异常,是正常退出流程
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ThreadSafeLogger.Log($"[ERROR] 相机 #{_cameraIndex} 处理线程异常: {ex.Message}");
|
ThreadSafeLogger.Log($"[ERROR] 相机 #{_cameraIndex} 处理线程异常: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 将 System.Drawing.Bitmap 安全地转换为 SkiaSharp.SKImage。
|
/// 将 System.Drawing.Bitmap 安全地转换为 SkiaSharp.SKImage。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace Check.Main.Camera
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 相机配置信息类,用于PropertyGrid显示和编辑
|
/// 相机配置信息类CameraSettings,用于PropertyGrid显示和编辑(****核心!****)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class CameraSettings : INotifyPropertyChanged, ICloneable
|
public class CameraSettings : INotifyPropertyChanged, ICloneable
|
||||||
{
|
{
|
||||||
@@ -39,8 +39,8 @@ namespace Check.Main.Camera
|
|||||||
|
|
||||||
private int _cameraIndex = 0;
|
private int _cameraIndex = 0;
|
||||||
private string _name = "Camera-1";
|
private string _name = "Camera-1";
|
||||||
private string _ipAddress = "192.168.1.100";
|
private string _ipAddress = "169.254.51.253";
|
||||||
private string _ipDeviceAddress = "192.168.1.101";
|
private string _ipDeviceAddress = "169.254.51.45";
|
||||||
private TriggerModeType _triggerMode = TriggerModeType.Continuous;
|
private TriggerModeType _triggerMode = TriggerModeType.Continuous;
|
||||||
private bool _isEnabled = true;
|
private bool _isEnabled = true;
|
||||||
private CheckType _checkType = CheckType.DeepLearning;
|
private CheckType _checkType = CheckType.DeepLearning;
|
||||||
|
|||||||
@@ -179,7 +179,9 @@ namespace Check.Main.Camera
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 默认设置为连续模式
|
// 默认设置为连续模式
|
||||||
SetContinuousMode();
|
//SetContinuousMode();
|
||||||
|
// 设置为硬触发模式(Line0)
|
||||||
|
SetTriggerMode(false);
|
||||||
|
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
OnCameraMessage("相机打开成功。", 0);
|
OnCameraMessage("相机打开成功。", 0);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
@@ -10,11 +10,14 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DockPanelSuite" Version="3.1.1" />
|
<PackageReference Include="DockPanelSuite" Version="3.1.1" />
|
||||||
<PackageReference Include="DockPanelSuite.ThemeVS2015" 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="Newtonsoft.Json" Version="13.0.3" />
|
||||||
<PackageReference Include="NPOI" Version="2.7.4" />
|
<PackageReference Include="NPOI" Version="2.7.4" />
|
||||||
<PackageReference Include="OpenCvSharp4" Version="4.10.0.20241108" />
|
<PackageReference Include="OpenCvSharp4" Version="4.10.0.20241108" />
|
||||||
@@ -26,11 +29,17 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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">
|
<Reference Include="HslCommunication">
|
||||||
<HintPath>..\..\..\HslCommunication-master\HslCommunication.dll</HintPath>
|
<HintPath>..\..\..\HslCommunication-master\HslCommunication.dll</HintPath>
|
||||||
</Reference>
|
</Reference>
|
||||||
<Reference Include="MvCameraControl.Net">
|
<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>
|
</Reference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ namespace Check.Main.Common
|
|||||||
_slaveId = slaveId;
|
_slaveId = slaveId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;//10.11添加
|
||||||
|
|
||||||
public async Task ConnectAsync()
|
public async Task ConnectAsync()
|
||||||
{
|
{
|
||||||
await _tcpClient.ConnectAsync(_ip, _port);
|
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 int CameraIndex { get; }
|
||||||
public long ProductId { 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;
|
CameraIndex = cameraIndex;
|
||||||
ProductId = productId;
|
ProductId = productId;
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ namespace Check.Main.Dispatch
|
|||||||
config = new ProcessConfig();
|
config = new ProcessConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
CurrentProductName = productName;
|
CurrentProductName = productName;
|
||||||
CurrentConfig = config;
|
CurrentConfig = config;
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ namespace Check.Main.Dispatch
|
|||||||
// 切换到列表中的第一个产品,或者如果列表为空,则创建一个默认产品
|
// 切换到列表中的第一个产品,或者如果列表为空,则创建一个默认产品
|
||||||
string nextProduct = ProductList.FirstOrDefault() ?? "DefaultProduct";
|
string nextProduct = ProductList.FirstOrDefault() ?? "DefaultProduct";
|
||||||
SwitchToProduct(nextProduct);
|
SwitchToProduct(nextProduct);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// 如果删除的不是当前产品,我们仍然需要触发一次事件,
|
// 如果删除的不是当前产品,我们仍然需要触发一次事件,
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ namespace Check.Main
|
|||||||
public partial class FrmMain : Form
|
public partial class FrmMain : Form
|
||||||
{
|
{
|
||||||
// private FrmCamConfig _formCameraConfig;
|
// private FrmCamConfig _formCameraConfig;
|
||||||
|
public static EasyPlcClient PlcClient;//定义全局PLC对象 --- 10.10添加①
|
||||||
|
|
||||||
private FrmConfig _frmConfig;
|
private FrmConfig _frmConfig;
|
||||||
private FrmLog _formLog;
|
private FrmLog _formLog;
|
||||||
private ThemeBase _theme = new VS2015BlueTheme(); // 外观主题
|
private ThemeBase _theme = new VS2015BlueTheme(); // 外观主题
|
||||||
@@ -40,11 +42,28 @@ namespace Check.Main
|
|||||||
_deserializeDockContent = new DeserializeDockContent(GetContentFromPersistString);
|
_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 easyPlcClient = new EasyPlcClient("127.0.0.1", 502, 1);
|
||||||
easyPlcClient.ConnectAsync();
|
easyPlcClient.ConnectAsync();
|
||||||
|
|
||||||
_frmConfig = new FrmConfig { Text = "主程序配置" };
|
_frmConfig = new FrmConfig { Text = "主程序配置" };
|
||||||
_formLog = new FrmLog { Text = "运行日志" };
|
_formLog = new FrmLog { Text = "运行日志" };
|
||||||
_formStatistics = new FormStatistics { Text = "生产统计" };
|
_formStatistics = new FormStatistics { Text = "生产统计" };
|
||||||
|
|||||||
@@ -127,5 +127,6 @@ namespace Check.Main.Infer
|
|||||||
{
|
{
|
||||||
return this.MemberwiseClone(); // 浅克隆对于这个类足够了
|
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>
|
/// </summary>
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
this.uiTableLayoutPanel1 = new Sunny.UI.UITableLayoutPanel();
|
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormControlPanel));
|
||||||
this.btnStartDevice = new Sunny.UI.UIButton();
|
uiTableLayoutPanel1 = new Sunny.UI.UITableLayoutPanel();
|
||||||
this.btnStartCheck = new Sunny.UI.UIButton();
|
btnStartDevice = new Sunny.UI.UIButton();
|
||||||
this.uiTableLayoutPanel1.SuspendLayout();
|
btnStartCheck = new Sunny.UI.UIButton();
|
||||||
this.SuspendLayout();
|
uiTableLayoutPanel1.SuspendLayout();
|
||||||
|
SuspendLayout();
|
||||||
//
|
//
|
||||||
// uiTableLayoutPanel1
|
// uiTableLayoutPanel1
|
||||||
//
|
//
|
||||||
this.uiTableLayoutPanel1.ColumnCount = 2;
|
resources.ApplyResources(uiTableLayoutPanel1, "uiTableLayoutPanel1");
|
||||||
this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
uiTableLayoutPanel1.Controls.Add(btnStartDevice, 0, 1);
|
||||||
this.uiTableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
|
uiTableLayoutPanel1.Controls.Add(btnStartCheck, 0, 3);
|
||||||
this.uiTableLayoutPanel1.Controls.Add(this.btnStartDevice, 0, 1);
|
uiTableLayoutPanel1.Name = "uiTableLayoutPanel1";
|
||||||
this.uiTableLayoutPanel1.Controls.Add(this.btnStartCheck, 0, 3);
|
uiTableLayoutPanel1.TagString = null;
|
||||||
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;
|
|
||||||
//
|
//
|
||||||
// btnStartDevice
|
// btnStartDevice
|
||||||
//
|
//
|
||||||
this.uiTableLayoutPanel1.SetColumnSpan(this.btnStartDevice, 2);
|
uiTableLayoutPanel1.SetColumnSpan(btnStartDevice, 2);
|
||||||
this.btnStartDevice.Cursor = System.Windows.Forms.Cursors.Hand;
|
btnStartDevice.Cursor = Cursors.Hand;
|
||||||
this.btnStartDevice.Dock = System.Windows.Forms.DockStyle.Fill;
|
resources.ApplyResources(btnStartDevice, "btnStartDevice");
|
||||||
this.btnStartDevice.FillPressColor = System.Drawing.Color.LimeGreen;
|
btnStartDevice.FillPressColor = Color.LimeGreen;
|
||||||
this.btnStartDevice.FillSelectedColor = System.Drawing.Color.LimeGreen;
|
btnStartDevice.FillSelectedColor = Color.LimeGreen;
|
||||||
this.btnStartDevice.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
|
btnStartDevice.Name = "btnStartDevice";
|
||||||
this.btnStartDevice.Location = new System.Drawing.Point(3, 25);
|
btnStartDevice.TipsFont = new Font("宋体", 9F, FontStyle.Regular, GraphicsUnit.Point, 134);
|
||||||
this.btnStartDevice.MinimumSize = new System.Drawing.Size(1, 1);
|
btnStartDevice.Click += btnStartDevice_Click;
|
||||||
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);
|
|
||||||
//
|
//
|
||||||
// btnStartCheck
|
// btnStartCheck
|
||||||
//
|
//
|
||||||
this.uiTableLayoutPanel1.SetColumnSpan(this.btnStartCheck, 2);
|
uiTableLayoutPanel1.SetColumnSpan(btnStartCheck, 2);
|
||||||
this.btnStartCheck.Cursor = System.Windows.Forms.Cursors.Hand;
|
btnStartCheck.Cursor = Cursors.Hand;
|
||||||
this.btnStartCheck.Dock = System.Windows.Forms.DockStyle.Fill;
|
resources.ApplyResources(btnStartCheck, "btnStartCheck");
|
||||||
this.btnStartCheck.FillPressColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0)))));
|
btnStartCheck.FillPressColor = Color.FromArgb(0, 192, 0);
|
||||||
this.btnStartCheck.FillSelectedColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(192)))), ((int)(((byte)(0)))));
|
btnStartCheck.FillSelectedColor = Color.FromArgb(0, 192, 0);
|
||||||
this.btnStartCheck.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
|
btnStartCheck.Name = "btnStartCheck";
|
||||||
this.btnStartCheck.Location = new System.Drawing.Point(3, 110);
|
btnStartCheck.TipsFont = new Font("宋体", 9F, FontStyle.Regular, GraphicsUnit.Point, 134);
|
||||||
this.btnStartCheck.MinimumSize = new System.Drawing.Size(1, 1);
|
btnStartCheck.Click += btnStartCheck_Click;
|
||||||
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);
|
|
||||||
//
|
//
|
||||||
// FormControlPanel
|
// FormControlPanel
|
||||||
//
|
//
|
||||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
|
resources.ApplyResources(this, "$this");
|
||||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
AutoScaleMode = AutoScaleMode.Font;
|
||||||
this.ClientSize = new System.Drawing.Size(230, 198);
|
ControlBox = false;
|
||||||
this.ControlBox = false;
|
Controls.Add(uiTableLayoutPanel1);
|
||||||
this.Controls.Add(this.uiTableLayoutPanel1);
|
MaximizeBox = false;
|
||||||
this.MaximizeBox = false;
|
MinimizeBox = false;
|
||||||
this.MinimizeBox = false;
|
Name = "FormControlPanel";
|
||||||
this.Name = "FormControlPanel";
|
uiTableLayoutPanel1.ResumeLayout(false);
|
||||||
this.Text = "启动管理";
|
ResumeLayout(false);
|
||||||
this.uiTableLayoutPanel1.ResumeLayout(false);
|
|
||||||
this.ResumeLayout(false);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ namespace Check.Main.UI
|
|||||||
public FormControlPanel()
|
public FormControlPanel()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
ConfigurationManager.OnConfigurationChanged += HandleConfigurationChanged;
|
ConfigurationManager.OnConfigurationChanged += HandleConfigurationChanged;
|
||||||
UpdateUI();
|
UpdateUI();
|
||||||
|
|
||||||
@@ -50,6 +50,8 @@ namespace Check.Main.UI
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//点击“ 启动设备” 按钮
|
||||||
private void btnStartDevice_Click(object sender, EventArgs e)
|
private void btnStartDevice_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (_isDeviceReady)//_isDeviceRunning
|
if (_isDeviceReady)//_isDeviceRunning
|
||||||
@@ -86,7 +88,7 @@ namespace Check.Main.UI
|
|||||||
ThreadSafeLogger.Log("用户点击“启动设备”,开始新的启动流程...");
|
ThreadSafeLogger.Log("用户点击“启动设备”,开始新的启动流程...");
|
||||||
|
|
||||||
// 1. 从单一数据源获取完整的配置对象
|
// 1. 从单一数据源获取完整的配置对象
|
||||||
var config = ConfigurationManager.GetCurrentConfig();
|
var config = ConfigurationManager.GetCurrentConfig();
|
||||||
// 2. 验证相机配置的有效性
|
// 2. 验证相机配置的有效性
|
||||||
if (config.CameraSettings == null || !config.CameraSettings.Any(c => c.IsEnabled))
|
if (config.CameraSettings == null || !config.CameraSettings.Any(c => c.IsEnabled))
|
||||||
{
|
{
|
||||||
@@ -124,6 +126,7 @@ namespace Check.Main.UI
|
|||||||
UpdateUI();//UpdateDeviceButtonUI();
|
UpdateUI();//UpdateDeviceButtonUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//点击 “开始检测” 按钮
|
||||||
private void btnStartCheck_Click(object sender, EventArgs e)
|
private void btnStartCheck_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (_isDetecting)
|
if (_isDetecting)
|
||||||
@@ -131,7 +134,7 @@ namespace Check.Main.UI
|
|||||||
// --- 停止检测 ---
|
// --- 停止检测 ---
|
||||||
ThreadSafeLogger.Log("用户点击“停止检测”,暂停数据流...");
|
ThreadSafeLogger.Log("用户点击“停止检测”,暂停数据流...");
|
||||||
// 停止硬触发模拟器
|
// 停止硬触发模拟器
|
||||||
CameraManager.StopHardwareTriggerSimulator();
|
//CameraManager.StopHardwareTriggerSimulator();
|
||||||
// 停止相机采集
|
// 停止相机采集
|
||||||
CameraManager.StopAll();
|
CameraManager.StopAll();
|
||||||
|
|
||||||
@@ -150,10 +153,11 @@ namespace Check.Main.UI
|
|||||||
|
|
||||||
// 启动硬触发模拟器(如果需要)
|
// 启动硬触发模拟器(如果需要)
|
||||||
var config = ConfigurationManager.GetCurrentConfig();
|
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;
|
ThreadSafeLogger.Log("相机设置为硬件触发模式,将由 PLC 输出脉冲信号控制拍照。");
|
||||||
CameraManager.StartHardwareTriggerSimulator();
|
//CameraManager.TriggerInterval = 100;
|
||||||
|
// CameraManager.StartHardwareTriggerSimulator();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 开始统计
|
// 开始统计
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<root>
|
<root>
|
||||||
<!--
|
<!--
|
||||||
Microsoft ResX Schema
|
Microsoft ResX Schema
|
||||||
|
|
||||||
Version 2.0
|
Version 2.0
|
||||||
|
|
||||||
The primary goals of this format is to allow a simple XML format
|
The primary goals of this format is to allow a simple XML format
|
||||||
that is mostly human readable. The generation and parsing of the
|
that is mostly human readable. The generation and parsing of the
|
||||||
various data types are done through the TypeConverter classes
|
various data types are done through the TypeConverter classes
|
||||||
associated with the data types.
|
associated with the data types.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
... ado.net/XML headers & schema ...
|
... ado.net/XML headers & schema ...
|
||||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
<resheader name="version">2.0</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>
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
<comment>This is a comment</comment>
|
<comment>This is a comment</comment>
|
||||||
</data>
|
</data>
|
||||||
|
|
||||||
There are any number of "resheader" rows that contain simple
|
There are any number of "resheader" rows that contain simple
|
||||||
name/value pairs.
|
name/value pairs.
|
||||||
|
|
||||||
Each data row contains a name, and value. The row also contains a
|
Each data row contains a name, and value. The row also contains a
|
||||||
type or mimetype. Type corresponds to a .NET class that support
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
text/value conversion through the TypeConverter architecture.
|
text/value conversion through the TypeConverter architecture.
|
||||||
Classes that don't support this are serialized and stored with the
|
Classes that don't support this are serialized and stored with the
|
||||||
mimetype set.
|
mimetype set.
|
||||||
|
|
||||||
The mimetype is used for serialized objects, and tells the
|
The mimetype is used for serialized objects, and tells the
|
||||||
ResXResourceReader how to depersist the object. This is currently not
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
extensible. For a given mimetype the value must be set accordingly:
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
that the ResXResourceWriter will generate, however the reader can
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
read any of the formats listed below.
|
read any of the formats listed below.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.binary.base64
|
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
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.soap.base64
|
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
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
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
|
: using a System.ComponentModel.TypeConverter
|
||||||
: and then encoded with base64 encoding.
|
: and then encoded with base64 encoding.
|
||||||
-->
|
-->
|
||||||
@@ -117,4 +117,137 @@
|
|||||||
<resheader name="writer">
|
<resheader name="writer">
|
||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</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>
|
</root>
|
||||||
Reference in New Issue
Block a user