opencv 原始指针/ cv::Mat数据作为shared_ptr

dced5bon  于 2023-01-02  发布在  其他
关注(0)|答案(2)|浏览(404)

库API将图像原始像素数据取为std::shared_ptr<uint8_t>
我拥有的图像文件可以是多种格式(例如.png、.jpg、.bmp),所以我使用cv::imread是为了实用。
但是,这将图像作为cv::Mat对象返回,我仍然需要将其转换为std::shared_ptr<uint8_t>以符合库API签名。
我在Internet上尝试过类似的解决方案,例如How to save image data in shared pointerCreating shared_ptr from raw pointer,以及std::make_shared,或者直接从cv::Mat

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

或从std::vector

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

但所有尝试都导致双重删除异常、数据损坏或编译错误。
我让它工作的唯一方法是将原始像素数据写入文件,然后将其读取到共享指针

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);

虽然这很有效,但对此有如此可怕的解决方案让我很困扰。
cv::Mat转换为共享指针以使数据符合库API签名的正确方法是什么?
完整的例子是这样的,虽然看起来很糟糕

#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;
}

我使用下面的CMakeLists.txt来编译它(需要安装OpenCV)

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

7xllpg7q1#

这是不可能的。复制矩阵或使用cv::imread以外的其他库读取图像,以反转所有权语义,如果这是一个性能问题。OpenCV提供了非所有权矩阵(从指针构造函数,例如用stbi_load()的输出馈送它),您可以将其用作std::span等简单视图。但是,在矩阵构造之后,您不能手动更改该标志。
查看modules/core/src/matrix.cpp处的源代码,可以手动添加cv::UMatData::USER_ALLOCATED标志以防止内存释放。
就像这样:

#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()调用release()release()调用deallocate()deallocate()调用u->unmap(),在几次函数调用之后:

if( !(u->flags & UMatData::USER_ALLOCATED) )
          {
              fastFree(u->origdata);
              u->origdata = 0;
          }
    • 然而**,它似乎不起作用,可能是因为OpenCV使用了一个自定义分配器(fastFree()),它可能与std::shared_ptr调用的delete不兼容。这可能是OpenCV不允许释放矩阵的原因。
ukdjmx9f

ukdjmx9f2#

正如评论中建议的那样,我将复制数据,以便在删除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());

这看起来已经比我以前的要好得多,但是更好的解决方案是cv::Mat释放它对数据的所有权。

相关问题