定期从 mexFile 向 MATLAB 发送数据
Periodically send data to MATLAB from mexFile
我现在正在开发完全编写的数据采集工具
在 MATLAB 中。我的同事希望我用 MATLAB 写这个东西
以便他们可以扩展和修改它。
该软件需要从两个连接的 USB 摄像头抓取图片。
这些相机的 API 是用 C++ 编写的,并记录在案 -> Here.
问题是:
当我写一个抓取图片的 mex 文件时,它包括
相机的初始化和配置加载
需要很长的时间。当我想抓取图片时
这样,MATLAB 需要超过 1 秒的时间来执行任务。
一旦初始化,摄像机就能够记录和发送 100 fps。
我需要的最低帧速率是 10 fps。
我需要能够将每张录制的照片发回
到 MATLAB。因为录制会话
需要采集工具大约需要 12 小时,我们
需要一个带有一些轻微后处理的实时屏幕。
是否可以在 mex 文件中生成循环
将数据发送到 MATLAB,然后等待来自 MATLAB 的 return 信号
并继续?
这样我就可以初始化相机并定期发送
图像到 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;
}
您不应将数据发送到 MATLAB,而应使您的 mex 文件存储相机相关设置,这样它就不会在每次调用时都进行初始化。一种方法是对您的 mex 文件使用两种调用模式。一个 'init' 调用和一个获取数据的调用。 MATLAB 中的伪代码为
cameraDataPtr = myMex('init');
while ~done
data = myMex('data', cameraDataPtr);
end
在您的 mex 文件中,您应该将相机设置存储在内存中,该内存在调用过程中保持不变。一种方法是在 C++ 中使用 'new'。您应该 return 此内存指针作为 int64 类型指向 MATLAB,在上面的代码中显示为 cameraDataPtr。当要求 'data' 时,您应该将 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 文件将要使用的唯一位置,您还可以使用 'static' 将相机设置存储在 c++ 中。这将避免为 returning 你的 c++ 指针编写一些 mxArray 处理代码。
如果将对此 mex 文件的调用包装在 MATLAB 对象中,您可以更轻松地控制初始化和 运行 时间过程,并向用户呈现更好的 API。
您可以周期性地循环并将图像发送回 MATLAB,但是您希望它如何出现在工作区中(多个 2D 图像、一个巨大的 3D/4D 阵列、单元等)?我认为您正在寻找的解决方案是 有状态 MEX 文件,可以使用 'init'
或 'new'
命令启动,然后使用 'capture'
已初始化相机的命令。
有一个example of how to do this in my GitHub. Start with class_wrapper_template.cpp and modify it for your commands (new
, capture
, delete
, etc.). Here is a rough and untested example of how the core of it might look (also 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 class 包装它。来自 cppclass.m to do this easily. For a derived class example see pqheap.m.
我 运行 遇到了同样的问题,想在 Matlab 中使用带有 mex API 的 Basler 相机。这里的贡献和提示无疑帮助我提出了一些想法。然而,有一个比之前提出的解决方案简单得多的解决方案。没有必要 return 将相机指针指向 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 的转换,这是 return 将其返回到 Matlab 所必需的。在两个循环中看到这一行:Yp[i][j] = openCvImage.at<uint8_t>(i,j);
我还没有想出如何使它更有效率。此外,该代码不能用于 return 多个图像返回到 Matlab。
也许有人有想法或提示可以更快地从 cv::mat
转换为 Matlab 数组类型。我已经在另一个 post 中提到了这个问题。看这里:How to Return a Opencv image cv::mat to Matlab with the Mex C++ API
我现在正在开发完全编写的数据采集工具 在 MATLAB 中。我的同事希望我用 MATLAB 写这个东西 以便他们可以扩展和修改它。
该软件需要从两个连接的 USB 摄像头抓取图片。 这些相机的 API 是用 C++ 编写的,并记录在案 -> Here.
问题是: 当我写一个抓取图片的 mex 文件时,它包括 相机的初始化和配置加载 需要很长的时间。当我想抓取图片时 这样,MATLAB 需要超过 1 秒的时间来执行任务。 一旦初始化,摄像机就能够记录和发送 100 fps。 我需要的最低帧速率是 10 fps。 我需要能够将每张录制的照片发回 到 MATLAB。因为录制会话 需要采集工具大约需要 12 小时,我们 需要一个带有一些轻微后处理的实时屏幕。
是否可以在 mex 文件中生成循环 将数据发送到 MATLAB,然后等待来自 MATLAB 的 return 信号 并继续? 这样我就可以初始化相机并定期发送 图像到 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;
}
您不应将数据发送到 MATLAB,而应使您的 mex 文件存储相机相关设置,这样它就不会在每次调用时都进行初始化。一种方法是对您的 mex 文件使用两种调用模式。一个 'init' 调用和一个获取数据的调用。 MATLAB 中的伪代码为
cameraDataPtr = myMex('init');
while ~done
data = myMex('data', cameraDataPtr);
end
在您的 mex 文件中,您应该将相机设置存储在内存中,该内存在调用过程中保持不变。一种方法是在 C++ 中使用 'new'。您应该 return 此内存指针作为 int64 类型指向 MATLAB,在上面的代码中显示为 cameraDataPtr。当要求 'data' 时,您应该将 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 文件将要使用的唯一位置,您还可以使用 'static' 将相机设置存储在 c++ 中。这将避免为 returning 你的 c++ 指针编写一些 mxArray 处理代码。
如果将对此 mex 文件的调用包装在 MATLAB 对象中,您可以更轻松地控制初始化和 运行 时间过程,并向用户呈现更好的 API。
您可以周期性地循环并将图像发送回 MATLAB,但是您希望它如何出现在工作区中(多个 2D 图像、一个巨大的 3D/4D 阵列、单元等)?我认为您正在寻找的解决方案是 有状态 MEX 文件,可以使用 'init'
或 'new'
命令启动,然后使用 'capture'
已初始化相机的命令。
有一个example of how to do this in my GitHub. Start with class_wrapper_template.cpp and modify it for your commands (new
, capture
, delete
, etc.). Here is a rough and untested example of how the core of it might look (also 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 class 包装它。来自 cppclass.m to do this easily. For a derived class example see pqheap.m.
我 运行 遇到了同样的问题,想在 Matlab 中使用带有 mex API 的 Basler 相机。这里的贡献和提示无疑帮助我提出了一些想法。然而,有一个比之前提出的解决方案简单得多的解决方案。没有必要 return 将相机指针指向 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 的转换,这是 return 将其返回到 Matlab 所必需的。在两个循环中看到这一行:Yp[i][j] = openCvImage.at<uint8_t>(i,j);
我还没有想出如何使它更有效率。此外,该代码不能用于 return 多个图像返回到 Matlab。
也许有人有想法或提示可以更快地从 cv::mat
转换为 Matlab 数组类型。我已经在另一个 post 中提到了这个问题。看这里:How to Return a Opencv image cv::mat to Matlab with the Mex C++ API