Baumer工业相机
Baumer工业相机堡盟相机是一种高性能、高质量的工业相机,可用于各种应用场景,如物体检测、计数和识别、运动分析和图像处理。
Baumer的万兆网相机拥有出色的图像处理性能,可以实时传输高分辨率图像。此外,该相机还具有快速数据传输、低功耗、易于集成以及高度可扩展性等特点。
Baumer工业相机的BGAPI SDK可以在回调函数里实现高速存储图像到本地的功能,在C++的环境下,先将Buffer图像数据转换为Gdiplus::Bitmap,然后将Gdiplus::Bitmap保存在图像数组并写入电脑内存,最后释放保存到本地,实现高速采集的一种功能。
Baumer工业相机技术背景
Baumer工业相机的BGAPI SDK可以提供相机的图像原始数据,通过原始数据的快速保存可以实现相机的高速采集,适用于高速等级的工业视觉检测。
工业相机的SDK(Software Development Kit)是为了方便开发人员对工业相机进行控制和图像采集而提供的一套软件工具。而Halcon是一款强大的机器视觉软件,能够进行图像处理、分析、识别等多种任务。
有关于Baumer工业相机的全帧率存储问题,之前已经有相关的技术博客可以参考:
Baumer万兆网工业相机堡盟相机VLXT-28M.I如何做全帧率图像存储到本地
这里主要描述如何在C++的平台下实现对应的功能的核心代码,由于C++图像数据为指针数据,因此需要先将图像数据转换为Gdiplus::Bitmap,存入Gdiplus::Bitmap数组,再最后进行释放。
代码分析
本文介绍使用BGAPI SDK对Baumer工业相机进行开发时,使用回调函数BufferEvent进行图像先进行图像转换再保存在本地内存的方式进行高速存储的功能。
第一步:先注册SDK回调函数BufferEvent
C++环境下注册回调函数BufferEvent库代码如下所示:
//为相机对应数据流注册回调函数事件 m_pDataStream->RegisterNewBufferEvent(BGAPI2::Events::EVENTMODE_EVENT_HANDLER); m_pDataStream->RegisterNewBufferEventHandler(this,(Events::NewBufferEventHandler)&BufferHandler);
第二步:在回调函数里进行Buffer图像转换为Gdiplus::Bitmap图像
后续进行图像转换为Gdiplus::Bitmap图像的核心代码,如下所示:
//图像回调函数 //================== void BGAPI2CALL BufferHandler( void * callBackOwner, Buffer * pBufferFilled ) { CGigeDemoDlg* pDlg = (CGigeDemoDlg*)callBackOwner; unsigned char* imagebuffer = NULL; USES_CONVERSION; try { if(pBufferFilled == NULL) { } else if(pBufferFilled->GetIsIncomplete() == true) { // queue buffer again pBufferFilled->QueueBuffer(); } else { pDlg->FrameID= pBufferFilled->GetFrameID(); //获取当前图像FrameID显示帧率 int width = 0, height = 0; width = (int)pBufferFilled->GetWidth();height = (int)pBufferFilled->GetHeight(); //获取当前图像像素长宽 CString PixelFormat1 = (CString)pBufferFilled->GetPixelFormat(); //获取当前图像像素格式 imagebuffer = (BYTE*)((bo_int64)pBufferFilled->GetMemPtr()+pBufferFilled->GetImageOffset());//获取当前图像数据 #pragma region //保存图像功能已停用(抓拍功能) if(pDlg->m_bSaveImage &&!pDlg->m_strDirectory.IsEmpty()) { /*CTime time = CTime::GetCurrentTime(); CString strtime; strtime.Format(_T("\\%4d%2d%2d%2d%2d%2d"),time.GetYear(),time.GetMonth(),time.GetDay(),time.GetHour(),time.GetMinute(),time.GetSecond()); CString strpath = pDlg->m_strDirectory+strtime+"-"; CString strpath2; strpath2.Format(_T("%s%d%s"),strpath,pDlg->FrameID,_T(".jpg")); pDlg->FullFrameSaveImageName.Add(strpath2); pDlg->SaveImageMono(strpath2, imagebuffer,width,height); pDlg->m_bSaveImage = false;*/ } #pragma endregion #pragma region //保存图像功能 CString strpath2; if(pDlg->m_bSaveImage) { CTime time = CTime::GetCurrentTime(); CString strtime1; strtime1.Format(_T("\\%4d%2d%2d%2d%2d%2d"),time.GetYear(),time.GetMonth(),time.GetDay(),time.GetHour(),time.GetMinute(),time.GetSecond()); SYSTEMTIME st; GetSystemTime(&st); // 获取系统时间,精确到毫秒 int msecond = st.wMilliseconds; // 获取毫秒数 CString strtime; strtime.Format(_T("\\%s-%d"),strtime1,msecond); CString strpath = pDlg->m_strDirectory+strtime+"-"; //保存图像名称 int FrameidNum = pDlg->FrameID; strpath2.Format(_T("%s%d%s"),strpath,FrameidNum,_T(".jpg")); pDlg->FullFrameSaveImageName.Add(strpath2); } #pragma endregion Gdiplus::Rect rc = Gdiplus::Rect(0,0,width,height); #pragma region 黑白相机代码:像素格式为mono时转Bitmap的代码,彩色相机此处代码不同 if(pDlg->m_pBitmap == NULL) { pDlg->m_pBitmap = new Gdiplus::Bitmap(width,height,PixelFormat8bppIndexed); } Gdiplus::BitmapData lockedbits; Gdiplus::ColorPalette * pal = (Gdiplus::ColorPalette*)new BYTE[sizeof(Gdiplus::ColorPalette)+255*sizeof(Gdiplus::ARGB)]; pal->Count=256; for(UINT i=0;i<256;i++) { UINT color=i*65536+i*256+i; color= color|0xFF000000; pal->Entries[i]=color; } pDlg->m_pBitmap->SetPalette(pal); Gdiplus::Status ret = pDlg->m_pBitmap->LockBits(&rc,Gdiplus::ImageLockModeWrite,PixelFormat8bppIndexed,&lockedbits); BYTE* pixels = (BYTE*)lockedbits.Scan0; BYTE* src = (BYTE*)imagebuffer; for (int row = 0; row < height; ++row) { CopyMemory(pixels, src, lockedbits.Stride); pixels += width; src += width; } pDlg->m_pBitmap->UnlockBits(&lockedbits); if(pDlg->m_bSaveImage) { //复制图像到新的指针空间 Gdiplus::Bitmap* targetBitmap = pDlg->m_pBitmap->Clone(0, 0, pDlg->m_pBitmap->GetWidth(), pDlg->m_pBitmap->GetHeight(), pDlg->m_pBitmap->GetPixelFormat()); //将新指针图像保存到图像数组 pDlg->bitmapArray[ImageSaveCount] = targetBitmap; 测试保存 //CLSID pngClsid; //GetEncoderClsid(L"image/png", &pngClsid); // 获取 PNG 编码器的 CLSID //targetBitmap->Save(strpath2, &pngClsid, NULL); // 保存为 PNG 格式的图像 ImageSaveCount++; int TestNum = pDlg->FullFrameSaveImageName.GetCount(); int timestamp1 = pBufferFilled->GetTimestamp(); CTime time0 = CTime(timestamp1); // 将时间戳转换为CTime对象 if(TestNum==500) { currentTimeEnd = CTime::GetCurrentTime(); // 计算时间差 CTimeSpan timeDiff = currentTimeEnd - currentTimeStart; double seconds = timeDiff.GetTotalSeconds()*1000; // 获取时间间隔的总秒数 pDlg->m_bSaveImage = false; //控制内存保存图像指令 pDlg->ControlSaveImage = true; //pDlg->ReleaseImageFromMemory(); } } #pragma endregion #pragma region //将图像显示在PictureControl控件上,在高速存储时将图像显示界面将会导致Buffer出现覆盖现象,从而会出现FrameID不连续 /*HDC hDC = ::GetDC(pDlg->m_stcPicture.m_hWnd); Gdiplus::Graphics GdiplusDC(hDC); CRect rcControl; pDlg->m_stcPicture.GetWindowRect(&rcControl); Gdiplus::Rect rtImage(0,0,rcControl.Width(),rcControl.Height()); GdiplusDC.DrawImage(pDlg->m_pBitmap,rtImage,0,0,width,height, Gdiplus::UnitPixel);*/ /*delete []pal; ::ReleaseDC(pDlg->m_stcPicture.m_hWnd,hDC);*/ delete pDlg->m_pBitmap ; pDlg->m_pBitmap =NULL; #pragma endregion // queue buffer again pBufferFilled->QueueBuffer(); } } catch (BGAPI2::Exceptions::IException& ex) { CString str; str.Format(_T("ExceptionType:%s! ErrorDescription:%s in function:%s"),ex.GetType(),ex.GetErrorDescription(),ex.GetFunctionName()); } }
第三步:在线程中将回调函数里保存的Gdiplus::Bitmap图像数组释放保存
核心代码如下所示:
void CGigeDemoDlg::OnBnClickedCheck1() { //开始全帧率保存图片 int state =((CButton *)GetDlgItem(IDC_CHECK1))->GetCheck(); if(state==1) { m_pDevice->GetRemoteNode("TriggerMode")->SetString("Off"); //关闭触发模式,进入自由采集图片流模式 m_pDevice->GetRemoteNode("AcquisitionStart")->Execute(); bitmapArray = new Gdiplus::Bitmap*[500]; ImageSaveCount = 0; iOperCount = 0; ControlSaveImage = false; FullFrameSaveImageName.RemoveAll(); #pragma region 线程显示帧率和网口数据通量(做参考) AfxBeginThread(FullFrameSave_hThread1, (void*)this); #pragma endregion } } UINT CGigeDemoDlg::FullFrameSave_hThread1(LPVOID pParam) { CGigeDemoDlg *dlg = (CGigeDemoDlg *)pParam; dlg->FullFrameSave(); return 0; } CString strtimeStart; void CGigeDemoDlg::FullFrameSave() { try { bool m_bRun0 = true; while (m_bRun0) { //m_bSaveImage = true; iOperCount++; CTime time = CTime::GetCurrentTime(); if(iOperCount==1) { strtimeStart.Format(_T("\\%4d%2d%2d%2d%2d%2d"),time.GetYear(),time.GetMonth(),time.GetDay(),time.GetHour(),time.GetMinute(),time.GetSecond()); currentTimeStart = CTime::GetCurrentTime(); m_bSaveImage = true; } if(iOperCount>1) { strtimeStart.Format(_T("\\%4d%2d%2d%2d%2d%2d"),time.GetYear(),time.GetMonth(),time.GetDay(),time.GetHour(),time.GetMinute(),time.GetSecond()); } if(ControlSaveImage) { ReleaseImageFromMemory(); m_bRun0 = false; } } ((CButton *)GetDlgItem(IDC_CHECK1))->SetCheck(0); } catch (int e) { MessageBox(_T("Camera FullFrameSaveimage Error")); } } //释放保存内存中图像资源 void CGigeDemoDlg::ReleaseImageFromMemory() { Gdiplus::Bitmap* m_pBitmapCur; CLSID pngClsid; GetEncoderClsid(L"image/png", &pngClsid); // 获取 PNG 编码器的 CLSID for (int i = 0; i < 500; i++) { CString CurStringname = FullFrameSaveImageName.GetAt(i); m_pBitmapCur = bitmapArray[i]; m_pBitmapCur->Save(CurStringname, &pngClsid, NULL); // 保存为 PNG 格式的图像 } delete m_pBitmapCur; //delete[] bitmapArray; ((CButton *)GetDlgItem(IDC_CHECK1))->SetCheck(0); MessageBox(_T("Camera FullFrameSaveimage OK")); }
工业相机图像保存电脑内存的方式存储的优点
将工业相机图像存储在计算机内存中,而不是传统的存储形式,如胶片或磁带,有几个优点:
更快的访问: 检索存储在计算机内存中的图像可以立即完成,而从胶片或磁带中寻找和检索图像可能是费时和乏味的。
提高图像质量: 存储在计算机内存中的数字图像不会像胶片那样受到物理损坏或老化的影响,从而可以获得更好的图像质量并保持原始图像的完整性。
更容易分享: 数字图像可以很容易地以数字方式分享和传输到其他设备或在互联网上,使之更容易与他人合作开展工业项目。
增加存储容量: 与传统的存储形式相比,计算机内存允许更大的存储容量。工业相机图像可以很容易地存储在硬盘、固态驱动器和USB闪存驱动器上。
成本效益高: 虽然购买计算机内存存储设备的初始成本可能高于传统的存储方式,但由于不需要物理存储空间,维护和管理数字图像的长期成本往往更低。