1. 工业视觉检测系统概述
在智能制造和工业4.0的背景下,机器视觉与人工智能的结合正在重塑传统工业检测领域。这套基于C#和YOLOv8的工业视觉检测系统,为工程师提供了一个完整的端到端解决方案,从图像采集到智能分析再到设备控制,覆盖了工业现场最常见的应用场景。
系统采用分层架构设计,充分发挥了各技术栈的优势:C# WinForms提供稳定可靠的上位机界面,海康工业相机SDK确保高质量的图像采集,Python+YOLOv8实现高性能的目标检测,Modbus TCP协议则负责与工业控制设备通信。这种架构既保证了系统的实时性和可靠性,又具备了深度学习带来的智能分析能力。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 整体架构解析
系统采用三层架构设计,各层之间通过清晰的接口进行通信:
-
数据采集层:由海康工业相机和配套SDK组成,负责实时采集高质量的工业图像。相机通过GigE或USB3.0接口与工控机连接,支持触发模式和连续采集模式。
-
智能分析层:核心是YOLOv8目标检测模型,运行在Python环境中。这一层接收来自C#的图像数据,返回带有检测结果的JSON格式数据。
-
应用控制层:基于C# WinForms的上位机程序,负责协调整个系统运行。包括图像显示、结果可视化、PLC通信等功能。
2.2 技术选型考量
选择C#作为主开发语言主要基于以下考虑:
- 工业领域广泛使用的上位机开发语言
- 强大的Windows窗体开发能力
- 与工业相机SDK的良好兼容性
- 稳定的多线程和进程间通信支持
Python+YOLOv8的组合则是当前工业视觉检测的最佳实践:
- YOLOv8在精度和速度上达到良好平衡
- Ultralytics库提供了简单易用的接口
- Python生态有丰富的图像处理工具链
3. 开发环境搭建
3.1 硬件配置建议
| 组件 | 推荐配置 | 说明 |
|---|---|---|
| 工业相机 | 海康MV-CA系列 | 支持GigE Vision或USB3.0协议 |
| 工控机 | i7处理器+16GB内存 | 需配备独立显卡(NVIDIA GTX 1660以上) |
| PLC | 西门子S7-1200 | 支持Modbus TCP协议 |
3.2 软件环境配置
- Visual Studio 2022:安装时需勾选.NET桌面开发工作负载
- 海康MVS开发包:从官网下载最新版SDK和驱动程序
- Python 3.9+:建议使用Anaconda管理环境
- CUDA Toolkit:版本需与显卡驱动匹配(如11.7)
- cuDNN:深度学习加速库,版本需与CUDA对应
安装关键Python包:
bash复制pip install ultralytics opencv-python numpy torch==1.13.1+cu117 --extra-index-url https://download.pytorch.org/whl/cu117
4. 工业相机图像采集实现
4.1 海康SDK集成
海康机器视觉SDK(MVS)提供了完整的相机控制接口,我们需要重点关注以下几个功能模块:
- 设备枚举:发现网络或USB连接的相机设备
- 参数配置:设置分辨率、曝光时间、增益等关键参数
- 图像采集:支持回调模式和主动抓取模式
- 图像转换:将原始数据转换为C#可处理的Bitmap格式
4.2 核心代码实现
csharp复制public class HikCamera : IDisposable
{
private MyCamera _camera;
private bool _isGrabbing;
// 相机状态事件
public event Action<string> StatusChanged;
public event Action<Bitmap> ImageCaptured;
public bool Open(string serialNumber = null)
{
try {
// 枚举设备
MyCamera.MV_CC_DEVICE_INFO_LIST deviceList = new MyCamera.MV_CC_DEVICE_INFO_LIST();
int ret = MyCamera.MV_CC_EnumDevices_NET(MyCamera.MV_GIGE_DEVICE | MyCamera.MV_USB_DEVICE, ref deviceList);
if (ret != 0 || deviceList.nDeviceNum == 0)
throw new Exception("未找到可用相机设备");
// 选择设备
MyCamera.MV_CC_DEVICE_INFO deviceInfo = deviceList.pDeviceInfo[0];
if (!string.IsNullOrEmpty(serialNumber))
{
for (int i = 0; i < deviceList.nDeviceNum; i++)
{
if (GetSerialNumber(deviceList.pDeviceInfo[i]) == serialNumber)
{
deviceInfo = deviceList.pDeviceInfo[i];
break;
}
}
}
// 创建设备实例
_camera = new MyCamera();
ret = _camera.MV_CC_CreateDevice_NET(ref deviceInfo);
if (ret != 0) throw new Exception($"创建设备失败: {ret}");
// 打开设备
ret = _camera.MV_CC_OpenDevice_NET();
if (ret != 0) throw new Exception($"打开设备失败: {ret}");
// 设置采集参数
_camera.MV_CC_SetEnumValue_NET("AcquisitionMode", (uint)MyCamera.MV_CAM_ACQUISITION_MODE.MV_ACQ_MODE_CONTINUOUS);
_camera.MV_CC_SetEnumValue_NET("PixelFormat", (uint)MyCamera.MV_PixelFormatEnums.PixelType_Gvsp_BGR8_Packed);
// 注册回调函数
ret = _camera.MV_CC_RegisterImageCallBack_NET(ImageCallback, IntPtr.Zero);
if (ret != 0) throw new Exception($"注册回调失败: {ret}");
// 开始采集
ret = _camera.MV_CC_StartGrabbing_NET();
if (ret != 0) throw new Exception($"开始采集失败: {ret}");
_isGrabbing = true;
StatusChanged?.Invoke("相机已连接");
return true;
}
catch (Exception ex) {
StatusChanged?.Invoke($"相机连接失败: {ex.Message}");
return false;
}
}
private void ImageCallback(IntPtr pData, ref MyCamera.MV_FRAME_OUT_INFO_EX pFrameInfo, IntPtr pUser)
{
if (!_isGrabbing || pFrameInfo.nFrameLen == 0) return;
try {
// 转换图像格式
Bitmap bitmap = new Bitmap((int)pFrameInfo.nWidth, (int)pFrameInfo.nHeight,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.WriteOnly, bitmap.PixelFormat);
IntPtr ptrBmp = bmpData.Scan0;
int stride = bmpData.Stride;
int bufferSize = stride * bitmap.Height;
// 拷贝图像数据
if (pFrameInfo.enPixelType == MyCamera.MV_PixelFormatEnums.PixelType_Gvsp_BGR8_Packed)
{
for (int y = 0; y < pFrameInfo.nHeight; y++)
{
IntPtr srcLine = IntPtr.Add(pData, y * (int)pFrameInfo.nWidth * 3);
IntPtr dstLine = IntPtr.Add(ptrBmp, y * stride);
CopyMemory(dstLine, srcLine, (int)pFrameInfo.nWidth * 3);
}
}
bitmap.UnlockBits(bmpData);
ImageCaptured?.Invoke(bitmap);
}
catch (Exception ex) {
StatusChanged?.Invoke($"图像处理错误: {ex.Message}");
}
}
[DllImport("kernel32.dll")]
private static extern void CopyMemory(IntPtr dest, IntPtr src, int length);
private string GetSerialNumber(MyCamera.MV_CC_DEVICE_INFO deviceInfo)
{
if (deviceInfo.nTLayerType == MyCamera.MV_GIGE_DEVICE)
return deviceInfo.SpecialInfo.stGigEInfo.chSerialNumber;
else if (deviceInfo.nTLayerType == MyCamera.MV_USB_DEVICE)
return deviceInfo.SpecialInfo.stUsb3VInfo.chSerialNumber;
return null;
}
public void Dispose()
{
if (_camera != null)
{
_isGrabbing = false;
_camera.MV_CC_StopGrabbing_NET();
_camera.MV_CC_CloseDevice_NET();
_camera.MV_CC_DestroyDevice_NET();
_camera = null;
}
}
}
4.3 相机参数优化技巧
- 曝光时间:根据物体运动速度调整,高速运动需要短曝光(100μs-1ms),静态场景可适当延长(5-10ms)
- 增益控制:优先使用硬件增益,软件增益会引入噪声
- 白平衡:对于彩色相机,建议使用自动白平衡或手动设置
- 触发模式:硬件触发可确保图像采集与设备运动同步
- ROI设置:只采集感兴趣区域可提高帧率
5. YOLOv8目标检测实现
5.1 Python推理服务设计
YOLOv8推理服务需要实现以下核心功能:
- 图像预处理:尺寸调整、归一化
- 模型加载:支持本地模型和自动下载
- 推理执行:GPU加速
- 结果后处理:非极大值抑制(NMS)、置信度过滤
- 结果格式化:转换为标准JSON输出
python复制import sys
import json
import base64
import cv2
import numpy as np
from ultralytics import YOLO
from typing import List, Dict, Any
class YOLOv8Detector:
def __init__(self, model_path: str = 'yolov8n.pt', device: str = 'cuda:0'):
"""
初始化YOLOv8检测器
:param model_path: 模型文件路径
:param device: 推理设备(cpu/cuda:0)
"""
self.model = YOLO(model_path)
self.device = device
self.class_names = self.model.names
def preprocess_image(self, image_base64: str) -> np.ndarray:
"""
将base64编码的图像转换为numpy数组
:param image_base64: base64编码的图像数据
:return: BGR格式的numpy数组
"""
img_bytes = base64.b64decode(image_base64)
nparr = np.frombuffer(img_bytes, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
return img
def detect(self, image: np.ndarray, conf_threshold: float = 0.5,
iou_threshold: float = 0.45) -> List[Dict[str, Any]]:
"""
执行目标检测
:param image: 输入图像(BGR格式)
:param conf_threshold: 置信度阈值
:param iou_threshold: IOU阈值(NMS用)
:return: 检测结果列表
"""
# 执行推理
results = self.model(image, conf=conf_threshold, iou=iou_threshold, device=self.device)
detections = []
for result in results:
boxes = result.boxes
if boxes is None:
continue
for box in boxes:
# 获取边界框坐标(左上右下)
x1, y1, x2, y2 = box.xyxy[0].tolist()
# 获取类别和置信度
cls = int(box.cls[0].item())
conf = box.conf[0].item()
detections.append({
"class_id": cls,
"class_name": self.class_names[cls],
"confidence": conf,
"bbox": [x1, y1, x2, y2],
"center_x": (x1 + x2) / 2,
"center_y": (y1 + y2) / 2,
"width": x2 - x1,
"height": y2 - y1
})
return detections
def process_request(self, image_base64: str) -> str:
"""
处理单个检测请求
:param image_base64: base64编码的图像数据
:return: JSON格式的检测结果
"""
try:
img = self.preprocess_image(image_base64)
detections = self.detect(img)
return json.dumps({
"status": "success",
"detections": detections,
"image_width": img.shape[1],
"image_height": img.shape[0]
})
except Exception as e:
return json.dumps({
"status": "error",
"message": str(e)
})
if __name__ == "__main__":
# 初始化检测器(首次运行会自动下载模型)
detector = YOLOv8Detector(model_path='yolov8n.pt')
# 从标准输入读取图像数据
input_data = sys.stdin.read().strip()
if not input_data:
sys.stderr.write("No image data provided\n")
sys.exit(1)
# 处理并输出结果
result = detector.process_request(input_data)
sys.stdout.write(result)
5.2 模型优化策略
-
模型选择:
- YOLOv8n:轻量级,适合嵌入式设备
- YOLOv8s:平衡型,推荐大多数工业场景
- YOLOv8m/l/x:高精度,适合复杂场景
-
量化加速:
- FP16半精度:减少显存占用,提升推理速度
- INT8量化:最大程度优化速度,精度损失可控
-
自定义训练:
- 使用工业场景特定数据集微调
- 调整anchor boxes匹配目标尺寸
- 优化损失函数权重
6. C#与Python进程通信
6.1 进程调用封装
csharp复制public class PythonProcessHelper : IDisposable
{
private Process _process;
private readonly string _pythonExe;
private readonly string _scriptPath;
private readonly StringBuilder _outputBuilder = new StringBuilder();
private readonly StringBuilder _errorBuilder = new StringBuilder();
private readonly AutoResetEvent _outputWaitHandle = new AutoResetEvent(false);
private readonly AutoResetEvent _errorWaitHandle = new AutoResetEvent(false);
public PythonProcessHelper(string pythonExe, string scriptPath)
{
_pythonExe = pythonExe;
_scriptPath = scriptPath;
}
public async Task<string> RunScriptAsync(string inputData, int timeout = 30000)
{
try {
_process = new Process();
_process.StartInfo.FileName = _pythonExe;
_process.StartInfo.Arguments = $"\"{_scriptPath}\"";
_process.StartInfo.UseShellExecute = false;
_process.StartInfo.RedirectStandardInput = true;
_process.StartInfo.RedirectStandardOutput = true;
_process.StartInfo.RedirectStandardError = true;
_process.StartInfo.CreateNoWindow = true;
_outputBuilder.Clear();
_errorBuilder.Clear();
_process.OutputDataReceived += (sender, e) => {
if (e.Data != null) _outputBuilder.AppendLine(e.Data);
else _outputWaitHandle.Set();
};
_process.ErrorDataReceived += (sender, e) => {
if (e.Data != null) _errorBuilder.AppendLine(e.Data);
else _errorWaitHandle.Set();
};
_process.Start();
_process.BeginOutputReadLine();
_process.BeginErrorReadLine();
// 写入输入数据
await _process.StandardInput.WriteLineAsync(inputData);
_process.StandardInput.Close();
// 等待进程结束
bool exited = _process.WaitForExit(timeout);
if (!exited) {
_process.Kill();
throw new TimeoutException("Python脚本执行超时");
}
_outputWaitHandle.WaitOne(timeout);
_errorWaitHandle.WaitOne(timeout);
// 检查错误输出
string errorOutput = _errorBuilder.ToString().Trim();
if (!string.IsNullOrEmpty(errorOutput)) {
throw new Exception($"Python脚本错误: {errorOutput}");
}
return _outputBuilder.ToString().Trim();
}
finally {
_process?.Dispose();
_process = null;
}
}
public void Dispose()
{
_process?.Dispose();
}
}
6.2 性能优化技巧
- 进程复用:改为HTTP服务或gRPC服务,避免频繁创建进程的开销
- 批量处理:一次传递多帧图像,减少通信次数
- 图像压缩:适当降低图像质量减少数据传输量
- 异步调用:确保UI线程不被阻塞
- 超时处理:设置合理的超时时间,避免卡死
7. 上位机界面设计与实现
7.1 WinForms界面布局
csharp复制public partial class MainForm : Form
{
private HikCamera _camera;
private PythonProcessHelper _pythonHelper;
private Bitmap _currentFrame;
private readonly object _frameLock = new object();
private ModbusController _modbusController;
private System.Windows.Forms.Timer _inferenceTimer;
private FpsCounter _fpsCounter = new FpsCounter();
// UI控件
private PictureBox cameraView;
private ComboBox cameraList;
private Button connectButton;
private Button startButton;
private Button stopButton;
private NumericUpDown fpsControl;
private CheckBox enableDetection;
private CheckBox enableModbus;
private TextBox modbusIp;
private NumericUpDown modbusPort;
private NumericUpDown modbusAddress;
private ListBox logList;
private PropertyGrid cameraParams;
public MainForm()
{
InitializeComponent();
// 初始化组件
InitializeUI();
// 初始化相机
_camera = new HikCamera();
_camera.StatusChanged += OnCameraStatusChanged;
_camera.ImageCaptured += OnImageCaptured;
// 初始化Python帮助类
_pythonHelper = new PythonProcessHelper("python", @"yolo_detect.py");
// 初始化Modbus控制器
_modbusController = new ModbusController();
// 初始化推理定时器
_inferenceTimer = new System.Windows.Forms.Timer();
_inferenceTimer.Interval = 100; // 10fps
_inferenceTimer.Tick += async (s, e) => await RunInference();
}
private void InitializeUI()
{
// 主窗体设置
this.Text = "工业视觉检测系统";
this.WindowState = FormWindowState.Maximized;
// 主布局使用TableLayoutPanel
var mainLayout = new TableLayoutPanel();
mainLayout.Dock = DockStyle.Fill;
mainLayout.ColumnCount = 3;
mainLayout.RowCount = 2;
mainLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20));
mainLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 60));
mainLayout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 20));
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 80));
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 20));
this.Controls.Add(mainLayout);
// 左侧控制面板
var leftPanel = new Panel();
leftPanel.Dock = DockStyle.Fill;
mainLayout.Controls.Add(leftPanel, 0, 0);
// 相机视图
cameraView = new PictureBox();
cameraView.Dock = DockStyle.Fill;
cameraView.BackColor = Color.Black;
cameraView.SizeMode = PictureBoxSizeMode.Zoom;
mainLayout.Controls.Add(cameraView, 1, 0);
// 右侧信息面板
var rightPanel = new Panel();
rightPanel.Dock = DockStyle.Fill;
mainLayout.Controls.Add(rightPanel, 2, 0);
// 底部日志面板
var logPanel = new Panel();
logPanel.Dock = DockStyle.Fill;
mainLayout.Controls.Add(logPanel, 0, 1);
mainLayout.SetColumnSpan(logPanel, 3);
// 构建左侧控制面板内容
BuildLeftControlPanel(leftPanel);
// 构建右侧信息面板内容
BuildRightInfoPanel(rightPanel);
// 构建日志面板
BuildLogPanel(logPanel);
}
private void BuildLeftControlPanel(Panel panel)
{
var layout = new TableLayoutPanel();
layout.Dock = DockStyle.Fill;
layout.RowCount = 8;
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
panel.Controls.Add(layout);
// 相机选择
var cameraLabel = new Label { Text = "选择相机:", Dock = DockStyle.Fill };
layout.Controls.Add(cameraLabel, 0, 0);
cameraList = new ComboBox { Dock = DockStyle.Fill };
layout.Controls.Add(cameraList, 0, 1);
// 连接按钮
connectButton = new Button { Text = "连接相机", Dock = DockStyle.Fill };
connectButton.Click += OnConnectCamera;
layout.Controls.Add(connectButton, 0, 2);
// 开始/停止按钮
startButton = new Button { Text = "开始采集", Dock = DockStyle.Fill, Enabled = false };
startButton.Click += OnStartCapture;
layout.Controls.Add(startButton, 0, 3);
stopButton = new Button { Text = "停止采集", Dock = DockStyle.Fill, Enabled = false };
stopButton.Click += OnStopCapture;
layout.Controls.Add(stopButton, 0, 4);
// 帧率控制
var fpsLabel = new Label { Text = "检测帧率:", Dock = DockStyle.Fill };
layout.Controls.Add(fpsLabel, 0, 5);
fpsControl = new NumericUpDown { Minimum = 1, Maximum = 30, Value = 10, Dock = DockStyle.Fill };
fpsControl.ValueChanged += (s, e) => _inferenceTimer.Interval = (int)(1000 / fpsControl.Value);
layout.Controls.Add(fpsControl, 0, 6);
// 相机参数控制
cameraParams = new PropertyGrid { Dock = DockStyle.Fill };
layout.Controls.Add(cameraParams, 0, 7);
}
private void OnImageCaptured(Bitmap bitmap)
{
lock (_frameLock)
{
_currentFrame?.Dispose();
_currentFrame = new Bitmap(bitmap);
}
// 更新UI需要在UI线程执行
if (cameraView.InvokeRequired)
{
cameraView.Invoke(new Action(() => {
cameraView.Image?.Dispose();
cameraView.Image = new Bitmap(bitmap);
}));
}
else
{
cameraView.Image?.Dispose();
cameraView.Image = new Bitmap(bitmap);
}
_fpsCounter.Update();
}
private async Task RunInference()
{
if (!enableDetection.Checked || _currentFrame == null) return;
Bitmap frameCopy;
lock (_frameLock)
{
if (_currentFrame == null) return;
frameCopy = new Bitmap(_currentFrame);
}
try
{
string base64 = ImageToBase64(frameCopy);
string jsonResult = await _pythonHelper.RunScriptAsync(base64);
var result = JsonConvert.DeserializeObject<DetectionResult>(jsonResult);
if (result?.Detections != null)
{
DrawDetections(result.Detections);
// Modbus控制逻辑
if (enableModbus.Checked && _modbusController.IsConnected)
{
bool hasDefect = result.Detections.Any(d => d.ClassId == 0 && d.Confidence > 0.8);
_modbusController.WriteCoil((ushort)modbusAddress.Value, hasDefect);
}
}
}
catch (Exception ex)
{
AddLog($"推理错误: {ex.Message}");
}
finally
{
frameCopy.Dispose();
}
}
private void DrawDetections(List<Detection> detections)
{
if (cameraView.Image == null) return;
Bitmap bmp = new Bitmap(cameraView.Image);
using (Graphics g = Graphics.FromImage(bmp))
using (var font = new Font("Arial", 12))
{
foreach (var det in detections)
{
// 绘制边界框
Color boxColor = GetClassColor(det.ClassId);
using (var pen = new Pen(boxColor, 2))
{
g.DrawRectangle(pen, det.Bbox[0], det.Bbox[1],
det.Bbox[2] - det.Bbox[0], det.Bbox[3] - det.Bbox[1]);
}
// 绘制标签
string label = $"{det.ClassName} {det.Confidence:P0}";
SizeF textSize = g.MeasureString(label, font);
using (var brush = new SolidBrush(boxColor))
{
g.FillRectangle(Brushes.Black,
det.Bbox[0], det.Bbox[1] - textSize.Height - 2,
textSize.Width, textSize.Height);
g.DrawString(label, font, brush,
det.Bbox[0], det.Bbox[1] - textSize.Height - 2);
}
}
}
// 更新显示
cameraView.Image?.Dispose();
cameraView.Image = bmp;
}
private Color GetClassColor(int classId)
{
// 为不同类别分配不同颜色
return classId switch
{
0 => Color.Red, // 缺陷
1 => Color.Green, // 合格品
2 => Color.Blue, // 异物
_ => Color.Yellow // 其他
};
}
private string ImageToBase64(Image image)
{
using (var ms = new MemoryStream())
{
// 使用JPEG格式压缩减少数据量
var encoder = ImageCodecInfo.GetImageEncoders()
.FirstOrDefault(c => c.FormatID == ImageFormat.Jpeg.Guid);
var encoderParams = new EncoderParameters(1);
encoderParams.Param[0] = new EncoderParameter(Encoder.Quality, 85L);
image.Save(ms, encoder, encoderParams);
return Convert.ToBase64String(ms.ToArray());
}
}
private void AddLog(string message)
{
if (logList.InvokeRequired)
{
logList.Invoke(new Action(() => {
logList.Items.Add($"{DateTime.Now:HH:mm:ss} - {message}");
logList.TopIndex = logList.Items.Count - 1;
}));
}
else
{
logList.Items.Add($"{DateTime.Now:HH:mm:ss} - {message}");
logList.TopIndex = logList.Items.Count - 1;
}
}
private class FpsCounter
{
private DateTime _lastTime = DateTime.Now;
private int _frameCount;
private double _currentFps;
public double CurrentFps => _currentFps;
public void Update()
{
_frameCount++;
var now = DateTime.Now;
var elapsed = (now - _lastTime).TotalSeconds;
if (elapsed >= 1.0)
{
_currentFps = _frameCount / elapsed;
_frameCount = 0;
_lastTime = now;
}
}
}
private class DetectionResult
{
public string Status { get; set; }
public List<Detection> Detections { get; set; }
public int ImageWidth { get; set; }
public int ImageHeight { get; set; }
}
private class Detection
{
public int ClassId { get; set; }
public string ClassName { get; set; }
public double Confidence { get; set; }
public float[] Bbox { get; set; } // [x1,y1,x2,y2]
public float CenterX { get; set; }
public float CenterY { get; set; }
public float Width { get; set; }
public float Height { get; set; }
}
}
7.2 界面优化建议
-
双缓冲技术:减少画面闪烁
csharp复制this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint, true); -
性能监控:显示帧率、推理耗时等指标
-
布局自适应:支持不同分辨率显示
-
主题定制:深色模式更适合工业环境
-
多语言支持:国际化需求考虑
8. Modbus TCP通信实现
8.1 Modbus控制器封装
csharp复制public class ModbusController : IDisposable
{
private TcpClient _tcpClient;
private ModbusIpMaster _master;
private bool _isConnected;
public bool IsConnected => _isConnected;
public event Action<string> StatusChanged;
public async Task<bool> ConnectAsync(string ip, int port, int timeout = 3000)
{
try {
if (_tcpClient != null) Disconnect();
_tcpClient = new TcpClient();
var connectTask = _tcpClient.ConnectAsync(ip, port);
// 设置连接超时
if (await Task.WhenAny(connectTask, Task.Delay(timeout)) != connectTask)
{
throw new TimeoutException("连接PLC超时");
}
_master = ModbusIpMaster.CreateIp(_tcpClient);
_master.Transport.ReadTimeout = 1000;
_master.Transport.WriteTimeout = 1000;
_master.Transport.Retries = 3;
_isConnected = true;
StatusChanged?.Invoke($"已连接到PLC {ip}:{port}");
return true;
}
catch (Exception ex) {
StatusChanged?.Invoke($"连接PLC失败: {ex.Message}");
Disconnect();
return false;
}
}
public void Disconnect()
{
try {
_master?.Dispose();
_tcpClient?.Close();
_tcpClient?.Dispose();
}
finally {
_master = null;
_tcpClient = null;
_isConnected = false;
StatusChanged?.Invoke("已断开PLC连接");
}
}
public void WriteCoil(ushort address, bool value)
{
if (!_isConnected) throw new InvalidOperationException("未连接到PLC");
try {
_master.WriteSingleCoil(address, value);
}
catch (Exception ex) {
StatusChanged?.Invoke($"写入线圈失败: {ex.Message}");
Disconnect();
throw;
}
}
public bool ReadCoil(ushort address)
{
if (!_isConnected) throw new InvalidOperationException("未连接到PLC");
try {
return _master.ReadCoils(address, 1)[0];
}
catch (Exception ex) {
StatusChanged?.Invoke($"读取线圈失败: {ex.Message}");
Disconnect();
throw;
}
}
public void WriteRegister(ushort address, ushort value)
{
if (!_isConnected) throw new InvalidOperationException("未连接到PLC");
try {
_master.WriteSingleRegister(address, value);
}
catch (Exception ex) {
StatusChanged?.Invoke($"写入寄存器失败: {ex.Message}");
Disconnect();
throw;
}
}
public ushort ReadRegister(ushort address)
{
if (!_isConnected) throw new InvalidOperationException("未连接到PLC");
try {
return _master.ReadHoldingRegisters(address, 1)[0];
}
catch (Exception ex) {
StatusChanged?.Invoke($"读取寄存器失败: {ex.Message}");
Disconnect();
throw;
}
}
public void Dispose()
{
Disconnect();
}
}
8.2 通信优化建议
-
心跳机制:定期检查连接状态
-
批量读写:减少通信次数
-
错误重试:自动重连机制
-
数据缓存:避免频繁读写同一地址
-
安全防护:防止地址越界访问
9. 系统部署与性能优化
9.1 部署方案
-
独立部署:所有组件运行在同一工控机
- 优点:简单易部署
- 缺点:资源竞争可能影响性能
-
分布式部署:
- 方案1:相机+工控机(采集)+服务器(推理)
- 方案2:相机+边缘计算盒(采集+推理)+工控机(控制)
- 优点:负载均衡,性能更好
- 缺点:网络要求高,部署复杂
9.2 性能优化实战
-
图像采集优化:
- 使用相机硬件触发模式
- 合理设置ROI减少数据量
- 启用相机硬件压缩
-
推理加速:
python复制# 使用TensorRT加速 model.export(format='engine', device=0) model = YOLO('yolov8n.engine') # 半精度推理 results = model(img, half=True) -
内存管理:
- 及时释放Bitmap和Mat对象
- 使用对象池重用资源
- 监控内存泄漏
-
多线程优化:
- 采集、推理、显示使用独立线程
- 合理设置线程优先级
- 使用生产者-消费者模式
10. 常见问题与解决方案
10.1 相机相关问题
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 无法发现相机 | 1. 驱动未安装 2. 网络配置错误 3. 防火墙阻挡 |
1. 安装MVS完整驱动包 2. 检查IP地址和子网掩码 3. 关闭防火墙或添加例外 |
| 图像卡顿 | 1. 带宽 |
