DALSA线阵CCD开发纪要(C++)-- 缓冲区读

908 阅读7分钟

blog.csdn.net/leo_888/art…

blog.csdn.net/lyc_daniel/… 基于Qt的Sapera LT开发

应用背景:晶体表面疵病工业检测,导轨运动的光栅尺反馈系统产生的脉冲用于外触发Dalsa相机进行图像采集。

解决问题:Dalsa线阵CCD直接采集的图像是当前一行的图像,配套的采集卡中用于存储图像的缓冲区有限,当平台连续长距离运动时,如果不及时读取缓冲区的图像,新采集的图像将覆盖之前采集的图像。

阅读Dalsa相机的开发文档中的继承图,如下:

我们最为关心的是缓冲区的内容SapBuffer和将采集内容转运到缓冲区的SapAcqToBuf,细心一点的话还能看到采集内容转运到缓冲区的回调函数的Info。

查看官方提供的一些开发Demo

 
  1. // Transfer callback function is called each time a complete frame is transferred.
  2. // The function below is a user defined callback function.
  3.  
  4.  
  5. void XferCallback(SapXferCallbackInfo *pInfo)
  6. {
  7.    // Display the last transferred frame
  8.    SapView *pView = (SapView *) pInfo->GetContext();
  9.    pView->Show();
  10. }
  11. // Example program
  12. //
  13. main()
  14. {
  15.    // Allocate acquisition object
  16.    SapAcquisition *pAcq =
  17.       new SapAcquisition(SapLocation (“X64-CL_1”, 0), “MyCamera.ccf”);
  18.  
  19.    // Allocate buffer object, taking settings directly from the acquisition
  20.    SapBuffer *pBuffer = new SapBuffer(1, pAcq);
  21.  
  22.    // Allocate view object, images will be displayed directly on the desktop
  23.    SapView *pView = new SapView(pBuffer, SapHwndDesktop);
  24.  
  25.    // Allocate transfer object to link acquisition and buffer
  26.    SapTransfer *pTransfer = new SapTransfer(XferCallback, pView);
  27.    pTransfer->AddPair(SapXferPair(pAcq, pBuffer));
  28.  
  29.    // Create resources for all objects
  30.    BOOL success = pAcq->Create();
  31.    success = pBuffer->Create();
  32.    success = pView->Create();
  33.    success = pTransfer->Create();
  34.  
  35.  
  36.    // Start a continuous transfer (live grab)
  37.    success = pTransfer->Grab();
  38.    printf("Press any key to stop grab\n");
  39.    getch();
  40.  
  41.  
  42.  
  43.  
  44.    // Stop the transfer and wait (timeout = 5 seconds)
  45.    success = pTransfer->Freeze();
  46.    success = pTransfer->Wait(5000);
  47.    printf("Press any key to terminate\n");
  48.    getch();
  49.  
  50.    // Release resources for all objects
  51.    success = pTransfer->Destroy();
  52.    success = pView->Destroy();
  53.    success = pBuffer->Destroy();
  54.    success = pAcq->Destroy();
  55.  
  56.    // Free all objects
  57.    delete pTransfer;
  58.    delete pView;
  59.    delete pBuffer;
  60.    delete pAcq;
  61.  
  62.    return 0;
  63. }

不难发现:

 

首先需要建立Acquisition,这里有设备信息,需要采集的图像信息和设置(如图像宽度,高度),亦可通过官方SDK自带的GUI对话框读取配置文件很快键地得到。

其次建立我们想要的缓冲区,由于我们的基本配置信息已经通过Acquisition得到,因此在SapBuffer的众多重载函数中选取了

 
  1. SapBuffer(
  2. int count,
  3. SapXferNode* pSrcNode,
  4. SapBuffer::Type type = SapBuffer::TypeScatterGather,
  5. SapLocation loc = SapLocation::ServerSystem
  6. );

其中count为缓冲区的数目,它们显然具有同配置文件中图像的大小,数据格式。SapXferNode为SapAcquisition的父类,直接传递Acquisition的指针即可,后面采用默认的参数。

(接下来是用于显示图像的SapView类,将缓冲区与用于显示的控件的窗口句柄绑定起来,就可以将图像显示在指定的控件上。这个与采集过程关系不大,但可视化这个,大家懂得。)

接下来是比较重要的步骤,建立了从图像数据采集到缓冲区的转移步骤。由于获取完整图像(理想情况下图像高度设置在80000,但缓冲区大小有限,设置在30000)时间较长,因此有足够的时间将缓冲区数据读取出来,不存在采集的速率高于转移的速率,用不上垃圾缓存区,因此使用下面的函数

 
  1. SapTransfer(
  2. SapXferCallback pCallback = NULL,
  3. void* pContext = NULL,
  4. SapLocation loc = SapLocation::ServerUnknown
  5. );

第一个为回调函数,第二个为回调函数的上下文信息,其实就是用于传递到回调函数的参数。

SDK中对此有一段话解答了我对这种线阵CCD图像和采集卡的使用方式:之前一直误以为一个Acquisition是获取当前一帧图像(即一次曝光获取的图像,一行图像),而事实上是已经拼接成整张图像(设置信息中的图像宽度和高度)。

 

By default, regular and trash buffer callback functions are called at each end of frame event, that is, when a complete image has been transferred.

SDK中特别指出

If you use this class , you must use the AddPair method to add transfer pairs of source and destination nodes. You must do this before calling the Create method.

所以后面紧接着写了addPair,将缓存区与采集区绑定起来。

接下里就是他们各自的创建了Create,使用默认的创建方法就好,其他定制化的方法没必要,也不会。

仿照着写了一个初始化的代码

 
  1. // TODO: 在此添加额外的初始化代码
  2.  
  3. CAcqConfigDlg dlg(this, NULL);
  4.  
  5. if (dlg.DoModal() == IDOK)
  6.  
  7. {
  8.  
  9. // Define on-line objects
  10.  
  11. m_pAcq = new SapAcquisition(dlg.GetAcquisition());
  12.  
  13. m_pBuffer = new SapBuffer(2, m_pAcq);
  14.  
  15. m_pView = new SapView(m_pBuffer, GetDlgItem(IDC_STATIC_VIEW)->GetSafeHwnd());
  16.  
  17. m_pTransfer = new SapTransfer(XferCallback, this);
  18.  
  19. m_pTransfer->AddPair(SapXferPair(m_pAcq, m_pBuffer));
  20.  
  21.  
  22. }
  23.  
  24. else
  25.  
  26. {
  27.  
  28. // Define off-line objects
  29.  
  30. m_pBuffer = new SapBuffer();
  31.  
  32. }
  33.  
  34. m_pAcq->Create();
  35.  
  36. m_pBuffer->Create();
  37.  
  38. m_pTransfer->Create();
  39.  
  40. m_pView->Create();


这里用到了官方的GUI配置对话框CAcqConfigDlg,而且开启了两个缓存区,这是因为在连续采集的过程中,如果要读取图像需要时间,这时候只有一个缓冲区,该缓冲区的前半部分将会被新采集转移的覆盖,而开启两个缓冲区,可以将新采集图像数据的转移到另一个缓冲区,循环错开就能避免这个问题。

 

重点就是回调函数的写法了。上一个代码区主要是用来显示新采集的图像。

我重写个及时保存图像的代码

 
  1. void CDalsaCameraDlg::XferCallback( SapXferCallbackInfo *pInfo )
  2.  
  3. {
  4.  
  5. CDalsaCameraDlg* pDlg = (CDalsaCameraDlg*)(pInfo->GetContext());
  6.  
  7. int pitch = pDlg->m_pBuffer->GetPitch();
  8.  
  9.  
  10. // Get the buffer data address
  11.  
  12. BYTE pData;
  13.  
  14. void* pDataAddr = &pData;
  15.  
  16. bool success = pDlg->m_pBuffer->GetAddress(staticCount, &pDataAddr);
  17.  
  18. int width = pDlg->m_pBuffer->GetWidth();
  19.  
  20. int height = pDlg->m_pBuffer->GetHeight();
  21.  
  22. Mat img = Mat::zeros(cv::Size(width, height), CV_8U);
  23.  
  24. memcpy(img.data, pDataAddr, width*height);
  25.  
  26.  
  27.  
  28. if (staticCount== 0)
  29.  
  30. {
  31.  
  32. imwrite("C:\\123.bmp", img);
  33.  
  34. staticCount = 1;
  35.  
  36. }
  37.  
  38. else (staticCount == 1)
  39.  
  40. {
  41.  
  42. imwrite("C:\\456.bmp", img);
  43.  
  44. staticCount = 0;
  45.  
  46. }
  47.  
  48. success = pDlg->m_pBuffer->ReleaseAddress(pDataAddr);
  49.  
  50. }

由于函调函数只能是静态成员函数,因此无法调用非静态成员函数和非静态成员变量,我们又需要读取缓冲区的内容,因此只能将整个类的指针传递到回调函数中来,并重新生成了这个类(这个有点像太乙真人用莲藕重造了哪吒的感觉)

 

这样就能操纵采集到的缓冲区类SapBuffer了,利用getAddress获取图像数据的首地址,这里用了

BOOL GetAddress(int index, void** pData);

其中index为缓冲区的序号,这样控制序号就可以交替读取两个缓冲区的数据内容了。每采集到一个完整图像之后读取一个缓冲区,而这段时间内新采集的图像存储在下一个缓冲区,数据之间不会存在覆盖的问题,而且能及时读出图像,保存在硬盘里。

这里使用了OpenCV图像库来保存图像,主要是windows自带的Bitmap不会(囧)。

最后程序终结直接记得释放新开辟的指针变量

 
  1. void CDalsaCameraDlg::OnDestroy()
  2.  
  3. {
  4.  
  5. CDialogEx::OnDestroy();
  6.  
  7.  
  8. // TODO: 在此处添加消息处理程序代码
  9.  
  10. // Release and free resources for SapBuffer object
  11.  
  12.  
  13. if (nullptr != m_pView)
  14.  
  15. {
  16.  
  17. m_pView->Destroy();
  18.  
  19. delete m_pView;
  20.  
  21. }
  22.  
  23. if (nullptr != m_pTransfer)
  24.  
  25. {
  26.  
  27. m_pTransfer->Destroy();
  28.  
  29. delete m_pTransfer;
  30.  
  31. }
  32.  
  33. if (nullptr != m_pBuffer)
  34.  
  35. {
  36.  
  37. m_pBuffer->Destroy();
  38.  
  39. delete m_pBuffer;
  40.  
  41. }
  42.  
  43. if (nullptr != m_pAcq)
  44.  
  45. {
  46.  
  47. m_pAcq->Destroy();
  48.  
  49. delete m_pAcq;
  50.  
  51. }
  52.  
  53. }

最好是按照开辟的顺序反过来一一释放,原因在于如SapTransfer是依赖于绑定的Acquisition和Buffer的,倘若先释放销毁掉Acquisition和Buffer,再销毁SapTransfer时欲解除与Acquisition和Buffer的联系时,发现已经找不到这两位了。

 

希望赶紧做我的毕设,这个Dalsa相机的开发到此结束。