天天看点

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的那些函数的执行顺序,应该不会有太大的问题。若里面的讲解存有问题,欢迎提出。

继续阅读