1. 程式人生 > >DALSA線陣CCD開發紀要(C++)

DALSA線陣CCD開發紀要(C++)

應用背景:晶體表面疵病工業檢測,導軌運動的光柵尺反饋系統產生的脈衝用於外觸發Dalsa相機進行影象採集。

解決問題:Dalsa線陣CCD直接採集的影象是當前一行的影象,配套的採集卡中用於儲存影象的緩衝區有限,當平臺連續長距離運動時,如果不及時讀取緩衝區的影象,新採集的影象將覆蓋之前採集的影象。

閱讀Dalsa相機的開發文件中的繼承圖,如下:

我們最為關心的是緩衝區的內容SapBuffer和將採集內容轉運到緩衝區的SapAcqToBuf,細心一點的話還能看到採集內容轉運到緩衝區的回撥函式的Info。

檢視官方提供的一些開發Demo

// Transfer callback function is called each time a complete frame is transferred.
// The function below is a user defined callback function.
 

void XferCallback(SapXferCallbackInfo *pInfo)
{
   // Display the last transferred frame
   SapView *pView = (SapView *) pInfo->GetContext();
   pView->Show();
}
// Example program
//
main()
{
   // Allocate acquisition object
   SapAcquisition *pAcq =
      new SapAcquisition(SapLocation (“X64-CL_1”, 0), “MyCamera.ccf”);
 
   // Allocate buffer object, taking settings directly from the acquisition
   SapBuffer *pBuffer = new SapBuffer(1, pAcq);
 
   // Allocate view object, images will be displayed directly on the desktop
   SapView *pView = new SapView(pBuffer, SapHwndDesktop);
 
   // Allocate transfer object to link acquisition and buffer
   SapTransfer *pTransfer = new SapTransfer(XferCallback, pView);
   pTransfer->AddPair(SapXferPair(pAcq, pBuffer));
 
   // Create resources for all objects
   BOOL success = pAcq->Create();
   success = pBuffer->Create();
   success = pView->Create();
   success = pTransfer->Create();
 
 
   // Start a continuous transfer (live grab)
   success = pTransfer->Grab();
   printf("Press any key to stop grab\n");
   getch();
 
 
 
 
   // Stop the transfer and wait (timeout = 5 seconds)
   success = pTransfer->Freeze();
   success = pTransfer->Wait(5000);
   printf("Press any key to terminate\n");
   getch();
 
   // Release resources for all objects
   success = pTransfer->Destroy();
   success = pView->Destroy();
   success = pBuffer->Destroy();
   success = pAcq->Destroy();
 
   // Free all objects
   delete pTransfer;
   delete pView;
   delete pBuffer;
   delete pAcq;
 
   return 0;
}

不難發現:

 

首先需要建立Acquisition,這裡有裝置資訊,需要採集的影象資訊和設定(如影象寬度,高度),亦可通過官方SDK自帶的GUI對話方塊讀取配置檔案很快鍵地得到。

其次建立我們想要的緩衝區,由於我們的基本配置資訊已經通過Acquisition得到,因此在SapBuffer的眾多過載函式中選取了

SapBuffer(
int count,
SapXferNode* pSrcNode,
SapBuffer::Type type = SapBuffer::TypeScatterGather,
SapLocation loc = SapLocation::ServerSystem
);

其中count為緩衝區的數目,它們顯然具有同配置檔案中影象的大小,資料格式。SapXferNode為SapAcquisition的父類,直接傳遞Acquisition的指標即可,後面採用預設的引數。

(接下來是用於顯示影象的SapView類,將緩衝區與用於顯示的控制元件的視窗控制代碼繫結起來,就可以將影象顯示在指定的控制元件上。這個與採集過程關係不大,但視覺化這個,大家懂得。)

接下來是比較重要的步驟,建立了從影象資料採集到緩衝區的轉移步驟。由於獲取完整影象(理想情況下影象高度設定在80000,但緩衝區大小有限,設定在30000)時間較長,因此有足夠的時間將緩衝區資料讀取出來,不存在採集的速率高於轉移的速率,用不上垃圾快取區,因此使用下面的函式

SapTransfer(
SapXferCallback pCallback = NULL,
void* pContext = NULL,
SapLocation loc = SapLocation::ServerUnknown
);

第一個為回撥函式,第二個為回撥函式的上下文資訊,其實就是用於傳遞到回撥函式的引數。

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,使用預設的建立方法就好,其他定製化的方法沒必要,也不會。

仿照著寫了一個初始化的程式碼

// TODO: 在此新增額外的初始化程式碼

CAcqConfigDlg dlg(this, NULL);

if (dlg.DoModal() == IDOK)

{

// Define on-line objects

m_pAcq = new SapAcquisition(dlg.GetAcquisition());

m_pBuffer = new SapBuffer(2, m_pAcq);

m_pView = new SapView(m_pBuffer, GetDlgItem(IDC_STATIC_VIEW)->GetSafeHwnd());

m_pTransfer = new SapTransfer(XferCallback, this);

m_pTransfer->AddPair(SapXferPair(m_pAcq, m_pBuffer));


}

else

{

// Define off-line objects

m_pBuffer = new SapBuffer();

}

m_pAcq->Create();

m_pBuffer->Create();

m_pTransfer->Create();

m_pView->Create();


這裡用到了官方的GUI配置對話方塊CAcqConfigDlg,而且開啟了兩個快取區,這是因為在連續採集的過程中,如果要讀取影象需要時間,這時候只有一個緩衝區,該緩衝區的前半部分將會被新採集轉移的覆蓋,而開啟兩個緩衝區,可以將新採集影象資料的轉移到另一個緩衝區,迴圈錯開就能避免這個問題。

 

重點就是回撥函式的寫法了。上一個程式碼區主要是用來顯示新採集的影象。

我重寫個及時儲存影象的程式碼

void CDalsaCameraDlg::XferCallback( SapXferCallbackInfo *pInfo )

{

CDalsaCameraDlg* pDlg = (CDalsaCameraDlg*)(pInfo->GetContext());

int pitch = pDlg->m_pBuffer->GetPitch();


// Get the buffer data address

BYTE pData;

void* pDataAddr = &pData;

bool success = pDlg->m_pBuffer->GetAddress(staticCount, &pDataAddr);

int width = pDlg->m_pBuffer->GetWidth();

int height = pDlg->m_pBuffer->GetHeight();

Mat img = Mat::zeros(cv::Size(width, height), CV_8U);

memcpy(img.data, pDataAddr, width*height);



if (staticCount== 0)

{

imwrite("C:\\123.bmp", img);

staticCount = 1;

}

else (staticCount == 1)

{

imwrite("C:\\456.bmp", img);

staticCount = 0;

}

success = pDlg->m_pBuffer->ReleaseAddress(pDataAddr);

}

由於函調函式只能是靜態成員函式,因此無法呼叫非靜態成員函式和非靜態成員變數,我們又需要讀取緩衝區的內容,因此只能將整個類的指標傳遞到回撥函式中來,並重新生成了這個類(這個有點像太乙真人用蓮藕重造了哪吒的感覺)

 

這樣就能操縱採集到的緩衝區類SapBuffer了,利用getAddress獲取影象資料的首地址,這裡用了

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

其中index為緩衝區的序號,這樣控制序號就可以交替讀取兩個緩衝區的資料內容了。每採集到一個完整影象之後讀取一個緩衝區,而這段時間內新採集的影象儲存在下一個緩衝區,資料之間不會存在覆蓋的問題,而且能及時讀出影象,儲存在硬盤裡。

這裡使用了OpenCV影象庫來儲存影象,主要是windows自帶的Bitmap不會(囧)。

最後程式終結直接記得釋放新開闢的指標變數

void CDalsaCameraDlg::OnDestroy()

{

CDialogEx::OnDestroy();


// TODO: 在此處新增訊息處理程式程式碼

// Release and free resources for SapBuffer object


if (nullptr != m_pView)

{

m_pView->Destroy();

delete m_pView;

}

if (nullptr != m_pTransfer)

{

m_pTransfer->Destroy();

delete m_pTransfer;

}

if (nullptr != m_pBuffer)

{

m_pBuffer->Destroy();

delete m_pBuffer;

}

if (nullptr != m_pAcq)

{

m_pAcq->Destroy();

delete m_pAcq;

}

}

最好是按照開闢的順序反過來一一釋放,原因在於如SapTransfer是依賴於繫結的Acquisition和Buffer的,倘若先釋放銷燬掉Acquisition和Buffer,再銷燬SapTransfer時欲解除與Acquisition和Buffer的聯絡時,發現已經找不到這兩位了。

 

希望趕緊做我的畢設,這個Dalsa相機的開發到此結束。