【问题标题】:Periodically send data to MATLAB from mexFile定期从 mexFile 向 MATLAB 发送数据
【发布时间】:2015-09-11 16:00:28
【问题描述】:

我现在正在开发一个完全编写的数据采集工具 在 MATLAB 中。我的同事希望我用 MATLAB 写这个东西 以便他们可以扩展和修改它。

该软件需要从两个连接的 USB 相机中抓取图片。 这些相机的 API 是用 C++ 编写的,并记录在案 -> Here

问题来了: 当我编写一个抓取图片的 mex 文件时,它包含 摄像机的初始化和配置加载 花费很长时间。当我想抓取图片时 这样,MATLAB 需要超过 1 秒的时间来执行任务。 一旦初始化,摄像机就能够以 100 fps 的速度记录和发送。 我需要的最低帧速率是 10 fps。 我需要能够发回每张录制的照片 到 MATLAB。因为录制会话 需要采集工具大约需要 12 小时,我们 需要一个带有一些轻微后处理的实时屏幕。

是否可以在 mex 文件中生成一个循环 向 MATLAB 发送数据,然后等待 MATLAB 的返回信号 并继续? 这样我可以初始化相机并定期发送 图像到 MATLAB。

我是 C++ 的初学者,我很可能 不明白一个基本概念为什么这样 是不可能的。

感谢您提供我可以查看的任何建议或资源。

请在下面找到初始化相机的代码 使用 Basler 提供的 Pylon API。

// Based on the Grab_MultipleCameras.cpp Routine from Basler
/*
This routine grabs one frame from 2 cameras connected
via two USB3 ports. It directs the Output to MATLAB.
*/

// Include files to use the PYLON API.
#include <pylon/PylonIncludes.h>
#include <pylon/usb/PylonUsbIncludes.h>
#include <pylon/usb/BaslerUsbInstantCamera.h>
#include <pylon/PylonUtilityIncludes.h>
// Include Files for MEX Generation
#include <matrix.h>
#include <mex.h>   

// Namespace for using pylon objects.
using namespace Pylon;

// We are lazy and use Basler USB namespace
using namespace Basler_UsbCameraParams;

// Standard namespace
using namespace std;

// Define Variables Globally to be remembered between each call
// Filenames for CamConfig
const String_t filenames[] = { "NodeMapCam1.pfs","NodeMapCam2.pfs" };

// Limits the amount of cameras used for grabbing.
static const size_t camerasToUse = 2;

// Create an array of instant cameras for the found devices and 
// avoid exceeding a maximum number of devices.
CBaslerUsbInstantCameraArray cameras(camerasToUse);

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
  // Automagically call PylonInitialize and PylonTerminate to ensure the pylon runtime system.
  // is initialized during the lifetime of this object
  PylonAutoInitTerm autoInitTerm;

  try
  {
    // Get the transport layer factory
    CTlFactory& tlFactory = CTlFactory::GetInstance();

    // Get all attached devices and exit application if no device or USB Port is found.
    DeviceInfoList_t devices;
    ITransportLayer *pTL = dynamic_cast<ITransportLayer*>(tlFactory.CreateTl(BaslerUsbDeviceClass));
    if (pTL == NULL)
    {
      throw RUNTIME_EXCEPTION("No USB transport layer available.");
    }

    if (pTL->EnumerateDevices(devices) == 0)
    {
      throw RUNTIME_EXCEPTION("No camera present.");
    }

    // Create and attach all Pylon Devices. Load Configuration
    for (size_t i = 0; i < cameras.GetSize(); ++i)
    {
      cameras[i].Attach(tlFactory.CreateDevice(devices[i]));
    }

    // Open all cameras.
    cameras.Open();

    // Load Configuration and execute Trigger
    for (size_t i = 0; i < cameras.GetSize(); ++i)
    {
      CFeaturePersistence::Load(filenames[i], &cameras[i].GetNodeMap());
    }
    if (cameras[0].IsOpen() && cameras[1].IsOpen())
    {
      mexPrintf("\nCameras are fired up and configuration is applied\n");
      // HERE I WOULD LIKE TO GRAB PICTURES AND SEND THEM
      // PERIODICALLY TO MATLAB.
    }
  }
  catch (GenICam::GenericException &e)
  {
    // Error handling
    mexPrintf("\nAn exception occured:\n");
    mexPrintf(e.GetDescription());
  }

  return;
}

【问题讨论】:

    标签: c++ matlab camera mex


    【解决方案1】:

    您可以定期循环并将图像发送回 MATLAB,但您希望它如何在工作区中(多个 2D 图像、巨大的 3D/4D 数组、单元等)?我认为您正在寻找的解决方案是一个有状态的 MEX 文件,可以使用 'init''new' 命令启动,然后使用 'capture' 命令重复调用已初始化的相机。

    有一个example of how to do this in my GitHub。从class_wrapper_template.cpp 开始并根据您的命令修改它(newcapturedelete 等)。下面是一个粗略且未经测试的示例,说明它的核心可能看起来如何(也是mirrored on Gist.GitHub):

    // pylon_mex_camera_interface.cpp
    #include "mex.h"
    #include <vector>
    #include <map>
    #include <algorithm>
    #include <memory>
    #include <string>
    #include <sstream>
    
    ////////////////////////  BEGIN Step 1: Configuration  ////////////////////////
    // Include your class declarations (and PYLON API).
    #include <pylon/PylonIncludes.h>
    #include <pylon/usb/PylonUsbIncludes.h>
    #include <pylon/usb/BaslerUsbInstantCamera.h>
    #include <pylon/PylonUtilityIncludes.h>
    
    // Define class_type for your class
    typedef CBaslerUsbInstantCameraArray class_type;
    
    // List actions
    enum class Action
    {
        // create/destroy instance - REQUIRED
        New,
        Delete,
        // user-specified class functionality
        Capture
    };
    
    // Map string (first input argument to mexFunction) to an Action
    const std::map<std::string, Action> actionTypeMap =
    {
        { "new",        Action::New },
        { "delete",     Action::Delete },
        { "capture",    Action::Capture }
    }; // if no initializer list available, put declaration and inserts into mexFunction
    
    using namespace Pylon;
    using namespace Basler_UsbCameraParams;
    
    const String_t filenames[] = { "NodeMapCam1.pfs","NodeMapCam2.pfs" };
    static const size_t camerasToUse = 2;
    /////////////////////////  END Step 1: Configuration  /////////////////////////
    
    // boilerplate until Step 2 below
    typedef unsigned int handle_type;
    typedef std::pair<handle_type, std::shared_ptr<class_type>> indPtrPair_type; // or boost::shared_ptr
    typedef std::map<indPtrPair_type::first_type, indPtrPair_type::second_type> instanceMap_type;
    typedef indPtrPair_type::second_type instPtr_t;
    
    // getHandle pulls the integer handle out of prhs[1]
    handle_type getHandle(int nrhs, const mxArray *prhs[]);
    // checkHandle gets the position in the instance table
    instanceMap_type::const_iterator checkHandle(const instanceMap_type&, handle_type);
    
    void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) {
    
        // static storage duration object for table mapping handles to instances
        static instanceMap_type instanceTab;
    
        if (nrhs < 1 || !mxIsChar(prhs[0]))
            mexErrMsgTxt("First input must be an action string ('new', 'delete', or a method name).");
    
        char *actionCstr = mxArrayToString(prhs[0]); // convert char16_t to char
        std::string actionStr(actionCstr); mxFree(actionCstr);
    
        for (auto & c : actionStr) c = ::tolower(c); // remove this for case sensitivity
    
        if (actionTypeMap.count(actionStr) == 0)
            mexErrMsgTxt(("Unrecognized action (not in actionTypeMap): " + actionStr).c_str());
    
        // If action is not 'new' or 'delete' try to locate an existing instance based on input handle
        instPtr_t instance;
        if (actionTypeMap.at(actionStr) != Action::New && actionTypeMap.at(actionStr) != Action::Delete) {
            handle_type h = getHandle(nrhs, prhs);
            instanceMap_type::const_iterator instIt = checkHandle(instanceTab, h);
            instance = instIt->second;
        }
    
        //////// Step 2: customize each action in the switch in mexFuction ////////
        switch (actionTypeMap.at(actionStr))
        {
        case Action::New:
        {
            if (nrhs > 1 && mxGetNumberOfElements(prhs[1]) != 1)
                mexErrMsgTxt("Second argument (optional) must be a scalar, N.");
    
            handle_type newHandle = instanceTab.size() ? (instanceTab.rbegin())->first + 1 : 1;
    
            // Store a new CBaslerUsbInstantCameraArray in the instance map
            std::pair<instanceMap_type::iterator, bool> insResult = 
                instanceTab.insert(indPtrPair_type(newHandle, std::make_shared<class_type>(camerasToUse)));
    
            if (!insResult.second) // sanity check
                mexPrintf("Oh, bad news.  Tried to add an existing handle."); // shouldn't ever happen
            else
                mexLock(); // add to the lock count
    
            // return the handle
            plhs[0] = mxCreateDoubleScalar(insResult.first->first); // == newHandle
    
            // Get all attached devices and exit application if no device or USB Port is found.
            CTlFactory& tlFactory = CTlFactory::GetInstance();
            // Check if cameras are attached
            ITransportLayer *pTL = dynamic_cast<ITransportLayer*>(tlFactory.CreateTl(BaslerUsbDeviceClass));
            // todo: some checking here... (pTL == NULL || pTL->EnumerateDevices(devices) == 0)
    
            // Create and attach all Pylon Devices. Load Configuration
            CBaslerUsbInstantCameraArray &cameras = *instance;
            DeviceInfoList_t devices;
            for (size_t i = 0; i < cameras.GetSize(); ++i) {
              cameras[i].Attach(tlFactory.CreateDevice(devices[i]));
            }
    
            // Open all cameras.
            cameras.Open();
    
            // Load Configuration and execute Trigger
            for (size_t i = 0; i < cameras.GetSize(); ++i) {
              CFeaturePersistence::Load(filenames[i], &cameras[i].GetNodeMap());
            }
    
            if (cameras[0].IsOpen() && cameras[1].IsOpen()) {
                mexPrintf("\nCameras are fired up and configuration is applied\n");
    
            break;
        }
        case Action::Delete:
        {
            instanceMap_type::const_iterator instIt = checkHandle(instanceTab, getHandle(nrhs, prhs));
            (instIt->second).close(); // may be unnecessary if d'tor does it
            instanceTab.erase(instIt);
            mexUnlock();
            plhs[0] = mxCreateLogicalScalar(instanceTab.empty()); // just info
            break;
        }
        case Action::Capture:
        {
            CBaslerUsbInstantCameraArray &cameras = *instance; // alias for the instance
    
            // TODO: create output array and capture a frame(s) into it
            plhs[0] = mxCreateNumericArray(...);
            pixel_type* data = (pixel_type*) mxGetData(plhs[0]);
            cameras[0].GrabOne(...,data,...);
            // also for cameras[1]?
            }
        }
        default:
            mexErrMsgTxt(("Unhandled action: " + actionStr).c_str());
            break;
        }
        ////////////////////////////////  DONE!  ////////////////////////////////
    }
    
    // See github for getHandle and checkHandle
    

    这个想法是你会调用它一次来初始化:

    >> h = pylon_mex_camera_interface('new');
    

    然后你会在 MATLAB 循环中调用它来获取帧:

    >> newFrame{i} = pylon_mex_camera_interface('capture', h);
    

    完成后:

    >> pylon_mex_camera_interface('delete', h)
    

    你应该用一个 MATLAB 类来包装它。从cppclass.m 派生以轻松完成此操作。有关派生类示例,请参阅pqheap.m

    【讨论】:

    • 哇。非常感谢你。伟大的努力和伟大的答案!这对我帮助很大。
    • @JulianCarpenter 没问题。我确定有错误,我很乐意在您解决问题时整合实际语法。
    • 我可以问一个后续问题吗?我试图实现你的代码,但我有点磕磕绊绊......在CBaslerUsbInstantCameraArray &amp;cameras = instance; 行中,你正在使用名为 instance 的智能指针初始化对象引用 .... 我的编译器正在抱怨它。我使用 Visual Studio 2015 并得到这个输出:error C2440: 'initializing': cannot convert from 'instPtr_t' to 'class_type &amp;' ...如果你能指出我 - ;) - 在正确的方向上找到一个解决方案,这将是非常棒的!
    • @JulianCarpenter 我的错误,instance 的类型是 shared_ptr&lt;class_type&gt; 而不仅仅是 class_type。使用实例时,必须dereference the shared_ptr。请改用CBaslerUsbInstantCameraArray &amp;cameras = *instance;。希望我可以编译您的代码...如果失败,请尝试直接使用它,例如(*instance)[i].Attach()
    • 再次感谢您!你刚刚拯救了我的一天......通过索引它最终工作的取消引用指针。摄像机通过多次通话保持打开状态。当我完成整个事情时,我会上传它并给你链接!再次感谢
    【解决方案2】:

    您应该让 mex 文件存储与相机相关的设置,而不是向 MATLAB 发送数据,这样它就不会在每次调用中进行初始化。一种方法是对 mex 文件使用两种调用模式。一个“init”调用和一个获取数据的调用。 MATLAB 中的伪代码是

    cameraDataPtr = myMex('init');
    while ~done
       data = myMex('data', cameraDataPtr);
    end
    

    在您的 mex 文件中,您应该将相机设置存储在一个内存中,该内存在调用中是持久的。一种方法是在 c++ 中使用“新”。您应该将此内存指针作为 int64 类型返回给 MATLAB,在上面的代码中显示为 cameraDataPtr。当询问“数据”时,您应该将 cameraDataPtr 作为输入并转换回您的相机设置。说在 C++ 中,你有一个 CameraSettings 对象,它存储与相机相关的所有数据,那么 C++ 中的粗略伪代码将是

    if prhs[0] == 'init' { // Use mxArray api to check this
      cameraDataPtr = new CameraSettings; // Initialize and setup camera
      plhs[0] = createMxArray(cameraDataPtr); // Use mxArray API to create int64 from pointer
      return;
    } else {
       // Need data
       cameraDataPtr = getCameraDataPtr(plhs[1]);
       // Use cameraDataPtr after checking validity to get next frame
    }
    

    这很有效,因为 mex 文件在加载后会一直保留在内存中,直到您清除它们。当 mex 文件从内存中卸载时,您应该使用 mexAtExit 函数释放相机资源。如果这是您的 mex 文件将被使用的唯一位置,您还可以使用“静态”将相机设置存储在 c++ 中。这将避免编写一些 mxArray 处理代码来返回您的 c++ 指针。

    如果您将对这个 mex 文件的调用封装在一个 MATLAB 对象中,您可以更轻松地控制初始化和运行时过程,并为您的用户提供更好的 API。

    【讨论】:

    • 很好的答案,非常感谢。它对我帮助很大!
    【解决方案3】:

    我遇到了同样的问题,想在 Matlab 中使用带有 mex API 的 Basler 相机。这里的贡献和提示无疑帮助我提出了一些想法。但是,有一个比之前提出的解决方案简单得多的解决方案。没有必要将相机指针返回给 Matlab,因为对象将在多个 mex 调用中保留在内存中。这是我使用新的 mex C++ API 编写的工作代码。玩得开心。

    这是可以用 mex 编译的 C++ 文件:

    #include <opencv2/core/core.hpp>
    #include <opencv2/opencv.hpp>
    #include <pylon/PylonIncludes.h>
    #include <pylon/usb/PylonUsbIncludes.h>
    #include <pylon/usb/BaslerUsbInstantCamera.h>
    #include <pylon/PylonUtilityIncludes.h>
    #include "mex.hpp"
    #include "mexAdapter.hpp"
    #include <chrono>
    #include <string>
    using namespace matlab::data;
    using namespace std;
    using namespace Pylon;
    using namespace Basler_UsbCameraParams;
    using namespace GenApi;
    using namespace cv;
    using matlab::mex::ArgumentList;
    
    class MexFunction : public matlab::mex::Function{
        matlab::data::ArrayFactory factory;
        double Number = 0;
        std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();
        std::ostringstream stream;
        Pylon::CInstantCamera* camera;
        INodeMap* nodemap;
        double systemTime;
        double cameraTime;
    public:
    
        MexFunction(){}
    
        void operator()(ArgumentList outputs, ArgumentList inputs) {
    
            try {
            Number = Number + 1;
    
            if(!inputs.empty()){
    
                matlab::data::CharArray InputKey = inputs[0];
    
                stream << "You called: " << InputKey.toAscii() << std::endl;
                displayOnMATLAB(stream);
    
                // If "Init" is the input value
                if(InputKey.toUTF16() == factory.createCharArray("Init").toUTF16()){
    
                    // Important: Has to be closed
                    PylonInitialize();
                    IPylonDevice* pDevice = CTlFactory::GetInstance().CreateFirstDevice();
                    camera = new CInstantCamera(pDevice);
    
                    nodemap = &camera->GetNodeMap();
    
                    camera->Open();
    
                    camera->RegisterConfiguration( new CSoftwareTriggerConfiguration, RegistrationMode_ReplaceAll, Cleanup_Delete);
    
                    CharArray DeviceInfo = factory.createCharArray(camera -> GetDeviceInfo().GetModelName().c_str());
    
                    stream << "Message: Used Camera is " << DeviceInfo.toAscii() << std::endl;
                    displayOnMATLAB(stream);
                }
    
    
                // If "Grab" is  called
                if(InputKey.toUTF16() == factory.createCharArray("Grab").toUTF16()){
                        static const uint32_t c_countOfImagesToGrab = 1;
                        camera -> StartGrabbing(c_countOfImagesToGrab);
                        CGrabResultPtr ptrGrabResult;
                        Mat openCvImage;
                        CImageFormatConverter formatConverter;
                        CPylonImage pylonImage;
                        while (camera -> IsGrabbing()) {
                            camera -> RetrieveResult(5000, ptrGrabResult, TimeoutHandling_ThrowException);
                            if (ptrGrabResult->GrabSucceeded()) {
                                formatConverter.Convert(pylonImage, ptrGrabResult);
    
                                Mat openCvImage = cv::Mat(ptrGrabResult->GetHeight(), ptrGrabResult->GetWidth(), CV_8UC1,(uint8_t *)pylonImage.GetBuffer(), Mat::AUTO_STEP);
    
                                const size_t rows = openCvImage.rows;
                                const size_t cols = openCvImage.cols;
    
                                matlab::data::TypedArray<uint8_t> Yp = factory.createArray<uint8_t>({ rows, cols });
    
                                for(int i = 0 ;i < openCvImage.rows; ++i){
    
                                    for(int j = 0; j < openCvImage.cols; ++j){
    
                                        Yp[i][j] = openCvImage.at<uint8_t>(i,j);
                                    }
                                }
                                outputs[0] =  Yp;
                            }
                        }
                }
    
                // if "Delete"
                if(InputKey.toUTF16() == factory.createCharArray("Delete").toUTF16()){
                    camera->Close();
                    PylonTerminate();
    
                    stream << "Camera instance removed" << std::endl;
                    displayOnMATLAB(stream);
                    Number = 0;
                    //mexUnlock();
                }
            }
    
            // ----------------------------------------------------------------
            stream << "Anzahl der Aufrufe bisher: " << Number << std::endl;
            displayOnMATLAB(stream);
            // ----------------------------------------------------------------
    
            }
            catch (const GenericException & ex) {
                matlabPtr->feval(u"disp", 0, std::vector<Array>({factory.createCharArray(ex.GetDescription()) }));
            }
        }
    
        void displayOnMATLAB(std::ostringstream& stream) {
            // Pass stream content to MATLAB fprintf function
            matlabPtr->feval(u"fprintf", 0,
                             std::vector<Array>({ factory.createScalar(stream.str()) }));
            // Clear stream buffer
            stream.str("");
        }
    };
    
    

    可以使用以下命令从 Matlab 调用此 mex 文件:

    % Initializes the camera. The camera parameters can also be loaded here.
    NameOfMexFile('Init');
    
    % Camera image is captured and sent back to Matlab
    [Image] = NameOfMexFile('Grab');
    
    % The camera connection has to be closed.
    NameOfMexFile('Delete');
    
    

    欢迎对此代码进行优化和改进。代码的效率仍然存在问题。图像采集大约需要 0.6 秒。这主要是由于从 cv::mat 图像转换为 TypedArray 所必需的,这是将其返回到 Matlab 所必需的。在两个循环中看到这一行:Yp[i][j] = openCvImage.at&lt;uint8_t&gt;(i,j);

    我还没有弄清楚如何提高效率。此外,该代码不能用于将多个图像返回到 Matlab。

    也许有人有想法或提示可以更快地从 cv::mat 转换为 Matlab 数组类型。我已经在另一篇文章中提到了这个问题。见这里:How to Return a Opencv image cv::mat to Matlab with the Mex C++ API

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-18
      • 2016-12-13
      相关资源
      最近更新 更多