.NET编程——利用C#调用海康机器人工业相机SDK实现回调取图与软触发取图【含免费源码】
目录
- 引言
- 前期准备
- 演示视频
- HikCamera类
- 打开或关闭相机
- 软触发单张取图
引言
海康机器人(HikRobot)作为机器视觉头部厂商之一,其工业相机被广泛应用于机器视觉领域,常见的Halcon和VisionPro这两个软件海康官方已经开发了原生的.dll,可以在MVS安装路径下ThirdPartyPlatformAdapter找到这两款软件更推荐使用官方提供的库,对于第三方开发的软件或者非常规的工业相机就会涉及到SDK取图的问题,本文教程主要介绍如何将调用海康SDK取图。
前期准备
开始前在海康机器人下载中心将最新版的MVS安装在默认路径下
演示视频
海康工业相机SDK取图
🔗免费下载链接
HikCamera类
#define MVCAMERAusing System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using MvCameraControl;
using System.Globalization;
using HalconDotNet;namespace MyCamera
{internal class HikCamera : BaseCamera{readonly DeviceTLayerType enumTLayerType = DeviceTLayerType.MvGigEDevice | DeviceTLayerType.MvUsbDevice| DeviceTLayerType.MvGenTLGigEDevice | DeviceTLayerType.MvGenTLCXPDevice | DeviceTLayerType.MvGenTLCameraLinkDevice | DeviceTLayerType.MvGenTLXoFDevice;public HikCamera() : base() { }#region param[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);List<IDeviceInfo> deviceInfoList = new List<IDeviceInfo>();List<string> deviceSNList = new List<string>();IDevice device = null;IInterface _ifInstance;IntPtr m_BufForDriver = IntPtr.Zero;UInt32 _bufferSize = 3072 * 2048 * 3;private byte[] _buffer;private uint _buffSizeForSaveImage = 3072 * 2048 * (16 * 3 + 4) + 2048;private byte[] _bufForSaveImage;private Object m_BufForSaveImageLock = new Object();public IntPtr m_pSaveImageBuf = IntPtr.Zero;#endregion#region operatepublic override List<string> GetListEnum(){List<string> deviceList = new List<string>();foreach (var item in GetListInfoEnum()){IDeviceInfo deviceInfo = item;if (deviceInfo.UserDefinedName != ""){//deviceList.Add(deviceInfo.TLayerType.ToString() + ": " + deviceInfo.UserDefinedName + " (" + deviceInfo.SerialNumber + ")");deviceList.Add(deviceInfo.SerialNumber);}else{//deviceList.Add(deviceInfo.TLayerType.ToString() + ": " + deviceInfo.ManufacturerName + " " + deviceInfo.ModelName + " (" + deviceInfo.SerialNumber + ")");deviceList.Add(deviceInfo.SerialNumber);}}return deviceList;}public override bool InitDevice(string CamSN){try{int nRet;if (string.IsNullOrEmpty(CamSN)) return false;var infolist = GetListInfoEnum();if (infolist.Count < 1) return false;IDeviceInfo deviceInfo = infolist[0];bool selectSNflag = false;foreach (var item in infolist){if (item.SerialNumber.Equals(CamSN)){deviceInfo = item;selectSNflag = true;break;}}if (!selectSNflag) return false;try{// ch:打开设备 | en:Open devicedevice = DeviceFactory.CreateDevice(deviceInfo);}catch (Exception ex){MessageBox.Show("Create Device fail!" + ex.Message);return false;}nRet = device.Open();if (nRet != MvError.MV_OK){ShowErrorMsg("Open Device fail!", nRet);return false;}// ch:注册回调函数 | en:Register image callback// 注册回调函数操作应放在打开采集流操作之前device.StreamGrabber.FrameGrabedEvent += CallBackEventHandler;// ch:探测网络最佳包大小(只对GigE相机有效) | en:Detection network optimal package size(It only works for the GigE camera)if (device is IGigEDevice){int packetSize;nRet = (device as IGigEDevice).GetOptimalPacketSize(out packetSize);if (packetSize > 0){nRet = device.Parameters.SetIntValue("GevSCPSPacketSize", packetSize);if (nRet != MvError.MV_OK){Console.WriteLine("Warning: Set Packet Size failed {0:x8}", nRet);}else{Console.WriteLine("Set PacketSize to {0}", packetSize);}}else{Console.WriteLine("Warning: Get Packet Size failed {0:x8}", nRet);}}// ch:设置触发模式为On || en:set trigger mode as On// 设置为Off会一直触发回调SetTriggerMode(TriggerMode.On, TriggerSource.Software);device.Parameters.SetEnumValueByString("AcquisitionMode", "Continuous");if (nRet != MvError.MV_OK){Console.WriteLine("Set TriggerMode failed:{0:x8}", nRet);return false;}if (!StartGrabbing()){Console.WriteLine("开始采集失败");return false;}//ch: 设置合适的缓存节点数量 | en: Setting the appropriate number of image nodesdevice.StreamGrabber.SetImageNodeNum(5);SN = CamSN;return true;}catch { return false; }}public override bool CloseDevice(){try{if (m_BufForDriver != IntPtr.Zero){Marshal.Release(m_BufForDriver);}// ch:关闭设备 | en:Close devicevar nRet = device.Close();if (MvError.MV_OK == nRet){// ch:销毁设备 | en:Destroy devicedevice.Dispose();return true;}nRet = device.Close();if (MvError.MV_OK != nRet){Console.WriteLine("Close device failed:{0:x8}", nRet);return false;}// ch:销毁设备 | en:Destroy devicedevice.Dispose();return true;}catch{device = null;return false;}}/// <summary>/// ch:软触发执行一次 | en:Trigger once by software/// </summary>public override bool SoftTrigger(){int nRet;// ch:触发命令 | en:Trigger commandnRet = device.Parameters.SetCommandValue("TriggerSoftware");return (MvError.MV_OK == nRet);}#endregion#region SettingConfigpublic override bool SetTriggerMode(TriggerMode mode = TriggerMode.On, TriggerSource triggerEnum = TriggerSource.Software){int nRet;switch (mode){case TriggerMode.Off:nRet = device.Parameters.SetEnumValueByString("TriggerMode", "Off");break;case TriggerMode.On:nRet = device.Parameters.SetEnumValueByString("TriggerMode", "On");break;default:nRet = device.Parameters.SetEnumValueByString("TriggerMode", "On");break;}bool flag1 = (MvError.MV_OK == nRet);switch (triggerEnum){case TriggerSource.Software:nRet = device.Parameters.SetEnumValueByString("TriggerSource", "Software");break;case TriggerSource.Line0:nRet = device.Parameters.SetEnumValueByString("TriggerSource", "Line0");break;case TriggerSource.Line1:nRet = device.Parameters.SetEnumValueByString("TriggerSource", "Line1");break;case TriggerSource.Line2:nRet = device.Parameters.SetEnumValueByString("TriggerSource", "Line2");break;case TriggerSource.Line3:nRet = device.Parameters.SetEnumValueByString("TriggerSource", "Line3");break;default:nRet = device.Parameters.SetEnumValueByString("TriggerSource", "Line4");break;}bool flag2 = (MvError.MV_OK == nRet);return flag1 && flag2;}public override bool GetTriggerMode(out TriggerMode mode, out TriggerSource source){int nRet;mode = TriggerMode.On;source = TriggerSource.Software;IEnumValue enumValue;nRet = device.Parameters.GetEnumValue("TriggerMode", out enumValue);bool flag1 = (MvError.MV_OK == nRet);if (flag1){switch (enumValue.CurEnumEntry.Symbolic){case "On":mode = TriggerMode.On;break;case "Off":mode = TriggerMode.Off;break;default:mode = TriggerMode.On;break;}}nRet = device.Parameters.GetEnumValue("TriggerSource", out enumValue);bool flag2 = (MvError.MV_OK == nRet);if (flag2){switch (enumValue.CurEnumEntry.Symbolic){case "TriggerSoftware":source = TriggerSource.Software;break;case "Line0":source = TriggerSource.Line0;break;case "Line1":source = TriggerSource.Line1;break;case "Line2":source = TriggerSource.Line2;break;default:source = TriggerSource.Line0;break;}}return flag1 && flag2;}public override bool SetExpouseTime(float value){int nRet = device.Parameters.SetFloatValue("ExposureTime", value);return (MvError.MV_OK == nRet);}public override bool GetExpouseTime(out float value){IFloatValue floatValue;int nRet = device.Parameters.GetFloatValue("ExposureTime", out floatValue);value = (float)floatValue.CurValue;return (MvError.MV_OK == nRet);}public override bool SetTriggerPolarity(TriggerPolarity polarity){IEnumValue enumValue;int nRet = _ifInstance.Parameters.GetEnumValue("TimerTriggerActivation", out enumValue);if (nRet != MvError.MV_OK){return false;}for (int i = 0; i < enumValue.SupportedNum; ++i){if (polarity.ToString().Equals(enumValue.SupportEnumEntries[i].Symbolic, StringComparison.OrdinalIgnoreCase)){nRet = _ifInstance.Parameters.SetEnumValue("TimerTriggerActivation", enumValue.SupportEnumEntries[i].Value);if (nRet != MvError.MV_OK){return false;}break;}}return (MvError.MV_OK == nRet);}public override bool GetTriggerPolarity(out TriggerPolarity polarity){polarity = TriggerPolarity.RisingEdge;IEnumValue enumValue;int nRet = _ifInstance.Parameters.GetEnumValue("TimerTriggerActivation", out enumValue);if (nRet != MvError.MV_OK){return false;}string activate = enumValue.CurEnumEntry.Symbolic;//1下降沿 0 上升沿if (activate == "RisingEdge"){ //上升沿polarity = TriggerPolarity.RisingEdge;}else if (activate == "FallingEdge"){ //下降沿polarity = TriggerPolarity.FallingEdge;}return (MvError.MV_OK == nRet);}public override bool SetTriggerFliter(float flitertime){int nRet = _ifInstance.Parameters.SetIntValue("LineDebouncerTimeNs", (uint)flitertime);nRet = _ifInstance.Parameters.SetIntValue("LineDebouncerTime", (uint)flitertime);return (MvError.MV_OK == nRet);}public override bool GetTriggerFliter(out float flitertime){flitertime = 1000;IFloatValue floatValue;int nRet = device.Parameters.GetFloatValue("ExposureTime", out floatValue);flitertime = (float)floatValue.CurValue;return (MvError.MV_OK == nRet);}public override bool SetTriggerDelay(float delay){int nRet = device.Parameters.SetFloatValue("TriggerDelay", delay);return (MvError.MV_OK == nRet);}public override bool GetTriggerDelay(out float delay){delay = 0;IFloatValue floatValue;int nRet = device.Parameters.GetFloatValue("TriggerDelay", out floatValue);delay = (float)floatValue.CurValue;return (MvError.MV_OK == nRet);}public override bool SetGain(float gain){int nRet = device.Parameters.SetFloatValue("Gain", (float)gain);return (MvError.MV_OK == nRet);}public override bool GetGain(out float gain){gain = 0;IFloatValue floatValue;int nRet = device.Parameters.GetFloatValue("Gain", out floatValue);gain = (float)floatValue.CurValue;return (MvError.MV_OK == nRet);}public override bool SetLineMode(IOLines line, LineMode mode){int nRet = device.Parameters.SetEnumValueByString(line.ToString(), mode.ToString());return (MvError.MV_OK == nRet);}public override bool SetLineStatus(IOLines line, LineStatus linestatus){int nRet = device.Parameters.SetBoolValue(line.ToString(), linestatus.Equals(LineStatus.Hight));return (MvError.MV_OK == nRet);}public override bool GetLineStatus(IOLines line, out LineStatus linestatus){bool resultsignal = false;int nRet = device.Parameters.GetBoolValue(line.ToString(), out resultsignal);linestatus = resultsignal ? LineStatus.Hight : LineStatus.Low;return (MvError.MV_OK == nRet);}public override bool AutoBalanceWhite(){int nRet = device.Parameters.SetEnumValueByString("BalanceWhiteAuto", "Once");return (MvError.MV_OK == nRet);}#endregion#region helper // 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 override bool StartGrabbing(){// Set default state after grabbing starts// Turn off real-time mode which is default// 0: real-time// 1: triggerint nRet = device.StreamGrabber.StartGrabbing();if (MvError.MV_OK != nRet) Debug.WriteLine("Grab start failed");isGrabbing = true;return (MvError.MV_OK == nRet);}public override bool StopGrabbing(){int nRet = device.StreamGrabber.StopGrabbing();if (MvError.MV_OK != nRet) Debug.WriteLine("Grab stop failed");isGrabbing = false;return (MvError.MV_OK == nRet);}public override void Dispose(){if(device != null){SN = string.Empty;device.Close();device.Dispose();}}private List<IDeviceInfo> GetListInfoEnum(){System.GC.Collect();deviceInfoList.Clear();// ch:创建设备列表 | en:Create Device Listint nRet = DeviceEnumerator.EnumDevices(enumTLayerType, out deviceInfoList);if (nRet != MvError.MV_OK){ShowErrorMsg("Enumerate devices fail!", nRet);return new List<IDeviceInfo>();}// ch:在窗体列表中显示设备名 | en:Display device name in the form listfor (int i = 0; i < deviceInfoList.Count; i++){IDeviceInfo deviceInfo = deviceInfoList[i];if (deviceInfo.UserDefinedName != ""){deviceSNList.Add(deviceInfo.TLayerType.ToString() + ": " + deviceInfo.UserDefinedName + " (" + deviceInfo.SerialNumber + ")");}else{deviceSNList.Add(deviceInfo.TLayerType.ToString() + ": " + deviceInfo.ManufacturerName + " " + deviceInfo.ModelName + " (" + deviceInfo.SerialNumber + ")");}}// ch:选择第一项 | en:Select the first itemif (deviceInfoList.Count != 0){deviceSNList[0] = deviceSNList[0];}return deviceInfoList;}static bool IsMonoPixelFormat(MvGvspPixelType enType){switch (enType){case MvGvspPixelType.PixelType_Gvsp_Mono10:case MvGvspPixelType.PixelType_Gvsp_Mono10_Packed:case MvGvspPixelType.PixelType_Gvsp_Mono12:case MvGvspPixelType.PixelType_Gvsp_Mono12_Packed:return true;default:return false;}}static bool IsColorPixelFormat(MvGvspPixelType enType){switch (enType){case MvGvspPixelType.PixelType_Gvsp_BGR8_Packed:case MvGvspPixelType.PixelType_Gvsp_YUV422_Packed:case MvGvspPixelType.PixelType_Gvsp_YUV422_YUYV_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerGR8:case MvGvspPixelType.PixelType_Gvsp_BayerRG8:case MvGvspPixelType.PixelType_Gvsp_BayerGB8:case MvGvspPixelType.PixelType_Gvsp_BayerBG8:case MvGvspPixelType.PixelType_Gvsp_BayerGB10:case MvGvspPixelType.PixelType_Gvsp_BayerGB10_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerBG10:case MvGvspPixelType.PixelType_Gvsp_BayerBG10_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerRG10:case MvGvspPixelType.PixelType_Gvsp_BayerRG10_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerGR10:case MvGvspPixelType.PixelType_Gvsp_BayerGR10_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerGB12:case MvGvspPixelType.PixelType_Gvsp_BayerGB12_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerBG12:case MvGvspPixelType.PixelType_Gvsp_BayerBG12_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerRG12:case MvGvspPixelType.PixelType_Gvsp_BayerRG12_Packed:case MvGvspPixelType.PixelType_Gvsp_BayerGR12:case MvGvspPixelType.PixelType_Gvsp_BayerGR12_Packed:return true;default:return false;}}/// <summary>/// 回调函数(不要在内部增加耗时操作)/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void CallBackEventHandler(object sender, FrameGrabbedEventArgs e){int nRet;Console.WriteLine("Get Image Buffer: Width[{0}] , Height[{1}] , FrameNum[{2}]", e.FrameOut.Image.Width, e.FrameOut.Image.Height, e.FrameOut.FrameNum);IImage inputImage = e.FrameOut.Image;IImage outImage = inputImage;MvGvspPixelType dstPixelType = MvGvspPixelType.PixelType_Gvsp_Undefined;if (IsColorPixelFormat(e.FrameOut.Image.PixelType)){dstPixelType = MvGvspPixelType.PixelType_Gvsp_RGB8_Packed;}else if (IsMonoPixelFormat(e.FrameOut.Image.PixelType)){dstPixelType = MvGvspPixelType.PixelType_Gvsp_Mono8;}else{Console.WriteLine("Don't need to convert!");}if (dstPixelType != MvGvspPixelType.PixelType_Gvsp_Undefined){// ch:像素格式转换 | en:Pixel type convert nRet = device.PixelTypeConverter.ConvertPixelType(inputImage, out outImage, dstPixelType);if (nRet != MvError.MV_OK){Console.WriteLine("Image Convert failed:{0:x8}", nRet);return;}Console.WriteLine("Image Convert success!");}//ch: 释放图像缓存 | en: Release image bufferdevice.StreamGrabber.FreeImageBuffer(e.FrameOut);if (outImage.ToBitmap() == null) return;CallBackImg = outImage.ToBitmap();ActionGetImage?.Invoke(SN, outImage.ToBitmap());}#endregion}
}
private void cmbSN_MouseDown(object sender, MouseEventArgs e)
{switch (cmbBrand.Text){case "Hik":camera = new HikCamera();foreach (var item in ((HikCamera)camera).GetListEnum()){if (!cmbSN.Items.Contains(item))cmbSN.Items.Add(item);}break;default:break;}
}
打开或关闭相机
private void btnOpen_Click(object sender, EventArgs e)
{switch (cmbBrand.Text){case "Hik":if (cmbSN.Items.Count > 0 && ((HikCamera)camera).InitDevice(cmbSN.Text.ToString())){((HikCamera)camera).ActionGetImage += GetImageBllComplete;MessageBox.Show(((HikCamera)camera).SN + "打开成功");}break;default:break;}
}private void btnClose_Click(object sender, EventArgs e)
{switch (cmbBrand.Text){case "Hik":if (((HikCamera)camera).CloseDevice()){MessageBox.Show(((HikCamera)camera).SN + "断开成功");}break;default:break;}
}
软触发单张取图
private void btnGrabOnce_Click(object sender, EventArgs e)
{switch (cmbBrand.Text){case "Hik":if(camera != null)((HikCamera)camera).GetImageWithSoftTrigger(out Bitmap bitmap);break;default:break;}
}/// <summary>
/// 软触发获取图像
/// </summary>
/// <param name="bitmap"></param>
/// <param name="outtime"></param>
/// <returns></returns>
public bool GetImageWithSoftTrigger(out Bitmap bitmap, int outtime = 3000)
{if (!isGrabbing)StartGrabbing();// 开始时间DateTime startTime = DateTime.Now; // 当前时间// 设置超时时间DateTime lastTime = startTime.AddMilliseconds(outtime);// 判断是否超时while (lastTime > DateTime.Now)// 设置超时时间为 3 秒{if (isGrabbing)break;}GetTriggerMode(out TriggerMode triggerMode, out TriggerSource triggerSource);SetTriggerMode(TriggerMode.On, TriggerSource.Software);bitmap = null;if (!SoftTrigger()) return false;GetImage(out bitmap, outtime);SetTriggerMode(triggerMode, triggerSource);return (bitmap != null);
}/// <summary>
/// 等待硬触发获取图像
/// </summary>
/// <param name="bitmap"></param>
/// <param name="outtime"></param>
/// <returns></returns>
public bool GetImage(out Bitmap bitmap, int outtime = 3000)
{bitmap = null;// 开始时间DateTime startTime = DateTime.Now; // 当前时间// 设置超时时间DateTime lastTime = startTime.AddMilliseconds(outtime);// 判断是否超时while (lastTime > DateTime.Now)// 设置超时时间为 3 秒{if (CallBackImg != null){bitmap = this.CallBackImg.Clone() as Bitmap;CallBackImg = null;break;}}return false;
}
相机取图前需要StartGrab()进入取流状态
建议进入取流状态前设置相机触发模式为On否则会一直触发回调函数
进入取流状态后软触发前需要设置触发源为Software
发送软触发指令即可单次取图