依赖项: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
函数用于设置相机的参数。
代码如下:
cs
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 thread
bool m_bShowLoop = true; // 线程控制变量 | thread looping flag
bool isGrabbing = false; // ch:是否正在取图 | en: Grabbing flag
List<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 message
private 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 SDK
SDKSystem.Initialize();
try
{
List<IDeviceInfo> devInfoList;
// ch:枚举设备 | en:Enum device
int ret = DeviceEnumerator.EnumDevices(devLayerType, out devInfoList);
if (ret != MvError.MV_OK)
{
Console.WriteLine("Enum device failed:{0:x8}", ret);
return;
}
// ch:创建设备 | en:Create device
device = 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 off
ret = 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 nodes
device.StreamGrabber.SetImageNodeNum(5);
// ch:注册回调函数 | en:Register image callback
device.StreamGrabber.FrameGrabedEvent += FrameGrabedEventHandler;
// ch:开启抓图 || en: start grab image
ret = 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 grabbing
int result = device.StreamGrabber.StopGrabbing();
if (result != MvError.MV_OK)
{
ShowErrorMsg("Stop Grabbing Fail!", result);
return;
}
// ch:关闭设备 | en:Close device
result = 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 true
isGrabbing = true;
}
catch (Exception ex)
{
MessageBox.Show("Start thread failed!, " + ex.Message);
throw;
}
device.StreamGrabber.FrameGrabedEvent += FrameGrabedEventHandler;
// ch:开始采集 | en:Start Grabbing
int 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);
}
}
}
}