依赖项:MvCameraControl.Net.dll 版本4.4.0.2
相机:MV-CH250-90YM
背景:使用自定义WPF控件显示图片,通过实现INotifyPropertyChanged接口,实现PropertyChanged事件,修改图像控件的显示值。海康自带Demo虽然有WPF的使用示例,底层还是使用Winform的WindowsFormHost。我的代码彻底抛弃Winform,通过多线程+队列+回调取图+Dispatcher.Invoke实现不卡顿,实时显示海康相机的结果图。
代码说明:
这段代码是一个WPF应用程序的一部分,主要用于处理和显示从相机捕获的图像。它使用了Hikvision的MvCameraControl库来控制和获取图像。以下是代码的主要逻辑:
-
在构造函数中,它初始化了一些变量,启动了一个接收线程用于处理和显示图像,并调用了
InitService
函数来初始化相机。 -
InitService
函数中,首先初始化了SDK,枚举设备并打开第一个设备。然后设置触发模式为off,设置了图像节点数量,注册了回调函数,并开始抓图。 -
FrameGrabedEventHandler
函数是图像抓取的回调函数,每当新的图像被抓取时,它会被调用,并将新的图像帧添加到m_frameList
队列中。 -
ShowThread
函数是在一个单独的线程中运行的,用于处理和显示m_frameList
队列中的图像帧。它会将图像帧转换为Bitmap,然后使用Dispatcher.Invoke
将Bitmap显示在UI上。 -
btnStopGrab_Click
和btnStartGrab_Click
函数分别用于停止和开始图像抓取。 -
GetTriggerMode
和btnGetSetting_Click
函数用于获取相机的设置,btnSetSetting_Click
函数用于设置相机的参数。
代码如下:
using MvCameraControl;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using WpfApp1.ViewModel;namespace WpfApp1
{/// <summary>/// PolishinLargeScreen.xaml 的交互逻辑/// </summary>public partial class PolishinLargeScreen : UserControl{const DeviceTLayerType devLayerType = DeviceTLayerType.MvGigEDevice | DeviceTLayerType.MvUsbDevice | DeviceTLayerType.MvGenTLCameraLinkDevice| DeviceTLayerType.MvGenTLCXPDevice | DeviceTLayerType.MvGenTLXoFDevice;IDevice device = null;Thread receiveThread = null; // ch:接收图像线程 | en: Receive image threadbool m_bShowLoop = true; // 线程控制变量 | thread looping flag bool isGrabbing = false; // ch:是否正在取图 | en: Grabbing flagList<IFrameOut> m_frameList = new List<IFrameOut>(); // 图像缓存列表 | frame data list Mutex m_mutex = new Mutex(); // 锁,保证多线程安全 | mutex PolishinLargeViewModel viewModel;public PolishinLargeScreen(){InitializeComponent();viewModel = new PolishinLargeViewModel();this.DataContext = viewModel;receiveThread = new Thread(ShowThread);receiveThread.Start();InitService();}void FrameGrabedEventHandler(object sender, FrameGrabbedEventArgs e){m_mutex.WaitOne();m_frameList.Add((IFrameOut)e.FrameOut.Clone());m_mutex.ReleaseMutex();}// display thread routine private void ShowThread(){while (m_bShowLoop){if (m_frameList.Count == 0){Thread.Sleep(10);continue;}// 图像队列取最新帧 // always get the latest frame in list m_mutex.WaitOne();IFrameOut frame = m_frameList.ElementAt(m_frameList.Count - 1);m_frameList.Clear();m_mutex.ReleaseMutex();// 主动调用回收垃圾 // call garbage collection GC.Collect();// 控制显示最高帧率为25FPS // control frame display rate to be 25 FPS if (false == isTimeToDisplay()){continue;}try{// 图像转码成bitmap图像 ......................................................................................................................................................................................................................................................................................................................................................................................// raw frame data converted to bitmap Dispatcher.Invoke(() =>{Bitmap bitmap = frame.Image.ToBitmap();using (MemoryStream memoryStream = new MemoryStream()){// 将 Bitmap 保存到内存流bitmap.Save(memoryStream, ImageFormat.Png);// 重置流的位置memoryStream.Position = 0;// 创建 BitmapImage 并从内存流加载BitmapImage bitmapImage = new BitmapImage();bitmapImage.BeginInit();bitmapImage.CacheOption = BitmapCacheOption.OnLoad; // 设置缓存选项bitmapImage.StreamSource = memoryStream;bitmapImage.EndInit();bitmapImage.Freeze(); // 冻结对象,使其不可修改viewModel.BitmapSource = bitmapImage;}bitmap.Dispose();}, DispatcherPriority.Background);}catch (Exception exception){//Catcher.Show(exception);}}}const int DEFAULT_INTERVAL = 40;Stopwatch m_stopWatch = new Stopwatch();// 判断是否应该做显示操作 // calculate interval to determine if it's show time now private bool isTimeToDisplay(){m_stopWatch.Stop();long m_lDisplayInterval = m_stopWatch.ElapsedMilliseconds;if (m_lDisplayInterval <= DEFAULT_INTERVAL){m_stopWatch.Start();return false;}else{m_stopWatch.Reset();m_stopWatch.Start();return true;}}// ch:显示错误信息 | en:Show error messageprivate void ShowErrorMsg(string message, int errorCode){string errorMsg;if (errorCode == 0){errorMsg = message;}else{errorMsg = message + ": Error =" + String.Format("{0:X}", errorCode);}switch (errorCode){case MvError.MV_E_HANDLE: errorMsg += " Error or invalid handle "; break;case MvError.MV_E_SUPPORT: errorMsg += " Not supported function "; break;case MvError.MV_E_BUFOVER: errorMsg += " Cache is full "; break;case MvError.MV_E_CALLORDER: errorMsg += " Function calling order error "; break;case MvError.MV_E_PARAMETER: errorMsg += " Incorrect parameter "; break;case MvError.MV_E_RESOURCE: errorMsg += " Applying resource failed "; break;case MvError.MV_E_NODATA: errorMsg += " No data "; break;case MvError.MV_E_PRECONDITION: errorMsg += " Precondition error, or running environment changed "; break;case MvError.MV_E_VERSION: errorMsg += " Version mismatches "; break;case MvError.MV_E_NOENOUGH_BUF: errorMsg += " Insufficient memory "; break;case MvError.MV_E_UNKNOW: errorMsg += " Unknown error "; break;case MvError.MV_E_GC_GENERIC: errorMsg += " General error "; break;case MvError.MV_E_GC_ACCESS: errorMsg += " Node accessing condition error "; break;case MvError.MV_E_ACCESS_DENIED: errorMsg += " No permission "; break;case MvError.MV_E_BUSY: errorMsg += " Device is busy, or network disconnected "; break;case MvError.MV_E_NETER: errorMsg += " Network error "; break;}MessageBox.Show(errorMsg, "PROMPT");}public async void InitService(){// ch: 初始化 SDK | en: Initialize SDKSDKSystem.Initialize();try{List<IDeviceInfo> devInfoList;// ch:枚举设备 | en:Enum deviceint ret = DeviceEnumerator.EnumDevices(devLayerType, out devInfoList);if (ret != MvError.MV_OK){Console.WriteLine("Enum device failed:{0:x8}", ret);return;}// ch:创建设备 | en:Create devicedevice = DeviceFactory.CreateDevice(devInfoList[0]);ret = device.Open();if (ret != MvError.MV_OK){Console.WriteLine("Open device failed:{0:x8}", ret);return;}// ch:设置触发模式为off || en:set trigger mode as offret = device.Parameters.SetEnumValue("TriggerMode", 0);if (ret != MvError.MV_OK){Console.WriteLine("Set TriggerMode failed:{0:x8}", ret);return;}//ch: 设置合适的缓存节点数量 | en: Setting the appropriate number of image nodesdevice.StreamGrabber.SetImageNodeNum(5);// ch:注册回调函数 | en:Register image callbackdevice.StreamGrabber.FrameGrabedEvent += FrameGrabedEventHandler;// ch:开启抓图 || en: start grab imageret = device.StreamGrabber.StartGrabbing();if (ret != MvError.MV_OK){m_bShowLoop = false;return;}}catch (Exception e){Console.Write("Exception: " + e.Message);}finally{ch:销毁设备 | en:Destroy device//if (device != null)//{// device.Dispose();// device = null;//}ch: 反初始化SDK | en: Finalize SDK//SDKSystem.Finalize();}}/// <summary>/// 停止采集/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnStopGrab_Click(object sender, RoutedEventArgs e){isGrabbing = false;//await receiveThread;device.StreamGrabber.FrameGrabedEvent -= FrameGrabedEventHandler;// ch:停止抓图 | en:Stop grabbingint result = device.StreamGrabber.StopGrabbing();if (result != MvError.MV_OK){ShowErrorMsg("Stop Grabbing Fail!", result);return;}// ch:关闭设备 | en:Close deviceresult = device.Close();if (result != MvError.MV_OK){Console.WriteLine("Close device failed:{0:x8}", result);return;}}private void btnStartGrab_Click(object sender, RoutedEventArgs e){try{int ret = device.Open();if (ret != MvError.MV_OK){Console.WriteLine("Open device failed:{0:x8}", ret);return;}// ch:标志位置位true | en:Set position bit trueisGrabbing = true;}catch (Exception ex){MessageBox.Show("Start thread failed!, " + ex.Message);throw;}device.StreamGrabber.FrameGrabedEvent += FrameGrabedEventHandler;// ch:开始采集 | en:Start Grabbingint result = device.StreamGrabber.StartGrabbing();if (result != MvError.MV_OK){isGrabbing = false;ShowErrorMsg("Start Grabbing Fail!", result);return;}}/// <summary>/// ch:获取触发模式 | en:Get Trigger Mode/// </summary>private void GetTriggerMode(){IEnumValue enumValue;int result = device.Parameters.GetEnumValue("TriggerMode", out enumValue);if (result == MvError.MV_OK){if (enumValue.CurEnumEntry.Symbolic == "On"){result = device.Parameters.GetEnumValue("TriggerSource", out enumValue);if (result == MvError.MV_OK){if (enumValue.CurEnumEntry.Symbolic == "TriggerSoftware"){}}}}}/// <summary>/// 获取参数/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnGetSetting_Click(object sender, RoutedEventArgs e){GetTriggerMode();IFloatValue floatValue;int result = device.Parameters.GetFloatValue("ExposureTime", out floatValue);if (result == MvError.MV_OK){tbExposure.Text = floatValue.CurValue.ToString("F1");}result = device.Parameters.GetFloatValue("ResultingFrameRate", out floatValue);if (result == MvError.MV_OK){tbFrameRate.Text = floatValue.CurValue.ToString("F1");}///数字增益使能bool boolValue;result = device.Parameters.GetBoolValue("DigitalShiftEnable", out boolValue);chk_Digital.IsChecked = false;if (result == MvError.MV_OK){if (boolValue){chk_Digital.IsChecked = true;}}///数字增益IFloatValue digitalShift;result = device.Parameters.GetFloatValue("DigitalShift", out digitalShift);if (result == MvError.MV_OK){nud_digitalShift.Value = double.Parse(digitalShift.CurValue.ToString());}}private void btnSetSetting_Click(object sender, RoutedEventArgs e){try{float.Parse(tbExposure.Text);float.Parse(tbFrameRate.Text);}catch{ShowErrorMsg("Please enter correct type!", 0);return;}device.Parameters.SetEnumValue("ExposureAuto", 0);int result = device.Parameters.SetFloatValue("ExposureTime", float.Parse(tbExposure.Text));if (result != MvError.MV_OK){ShowErrorMsg("Set Exposure Time Fail!", result);}result = device.Parameters.SetFloatValue("AcquisitionFrameRate", float.Parse(tbFrameRate.Text));if (result != MvError.MV_OK){ShowErrorMsg("Set Frame Rate Fail!", result);}///数字增益使能result = device.Parameters.SetBoolValue("DigitalShiftEnable", (bool)chk_Digital.IsChecked);if (result != MvError.MV_OK){ShowErrorMsg("Set DigitalShiftEnable Fail!", result);}result = device.Parameters.SetFloatValue("DigitalShift", (float)nud_digitalShift.Value);if (result != MvError.MV_OK){ShowErrorMsg("Set DigitalShiftEnable Fail!", result);}}}}