天天看點

Basler Blaze-101開發實踐(2)——觸發采圖前言效果圖正文總結

目錄

  • 前言
  • 效果圖
  • 正文
    • 回調函數的注冊
    • 開啟采集
    • 線程詢問
    • 采集的關閉
    • 觸發采集模式的關閉
  • 總結

前言

這裡接上面那篇文章所完成的實時采圖的功能,這裡接下去完成觸發采圖,所謂觸發采圖,就是我主動給一個觸發信号,在相機收到我的觸發資訊進行采集圖像,我這裡實作的是采集一幀圖像,當然,也可以用回調采集多幀圖像,隻要你控制采集的頻率基本就能使圖像比較流暢,看起來像視訊了。

效果圖

Basler Blaze-101開發實踐(2)——觸發采圖前言效果圖正文總結

上面這個效果圖都是在相機沒有加光源以及沒有加鏡頭的情況下進行采集的。這裡的觸發采集會比較糊,我也不知道為啥,我自己錄起來是沒事的,但是放在CSDN就這樣了。反正圖像也還行。

正文

回調函數的注冊

首先,你先要進行回調函數的注冊,告訴相機說,你在接收到觸發信号的時候,你要調用哪個觸發函數。先給出整個函數,我們再進行詳聊:

qint32  MDeviceBlaze3d::GrabThreadStart()
{
    qint32 ret = RETURN_FAIL;

    if(m_bLoaded)
    {
#ifdef WIN_BLAZE_3D
        if(m_iFinishAcqFlag)
        {
            m_camereInstant.FinishAcquisition();
            m_iFinishAcqFlag = false;
        }
        //首先,先對四個參數進行設定
        GenApi::CEnumerationPtr ptrAcquisitionMode = m_camereInstant.GetParameter("AcquisitionMode");
        ptrAcquisitionMode->FromString("Continuous");

        GenApi::CEnumerationPtr ptrTriggerSelector = m_camereInstant.GetParameter("TriggerSelector");
        ptrTriggerSelector->FromString("FrameStart");

        GenApi::CEnumerationPtr ptrTriggerMode = m_camereInstant.GetParameter("TriggerMode");
        ptrTriggerMode->FromString("On");

        GenApi::CEnumerationPtr ptrTriggerSource = m_camereInstant.GetParameter("TriggerSource");
        ptrTriggerSource->FromString("Software");
		//對于觸發函數,你要進行準備資源,因為這部分沒有類似與GrabSingleImage,裡面那麼全的一些操作
        m_camereInstant.PrepareAcquisition(nBuffers);
        m_camereInstant.QueueBuffer(idxNextBuffer);

		//這是觸發類的注冊函數
        if(m_pGralObj&&m_fGrabCallbackEx&&m_pCallUser)
        {
            m_pGralObj->RegisterCallBackFunEx(m_fGrabCallbackEx,m_pCallUser);
            m_pGralObj->Restart();
        }
        ret = RETURN_OK;
#endif
    }
    return ret;
}
           

詳解:

  1. 首先,是對相機的四個參數進行打開,裡面的操作大家應該是比較熟悉的,這個部分在demo上面也是較為詳細的。對這四個參數,我也進行詳細的講解:
a. 下面這個是将采集模式設定為連續采集的模式,也就是相機時處于實時響應的狀态。
GenApi::CEnumerationPtr ptrAcquisitionMode = m_camereInstant.GetParameter("AcquisitionMode");
        ptrAcquisitionMode->FromString("Continuous");
           
b. 下面是對幀開始的狀态進行設定設定為幀開始。
GenApi::CEnumerationPtr ptrTriggerSelector = m_camereInstant.GetParameter("TriggerSelector");
        ptrTriggerSelector->FromString("FrameStart");
           
c. 下面這個是打開觸發模式 :
GenApi::CEnumerationPtr ptrTriggerMode = m_camereInstant.GetParameter("TriggerMode");
        ptrTriggerMode->FromString("On");
           
d.而觸發又分為軟體觸發和硬體觸發,我們這裡是開啟軟觸發。
GenApi::CEnumerationPtr ptrTriggerSource = m_camereInstant.GetParameter("TriggerSource");
        ptrTriggerSource->FromString("Software");
           
  1. 接下來進行資源的準備,這裡要設定一下隊列,demo裡面有比較詳細的講解,不然,我這裡也貼出來給大家看下,這是裡面比較重要的一個函數,基本就是圍繞這個函數進行編寫的:
int Sample::run()
{
    int result = EXIT_SUCCESS;
    const int nBuffers = 2;   // Number of buffers used per camera
    int key = 0;

    try {
        // Open and configure the cameras.
        setupToFCamera();//首先打開觸發模式為軟觸發
        setupColorCamera();

        GenApi::CCommandPtr ptrColorCameraSoftwareTrigger = m_colorCamera.GetParameter("TriggerSoftware");
        GenApi::CCommandPtr ptrToFCameraSoftwareTrigger = m_blazeCamera.GetParameter("TriggerSoftware");

        // Allocate memory buffers used for grabbing.
        m_blazeCamera.PrepareAcquisition(nBuffers);
        m_colorCamera.PrepareAcquisition(nBuffers);

        unsigned int idxNextBuffer = 0;

        // For each camera, enqueue one buffer which will receive the image data of the next image
        // the corresponding camera sends.
        m_blazeCamera.QueueBuffer(idxNextBuffer);
        m_colorCamera.QueueBuffer(idxNextBuffer);

        // Start the acquisition on the host. Now, the enqueued buffers will be filled with data sent by the cameras.
        m_blazeCamera.StartAcquisition();
        m_colorCamera.StartAcquisition();

        // The camera won't send any data until they are started.
        m_blazeCamera.IssueAcquisitionStartCommand();
        m_colorCamera.IssueAcquisitionStartCommand();

        // The cameras run in software trigger mode. They will expose and transfer a new image for
        // each software trigger they receive.
        // To optimize bandwidth usage, the color camera is triggered first to
        // allow it to already transfer image data 
        // processing the acquired raw data.while the blaze camera is still internally
        ptrColorCameraSoftwareTrigger->Execute(); // Issue software trigger for the color camera.
        ptrToFCameraSoftwareTrigger->Execute();   // Issue software trigger for the blaze camera.

        std::cout << "To exit, press 'q' in one of the image windows" << std::endl;

        // Grab loop. Acquire and process data from both cameras.
        // While the data is being processed for each camera, a new buffer will be filled in
        // the background.
        int nSuccess = 0;
        while (result == EXIT_SUCCESS) { // Process n images from each camera.
            GrabResult ToFResult;
            GrabResult ColorResult;
            // Wait up to 1000 ms until the enqueued buffers have been completely filled.
            m_blazeCamera.GetGrabResult(ToFResult, 1000);
            m_colorCamera.GetGrabResult(ColorResult, 1000);

            // Enqueue the next buffers.
            // The new buffers will be filled asynchronously in the background.
            // Each buffer will be filled with the next image received from the corresponding camera.
            idxNextBuffer++;
            idxNextBuffer %= nBuffers;
            m_blazeCamera.QueueBuffer(idxNextBuffer);
            m_colorCamera.QueueBuffer(idxNextBuffer);
            // Issue software triggers.
            ptrColorCameraSoftwareTrigger->Execute();
            ptrToFCameraSoftwareTrigger->Execute();

            // While the next buffers are acquired, process the current ones.
            if (ToFResult.status != GrabResult::Ok) {
                std::cerr << "Failed to grab image from blaze camera." << std::endl;
                if (ToFResult.status == GrabResult::Timeout) {
                    std::cerr << "Timeout occurred." << std::endl;
                }
                result = EXIT_FAILURE;
            }

            if (ColorResult.status != GrabResult::Ok) {
                std::cerr << "Failed to grab image from color camera." << std::endl;
                if (ColorResult.status == GrabResult::Timeout) {
                    std::cerr << "Timeout occurred." << std::endl;
                }
                result = EXIT_FAILURE;
            }

            if (result == EXIT_SUCCESS) {
                nSuccess++;
                // Successfully acquired the images.

                // Now process the data.
                processToF
                Data(ToFResult);
                processColorData(ColorResult);
            }

            key = cv::waitKey(1);
            if ('q' == (char)key) {
                break;
            }
        }

        // Shut down acquisition. Stops cameras and frees all grab-related resources.
        m_blazeCamera.FinishAcquisition();
        m_colorCamera.FinishAcquisition();

        // Disable software trigger.
        GenApi::CEnumerationPtr(m_blazeCamera.GetParameter("TriggerMode"))->FromString("Off");
        GenApi::CEnumerationPtr(m_colorCamera.GetParameter("TriggerMode"))->FromString("Off");

        // Clean-up
        m_blazeCamera.Close();
        m_colorCamera.Close();
    }
    catch (std::runtime_error& e)
    {
        std::cerr << "Exception occurred: " << e.what() << std::endl;
        result = EXIT_FAILURE;
    } catch (const GenICam::GenericException& e) {
        std::cerr << "Exception occurred: " << e.GetDescription() << std::endl;
        result = EXIT_FAILURE;
    }

    return result;
}
           

基本給出的這個demo的整個環節就比較全了,可以大概着看一下。

  1. 它上面的demo是直接觸發,然後進行擷取,但我們的要求是給它一個觸發信号,它能立刻響應我,是以我們這裡線上程中進行不斷的詢問,隻要有觸發信号,我們就執行采集的操作。是以,下面的這兩句代碼就是對這個線程進行注冊以及開啟這個實時詢問的程序。
m_pGralObj->RegisterCallBackFunEx(m_fGrabCallbackEx,m_pCallUser);
m_pGralObj->Restart();
           

開啟采集

這個開啟采集與之前的實時采集的那部分的開啟采集是一樣的,但裡面的一些辨別符我們這裡就可以開始講一講了。我們這裡也先給出整個函數:

{
    qint32 ret = RETURN_FAIL;
#ifdef WIN_BLAZE_3D
    QVariant triggerMode;
    this->GetParameter("TriggerMode",triggerMode);
    QString triggerMode2 = triggerMode.toString();
    try
    {
        if(m_bLoaded)
        {
            if(m_bStopWork)
            {
                m_bStopWork =false;
                m_iAcqStartNumber++;

                if((triggerMode2=="Off"&&m_iFinishAcqFlag))
                {
                    m_camereInstant.FinishAcquisition();
                }

                if(triggerMode2=="Off"&&m_iFinishAcqFlag)
                {
                    m_camereInstant.PrepareAcquisition(nBuffers);
                    m_camereInstant.QueueBuffer(idxNextBuffer);
                }

                if(triggerMode2=="Off"&&m_iGrabStopFlag&&m_iAcqStartNumber==2)
                {
                    m_camereInstant.FinishAcquisition();
                }

                m_camereInstant.StartAcquisition();
                m_camereInstant.IssueAcquisitionStartCommand();

                if(m_pConfigureObj->UpdateConfigureFromDevice())
                    m_pConfigureObj->SlotConfigChanged();
                if(m_pConfigureObj->UpdateSimplyConfigureFromDevice())
                    m_pConfigureObj->SlotSimplyConfigChanged();

                ret = RETURN_OK;
            }
        }
    }
    catch(GenICam::GenericException &e)
    {
        qDebug()<< "error: AcquisitionStart"<<e.GetDescription();
    }
#endif
    return ret;
}

           
  1. 首先,下面這句是為了防止模式切換的時候,防止前面的模式沒有對資源進行釋放,關于标志的用法,可能會依據你這幾個函數的具體執行順序而有所改變:
if((triggerMode2=="Off"&&m_iFinishAcqFlag))
 {
     m_camereInstant.FinishAcquisition();
 }
           
  1. 這個是對防止前面對資源進行finish了,但沒有進行prepare。是以做的這個操作。
if(triggerMode2=="Off"&&m_iFinishAcqFlag)
 {
     m_camereInstant.PrepareAcquisition(nBuffers);
     m_camereInstant.QueueBuffer(idxNextBuffer);
 }
           
  1. 這個是因為我的函數順序所列出的一個比較特殊的判斷條件,是以,這個應該對你們沒有太大的參考意義。
if(triggerMode2=="Off"&&m_iGrabStopFlag&&m_iAcqStartNumber==2)
{
    m_camereInstant.FinishAcquisition();
}
           
  1. 然後就是開啟抓圖了,這個前面實時圖的那部分已經講過了。就是直接開啟抓圖:

線程詢問

這部分就講一下,我寫的那個不斷進行詢問的那個線程所做的一些操作。

這裡講兩個比較主要的函數:

首先,是線程的開始運作函數:

void MGrabBlaze3d::run()
{
#ifdef WIN_BLAZE_3D
    GrabResult ToFResult;
    while (!m_bStopped)
    {
        try
        {
            qDebug()<<"m_pDevice and m_camereInstant"<<m_pDevice;
            if(m_pDevice!=Q_NULLPTR)
            {
                m_pDevice->m_camereInstant.GetGrabResult(ToFResult,1000);
                if (ToFResult.status == GrabResult::Ok) {
                    qDebug()<<"GrabResult is OK"<<ToFResult.pixelFormat;
                    idxNextBuffer++;
                    if(idxNextBuffer>1)
                    {
                        idxNextBuffer = 0;
                    }
                    onImageGrabbed(ToFResult);
                    m_pDevice->m_camereInstant.QueueBuffer(idxNextBuffer);
                }
            }

        }
        catch(QException &e)
        {
            Q_UNUSED(e)
            qDebug()<<"-->Error:MGrabBlaze3d::run() Basler Callback:";
        }
    }
#endif
    m_bStopped = true;
}
           
  1. 相信你也看到了上面的那個while循環,就是不斷的進行循環抓取的。主要是為了知道那個result裡面有沒有東西,因為隻要有觸發過來,那個result裡面就會有東西了,是以下面這個函數就是進行抓取的:

這個函數的源碼我們也稍微看一下:

void GenTLConsumerImplHelper::CGenTLCamera<ProducerName>::GetGrabResult(GrabResult& result, uint32_t timeout_ms)
{
    Internal::LockGuard lock(m_Lock);
    CIH_CHECK_INITIALIZED();
    CIH_CHECK_OPEN();
    using namespace GenTL;

    EVENT_NEW_BUFFER_DATA bufferData;

    size_t evtSize = sizeof(bufferData);
    GC_ERROR status = s_Producer.EventGetData(m_ptrGrabResources->GetNewBufferEvent(), &bufferData, &evtSize, timeout_ms);
    if (status == GC_ERR_SUCCESS)
    {
        m_ptrGrabResources->GetBuffer(bufferData.BufferHandle).setQueued(false);
        // grabbed image data is available
        FillGrabResult(result, bufferData);
    }
    else if (status != GC_ERR_TIMEOUT)
    {
        result.status = GrabResult::Failed;
    }
    else
    {
        assert(status == GC_ERR_TIMEOUT);
        // timeout occurred
        result.status = GrabResult::Timeout;
    }
}
           

裡面就是先探知有沒有幀事件,然後去抓取這個幀。

  1. 接下來,有幀過來,肯定是對幀進行處理了,傳給我的處理函數:
void MGrabBlaze3d::onImageGrabbed(GrabResult& ptrGrabResult)
{
    BufferParts parts;
    if(m_pDevice!=Q_NULLPTR)
    {
        m_pDevice->m_camereInstant.GetBufferParts(ptrGrabResult, parts);

        MFrameInfo info;
        info.nWidth  =(qint32) parts[1].width;
        info.nHeight =(qint32) parts[1].height;
        info.nFramerLen = (qint32)parts[1].size;
        info.cFormat = PFNC_Mono16;
        QImage::Format format = QImage::Format_Grayscale16;

        if((img->width() !=info.nWidth) ||
                (img->height() !=info.nHeight) ||
                (img->format() !=format) )
        {
            qDebug()<<"--> GrabBlaze3ds:onImageGrabbed reset image memory! ";
            *img = QImage(info.nWidth,info.nHeight,format);
//            if(format == QImage::Format_Indexed8)
//                img->setColorTable(m_vColorTabel);
        }
        uint16_t* pIntensity = (uint16_t*)parts[1].pData;

        if(img->byteCount() >= info.nFramerLen)
            memcpy(img->bits(),pIntensity,info.nFramerLen);
        else
            memcpy(img->bits(),pIntensity,img->byteCount());
        if(m_fGrabCallbackEx)
        {
            if(m_fGrabCallbackEx)
            {
                 m_fGrabCallbackEx(m_pCallUser,(uchar *)img->bits(),&info);
            }
            else
            {
                qDebug()<<"m_pCallUser is null";
            }
        }
        else
        {
            qDebug()<<"m_fGrabCallbackEx is null";
        }
    }
}
           

a. 首先,我們可以先看下這個函數:

這個函數就擷取幀事件裡面東西的一個函數,接下來,我們看一下源碼:

void GenTLConsumerImplHelper::CGenTLCamera<ProducerName>::GetBufferParts(const GrabResult& grabResult, BufferParts& parts)
{
    CIH_CHECK_INITIALIZED();
    CIH_CHECK_OPEN();

    using namespace GenTL;
    parts.resize(0);

    if (grabResult.payloadType != PAYLOAD_TYPE_MULTI_PART)
    {
        // Grab result doesn't represent a multi-part image, so return one single part
        parts.resize(1);
        parts[0].dataFormat = grabResult.pixelFormat;
        parts[0].pData = grabResult.pImageData;
        parts[0].size = grabResult.payloadSize;
        parts[0].partType = Intensity;
        GetBufferInfo(grabResult.hBuffer, BUFFER_INFO_WIDTH, &parts[0].width);
        GetBufferInfo(grabResult.hBuffer, BUFFER_INFO_HEIGHT, &parts[0].height);
        return;
    }

    uint32_t numParts;
    GC_ERROR status = s_Producer.DSGetNumBufferParts(m_hDataStream, grabResult.hBuffer, &numParts);
    if (status != GC_ERR_SUCCESS)
    {
        std::ostringstream s;
        throw RUNTIME_EXCEPTION(s_Producer.CreateErrorMessage(static_cast<std::ostringstream&>(s << "Failed to get number of buffer parts").str()));
    }

    parts.resize(numParts);
    for (uint32_t idx = 0; idx < numParts; ++idx)
    {
        GetBufferPartInfo(grabResult.hBuffer, idx, BUFFER_PART_INFO_BASE, &parts[idx].pData);
        GetBufferPartInfo(grabResult.hBuffer, idx, BUFFER_PART_INFO_DATA_SIZE, &parts[idx].size);
        GetBufferPartInfo(grabResult.hBuffer, idx, BUFFER_PART_INFO_WIDTH, &parts[idx].width);
        GetBufferPartInfo(grabResult.hBuffer, idx, BUFFER_PART_INFO_HEIGHT, &parts[idx].height);
        GetBufferPartInfo(grabResult.hBuffer, idx, BUFFER_PART_INFO_DATA_FORMAT, &parts[idx].dataFormat);
        GenTL::PARTDATATYPE_ID partDataTypeId(PART_DATATYPE_UNKNOWN);
        GetBufferPartInfo(grabResult.hBuffer, idx, BUFFER_PART_INFO_DATA_TYPE, &partDataTypeId);

        switch (partDataTypeId)
        {
        case PART_DATATYPE_2D_IMAGE:
            // The producer is reporting the color range map as "2D image".
            if (parts[idx].dataFormat == PFNC_RGB8)
            {
                parts[idx].partType = Range;
            }
            else
            {
                parts[idx].partType = Intensity;
            }
            break;
        case PART_DATATYPE_3D_IMAGE:
            parts[idx].partType = Range;
            break;
        case PART_DATATYPE_CONFIDENCE_MAP:
            parts[idx].partType = Confidence;
            break;
        default:
            parts[idx].partType = Undefined;
        }
    }
}
           

從上面這個源碼,我們就可以看出這個幀的很多東西都是隐藏在那個part裡面,是以,拿出裡面的東西就很重要了。

b. 接下來就是拿到part裡面的一些資訊

info.nWidth  =(qint32) parts[1].width;
info.nHeight =(qint32) parts[1].height;
info.nFramerLen = (qint32)parts[1].size;
info.cFormat = PFNC_Mono16;
           
  1. 接下來,我們為了将buffer放在QImage之中,我們需要對那個img進行一些操作:
if((img->width() !=info.nWidth) ||
               (img->height() !=info.nHeight) ||
               (img->format() !=format) )
 {
	 qDebug()<<"--> GrabBlaze3ds:onImageGrabbed reset image memory! ";
	 *img = QImage(info.nWidth,info.nHeight,format);
//            if(format == QImage::Format_Indexed8)
//                img->setColorTable(m_vColorTabel);
}
           

這裡是對當第一次進行采集的時候,需要對img進行相片格式的設定。進而得到一個可以容納那個buffer的QImage 。

  1. 接下來,就是把那個buffer的位址傳給img。然後再傳遞給回調函數就可以了。
uint16_t* pIntensity = (uint16_t*)parts[1].pData;

        if(img->byteCount() >= info.nFramerLen)
            memcpy(img->bits(),pIntensity,info.nFramerLen);
        else
            memcpy(img->bits(),pIntensity,img->byteCount());
        if(m_fGrabCallbackEx)
        {
            if(m_fGrabCallbackEx)
            {
                 m_fGrabCallbackEx(m_pCallUser,(uchar *)img->bits(),&info);
            }
            else
            {
                qDebug()<<"m_pCallUser is null";
            }
        }
        else
        {
            qDebug()<<"m_fGrabCallbackEx is null";
        }
           

記得你要稍微先判斷一下那個buffer會大一點,要按照那個大的進行傳遞,這樣才不會出現丢幀的現象。

采集的關閉

這部分是比較簡單的,就稍微貼下代碼就行。跟之前的實時采圖部分是重複的。

qint32  MDeviceBlaze3d::AcquisitionStop()
{
    qint32 ret = RETURN_FAIL;
#ifdef WIN_BLAZE_3D
    QVariant triggerMode;
    this->GetParameter("TriggerMode",triggerMode);
    QString triggerMode2 = triggerMode.toString();
    try
    {
        if(m_bLoaded)
        {
            if(!m_bStopWork)
            {
                m_bStopWork = true;
                m_iFinishAcqFlag = true;
                if(m_pGralObj!=NULL && m_pGralObj->isRunning())
                {
                    qDebug()<<"m_pGrabObj is stop";
                    m_pGralObj->Stop();
                }

                m_camereInstant.StopAcquisition();
                m_camereInstant.IssueAcquisitionStopCommand();

                if(m_pConfigureObj!=NULL)
                {
                    if(m_pConfigureObj->UpdateConfigureFromDevice())
                        m_pConfigureObj->SlotConfigChanged();
                    if(m_pConfigureObj->UpdateSimplyConfigureFromDevice())
                        m_pConfigureObj->SlotSimplyConfigChanged();
                }
                ret = RETURN_OK;
            }
        }
    }
    catch(GenICam::GenericException &e)
    {
        qDebug()<< "error: AcquisitionStop"<<e.GetDescription();
    }
#endif
    return ret;
}
           
  1. 這裡要把進行實時觀測的那個線程進行關閉,注意,不是銷毀,因為有可能還要重新啟動這個線程。
  2. 然後就是那個采集開關的關閉了。

觸發采集模式的關閉

這部分就是對相機的一些參數進行關閉:

qint32  MDeviceBlaze3d::GrabThreadStop()
{
    qint32 ret = RETURN_FAIL;

    if(m_bLoaded)
    {
#ifdef WIN_BLAZE_3D
        m_iGrabThreadStopNumber++;
        if(m_iGrabThreadStopNumber>1)
        {
            GenApi::CEnumerationPtr ptrTriggerMode = m_camereInstant.GetParameter("TriggerMode");
            ptrTriggerMode->FromString("Off");
            m_iGrabStopFlag = true;
        }
        ret = RETURN_OK;
#endif
    }
           

總結

對我來說,這部分的難點,肯定是在那個觸發資源的準備與消除,确實整個函數的關系較為複雜,花了我較多的時間進行解決。其他就是那個線程回調函數的注冊,确實對整個程式的架構要求較高,其他就還好了,隻要按照我裡面給的那個demo的那些函數的執行順序,應該不會有太大的問題。若裡面的講解存有問題,歡迎提出。

繼續閱讀