(matlab coder)How should the coder::array member function set convert opencv's mat type to its type in-place?
Mostra commenti meno recenti
When using matlab coder to generate C++ code, the generator automatically generates a C++ class for coder::array, which is defined in the `coder_array.h` file (located in fullfile(matlabroot,'extern','include','coder','coder _array','coder_array_rtw_cpp11.h')), which has this usage note.
// set(T const *data, SizeType sz1, SizeType sz2, ...)
// : Set data with dimensions.
// : (Data is not copied, data is not deleted)
In addition, I have read in an image from an external Opencv and would like to convert it directly in situ using this set() member
cv::Mat srcImg = cv::imread("C:\\Program Files\\MATLAB\\R2022b\\toolbox\\matlab\\imagesci\\peppers.png");
// test cv::Mat to coder::array type directly
coder::array<unsigned char, 3U> result;
result.set((unsigned char*)srcImg.data, srcImg.rows, srcImg.cols,srcImg.channels());
But unfortunately the above conversion did not work and the result debugging found empty, how do I use it correctly?
Run in R2022b
Risposta accettata
Più risposte (1)
Alexander Bottema
il 23 Feb 2023
Modificato: Alexander Bottema
il 23 Feb 2023
The issue here is that MATLAB (and MATLAB Coder) uses column-major format for matrices. This means the first dimension is the one that traverses memory locations consecutively, whereas C usually uses row-major memory layout; the last dimension traverses consecutive memory locations. You can toggle between the two memory layouts by reversing the size vector. You can either do this manually, or automatically. Manually:
codegen -args { coder.typeof(uint8(0), [inf inf inf] } mytest
function im = mytest(im)
% You have size(im,1) == channels, size(im,2) == columns, size(im,3) == rows
...
and:
coder::array<unsigned char, 3U> result;
result.set((unsigned char*)srcImg.data, srcImg.channels(), srcImg.cols, srcImg.rows);
mytest(arr);
If the top-level input is in row-major format you can also annotate your function to use row-major, and thus you don't need to reverse the size vector in your head:
function im = mytest(im)
coder.rowMajor;
% You have size(im,1) == rows, size(im,2) == cols, size(im,3) == channels
...
and:
coder::array<unsinged char, 3U> result;
result.set((unsigned char*)srcImg.data, srcImg.rows, srcImg.cols, srcImg.channels());
mytest(arr);
18 Commenti
xingxingcui
il 24 Feb 2023
Modificato: xingxingcui
il 24 Feb 2023
xingxingcui
il 24 Feb 2023
Alexander Bottema
il 24 Feb 2023
The issue here is that coder::array and OpenCV use different memory managers. coder::array has support to not bother about memory managers when the owner is set to false (set_owner(false), which also happens implicitly if you use set(....)). You should never set_owner(true) for a coder::array object that hasn't allocated the data. If you use "set_size", then there's a chance that coder::array will reallocate the data, and when that happens, it owns the data; i.e. the memory manager of coder::array owns it and also knows how to destroy it.
The similar is true for OpenCV, and there's no guarantee that both memory managers are compatible with each other. Furthermore, when coder::array has (re)allocated the data, then the data() pointer will be something different and does becomes detached from cv::Mat's representation.
So to transfer the data from coder::array back to a cv::Mat would require reading the documentation of OpenCV to see if there's a way to create a similar object as you can with coder::array so that it doesn't own the data. I don't know whether this is possible to do so. Skimming through the documentation of OpenCV indicates that this is a reference counted system and uses a flexible system where memory can optionally be on a CUDA host, etc. There's something called a MatAllocator as well, but it requires a context to be used. In general, the memory management for OpenCV is quite different from coder::array.
Furthermore, even if you manage to transfer the data pointer to the cv::Mat object you would also need to make sure that coder::array uses the same memory manager as OpenCV. For this there are macro hooks for coder::array that you can use (CODER_ARRAY_ALLOC, CODER_ALLOC, CODER_DEALLOC). Once the memory is transferred you need to set_owner(false) on the coder::array object to indicate that it shouldn't own the memory (and will not get freed when its destructor is called.)
Memory interoperability between OpenCV and MATLAB Coder is no easy task, but I think it is possible. I'm not sure how though.
The safest option is to copy the data between OpenCV and MATLAB Coder. Then each respective memory manager knows how to deal with it.
Alexander Bottema
il 24 Feb 2023
Another idea is to write your own custom coder::array class that wraps a cv::Mat. coder::array is a boilerplate template class (and is not auto generated.) It is by design so that you can replace it with your own custom coder::array version. There aren't that many methods you need to implement.
Alexander Bottema
il 24 Feb 2023
Just to clarify:
1) coder::array has two modes: either it owns the data (owner = true) which means that it is supposed to be in control over the data. It knows how to destroy it, etc. This is likely incompatible with cv::Mat.
2) it does not own the data. This happens when you use the ".set" method. Then you need to make sure that coder::array doesn't reallocate the memory (don't call set_size, or anything else that may result in memory reallocation)
In the first version you posted you issued "set_size" and then iterated over the pixels to transfer the data. This means that coder::array owns the data. You need to do a similar operation to transfer it back to an cv::Mat. You cannot mix (1) and (2). So you need to choose between set_size() + writing pixels or only use set() (and then make sure coder::array doesn't reallocate it = don't use set_size on it)
If coder::array allocates its data, then you need to create a new cv::Mat and copy over the data. Again, this is because the two representations use different memory managers. The only way of making sure that there are no copying between coder::array and cv::Mat is to make sure they are fully compatible. This would require you to write your own custom coder::array that uses cv::Mat.
Alexander Bottema
il 24 Feb 2023
I just saw in the documentation of OpenCV that there's a similar way of creating an non-owned cv::Mat (that you can do with coder::array's set method):
cv::Mat(int rows, int cols, int type, char* preAllocatedPointerToData);
So there's a constructor which enables you to pass a pre-allocated piece of data.
This means that whenever coder::array owns the data, you can do:
coder::array<unsigned char, 3U> arr;
arr.set_size( ... );
...
cv::Mat im(arr.size(0), arr.size(1), CV_8UC3, arr.data());
.... to be used in OpenCV ...
coder::~array() is called and frees the data of 'arr'.
xingxingcui
il 25 Feb 2023
Modificato: xingxingcui
il 25 Feb 2023
xingxingcui
il 25 Feb 2023
Modificato: xingxingcui
il 25 Feb 2023
Alexander Bottema
il 25 Feb 2023
The argInit version you wrote above is incorrect. You return a reference to a coder::array that has been allocated in the local stack frame. This is an invalid operation in C++. In general, I discourage you to return coder::array from functions. Pass them around by reference instead.
Alexander Bottema
il 25 Feb 2023
To clarify, I recommend something like this:
void argInit( coder::array<unsigned char> &result ) {
...
}
Furthermore, you should be very careful when using .set(...) which does not copy tbe data. This means that the data pointer that is being wrapped by the coder::array is owned elsewhere and whenever that object is freed, then the data will be freed as well.
xingxingcui
il 25 Feb 2023
Alexander Bottema
il 25 Feb 2023
Your argInit_.... function have are more problems. You allocate a variable in the function's local stack frame (matlab_data), and then wrap that data inside a coder::array (that you attempt to return). Once the function returns, the stack allocated matlab_data is deallocated and you can no longer trust the data inside coder::array. You need to be very careful on who owns the memory and how. C/C++ in general can be complicated when it comes to memory management.
xingxingcui
il 25 Feb 2023
Modificato: xingxingcui
il 25 Feb 2023
xingxingcui
il 25 Feb 2023
Alexander Bottema
il 25 Feb 2023
Again, I'd recommend that you don't return coder::array and instead use the pattern as shown above; i.e. pass the variables as a function argument by reference. I know that the example main code generation does return coder::array, but it is of historical reasons what we did for C code generation and that idiom got transferred to the C++ version. Note that all other code generation logic doesn't do this. It's only the example function that returns coder::array.
The reason why you see changes in pixel values has nothing to do with coder::array, set() or anything else. It's just the fact that the pixel data gets deallocated (somewhere) and the coder::array uses a dangling pointer.
If you use .set() to reuse an existing pointer, and the original data gets deallocated (either on stack, or by some other memory manager), then the coder:array data will continue reference unallocated data.
Changing a function to return a coder::array by reference isn't suitable for debugging purpose either; it is undefined behavior in C++. You cannot trust the code, not even during debugging.
Alexander Bottema
il 25 Feb 2023
Instead of doing this:
static coder::array<unsigned char, 3U> argInit_d1000xd1000xd3_uint8_T()
{
coder::array<unsigned char, 3U> result,temp;
cv::Mat dst;
cv::Mat srcImg = cv::imread(
"C:\\Program Files\\MATLAB\\R2022b\\toolbox\\matlab\\imagesci\\peppers.png");
unsigned char matlab_data[589824]; // numbers of pixels, peppers.png,384*521*3=589824
convertCVToMatrix(srcImg, srcImg.rows, srcImg.cols, srcImg.channels(),
matlab_data);
result.set((unsigned char *)matlab_data,srcImg.rows, srcImg.cols,srcImg.channels());
temp = result;
return temp; // This return value has the correct dimension and matlab type memory layout!
}
Do this instead:
static void argInit_d1000xd1000xd3_uint8_T(coder::array<unsigned char, 3U> &result)
{
cv::Mat dst;
cv::Mat srcImg = cv::imread(
"C:\\Program Files\\MATLAB\\R2022b\\toolbox\\matlab\\imagesci\\peppers.png");
unsigned char matlab_data[589824]; // numbers of pixels, peppers.png,384*521*3=589824
result.set_size(srcImg.rows, srcImg.cols, 3);
convertCVToMatrix(srcImg, srcImg.rows, srcImg.cols, srcImg.channels(),
result.data());
}
Don't rely on coder::array move semantics, especially when you wrap an existing data pointer.
xingxingcui
il 25 Feb 2023
Modificato: xingxingcui
il 25 Feb 2023
xingxingcui
il 25 Feb 2023
Categorie
Scopri di più su Use COM Objects in MATLAB in Centro assistenza e File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!

