Compare commits

..

11 Commits

Author SHA1 Message Date
Maikouce China 07c8e6ec91 222 2025-10-20 17:47:48 +08:00
TYF 31d9f8d6b6 取消 2025-10-20 17:44:48 +08:00
玉芬 唐 24a905b517 测试111 2025-10-20 17:42:34 +08:00
vcrmn 42f8bb7642 取消注释 2025-10-20 17:32:50 +08:00
Maikouce China 165963d337 Merge branch 'master' of https://gitea.star-rising.cn/vcrmn/CheckDevice 2025-10-20 17:28:25 +08:00
Maikouce China 715d7166bc 增加注释 2025-10-20 17:28:18 +08:00
vcrmn 679320311d Merge branch 'master' of https://gitea.star-rising.cn/vcrmn/CheckDevice 2025-10-20 16:52:07 +08:00
vcrmn 6eee4eda24 // 2025-10-20 16:52:00 +08:00
xiaohuimin e7341bea87 10-20 2025-10-20 16:44:55 +08:00
vcrmn 5dedb1a632 10/20 // 2025-10-20 16:36:27 +08:00
17860779768 dca4b2afac 10.20PLC+相机2.3视觉修改 2025-10-20 16:07:46 +08:00
8 changed files with 169 additions and 308 deletions
+2 -1
View File
@@ -22,6 +22,7 @@ namespace Check.Main.Camera
/// <summary>
/// 静态全局相机管理器,负责所有相机的生命周期、配置应用和多相机同步
/// </summary>
/// 222222
public static class CameraManager
{
//1、相机与UI管理----管理每台相机对象和对应的图像显示窗口。
@@ -692,7 +693,7 @@ namespace Check.Main.Camera
/// <summary>
/// 【将 SkiaSharp.SKImage 安全地转换为 System.Drawing.Bitmap。
/// </summary>
public static Bitmap ConvertSKImageToBitmap(SKImage skImage)
private static Bitmap ConvertSKImageToBitmap(SKImage skImage)
{
if (skImage == null) return null;
+39 -49
View File
@@ -58,13 +58,13 @@ namespace Check.Main.Camera
/// </summary>
private void ProcessQueue()
{
// 从模型管理器获取此线程专属的YOLO模型
var yoloModel = YoloModelManager.GetModel(_modeId);
if (yoloModel == null)
{
ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型,处理线程已中止。");
return; // 如果没有模型,此线程无法工作
}
//// 从模型管理器获取此线程专属的YOLO模型
//var yoloModel = YoloModelManager.GetModel(_modeId);
//if (yoloModel == null)
//{
// ThreadSafeLogger.Log($"[错误] 相机 #{_modeId} 无法获取对应的YOLO模型,处理线程已中止。");
// return; // 如果没有模型,此线程无法工作
//}
//训练阶段(相机2
var trainer = new LogoTemplateTrainer();
@@ -103,34 +103,32 @@ namespace Check.Main.Camera
ImageData data = _imageQueue.Take();
using (data)
{
string result = "";
using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期
{
if (skImage == null) continue;
var predictions = yoloModel.RunObjectDetection(skImage);
result = predictions.Any() ? "NG" : "OK";
ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},检测到 {predictions.Count} 个目标,结果: {result}");
//using (var skImage = ConvertBitmapToSKImage(data.Image)) // 转换图像格式并管理其生命周期
//{
// if (skImage == null) continue;
// var predictions = yoloModel.RunObjectDetection(skImage);
// string result = predictions.Any() ? "NG" : "OK";
// 将处理结果交给协调器进行组装
DetectionCoordinator.AssembleProduct(data, result);
// ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},检测到 {predictions.Count} 个目标,结果: {result}");
if (OnProcessingCompleted != null)
{
using (var resultSkImage = skImage.Draw(predictions))
{
// 4. 触发事件,将绘制好的 resultSkImage 传递出去
// // 将处理结果交给协调器进行组装
// DetectionCoordinator.AssembleProduct(data, result);
Bitmap bitmap = CameraManager.ConvertSKImageToBitmap(resultSkImage);
// 所有权在这里被转移
OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs(
_cameraIndex,
data.ProductId,
bitmap
));
}
}
}
// if (OnProcessingCompleted != null)
// {
// using (var resultSkImage = skImage.Draw(predictions))
// {
// // 4. 触发事件,将绘制好的 resultSkImage 传递出去
// // 所有权在这里被转移
// OnProcessingCompleted?.Invoke(this, new ProcessingCompletedEventArgs(
// _cameraIndex,
// data.ProductId,
// resultSkImage
// ));
// }
// }
//}
//***********************************使用Halcon模板匹配进行检测****************************************************
if (data.Image == null) continue;
@@ -162,7 +160,7 @@ namespace Check.Main.Camera
//score= ProcessImg.ProcessImagesInFolder(filepath,cam);
//string result = (score > 0.5) ? "OK" : "NG";
string result = (score > 0.5) ? "OK" : "NG";
ThreadSafeLogger.Log($"相机 #{_cameraIndex} 处理产品 #{data.ProductId},结果: {result},得分: {score}");
@@ -171,20 +169,12 @@ namespace Check.Main.Camera
//给PLC的M90、M91写值(10.10
if (FrmMain.PlcClient != null && FrmMain.PlcClient.IsConnected)
if (FrmMain.PlcClient != null)
{
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();
FrmMain.PlcClient.WriteBool("90", true); // 写入M90为1
// 延时复位
Task.Run(async () =>
{
@@ -195,12 +185,7 @@ namespace Check.Main.Camera
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);
FrmMain.PlcClient.WriteBool("91", true);// 写入M91为1
// 延时复位
Task.Run(async () =>
{
@@ -208,10 +193,15 @@ namespace Check.Main.Camera
//await FrmMain.PlcClient.WriteAsync("M91", 0);
});
}
//完成一次检测进行刷新
Thread.Sleep(2000);
FrmMain.PlcClient.WriteBool("90", false); //
FrmMain.PlcClient.WriteBool("91", false); // 写入M90为1
}
else
{
ThreadSafeLogger.Log("[PLC] 未连接,跳过写入。");
ThreadSafeLogger.Log("6,跳过写入。");
}
+1 -1
View File
@@ -39,7 +39,7 @@
<HintPath>..\..\..\HslCommunication-master\HslCommunication.dll</HintPath>
</Reference>
<Reference Include="MvCameraControl.Net">
<HintPath>..\..\..\..\XKRS2025\XKRS2025\CheckDevice\Check.Main\bin\Debug\MvCameraControl.Net.dll</HintPath>
<HintPath>..\..\XKRS2025\CheckDevice\Check.Main\bin\Debug\MvCameraControl.Net.dll</HintPath>
</Reference>
</ItemGroup>
-238
View File
@@ -1,238 +0,0 @@
using NPOI.SS.Formula.Functions;
using Sunny.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Check.Main.Common
{
/// <summary>
/// 汇川 Easy 系列 PLC 客户端 (Modbus TCP)
/// 支持 D/M/X/Y 地址读写,整数、浮点数、字符串
/// </summary>
public class EasyPlcClient : IDisposable
{
private readonly TcpClient _tcpClient = new();
private NetworkStream _stream;
private ushort _transactionId = 0;
private readonly SemaphoreSlim _lock = new(1, 1);
private readonly string _ip;
private readonly int _port;
private readonly byte _slaveId;
public EasyPlcClient(string ip, int port = 502, byte slaveId = 1)
{
_ip = ip;
_port = port;
_slaveId = slaveId;
}
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;//10.11添加
public async Task ConnectAsync()
{
await _tcpClient.ConnectAsync(_ip, _port);
_stream = _tcpClient.GetStream();
}
#region ---- Modbus ----
private ushort GetTransactionId() =>
unchecked((ushort)Interlocked.Increment(ref Unsafe.As<ushort, int>(ref _transactionId)));
private async Task<byte[]> SendAndReceiveAsync(byte[] pdu, ushort startAddr, ushort length = 1)
{
ushort tid = GetTransactionId();
ushort lengthField = (ushort)(pdu.Length + 1);
byte[] mbap = {
(byte)(tid >> 8), (byte)tid,
0x00, 0x00, // Protocol = 0
(byte)(lengthField >> 8), (byte)lengthField,
_slaveId
};
byte[] adu = new byte[mbap.Length + pdu.Length];
Buffer.BlockCopy(mbap, 0, adu, 0, mbap.Length);
Buffer.BlockCopy(pdu, 0, adu, mbap.Length, pdu.Length);
await _lock.WaitAsync();
try
{
await _stream.WriteAsync(adu, 0, adu.Length);
byte[] header = new byte[7];
await _stream.ReadAsync(header, 0, 7);
int respLength = (header[4] << 8) + header[5];
byte[] resp = new byte[respLength - 1];
await _stream.ReadAsync(resp, 0, resp.Length);
return resp;
}
finally { _lock.Release(); }
}
#endregion
#region ---- ----
private void ParseAddress(string address, out string area, out ushort offset)
{
var letters = Regex.Match(address, @"^[A-Za-z]+").Value.ToUpper();
var digits = Regex.Match(address, @"\d+").Value;
if (string.IsNullOrEmpty(letters) || string.IsNullOrEmpty(digits))
throw new ArgumentException($"地址 {address} 无效");
area = letters;
offset = (ushort)(ushort.Parse(digits) - 1);
// 👉 注意:这里的偏移需根据你的 PLC Modbus 地址表调整
// 例如 D 区可能是 40001 起,M 区可能是 00001 起
// 目前假设 D -> Holding Register, M/Y -> Coil, X -> Discrete Input
}
#endregion
#region ---- ----
public async Task WriteAsync(string address, int value)
{
ParseAddress(address, out string area, out ushort offset);
switch (area)
{
case "D": // 写寄存器
byte[] pdu = {
0x06,
(byte)(offset >> 8), (byte)offset,
(byte)(value >> 8), (byte)value
};
await SendAndReceiveAsync(pdu, offset);
break;
case "M":
case "Y": // 写单线圈
byte[] coil = {
0x05,
(byte)(offset >> 8), (byte)offset,
(byte)(value != 0 ? 0xFF : 0x00), 0x00
};
await SendAndReceiveAsync(coil, offset);
break;
default:
throw new NotSupportedException($"写 {area} 区未支持");
}
}
public async Task<int> ReadAsync(string address)
{
ParseAddress(address, out string area, out ushort offset);
switch (area)
{
case "D": // Holding Register
byte[] pdu = { 0x03, (byte)(offset >> 8), (byte)offset, 0x00, 0x01 };
var resp = await SendAndReceiveAsync(pdu, offset);
return (resp[1] << 8) + resp[2];
case "M":
case "Y": // Coils
byte[] pdu1 = { 0x01, (byte)(offset >> 8), (byte)offset, 0x00, 0x01 };
var resp1 = await SendAndReceiveAsync(pdu1, offset);
return (resp1[2] & 0x01) != 0 ? 1 : 0;
case "X": // Inputs
byte[] pdu2 = { 0x02, (byte)(offset >> 8), (byte)offset, 0x00, 0x01 };
var resp2 = await SendAndReceiveAsync(pdu2, offset);
return (resp2[2] & 0x01) != 0 ? 1 : 0;
default:
throw new NotSupportedException($"读 {area} 区未支持");
}
}
#endregion
#region ---- (2) ----
public async Task WriteFloatAsync(string address, float value)
{
ParseAddress(address, out string area, out ushort offset);
if (area != "D") throw new NotSupportedException("浮点仅支持 D 区");
byte[] bytes = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian) Array.Reverse(bytes);
byte[] pdu = {
0x10,
(byte)(offset >> 8), (byte)offset,
0x00, 0x02,
0x04,
bytes[0], bytes[1], bytes[2], bytes[3]
};
await SendAndReceiveAsync(pdu, offset, 2);
}
public async Task<float> ReadFloatAsync(string address)
{
ParseAddress(address, out string area, out ushort offset);
if (area != "D") throw new NotSupportedException("浮点仅支持 D 区");
byte[] pdu = { 0x03, (byte)(offset >> 8), (byte)offset, 0x00, 0x02 };
var resp = await SendAndReceiveAsync(pdu, offset, 2);
byte[] data = { resp[1], resp[2], resp[3], resp[4] };
if (BitConverter.IsLittleEndian) Array.Reverse(data);
return BitConverter.ToSingle(data, 0);
}
#endregion
#region ---- ----
public async Task WriteStringAsync(string address, string value, int maxLength)
{
ParseAddress(address, out string area, out ushort offset);
if (area != "D") throw new NotSupportedException("字符串仅支持 D 区");
byte[] strBytes = Encoding.ASCII.GetBytes(value);
if (strBytes.Length > maxLength) Array.Resize(ref strBytes, maxLength);
// 每寄存器2字节
int regCount = (strBytes.Length + 1) / 2;
byte[] data = new byte[regCount * 2];
Array.Copy(strBytes, data, strBytes.Length);
byte[] pdu = new byte[7 + data.Length];
pdu[0] = 0x10;
pdu[1] = (byte)(offset >> 8); pdu[2] = (byte)offset;
pdu[3] = (byte)(regCount >> 8); pdu[4] = (byte)regCount;
pdu[5] = (byte)(data.Length);
Buffer.BlockCopy(data, 0, pdu, 6, data.Length);
await SendAndReceiveAsync(pdu, offset, (ushort)regCount);
}
public async Task<string> ReadStringAsync(string address, int length)
{
ParseAddress(address, out string area, out ushort offset);
if (area != "D") throw new NotSupportedException("字符串仅支持 D 区");
int regCount = (length + 1) / 2;
byte[] pdu = { 0x03, (byte)(offset >> 8), (byte)offset, (byte)(regCount >> 8), (byte)regCount };
var resp = await SendAndReceiveAsync(pdu, offset, (ushort)regCount);
byte[] data = new byte[resp[0]];
Array.Copy(resp, 1, data, 0, data.Length);
return Encoding.ASCII.GetString(data).TrimEnd('\0');
}
#endregion
public void Dispose()
{
_stream?.Dispose();
_tcpClient?.Close();
_lock?.Dispose();
}
}
}
+108
View File
@@ -0,0 +1,108 @@
using HslCommunication;
using HslCommunication.ModBus;
using System;
using System.Net.Sockets;
// ModbusTcp读写服务类,线程安全,互斥锁
namespace Check.Main.Common
{
public class ModbusTcpService
{
private readonly TcpClient _tcpClient = new();
private readonly ModbusTcpNet _plc;
private readonly object _lock = new();
public bool IsConnected => _tcpClient != null && _tcpClient.Connected;//
//
public ModbusTcpService(string ip, int port = 502, byte station = 1)
{
_plc = new ModbusTcpNet(ip, port, station)
{
// CDAB 格式:PLC的不同品牌的modelbus TCP存在正反高低位。如果读写异常。删掉或者补充下面这句函数进行修复
DataFormat = HslCommunication.Core.DataFormat.CDAB
};
}
// 读取 32 位整数
public OperateResult<int> ReadInt32(string address)
{
lock (_lock)
{
return _plc.ReadInt32(address);
}
}
// 写入 32 位整数(int
public OperateResult WriteInt32(string address, int value)
{
lock (_lock)
{
return _plc.Write(address, value);
}
}
// 读取 16 位整数(short
public OperateResult<short> ReadInt16(string address)
{
lock (_lock)
{
return _plc.ReadInt16(address);
}
}
// 写入 16 位整数(short
public OperateResult WriteInt16(string address, short value)
{
lock (_lock)
{
return _plc.Write(address, value);
}
}
// 读取布尔量
public OperateResult<bool> ReadBool(string address)
{
lock (_lock)
{
return _plc.ReadBool(address);
}
}
// 写入布尔量
public OperateResult WriteBool(string address, bool value)
{
lock (_lock)
{
return _plc.Write(address, value);
}
}
// 读取浮点数(float
public OperateResult<float> ReadFloat(string address)
{
lock (_lock)
{
return _plc.ReadFloat(address);
}
}
// 写入浮点数(float
public OperateResult WriteFloat(string address, float value)
{
lock (_lock)
{
return _plc.Write(address, value);
}
}
// 关闭连接
public void Close()
{
lock (_lock)
{
_plc.ConnectClose();
}
}
}
}
+5 -5
View File
@@ -19,7 +19,7 @@ namespace Check.Main
public partial class FrmMain : Form
{
// private FrmCamConfig _formCameraConfig;
public static EasyPlcClient PlcClient;//定义全局PLC对象 --- 10.10添加①
public static ModbusTcpService PlcClient;//定义全局PLC对象 --- 10.10添加①
private FrmConfig _frmConfig;
private FrmLog _formLog;
@@ -47,8 +47,8 @@ namespace Check.Main
{
try
{
PlcClient = new EasyPlcClient("192.168.1.88", 502);
await PlcClient.ConnectAsync();
PlcClient = new ModbusTcpService("192.168.1.88", 502);
// await PlcClient.ConnectAsync();
ThreadSafeLogger.Log("[PLC] 已成功连接到汇川PLC");
}
catch (Exception ex)
@@ -62,8 +62,8 @@ namespace Check.Main
// 初始化PLC连接-----10.10添加
await InitPlcConnection();
EasyPlcClient easyPlcClient = new EasyPlcClient("127.0.0.1", 502, 1);
easyPlcClient.ConnectAsync();
ModbusTcpService easyPlcClient = new ModbusTcpService("192.168.1.88", 502, 1);
//easyPlcClient.ConnectAsync();
_frmConfig = new FrmConfig { Text = "主程序配置" };
_formLog = new FrmLog { Text = "运行日志" };
_formStatistics = new FormStatistics { Text = "生产统计" };
+12 -12
View File
@@ -18,16 +18,16 @@ namespace Check.Main.Infer
[Description("GPU")]
GPU,
[Description("VPU")]
VPU,
//[Description("VPU")]
//VPU,
}
public enum ModelVersion
public enum AlgorithmType
{
[Description("v8")]
V8 = 0,
[Description("传统算法")]
Tradition = 0,
[Description("v11")]
V11,
[Description("深度学习")]
DeepLearning,
}
public enum CheckModelType
{
@@ -56,7 +56,7 @@ namespace Check.Main.Infer
private string _name = "New Model";
private string _path = "";
private DetectDevice _checkDevice=DetectDevice.CPU;
private ModelVersion _mVersion=ModelVersion.V8;
private AlgorithmType _mAType = AlgorithmType.Tradition;
private CheckModelType _mType = CheckModelType.Classification;
private bool _isEnabled = true;
@@ -81,12 +81,12 @@ namespace Check.Main.Infer
set { if (_checkDevice != value) { _checkDevice = value; OnPropertyChanged(); } }
}
[Category("基本信息"), DisplayName("模型版本"), Description("推理模型的版本。")]
[Category("基本信息"), DisplayName("算法类型"), Description("所使用的算法的类型。")]
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public ModelVersion MVersion
public AlgorithmType M_AType
{
get => _mVersion;
set { if (_mVersion != value) { _mVersion = value; OnPropertyChanged(); } }
get => _mAType;
set { if (_mAType != value) { _mAType = value; OnPropertyChanged(); } }
}
[Category("基本信息"), DisplayName("模型类型"), Description("推理模型的类型。")]
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
+1 -1
View File
@@ -9,7 +9,7 @@ using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Threading;
//cyf
public class ProcessImg
{
// 对单个图像进行模板匹配