Home > Net >  Raw pointer / cv::Mat data as shared_ptr
Raw pointer / cv::Mat data as shared_ptr

Time:01-01

A library API takes an image raw pixel data as std::shared_ptr<uint8_t>.

The image files I have can be in multiple formats (e.g. .png, .jpg, .bmp), so I use cv::imread for practicality.

However, this returns the image as a cv::Mat object, which I still need to convert to std::shared_ptr<uint8_t> to comply with the library API signature.

I have tried similar solutions on the Internet, such as How to save image data in shared pointer and Creating shared_ptr from raw pointer, as well as std::make_shared, either directly from the cv::Mat

std::make_shared<uint8_t>(*mat.data);

or from a std::vector

data.assign(mat.data, mat.data   mat.total() * mat.channels()); 
std::make_shared<uint8_t>(*data.data());

But all attempts resulted in either double deletion exception, corrupted data or compilation error.

The only way I got it to work was to write the raw pixels data to a file then read it to a shared pointer with

std::shared_ptr<uint8_t> data;

std::ofstream output("temp", std::ios::out | std::ios::binary);
output.write(reinterpret_cast<char*>(mat.data), size);

std::ifstream input("temp", std::ios::in | std::ios::binary);
data.reset(new uint8_t[size], std::default_delete<uint8_t[]>());
input.read((char*)data.get(), size);

Although this works, it bothers me to have such terrible solution for this.

What is the proper way to convert cv::Mat to a shared pointer so that the data complies with the library API signature?


The full example that works, but looks terrible is

#include <opencv2/core.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

#include <fstream>

int main(int argc,char* argv[])
{
    std::string image_path = cv::samples::findFile("starry_night.jpg");
    cv::Mat mat = cv::imread(image_path, cv::IMREAD_COLOR);
    size_t size = mat.cols * mat.rows * mat.channels();

    std::shared_ptr<uint8_t> data;

    std::ofstream output("temp", std::ios::out | std::ios::binary);
    output.write(reinterpret_cast<char*>(mat.data), size);

    std::ifstream input("temp", std::ios::in | std::ios::binary);
    data.reset(new uint8_t[size], std::default_delete<uint8_t[]>());
    input.read((char*)data.get(), size);

    // function(std::shared_ptr<uint8_t> data);

    return 0;
}

I use the following CMakeLists.txt to compile it (required OpenCV installed)

project(help)
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
add_executable(help help.cpp)
target_link_libraries(help ${OpenCV_LIBS})

CodePudding user response:

Its impossible. Copy the matrix or read the image using another library than cv::imread to reverse the ownership semantics if it is a performance concern. OpenCV provides non-owning matrices (constructor from pointer, e.g. feed it with the output of stbi_load()) which you can use as simple view like std::span. However, you can't change that flag manually after the construction of the matrix.

Looking at the source code at modules/core/src/matrix.cpp, it may be possible to add manually the flag cv::UMatData::USER_ALLOCATED to prevent deallocation of memory.

Like that:

#include <opencv2/core.hpp>

int main()
{
    cv::Mat img(10, 10, CV_8UC1);
    
    auto ptr = std::shared_ptr<unsigned char>(img.data);
    
    if(img.u)
    {
        img.u->flags |= cv::UMatData::USER_ALLOCATED;
    }

    // Single memory deallocation at the end of main()
}

~Mat() calls release() which calls deallocate() which calls u->unmap() and after a few function calls:

          if( !(u->flags & UMatData::USER_ALLOCATED) )
          {
              fastFree(u->origdata);
              u->origdata = 0;
          }

However, it doesn't seem to work, maybe because OpenCV uses a custom allocator (fastFree()) which may not be compatible with delete called by the std::shared_ptr. This may be the reason why OpenCV don't let release the matrix.

CodePudding user response:

As suggested in the comments, I will copy the data so that I can keep it after deleting cv::Mat

cv::Mat mat = cv::imread(image_path, cv::IMREAD_COLOR);
size_t size = mat.cols * mat.rows * mat.channels();

std::shared_ptr<uint8_t> data(new uint8_t[size]);
std::copy(mat.data, mat.data   size, data.get());

This already looks way better than what I had before, but a better solution would be to cv::Mat release its ownership of the data.

  • Related