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(); } } }