项目要求一个后台服务,通过交换机连接并监控多个Basler相机,项目的细节点就在于实时监控配置列表中所有的相机(相机未在线等待事件,相机断连事件,相机参数初始化,相机拍照结果返回获取等)
当安装Basler相机驱动时选择Developer安装则可以在安装目录下发现现成的demo,如下图
这里我们用C#开发,根据demo可以知道basler开发所引用的底层库有两种PylonC.Net.dll & Basler.Pylon.dll, 项目里我引用的是 PylonC.Net.dll(我测试发现PylonC要稳定些),确保所有相机和电脑都在同一局域网且网段相同。
下面是简单的调用和连接相机:
using PylonC.Net;
public void OpenCamera()
{
Pylon.Initialize();//初始化
uint deviceNums = Pylon.EnumerateDevices();//获取当前所有可连接相机数
for(int i=0; i<=deviceNums -1; i++)
{
PYLON_DEVICE_HANDLE hDev = Pylon.CreateDeviceByIndex(i);
PYLON_DEVICE_INFO_HANDLE deviceInfo = Pylon.GetDeviceInfoHandle(i);
Pylon.DeviceOpen(hDev, Pylon.cPylonAccessModeControl | Pylon.cPylonAccessModeStream);//打开相机,并设定连接模式
}
}
相机已经打开了那就获取照片:
private void SaveImage(PYLON_DEVICE_HANDLE hDev)
{
PylonBuffer<Byte> imgBuf = null; //图像缓存
PylonGrabResult_t grabResult; //图像结果返回
if(Pylon.DeviceFeatureIsAvailable(hDev,"EnumEntry_PixelFormat_Mono8")) //检查相机设备是否支持该像素Mono8设置
{
Pylon.DeviceFeatureFromString(hDev,"PixelFormat","Mono8"); //设置像素格式
}
if(Pylon.DeviceFeatureIsAvailable(hDev, "EnumEntry_TriggerSelector_FrameStart"))
{
Pylon.DeviceFeatureFromString(hDev, "TriggerSelector", "FrameStart");
Pylon.DeviceFeatureFromString(hDev, "TriggerMode", "Off"); //设置触发模式,off就是关闭触发模式
}
if(Pylon.DeviceFeatureIsWritable(hDev, "GevSCPSPacketSize"))
{
Pylon.DeviceSetIntegerFeature(hDev, "GevSCPSPacketSize", 1500); //设置数据传输包的大小
}
if(!Pylon.DeviceGrabSingleFrame(hDev, 0, ref imgBuf, out grabResult, 500))
{
/*图片采集时间超出*/
}
if(grabResult.Status == EPylonGrabStatus.Grabbed)//图片已经采集
{
Pylon.ImageWindowDisplayImage<Byte>(0, imgBuf, grabResult);//显示图片
}
}
像上面那段代码关于相机参数的设置还有很多项,从相机驱动的设置界面就可以感受到。在实际项目中,对应这样的设置后端一般都是使用配置文件,把需要配置的参数根据需求维护上,在循环设置就好。
配置
<ArrayOfCameraSetData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<CameraSetData>
<MAC>003053285C3CMAC>
<SNO>01SNO>
<TimeOut>180000TimeOut>
<CameraParams>
<CameraParam>
<IsAvailMethod>DeviceFeatureIsAvailableIsAvailMethod>
<SetValueMethod>DeviceFeatureFromStringSetValueMethod>
<FeatureName>EnumEntry_PixelFormat_Mono8FeatureName>
<Name>PixelFormatName>
<Value>Mono8Value>
CameraParam>
<CameraParam>
<IsAvailMethod>DeviceFeatureIsAvailableIsAvailMethod>
<SetValueMethod>DeviceFeatureFromStringSetValueMethod>
<FeatureName>EnumEntry_TriggerSelector_FrameStartFeatureName>
<Name>TriggerSelectorName>
<Value>FrameStartValue>
CameraParam>
CameraParams>
CameraSetData>
ArrayOfCameraSetData>
上面的配置主要就是针对多相机情况下的设置,需要维护对应相机Mac地址以及固定编号,参数维护只写了两个。
关于xml的解析直接XML序列化,当然需要对应的实体:
///
/// 相机参数类
///
[Serializable]
public class CameraSetData
{
public string MAC { get; set; }
public string SNO { get; set; }
public uint TimeOut { get; set; }
public List<CameraParam> CameraParams { get; set; }
}
[Serializable]
public class CameraParam
{
///
/// 判断方法是否可用
///
public string IsAvailMethod { get; set; }
///
/// 相机参数值设置方法
///
public string SetValueMethod { get; set; }
public string FeatureName { get; set; }
public string Name { get; set; }
public string Value { get; set; }
}
序列化以及参数配置写法
using (FileStream fs = new FileStream(CacheHelper.cameraSettingPath, FileMode.Open, FileAccess.Read))
{
XmlSerializer xml = new XmlSerializer(typeof(List<CameraSetData>));
listSetData = (List<CameraSetData>)xml.Deserialize(fs);
}
for(int i = 0; i < Pylon.EnumerateDevices();i++)
{
PYLON_DEVICE_INFO_HANDLE deviceInfo = Pylon.GetDeviceInfoHandle(i);//取对应编号的相机信息
PYLON_DEVICE_HANDLE hDev = Pylon.CreateDeviceByIndex(i);
String mac = Pylon.DeviceInfoGetPropeertyValueByName( deviceInfo, Pylon.cPylonDeviceInfoMacAddressKey);
CameraSetData setData = listSetData.Where(a => a.Mac == mac).FirstOrDefault();
if(setData !=null)
{
if (!Pylon.DeviceIsOpen(hDev))
Pylon.DeviceOpen(hDev, Pylon.cPylonAccessModeControl | Pylon.cPylonAccessModeStream);
List<CameraParam> listParam = setData.CameraParams;
for(int j = 0; j < listParam.Count; j++)
{
isAvail = Pylon.DeviceFeatureIsAvailable(hDev, listParam[j].FeatureName);
if(isAvail)
{
Pylon.DeviceFeatureFromString(hDev, listParam[j].Name, listParam[j].Value);
}
}
}
}
上面的是简写,实际情况还要区分一些属性判断,因为属性也分在大类里。
然后要讲下我在实际项目中遇到的一个坑,这个坑就是参数没有设置好的原因
就如下图,图片返回始终丢帧,也就是在程序实际采集中一直无法正确的grabbed,最后用驱动一直慢慢调慢慢调才找到是因为pacekt size 和delpoy参数没有设置好。因为数据传输层是走网卡的,当多个相机同时连接在一个交换机的时候它们的包参数就要适当的修改下,而且对应于不同的交互机不同的网卡参数往往还不一样。
当按照设定的配置文件需要连接多个相机时,可以启一个连接检查线程,用来检测还未连接上的相机。如果一直有相机未连接上那么这个线程就随程序一直运行着直到这个相机上线为止。(不过没有写线程守护机制哈)配置检测都是用相机的Mac地址。
static List<CameraSetData> listSetData = new List<CameraSetData>();
static List<PYLONDEVICE> listDevices = new List<PYLONDEVICE>();
public static void OpenCamera()
{
lock(objLock)
{
listSetData.Clear();//所配置相机集合
listDevices.Clear();//所检测到的相机集合
}
//获取配置相机集合
using (FileStream fs = new FileStream(CacheHelper.cameraSettingPath, FileMode.Open, FileAccess.Read))
{
XmlSerializer xml = new XmlSerializer(typeof(List<CameraSetData>));
listSetData = (List<CameraSetData>)xml.Deserialize(fs);
}
Pylon.Initialize();//驱动初始化
int numDevices = Pylon.EnumerateDevices(); //获取当前网域下相机连接数
if(numDevices != listSetData.Count)
{
Thread.Sleep(10000);
numDevices = Pylon.EnumerateDevices();
}
for (uint i = 0; i < numDevices; i++)
{
PYLON_DEVICE_HANDLE hDev = Pylon.CreateDeviceByIndex(i);
PYLON_DEVICE_INFO_HANDLE deviceInfo = Pylon.GetDeviceInfoHandle(i);
SetCameraSetting(hDev, deviceInfo, true);//这个方法就是细节处理相机,比如上面写的参数设置等
}
if(listDevices.Count < listSetData.Count)
{
Thread tCheck = new Thread(() =>
{
CheckCameras();
});
tCheck.IsBackground = true;
tCheck.Start();
}
}
private static void CheckCameras()
{
PYLON_DEVICE_INFO_HANDLE deviceInfo = null;
string mac = string.Empty;
PYLON_DEVICE_HANDLE hDev = null;
PYLONDEVICE deviceTemp = null;
unint num;
while(true)
{
try
{
num = Pylon.EnumerateDevices();
for(uint i = 0;i<num;i++)
{
deviceinfo = Pylon.GetDeviceInfoHandle(i);
mac = Pylon.DeviceInfoGetPropertyValueByName(deviceInfo,Pylon.cPylonDeviceInfoMacAddressKey);
hDev = Pylon.CreateDeviceByIndex(i);// 创建设备
deviceTemp = listDevices.Where(a => a.MAC == mac).FirstOrDefault();
if(deviceTemp == null)
{
SetCameraSetting(hDev,deviceInfo,true);
}
}
if(num == listSetData.Count)
break;//检测到的相机都在线了
}
catch(Exception)
{
}
finally
{
deviceInfo = null;
mac = string.Empty
hDev = null;
deviceTemp = null;
GC.Collect();
}
Thread.Sleep(1000);
}
}
下面这个方法就是SetCameraSetting 里面添加了断线重连机制
private static void SetCameraSetting(PYLON_DEVICE_HANDLE hDev, PYLON_DEVICE_INFO_HANDLE deviceInfo, bool isAdd)
{
String mac = Pylon.DeviceInfoGetPropertyValueByName(deviceInfo, Pylon.cPylonDeviceInfoMacAddressKey);
CameraSetData setData = listSetData.Where(a => a.MAC == mac).FirstOrDefault();
if (setData != null)
{
lock (objLock)
{
listDevices.Add(device);
}
DeviceCallbackHandler cb = new DeviceCallbackHandler();//重连操作
cb.CallbackEvent += cb_CallbackEvent;//注册重连事件
Pylon.DeviceRegisterRemovalCallback(hDev, cb);//重连注册的回调
}
//这里还应该开个线程去采图,也就是每个相机事件都要开个采图线程去监听有没有图片返回
}
static void cb_CallbackEvent(PYLON_DEVICE_HANDLE hDev)
{
Pylon.DeviceClose(hDev);
Pylon.DestroyDevice(hDev);
PYLONDEVICE devTemp = listDevices.Where(a => a.Device == hDev).FirstOrDefault();
if (devTemp != null)
{
lock (objLock)
{
listDevices.Remove(devTemp);
}
}
//再次开启检测线程
Thread tCheck = new Thread(() =>
{
CheckCameras();
});
tCheck.IsBackground = true;
tCheck.Start();
}
上面这部分只是相机业务,实际项目中可能会综合整个框架去着手,例如状态监控,例如上位操作等等。。。以后有机会了说下。
代码上传地址:多个Basler相机连接测试