Периодически отправляйте данные в 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 ответа:
Вы можете периодически делать циклы и отправлять изображения обратно в 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.