using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using MVSDK_Net;
using VM.Core;
using VM.PlatformSDKCS;
using HuarayVMCore;
using HuarayVMCore.Camera;
using HuarayVMCore.Core;
using HuarayVMCore.VisionMaster;
using ImageConverter = HuarayVMCore.Core.ImageConverter;
namespace HuarayVMDemo.Forms
{
///
/// 主窗体。
/// 集成华睿相机采集和海康VisionMaster视觉检测的演示界面。
///
public partial class MainForm : Form
{
#region 字段
/// 华睿相机实例
private HuarayCamera _camera;
/// VM桥接实例
private VMBridge _vmBridge;
/// 枚举到的设备列表
private System.Collections.Generic.List _deviceList;
/// 是否正在连续执行检测
private bool _isContinuousRunning = false;
/// 连续执行线程
private Thread _continuousThread;
/// UI更新用的同步锁
private readonly object _uiLock = new object();
/// 跨线程更新PictureBox的委托
private delegate void UpdateImageDelegate(PictureBox pic, Bitmap bmp);
/// 跨线程更新Control文本的委托
private delegate void UpdateTextDelegate(Control ctrl, string text);
/// 跨线程更新ToolStripItem文本的委托
private delegate void UpdateToolStripItemTextDelegate(ToolStripItem ctrl, string text);
/// 跨线程追加日志的委托
private delegate void AppendLogDelegate(string text);
#endregion
#region 构造函数
///
/// 构造函数
///
public MainForm()
{
InitializeComponent();
_camera = new HuarayCamera();
_vmBridge = new VMBridge();
// 注册相机事件
_camera.FrameReceived += OnFrameReceived;
_camera.ConnectionChanged += OnConnectionChanged;
_camera.StreamError += OnStreamError;
// 配置日志助手
LogHelper.EnableConsoleOutput = false;
LogHelper.EnableFileOutput = true;
LogHelper.MinLogLevel = LogLevel.Debug;
// 重定向日志到UI
LogHelper.LogInfo("系统集成初始化完成");
AppendLog("系统初始化完成,请枚举设备并连接相机");
}
#endregion
#region 相机事件处理
///
/// 帧接收事件处理(异步回调取图模式)
///
private void OnFrameReceived(object sender, FrameReceivedEventArgs e)
{
if (IsDisposed || !IsHandleCreated)
return;
// 在UI线程显示图像
if (chkContinuousDisplay.Checked)
{
try
{
Bitmap bmp = ImageConverter.ToBitmap(e);
UpdatePictureBox(picCameraView, bmp);
}
catch (Exception ex)
{
LogHelper.LogError("图像显示失败: " + ex.Message);
}
}
// 如果正在连续执行VM检测,将图像送入VM
if (_isContinuousRunning && _vmBridge.IsLoaded)
{
try
{
// 转换为VM图像数据并送入
ImageBaseData imageData = ImageConverter.ToImageBaseData(e);
_vmBridge.SetImageData(
txtFlowName.Text,
txtImageSourceName.Text,
imageData);
// 执行流程
_vmBridge.RunProcedure(txtFlowName.Text);
// 获取结果图像并显示
ImageBaseData resultImage = _vmBridge.GetProcedureOutputImage(
txtFlowName.Text, "ImageData");
if (resultImage != null)
{
Bitmap resultBmp = ImageBaseDataToBitmap(resultImage);
UpdatePictureBox(picVMResultView, resultBmp);
}
UpdateStatusLabel(lblRunStatus, "检测完成 - 帧#" + e.FrameNumber);
}
catch (VMBridgeException ex)
{
AppendLog("VM检测异常: " + ex.Message);
}
catch (Exception ex)
{
AppendLog("图像处理异常: " + ex.Message);
}
}
}
///
/// 连接状态变化事件处理
///
private void OnConnectionChanged(object sender, CameraConnectionEventArgs e)
{
string status = e.IsConnected ? "已连接" : "已断开";
AppendLog("相机连接状态: " + status);
UpdateStatusLabel(tsslStatus, "相机" + status);
if (!e.IsConnected)
{
UpdateUIState(false, false);
}
}
///
/// 流异常事件处理
///
private void OnStreamError(object sender, CameraStreamErrorEventArgs e)
{
AppendLog("流异常: " + e.ErrorMessage);
}
#endregion
#region 相机控制按钮事件
///
/// 枚举设备按钮
///
private void btnRefreshDevices_Click(object sender, EventArgs e)
{
try
{
cmbDeviceList.Items.Clear();
AppendLog("正在枚举设备...");
_deviceList = HuarayCamera.EnumerateDevices();
if (_deviceList.Count == 0)
{
AppendLog("未找到任何相机设备");
return;
}
foreach (var dev in _deviceList)
{
string display = string.Format("[{0}] {1} ({2})",
dev.InterfaceType, dev.ModelName, dev.SerialNumber);
cmbDeviceList.Items.Add(display);
}
cmbDeviceList.SelectedIndex = 0;
AppendLog(string.Format("找到 {0} 个设备", _deviceList.Count));
}
catch (Exception ex)
{
AppendLog("枚举设备失败: " + ex.Message);
LogHelper.LogError("枚举设备失败", ex);
}
}
///
/// 连接相机按钮
///
private void btnConnectCamera_Click(object sender, EventArgs e)
{
if (cmbDeviceList.SelectedIndex < 0 || _deviceList == null || _deviceList.Count == 0)
{
MessageBox.Show("请先枚举设备并选择一个相机", "提示");
return;
}
try
{
int index = cmbDeviceList.SelectedIndex;
CameraDeviceInfo devInfo = _deviceList[index];
AppendLog("正在连接相机: " + devInfo.ModelName);
_camera.Open(devInfo);
AppendLog("相机连接成功: " + devInfo.ModelName + " (SN: " + devInfo.SerialNumber + ")");
// 读取当前参数显示到界面
txtExposure.Text = _camera.GetExposureTime().ToString("F1");
txtGain.Text = _camera.GetGain().ToString("F2");
UpdateUIState(true, false);
UpdateStatusLabel(tsslStatus, "相机已连接");
}
catch (Exception ex)
{
AppendLog("连接相机失败: " + ex.Message);
LogHelper.LogError("连接相机失败", ex);
}
}
///
/// 断开相机按钮
///
private void btnDisconnectCamera_Click(object sender, EventArgs e)
{
try
{
if (_camera.IsGrabbing)
{
_camera.StopGrabbing();
}
_camera.Close();
AppendLog("相机已断开");
UpdateUIState(false, false);
picCameraView.Image = null;
UpdateStatusLabel(tsslStatus, "相机已断开");
}
catch (Exception ex)
{
AppendLog("断开相机失败: " + ex.Message);
}
}
///
/// 设置曝光时间
///
private void btnSetExposure_Click(object sender, EventArgs e)
{
try
{
double exposure = double.Parse(txtExposure.Text);
_camera.SetDoubleFeatureValue("ExposureTime", exposure);
AppendLog(string.Format("曝光时间已设置: {0:F1} μs", exposure));
}
catch (FormatException)
{
MessageBox.Show("请输入有效的数字", "提示");
}
catch (Exception ex)
{
AppendLog("设置曝光失败: " + ex.Message);
}
}
///
/// 设置增益
///
private void btnSetGain_Click(object sender, EventArgs e)
{
try
{
double gain = double.Parse(txtGain.Text);
_camera.SetGain(gain);
AppendLog(string.Format("增益已设置: {0:F2}", gain));
}
catch (FormatException)
{
MessageBox.Show("请输入有效的数字", "提示");
}
catch (Exception ex)
{
AppendLog("设置增益失败: " + ex.Message);
}
}
///
/// 触发模式切换
///
private void cmbTriggerMode_SelectedIndexChanged(object sender, EventArgs e)
{
if (!_camera.IsOpened)
return;
try
{
if (cmbTriggerMode.SelectedIndex == 0)
{
// 连续采集
_camera.SetTriggerMode(TriggerMode.Continuous);
btnSoftwareTrigger.Enabled = false;
AppendLog("触发模式: 连续采集");
}
else
{
// 软触发
_camera.SetTriggerMode(TriggerMode.Software, TriggerSource.Software);
btnSoftwareTrigger.Enabled = true;
AppendLog("触发模式: 软触发");
}
}
catch (Exception ex)
{
AppendLog("设置触发模式失败: " + ex.Message);
}
}
///
/// 开始采集
///
private void btnStartGrabbing_Click(object sender, EventArgs e)
{
try
{
// 设置触发模式
if (cmbTriggerMode.SelectedIndex == 0)
{
_camera.SetTriggerMode(TriggerMode.Continuous);
}
else
{
_camera.SetTriggerMode(TriggerMode.Software, TriggerSource.Software);
}
// 设置缓冲区数量
_camera.SetBufferCount(30);
// 开始采集
_camera.StartGrabbing();
AppendLog("开始采集图像");
btnStartGrabbing.Enabled = false;
btnStopGrabbing.Enabled = true;
UpdateStatusLabel(tsslStatus, "正在采集...");
}
catch (Exception ex)
{
AppendLog("开始采集失败: " + ex.Message);
LogHelper.LogError("开始采集失败", ex);
}
}
///
/// 停止采集
///
private void btnStopGrabbing_Click(object sender, EventArgs e)
{
try
{
_camera.StopGrabbing();
AppendLog("停止采集");
btnStartGrabbing.Enabled = true;
btnStopGrabbing.Enabled = false;
UpdateStatusLabel(tsslStatus, "已停止采集");
}
catch (Exception ex)
{
AppendLog("停止采集失败: " + ex.Message);
}
}
///
/// 软触发
///
private void btnSoftwareTrigger_Click(object sender, EventArgs e)
{
try
{
_camera.TriggerSoftware();
AppendLog("软触发已发送");
}
catch (Exception ex)
{
AppendLog("软触发失败: " + ex.Message);
}
}
#endregion
#region VisionMaster控制按钮事件
///
/// 浏览方案文件
///
private void btnBrowseSolution_Click(object sender, EventArgs e)
{
using (OpenFileDialog ofd = new OpenFileDialog())
{
ofd.Filter = "VM方案文件 (*.sol)|*.sol|所有文件 (*.*)|*.*";
ofd.Title = "选择VisionMaster方案文件";
if (ofd.ShowDialog() == DialogResult.OK)
{
txtSolutionPath.Text = ofd.FileName;
}
}
}
///
/// 加载VM方案
///
private void btnLoadSolution_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(txtSolutionPath.Text))
{
MessageBox.Show("请先选择方案文件", "提示");
return;
}
try
{
AppendLog("正在加载VM方案: " + txtSolutionPath.Text);
_vmBridge.LoadSolution(txtSolutionPath.Text, "");
// 设置图像输入模块为SDK模式
_vmBridge.SetImageSourceToSDK(
txtFlowName.Text,
txtImageSourceName.Text,
chkOutMono8.Checked);
AppendLog("VM方案加载成功,图像输入已设置为SDK模式");
UpdateStatusLabel(tsslStatus, "VM方案已加载");
btnLoadSolution.Enabled = false;
btnUnloadSolution.Enabled = true;
btnRunOnce.Enabled = true;
btnStartContinuous.Enabled = true;
}
catch (VMBridgeException ex)
{
AppendLog("加载VM方案失败: " + ex.Message);
MessageBox.Show("加载VM方案失败:\n" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
catch (Exception ex)
{
AppendLog("加载VM方案异常: " + ex.Message);
MessageBox.Show("加载VM方案异常:\n" + ex.Message, "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
///
/// 卸载VM方案
///
private void btnUnloadSolution_Click(object sender, EventArgs e)
{
try
{
// 先停止连续执行
if (_isContinuousRunning)
{
StopContinuousRun();
}
_vmBridge.UnloadSolution();
AppendLog("VM方案已卸载");
btnLoadSolution.Enabled = true;
btnUnloadSolution.Enabled = false;
btnRunOnce.Enabled = false;
btnStartContinuous.Enabled = false;
picVMResultView.Image = null;
UpdateStatusLabel(tsslStatus, "VM方案已卸载");
}
catch (Exception ex)
{
AppendLog("卸载VM方案异常: " + ex.Message);
}
}
///
/// 单次执行(采图+检测)
///
private void btnRunOnce_Click(object sender, EventArgs e)
{
if (!_camera.IsOpened)
{
MessageBox.Show("请先连接相机", "提示");
return;
}
if (!_vmBridge.IsLoaded)
{
MessageBox.Show("请先加载VM方案", "提示");
return;
}
try
{
UpdateStatusLabel(lblRunStatus, "正在采图...");
// 采图(同步方式)
FrameReceivedEventArgs frame = _camera.GetOneFrame(5000);
if (frame == null)
{
AppendLog("采图超时,未获取到图像");
UpdateStatusLabel(lblRunStatus, "采图超时");
return;
}
AppendLog(string.Format("采图成功: {0}x{1}, 帧#{2}", frame.Width, frame.Height, frame.FrameNumber));
// 显示采集图像
Bitmap srcBmp = ImageConverter.ToBitmap(frame);
UpdatePictureBox(picCameraView, srcBmp);
UpdateStatusLabel(lblRunStatus, "正在执行检测...");
// 转换为VM图像数据
ImageBaseData imageData = ImageConverter.ToImageBaseData(frame);
// 送入VM
_vmBridge.SetImageData(
txtFlowName.Text,
txtImageSourceName.Text,
imageData);
// 执行流程
_vmBridge.RunProcedure(txtFlowName.Text);
AppendLog("VM检测执行完成");
// 获取结果图像
try
{
ImageBaseData resultImage = _vmBridge.GetProcedureOutputImage(
txtFlowName.Text, "ImageData");
if (resultImage != null)
{
Bitmap resultBmp = ImageBaseDataToBitmap(resultImage);
UpdatePictureBox(picVMResultView, resultBmp);
AppendLog("已获取检测结果图像");
}
}
catch (VMBridgeException ex)
{
AppendLog("获取结果图像失败: " + ex.Message);
}
UpdateStatusLabel(lblRunStatus, "检测完成");
}
catch (Exception ex)
{
AppendLog("单次执行失败: " + ex.Message);
LogHelper.LogError("单次执行失败", ex);
UpdateStatusLabel(lblRunStatus, "检测失败");
}
}
///
/// 开始连续执行
///
private void btnStartContinuous_Click(object sender, EventArgs e)
{
if (!_camera.IsOpened)
{
MessageBox.Show("请先连接相机", "提示");
return;
}
if (!_vmBridge.IsLoaded)
{
MessageBox.Show("请先加载VM方案", "提示");
return;
}
if (_isContinuousRunning)
return;
_isContinuousRunning = true;
// 确保相机在连续采集模式
if (!_camera.IsGrabbing)
{
cmbTriggerMode.SelectedIndex = 0; // 切换到连续采集
try
{
_camera.SetTriggerMode(TriggerMode.Continuous);
_camera.SetBufferCount(30);
_camera.StartGrabbing();
btnStartGrabbing.Enabled = false;
btnStopGrabbing.Enabled = true;
}
catch (Exception ex)
{
AppendLog("启动采集失败: " + ex.Message);
_isContinuousRunning = false;
return;
}
}
btnStartContinuous.Enabled = false;
btnStopContinuous.Enabled = true;
btnRunOnce.Enabled = false;
AppendLog("开始连续检测");
UpdateStatusLabel(lblRunStatus, "连续检测中...");
UpdateStatusLabel(tsslStatus, "连续检测运行中");
}
///
/// 停止连续执行
///
private void btnStopContinuous_Click(object sender, EventArgs e)
{
StopContinuousRun();
}
///
/// 停止连续执行(内部方法)
///
private void StopContinuousRun()
{
_isContinuousRunning = false;
btnStartContinuous.Enabled = true;
btnStopContinuous.Enabled = false;
btnRunOnce.Enabled = true;
AppendLog("已停止连续检测");
UpdateStatusLabel(lblRunStatus, "已停止连续检测");
UpdateStatusLabel(tsslStatus, "连续检测已停止");
}
#endregion
#region 日志和通用按钮事件
///
/// 清空日志
///
private void btnClearLog_Click(object sender, EventArgs e)
{
txtLog.Clear();
}
#endregion
#region 窗体事件
///
/// 窗体关闭事件
///
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
try
{
// 停止连续执行
if (_isContinuousRunning)
{
StopContinuousRun();
}
// 停止采集
if (_camera != null && _camera.IsGrabbing)
{
_camera.StopGrabbing();
}
// 关闭相机
if (_camera != null && _camera.IsOpened)
{
_camera.Close();
}
// 卸载VM方案
if (_vmBridge != null && _vmBridge.IsLoaded)
{
_vmBridge.UnloadSolution();
}
// 释放资源
_camera?.Dispose();
_vmBridge?.Dispose();
LogHelper.LogInfo("系统资源已释放,程序退出");
}
catch (Exception ex)
{
LogHelper.LogError("程序退出时异常", ex);
}
}
#endregion
#region 辅助方法
///
/// 更新PictureBox图像(线程安全)
///
private void UpdatePictureBox(PictureBox pic, Bitmap bmp)
{
if (pic.InvokeRequired)
{
pic.BeginInvoke(new UpdateImageDelegate(UpdatePictureBox), pic, bmp);
return;
}
Bitmap oldBmp = pic.Image as Bitmap;
pic.Image = bmp;
oldBmp?.Dispose();
}
///
/// 更新控件文本(线程安全,Control版本)
///
private void UpdateStatusLabel(Control ctrl, string text)
{
if (ctrl.InvokeRequired)
{
ctrl.BeginInvoke(new UpdateTextDelegate(UpdateStatusLabel), ctrl, text);
return;
}
ctrl.Text = text;
}
///
/// 更新状态栏文本(线程安全,ToolStripItem版本)
///
private void UpdateStatusLabel(ToolStripItem ctrl, string text)
{
if (InvokeRequired)
{
BeginInvoke(new UpdateToolStripItemTextDelegate(UpdateStatusLabel), ctrl, text);
return;
}
ctrl.Text = text;
}
///
/// 追加日志(线程安全)
///
private void AppendLog(string text)
{
if (txtLog.InvokeRequired)
{
txtLog.BeginInvoke(new AppendLogDelegate(AppendLog), text);
return;
}
string logLine = string.Format("[{0:HH:mm:ss.fff}] {1}", DateTime.Now, text);
txtLog.AppendText(logLine + Environment.NewLine);
if (chkAutoScroll.Checked)
{
txtLog.SelectionStart = txtLog.TextLength;
txtLog.ScrollToCaret();
}
// 同时写入文件日志
LogHelper.LogInfo(text);
}
///
/// 更新UI控件状态
///
private void UpdateUIState(bool cameraConnected, bool isGrabbing)
{
if (InvokeRequired)
{
BeginInvoke(new Action(() => UpdateUIState(cameraConnected, isGrabbing)));
return;
}
btnConnectCamera.Enabled = !cameraConnected;
btnDisconnectCamera.Enabled = cameraConnected;
btnRefreshDevices.Enabled = !cameraConnected;
cmbDeviceList.Enabled = !cameraConnected;
btnSetExposure.Enabled = cameraConnected;
btnSetGain.Enabled = cameraConnected;
cmbTriggerMode.Enabled = cameraConnected;
btnStartGrabbing.Enabled = cameraConnected && !isGrabbing;
btnStopGrabbing.Enabled = cameraConnected && isGrabbing;
}
///
/// 将VM的ImageBaseData转换为Bitmap用于显示
///
private Bitmap ImageBaseDataToBitmap(ImageBaseData imageData)
{
if (imageData == null)
return null;
// 通过反射获取ImageBaseData的内部数据
// ImageBaseData可能不直接暴露byte[],需要通过反射或已知接口获取
// VM SDK中ImageBaseData通常包含: Data(byte[]), Width, Height, PixelFormat等
var type = imageData.GetType();
// 尝试获取Data属性
var dataProp = type.GetProperty("Data");
var widthProp = type.GetProperty("Width") ?? type.GetProperty("ImageWidth");
var heightProp = type.GetProperty("Height") ?? type.GetProperty("ImageHeight");
var formatProp = type.GetProperty("PixelFormat");
if (dataProp == null)
{
// 如果无法通过反射获取,尝试使用VM SDK提供的方式
// VM SDK可能提供GetImage方法或直接可以转换为Bitmap
return null;
}
byte[] data = dataProp.GetValue(imageData) as byte[];
int width = (int)(widthProp?.GetValue(imageData) ?? 0);
int height = (int)(heightProp?.GetValue(imageData) ?? 0);
int pixelFormat = (int)(formatProp?.GetValue(imageData) ?? 0);
if (data == null || width <= 0 || height <= 0)
return null;
// 根据像素格式转换为Bitmap
// PixelFormatF.MONO8 = 0x01080001
// PixelFormatF.BGR8 = 0x02180003
if (pixelFormat == 0x01080001 || pixelFormat == (int)VMPixelFormat.VM_PIXEL_MONO_08)
{
return ImageConverter.Mono8ToBitmap(data, width, height);
}
else
{
// 默认按BGR8处理
return ImageConverter.BGR8ToBitmap(data, width, height);
}
}
#endregion
private void button1_Click(object sender, EventArgs e)
{
_camera.SetTriggerMode(TriggerMode.Software, TriggerSource.Software);
// 设置缓冲区数量
_camera.SetBufferCount(30);
// 开始采集
_camera.StartGrabbing();
Thread.Sleep(500);//等待4s取图
_camera.TriggerSoftware();
}
}
}