项目场景:
Baumer工业相机堡盟相机传统开发包BGAPI SDK进行工业视觉软件整合时,常常需要将SDK中一些功能整合到图像处理软件中,方便项目的推进使用;
在项目的图像处理任务中,可能会因为一些硬件比如线缆网卡的原因导致出现偶尔掉线,而软件重启则可以重新连上,这时为了适用于自动化设备不停线的情况下,需要图像处理软件可以自动进行断线重新连接。
注意:本文是基于Baumer的BGAPI SDK的基础上使用C++语言来实现相机的掉线重新连接。
如何发现掉线
Baumer工业相机BGAPI SDK中在相机事件中存在一种PnpEvent事件可以监控相机是否掉线,从而能够及时发现相机的问题。
在BGAPI SDK的C++开发包里存在PnpEvent的事件例程,关键字为:013_PnPEventMode_Handler
下面为例程中PnPEvent事件代码
// CALLBACK void BGAPI2CALL PnPEventHandler(void * callbackOwner, BGAPI2::Events::PnPEvent * pPnPEvent) { if (NULL != pPnPEvent) { std::cout << std::endl; std::cout << std::endl; std::cout << " [callback of " << ((BGAPI2::Interface *)callbackOwner)->GetDisplayName() << "] "; std::cout << " EventID " << pPnPEvent->GetId() << " PnPType: " << ((pPnPEvent->GetPnPType() == 0) ? "removed" : "added ") << " SerialNumber: " << pPnPEvent->GetSerialNumber() << std::endl; std::cout << std::endl; #if defined(_WIN32) Sleep(1000); #else usleep(1000000); #endif // WARNING ACCESS IS NOT THREAD SAVE - YOU SHOULD ALWAYS USE A LOCK (LIKE MUTEX) // TO ACCESS THEM FROM DIFFERENT THREADS // This example does not use std::mutex to support old compiler without C++11 gDisplayDeviceLists = true; // enable refresh of device list display } else { std::cout << " [callback] - received invalid Interface Event! " << std::endl; } return; }
相机掉线重连实现方法:
1、在相机初始化的过程中,在相机对应的网口中注册SDK中PnPEvent回调函数
代码如下所示:
//为相机对应数据流注册掉线触发事件 m_pInterface->RegisterPnPEvent(BGAPI2::Events::EVENTMODE_EVENT_HANDLER); m_pInterface->RegisterPnPEventHandler(this, (Events::PnPEventHandler) &PnPEventHandler);
2、在PnPEvent回调函数里进行相机掉线事件的分析并对应实现相机的再次查找
注意在回调里尽量不要处理太多任务,可以从回调里发出信息,在外部处理回调发出的任务信息。本例在回调里处理任务方便参考,请大家理解。
PnPEvent回调函数代码如下所示:
//相机掉线事件 void BGAPI2CALL PnPEventHandler(void * callbackOwner, BGAPI2::Events::PnPEvent * pPnPEvent) { USES_CONVERSION; if (NULL != pPnPEvent) { AfxMessageBox(_T("Trigger Camera lost signal")); CGigeDemoDlg* pDlg = (CGigeDemoDlg*)callbackOwner; int syscount = 0; while(syscount==0) { pDlg->m_pSystemList = SystemList::GetInstance(); pDlg->m_pSystemList->Refresh(); int sysindex = 0; for(SystemList::iterator sysIterator = pDlg->m_pSystemList->begin();sysIterator != pDlg->m_pSystemList->end();sysIterator++) { CString sysDisplay0 = A2W(sysIterator->second->GetDisplayName()); CString sysModel0 = A2W(sysIterator->second->GetModel()); //bgapi_gige CString sysPathName0 = A2W(sysIterator->second->GetPathName()); if(sysModel0.Find(_T("gige"))!=-1) //此例程只连接网口Gige相机 { syscount = pDlg->m_pSystemList->size(); AfxMessageBox(_T("Camera find")); break; } sysindex++; } } if(syscount>0&!pDlg->ReConnectCheck) { pDlg->ReConnectCheck = true; pDlg->OnBnClickedBtntestremove(); //释放相机资源 //pDlg->OnBnClickedBtntestreconnect(); //重新检查连接,此处可以用message触发外部重连功能 pDlg->ReConnectCheck = false; } #if defined(_WIN32) Sleep(1000); #else usleep(1000000); #endif } else { //std::cout << " [callback] - received invalid Interface Event! " << std::endl; } return; }
3、在PnPEvent掉线发生后在回调中检查相机是否存在,若已经完全掉线,则需要释放对应的资源,防止内存泄漏,若再次发现相机,在未知掉线的情况下,建议对该相机重新初始化。
释放相机资源代码如下所示:
void CGigeDemoDlg::OnBnClickedBtntestremove() { //设置相机移除功能:该Demo只移除当前连接的相机 StopShowFrame_hThread1();//停止UI上帧率和网口数据通量的显示 #pragma region try { try { if (m_pDevice != NULL) m_pDevice->GetRemoteNode("AcquisitionStop")->Execute();//发送相机停止采集命令 } catch (BGAPI2::Exceptions::IException& ex) {} try { if (m_pDataStream != NULL) { m_pDataStream->StopAcquisition();/停止采集相机数据流 m_pBufferList->DiscardAllBuffers();//释放输入缓冲区队列和输出缓冲区队列中的所有Buffer对象。 } } catch (BGAPI2::Exceptions::IException& ex) {} if (m_pDataStream != NULL) { try { while (m_pBufferList->size() > 0) //对Bufferlist里每个buffer对象进行释放删除 { m_pBuffer = m_pBufferList->begin()->second; m_pBufferList->RevokeBuffer(m_pBuffer); //对当前buffer对象进行释放 delete m_pBuffer; //对当前buffer对象进行删除 } } catch (BGAPI2::Exceptions::IException& ex) {} try { //下面关闭步骤和初始化相机正好相反 m_pDataStream->Close(); //关闭相机数据流 m_pDevice->Close(); //关闭相机本身 m_pInterface->Close(); //关闭相机对应网口 m_pSystem->Close(); //关闭BGAPI相机主类System //BGAPI2::SystemList::ReleaseInstance(); //释放BGAPI相机主类Systemlist资源 } catch (BGAPI2::Exceptions::IException& ex) {} try { BGAPI2::SystemList::ReleaseInstance(); //释放BGAPI相机主类Systemlist资源 } catch (BGAPI2::Exceptions::IException& ex) {} //Enable the button_inialize GetDlgItem(ID_BTNINITIALIZE)->EnableWindow(TRUE);//恢复初始化按钮 AfxMessageBox(_T("相机已经断开")); } } catch (BGAPI2::Exceptions::IException& ex) { CString Errinfo; Errinfo.Format(_T("ExceptionType:%s! ErrorDescription:%s in function:%s"),ex.GetType(),ex.GetErrorDescription(),ex.GetFunctionName()); //MessageBox(Errinfo); } #pragma endregion }
4、在对应相机释放资源后,对相机进行断线重连操作,先初始化该相机,在重新注册相机流、相机PnPEvent事件、相机图像事件等等,基本上是对相机进行重新连接。
重连函数代码如下所示:
void CGigeDemoDlg::OnBnClickedBtntestreconnect() { // 测试相机重连功能 OnBnClickedBtninitialize(); OnBnClickedBtnplay(); MessageBox(_T("相机已经重新连接")); }
5、重连操作:先初始化对应相机,在进行相机的重新采集图像。
下面的代码注释较为详细,可以作为参考。
相机初始化代码如下所示:
void CGigeDemoDlg::OnBnClickedBtninitialize() { #pragma region//初始化BGAPI采集类 USES_CONVERSION; //m_imgProcessor = new BGAPI2::ImageProcessor(); m_pSystemList = SystemList::GetInstance(); m_pSystemList->Refresh(); int syscount = m_pSystemList->size(); #pragma endregion #pragma region//循环检查获取的Systemlist序列信息:即获取网口和USB相机的系统配置文件来查询当前已连接相机 int sysindex = 0; for(SystemList::iterator sysIterator = m_pSystemList->begin();sysIterator != m_pSystemList->end();sysIterator++) { CString sysDisplay0 = A2W(sysIterator->second->GetDisplayName()); CString sysModel0 = A2W(sysIterator->second->GetModel()); //bgapi_gige CString sysPathName0 = A2W(sysIterator->second->GetPathName()); if(sysModel0.Find(_T("gige"))!=-1) //此例程只连接网口Gige相机 { //m_pSystem = (*m_pSystemList)[sysindex];//将Gige接口赋予m_pSystem m_pSystem = sysIterator->second; break; } sysindex++; } #pragma endregion #pragma region //陆续执行Gige接口查询和连接、接口上相机设备的查询和连接、相机设备的数据流的注册和连接、相机设备的信息获取和初始化开启 if(m_pSystem!=NULL) { #pragma region//测试获取Gige相机接口的相关参数(供参考) CString sysType;CString sysVersion;CString sysName;CString sysID; CString sysVendor;CString sysModel;CString sysPathName;CString sysDisplay; //USES_CONVERSION; sysName = A2W(m_pSystem->GetFileName()); sysType = A2W(m_pSystem->GetTLType()); sysVersion = A2W(m_pSystem->GetVersion()); sysID = A2W(m_pSystem->GetID()); sysVendor = A2W(m_pSystem->GetVendor()); //Baumer sysModel = A2W(m_pSystem->GetModel()); //bgapi_gige sysPathName = A2W(m_pSystem->GetPathName()); sysDisplay = A2W(m_pSystem->GetDisplayName()); #pragma endregion #pragma region //System开启后执行查询网口interface操作 if(!m_pSystem->IsOpen()) //使用IsOpen检查,可以关闭相机后重新打开初始化功能 m_pSystem->Open(); if(m_pSystem->IsOpen()) { m_pInterfaceList = m_pSystem->GetInterfaces(); m_pInterfaceList->Refresh(100); int infcount = m_pInterfaceList->size(); //可以找到多个interface网络接口(包含实际接口和虚拟接口) for(InterfaceList::iterator ifIterator = m_pInterfaceList->begin(); ifIterator != m_pInterfaceList->end();ifIterator++) { CString infType = A2W(ifIterator->second->GetTLType()); CString infName = A2W(ifIterator->second->GetDisplayName()); BGAPI2::Interface* m_pInterface0 = ifIterator->second; bool isopen1 = m_pInterface0->IsOpen(); if(m_pInterface0->IsOpen()) //检查该接口是否已经被占用使用中,被占用则检查下一个 continue; if(!m_pInterface0->IsOpen()) //打开该接口 m_pInterface0->Open(); if(m_pInterface0->IsOpen()) //接口确认开启的情况下检查接口连接的设备数量 { BGAPI2::DeviceList* m_pDeviceList0 = m_pInterface0->GetDevices(); m_pDeviceList0->Refresh(100); int devcount = m_pDeviceList0->size();//检查接口上是否连接相机设备 if(devcount>0) { BGAPI2::Device * m_pDevice0 = (*m_pDeviceList0)[0]; CString Accessstatus = A2W(m_pDevice0->GetAccessStatus());//判断相机设备是否可连接 if(Accessstatus!=_T("RW")) //"RO"代表被占用,"RW"代表可连接,"U"代表未知情况 continue; //若不可连接,则继续检查下一个接口 m_pInterface = ifIterator->second;//此处确认可连接相机连接接口后跳出循环,若要连接多个相机请自主修改 break; } } } } #pragma endregion #pragma region //打开该接口上已存在的连接相机设备:此处只连接一个相机 if(m_pInterface==NULL) //若没有可用接口连接相机设备,则返回 return; CString infName = A2W(m_pInterface->GetDisplayName());//获取接口名称 if(m_pInterface->IsOpen()) //检查网口是否开启 { m_pDeviceList = m_pInterface->GetDevices(); m_pDeviceList->Refresh(100); int devcount = m_pDeviceList->size(); //一般为1 if(devcount>0) m_pDevice = (*m_pDeviceList)[0]; if(m_pDevice!=NULL) //检查相机设备是否存在和开启 if(!m_pDevice->IsOpen()) m_pDevice->Open(); //为相机对应数据流注册掉线触发事件 m_pInterface->RegisterPnPEvent(BGAPI2::Events::EVENTMODE_EVENT_HANDLER); m_pInterface->RegisterPnPEventHandler(this, (Events::PnPEventHandler) &PnPEventHandler); } #pragma endregion #pragma region //打开该接口上已存在的相机设备并进行数据流连接 if(m_pDevice==NULL) //若接口上无可用相机设备,则返回 return; //获取相机设备名称 CString devName = A2W(m_pDevice->GetDisplayName()); bool DevicealreadyOpen = false; //相机设备数据流是否已开启 if(m_pDevice->IsOpen()) { CString infID = A2W(m_pDevice->GetID()); //获取相机设备ID m_pDatastreamList = m_pDevice->GetDataStreams();//获取相机设备数据流序列 m_pDatastreamList->Refresh(); int iDscount = m_pDatastreamList->size();//一般为1 if(iDscount>0) m_pDataStream = (*m_pDatastreamList)[0]; if(m_pDataStream!=NULL) { if(!m_pDataStream->IsOpen()) m_pDataStream->Open(); else DevicealreadyOpen =true; } } #pragma endregion #pragma region //设置相机上对应数据流的buffer序列、注册相机回调函数,初始化相机参数设置 if(m_pDataStream==NULL) //若相机设备上无可用数据流,则返回 return; if(m_pDataStream->IsOpen()&!DevicealreadyOpen) //检查相机设备是否被占用或者设备数据流是否开启 { m_pBufferList = m_pDataStream->GetBufferList();//获取相机设备数据流的Buffer序列 for(int i=0; i<6; i++) //设置的Buffer序列为4组 { m_pBuffer = new BGAPI2::Buffer(); m_pBufferList->Add(m_pBuffer); } for (BGAPI2::BufferList::iterator bfIterator = m_pBufferList->begin();bfIterator != m_pBufferList->end();bfIterator++) { bfIterator->second->QueueBuffer(); } int iQBuffer = m_pBufferList->GetQueuedCount(); int x=0; //为相机对应数据流注册回调函数事件 m_pDataStream->RegisterNewBufferEvent(BGAPI2::Events::EVENTMODE_EVENT_HANDLER); m_pDataStream->RegisterNewBufferEventHandler(this,(Events::NewBufferEventHandler) &BufferHandler); //获取当前相机的触发模式的数据 String strTrgStatus; strTrgStatus = m_pDevice->GetRemoteNode("TriggerMode")->GetString(); CString strTrgStatusC = A2W(strTrgStatus); //初始化相机时将触发模式设为Off m_pDevice->GetRemoteNode("TriggerMode")->SetString("Off"); m_pDevice->GetRemoteNodeList()->GetNodePresent("ExposureAuto"); //开启相机流 m_pDataStream->StartAcquisitionContinuous(); //将相机名称显示在对应编辑框内 m_edtCameraType.SetWindowTextW(devName); //将相机的曝光值和增益值显示在界面上 if (m_pDevice->GetRemoteNodeList()->GetNodePresent("ExposureTime")) { long iShutter;CString strShutter; float fGain;CString strGain; iShutter = (long)m_pDevice->GetRemoteNode("ExposureTime")->GetDouble(); strShutter.Format(_T("%ld"),iShutter); fGain = (float)m_pDevice->GetRemoteNode("Gain")->GetDouble(); strGain.Format(_T("%.2f"),fGain); //将相机曝光值显示界面上 m_edtShutter.SetWindowTextW(strShutter); //将相机增益值显示界面上 m_edtGain.SetWindowText(strGain); } //Enable the button_inialize GetDlgItem(ID_BTNINITIALIZE)->EnableWindow(FALSE); //AfxMessageBox(devName+_T("已连接")); } #pragma endregion } else { AfxMessageBox(_T("未找到可用的相机接口")); } #pragma endregion }
相机连接代码如下所示:
void CGigeDemoDlg::OnBnClickedBtnplay() { // TODO: 在此添加控件通知处理程序代码 USES_CONVERSION; if(m_pDevice != NULL) { try { m_pDevice->GetRemoteNode("TriggerMode")->SetString("Off"); //关闭触发模式,进入自由采集图片流模式 m_pDevice->GetRemoteNode("AcquisitionStart")->Execute(); #pragma region 线程显示帧率和网口数据通量(做参考) m_bRun0 = true; //AfxBeginThread(ShowFrame_hThread1, (void*)this); #pragma endregion } catch (BGAPI2::Exceptions::IException& ex) { CString str1; str1.Format(_T("ExceptionType:%s! ErrorDescription:%s in function:%s"),ex.GetType(),ex.GetErrorDescription(),ex.GetFunctionName()); MessageBox(str1); } } }
6、完成上面操作后,就可以及时对掉线相机进行断线重连操作,可以尝试重新连接,也可以检查对应相机状态,需要对应掉线事件操作什么,取决于开发者想要什么的功能实现。
结论要点
工业相机的重连机制依托于相机的PnPEvent事件触发,并且出现PnPEvent事件后可以触发一个间隔1-2s的线程,反复刷新对应网口interface的相机,找到相机后,然后线程终止,然后再触发一个重新连接的功能或者方法。
PnPEvent事件类型有移除和增加两种类型,可以针对两种类型做相应的自定义处理。
事件类型如下图所示:
并且在刷新工业相机List的过程中可以根据类的构成和习惯有所不同,比如锁定好接口,方便快速只刷新这个接口,从而适用于多相机的系统。
注意
工业相机的重连功能基本上是属于软件层面的重新连接,若是涉及到硬件层面的变更和重连则需要更为严格的对象设计和逻辑优化。