目录
引言
海康机器人(HikRobot)作为机器视觉头部厂商之一,其工业相机被广泛应用于机器视觉领域,常见的Halcon和VisionPro这两个软件海康官方已经开发了原生的.dll,可以在MVS安装路径下ThirdPartyPlatformAdapter找到这两款软件更推荐使用官方提供的库,对于第三方开发的软件或者非常规的工业相机就会涉及到SDK取图的问题,本文教程主要介绍如何将调用海康SDK取图。
前期准备
开始前在海康机器人下载中心将最新版的MVS安装在默认路径下
演示视频
海康工业相机SDK取图
HikCamera类
csharp
#define MVCAMERA
using 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 operate
public 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 device
device = 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 nodes
device.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 device
var nRet = device.Close();
if (MvError.MV_OK == nRet)
{
// ch:销毁设备 | en:Destroy device
device.Dispose();
return true;
}
nRet = device.Close();
if (MvError.MV_OK != nRet)
{
Console.WriteLine("Close device failed:{0:x8}", nRet);
return false;
}
// ch:销毁设备 | en:Destroy device
device.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 command
nRet = device.Parameters.SetCommandValue("TriggerSoftware");
return (MvError.MV_OK == nRet);
}
#endregion
#region SettingConfig
public 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 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 override bool StartGrabbing()
{
// Set default state after grabbing starts
// Turn off real-time mode which is default
// 0: real-time
// 1: trigger
int 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 List
int 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 list
for (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 item
if (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 buffer
device.StreamGrabber.FreeImageBuffer(e.FrameOut);
if (outImage.ToBitmap() == null) return;
CallBackImg = outImage.ToBitmap();
ActionGetImage?.Invoke(SN, outImage.ToBitmap());
}
#endregion
}
}
读者自行断开继承关系使用 # 搜索可用相机
csharp
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;
}
}
打开或关闭相机
csharp
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;
}
}
软触发单张取图
csharp
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
发送软触发指令即可单次取图
相机进入取流模式但没有触发回调函数需要检查相机触发模式和触发源