Периодически отправляйте данные в MATLAB из mexFile


Прямо сейчас я работаю над инструментом сбора данных, полностью написанным в MATLAB. Это было желание моих коллег, чтобы я написал эту вещь в MATLAB чтобы они могли расширить и модифицировать его.

Программное обеспечение должно захватить изображение с двух подключенных USB-камер. API для этих камер написан на C++ и документирован - > здесь.

вот в чем проблема: Когда я пишу файл mex, который захватывает картинку, он включает в себя инициализация и конфигурация-загрузка камер, которые это займет много времени. Когда я хочу захватить фотографии таким образом, для выполнения задачи MATLAB требуется более 1 секунды. Камеры способны, после инициализации, записывать и передавать 100 кадров в секунду. Минимальная частота кадров, которая мне нужна, составляет 10 кадров в секунду. Мне нужно иметь возможность отправить назад каждую записанную картинку в MATLAB. Потому что запись сессии, для которой Приобретение инструмента необходимо занимает около 12 часов, и мы нужен живой экран с небольшой постобработкой.

Так ли это возможно создать цикл внутри файла mex, который отправляет данные в MATLAB, а затем ожидает ответного сигнала от MATLAB и продолжает ? Таким образом, я мог инициализировать камеры и периодически отправлять их изображения в MATLAB.

Я новичок в C++ , и вполне возможно, что я не понимаю фундаментальной концепции, почему это не возможный.

Спасибо за любые советы или источники, где я мог бы искать.

Пожалуйста, найдите ниже код, который инициализирует камеры использование Pylon API, предоставленный Basler.

// 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 appliedn");
      // 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;
}
2 3

2 ответа:

Вы можете периодически делать циклы и отправлять изображения обратно в MATLAB, но как вы хотите, чтобы это было в рабочей области (несколько 2D-изображений, огромный массив 3D/4D, ячейка и т. д.)? Я думаю, что решение, которое вы ищете, - этоstateful mex file , который может быть запущен с помощью команды 'init' или 'new', а затем повторно вызываться с помощью команд 'capture' для уже инициализированной камеры.

Есть пример того, как это сделать в моем GitHub. Начнем с class_wrapper_template.cpp и измените его для ваших команд (new, capture, delete, и т.д.). Вот примергрубого и непроверенного , как может выглядеть его ядро (также отраженное на 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

Идея заключается в том, что вы могли бы вызвать его один раз, чтобы init:

>> 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 .

Вместо отправки данных в MATLAB вы должны сделать так, чтобы ваш файл mex сохранял настройки, связанные с камерой, чтобы он не инициализировался при каждом вызове. Один из способов сделать это-использовать два режима вызовов для вашего файла mex. Вызов "init" и вызов для получения данных. Псевдокод в MATLAB будет

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

В вашем файле mex, вы должны хранить настройки камеры в памяти, которая является постоянной между вызовами. Один из способов сделать это-использовать " new " В 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 остаются в памяти после загрузки, пока вы их не очистите. Вы должны использовать функцию mexAtExit, чтобы освободить ресурс камеры, когда файл mex выгружается из памяти. Вы можно также использовать 'static' для хранения настроек камеры в c++, если это единственное место, где будет использоваться ваш файл mex. Это позволит избежать написания некоторого кода обработки mxArray для возврата указателя c++.

Если вы обернете вызов этого mex-файла в объект MATLAB, вы сможете легче управлять процессом инициализации и выполнения и представить пользователям лучший API.