C++ 在 OpenCV 中将 Mat 转换为数组/矢量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26681713/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 11:40:21  来源:igfitidea点击:

Convert Mat to Array/Vector in OpenCV

c++arraysopencvvectorsynthesis

提问by Main

I am novice in OpenCV. Recently, I have troubles finding OpenCV functions to convert from Mat to Array. I researched with .ptr and .at methods available in OpenCV APIs, but I could not get proper data. I would like to have direct conversion from Mat to Array(if available, if not to Vector). I need OpenCV functions because the code has to be undergo high level synthesis in Vivado HLS. Please help.

我是 OpenCV 的新手。最近,我在寻找 OpenCV 函数以将 Mat 转换为 Array 时遇到了麻烦。我研究了 OpenCV API 中可用的 .ptr 和 .at 方法,但我无法获得正确的数据。我想要从 Mat 到 Array 的直接转换(如果可用,如果不是 Vector)。我需要 OpenCV 函数,因为代码必须在 Vivado HLS 中进行高级综合。请帮忙。

回答by herohuyongtao

If the memory of the Mat matis continuous (all its data is continuous), you can directly get its data to a 1D array:

如果内存Mat mat是连续的(它的所有数据都是连续的),可以直接把它的数据拿到一维数组中:

std::vector<uchar> array(mat.rows*mat.cols);
if (mat.isContinuous())
    array = mat.data;

Otherwise, you have to get its data row by row, e.g. to a 2D array:

否则,您必须逐行获取其数据,例如获取到二维数组:

uchar **array = new uchar*[mat.rows];
for (int i=0; i<mat.rows; ++i)
    array[i] = new uchar[mat.cols];

for (int i=0; i<mat.rows; ++i)
    array[i] = mat.ptr<uchar>(i);


UPDATE:It will be easier if you're using std::vector, where you can do like this:

更新:如果您使用std::vector,会更容易,您可以这样做:

std::vector<uchar> array;
if (mat.isContinuous()) {
  // array.assign(mat.datastart, mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
  array.assign(mat.data, mat.data + mat.total());
} else {
  for (int i = 0; i < mat.rows; ++i) {
    array.insert(array.end(), mat.ptr<uchar>(i), mat.ptr<uchar>(i)+mat.cols);
  }
}

p.s.: For cv::Mats of other types, like CV_32F, you should do like this:

ps:对于cv::Mat其他类型的 s,比如CV_32F,你应该这样做:

std::vector<float> array;
if (mat.isContinuous()) {
  // array.assign((float*)mat.datastart, (float*)mat.dataend); // <- has problems for sub-matrix like mat = big_mat.row(i)
  array.assign((float*)mat.data, (float*)mat.data + mat.total());
} else {
  for (int i = 0; i < mat.rows; ++i) {
    array.insert(array.end(), mat.ptr<float>(i), mat.ptr<float>(i)+mat.cols);
  }
}


UPDATE2:For OpenCV Mat data continuity, it can be summarized as follows:

UPDATE2:对于OpenCV Mat数据连续性,可以总结如下:

  • Matrices created by imread(), clone(), or a constructor will always be continuous.
  • The only time a matrix will not be continuous is when it borrows data (except the data borrowed is continuous in the big matrix, e.g. 1. single row; 2. multiple rows with full original width) from an existing matrix (i.e. created out of an ROI of a big mat).
  • 通过创建矩阵imread()clone()或构造将永远是连续的。
  • 矩阵不连续的唯一时间是当它从现有矩阵(即从大垫子的投资回报率)。

Please check out this code snippetfor demonstration.

请查看此代码片段以进行演示。

回答by mrgloom

Here is another possible solution assuming matrix have one column( you can reshape original Mat to one column Mat via reshape):

这里是另一个可能的解决方案假设矩阵有一列(您可以通过重塑原来垫一列垫重塑):

Mat matrix= Mat::zeros(20, 1, CV_32FC1);
vector<float> vec;
matrix.col(0).copyTo(vec);

回答by sziraqui

Can be done in two lines :)

可以在两行中完成:)

Mat to array

垫到阵列

uchar * arr = image.isContinuous()? image.data: image.clone().data;
uint length = image.total()*image.channels();

Mat to vector

垫到矢量

cv::Mat flat = image.reshape(1, image.total()*image.channels());
std::vector<uchar> vec = image.isContinuous()? flat : flat.clone();

Both work for anygeneral cv::Mat.

两者都适用于任何将军cv::Mat

Explanation with a working example

用工作示例说明

    cv::Mat image;
    image = cv::imread(argv[1], cv::IMREAD_UNCHANGED);   // Read the file
    cv::namedWindow("cvmat", cv::WINDOW_AUTOSIZE );// Create a window for display.
    cv::imshow("cvmat", image );                   // Show our image inside it.

    // flatten the mat.
    uint totalElements = image.total()*image.channels(); // Note: image.total() == rows*cols.
    cv::Mat flat = image.reshape(1, totalElements); // 1xN mat of 1 channel, O(1) operation
    if(!image.isContinuous()) {
        flat = flat.clone(); // O(N),
    }
    // flat.data is your array pointer
    auto * ptr = flat.data; // usually, its uchar*
    // You have your array, its length is flat.total() [rows=1, cols=totalElements]
    // Converting to vector
    std::vector<uchar> vec(flat.data, flat.data + flat.total());
    // Testing by reconstruction of cvMat
    cv::Mat restored = cv::Mat(image.rows, image.cols, image.type(), ptr); // OR vec.data() instead of ptr
    cv::namedWindow("reconstructed", cv::WINDOW_AUTOSIZE);
    cv::imshow("reconstructed", restored);

    cv::waitKey(0);     

Extended explanation:

扩展解释:

Matis stored as a contiguous block of memory, if created using one of its constructors or when copied to another Matusing clone()or similar methods. To convert to an array or vectorwe need the address of its first block and array/vector length.

Mat如果使用其构造函数之一创建或Mat使用clone()或类似方法复制到另一个构造函数,则存储为连续的内存块。要转换为数组,或者vector我们需要它的第一个块的地址和数组/向量长度。

Pointer to internal memory block

指向内部存储块的指针

Mat::datais a public uchar pointer to its memory.
But this memory may not be contiguous. As explained in other answers, we can check if mat.datais pointing to contiguous memory or not using mat.isContinous(). Unless you need extreme efficiency, you can obtain a continuous version of the mat using mat.clone()in O(N) time. (N = number of elements from all channels). However, when dealing images read by cv::imread()we will rarely ever encounter a non-continous mat.

Mat::data是指向其内存的公共 uchar 指针。
但是这个内存可能不是连续的。如其他答案中所述,我们可以检查是否mat.data指向连续内存或不使用mat.isContinous(). 除非您需要极高的效率,否则您可以mat.clone()在 O(N) 时间内获得连续版本的垫子。(N = 来自所有通道的元素数)。然而,在处理读取的图像时,cv::imread()我们很少会遇到不连续的垫子。

Length of array/vector

数组/向量的长度

Q: Should be row*cols*channelsright?
A: Not always. It can be rows*cols*x*y*channels.
Q: Should be equal to mat.total()?
A: True for single channel mat. But not for multi-channel mat
Length of the array/vector is slightly tricky because of poor documentation of OpenCV. We have Mat::sizepublic member which stores only the dimensions of single Mat withoutchannels. For RGB image, Mat.size = [rows, cols] and not [rows, cols, channels]. Mat.total()returns total elements in a single channel of the mat which is equal to product of values in mat.size. For RGB image, total() = rows*cols. Thus, for any general Mat, length of continuous memory block would be mat.total()*mat.channels().

问:应该对row*cols*channels吧?
答:并非总是如此。它可以rows*cols*x*y*channels
问:应该等于 mat.total() 吗?
A:适用于单通道垫。但不适用于多通道垫
数组/向量的长度由于 OpenCV 的文档不佳而有点棘手。我们有Mat::size公共成员,它只存储没有通道的单个 Mat 的尺寸。对于 RGB 图像,Mat.size = [rows, cols] 而不是 [rows, cols, channels]。Mat.total()返回 mat 的单个通道中的总元素,它等于 中值的乘积mat.size。对于 RGB 图像,total() = rows*cols. 因此,对于任何通用 Mat,连续内存块的长度将为mat.total()*mat.channels().

Reconstructing Mat from array/vector

从数组/向量重建 Mat

Apart from array/vector we also need the original Mat's mat.size[array like] and mat.type()[int]. Then using one of the constructors that take data's pointer, we can obtain original Mat. The optional step argument is not required because our data pointer points to continuous memory. I used this method to pass Mat as Uint8Array between nodejs and C++. This avoided writing C++ bindings for cv::Mat with node-addon-api.

除了数组/向量,我们还需要原始 Mat 的mat.size[array like] 和mat.type()[int]。然后使用其中一个获取数据指针的构造函数,我们可以获得原始的 Mat。可选的 step 参数不是必需的,因为我们的数据指针指向连续内存。我使用这种方法在 nodejs 和 C++ 之间将 Mat 作为 Uint8Array 传递。这避免了使用 node-addon-api 为 cv::Mat 编写 C++ 绑定。

References:

参考:

回答by CorvusCorax

None of the provided examples here work for the generic case, which are N dimensional matrices. Anything using "rows" assumes theres columns and rows only, a 4 dimensional matrix might have more.

这里提供的示例都不适用于一般情况,即 N 维矩阵。任何使用“行”的东西都假设只有列和行,4 维矩阵可能有更多。

Here is some example code copying a non-continuous N-dimensional matrix into a continuous memory stream - then converts it back into a Cv::Mat

这是将非连续 N 维矩阵复制到连续内存流中的一些示例代码 - 然后将其转换回 Cv::Mat

#include <iostream>
#include <cstdint>
#include <cstring>
#include <opencv2/opencv.hpp>

int main(int argc, char**argv)
{
    if ( argc != 2 )
    {
        std::cerr << "Usage: " << argv[0] << " <Image_Path>\n";
        return -1;
    }
    cv::Mat origSource = cv::imread(argv[1],1);

    if (!origSource.data) {
        std::cerr << "Can't read image";
        return -1;
    }

    // this will select a subsection of the original source image - WITHOUT copying the data
    // (the header will point to a region of interest, adjusting data pointers and row step sizes)
    cv::Mat sourceMat = origSource(cv::Range(origSource.size[0]/4,(3*origSource.size[0])/4),cv::Range(origSource.size[1]/4,(3*origSource.size[1])/4));

    // correctly copy the contents of an N dimensional cv::Mat
    // works just as fast as copying a 2D mat, but has much more difficult to read code :)
    // see http://stackoverflow.com/questions/18882242/how-do-i-get-the-size-of-a-multi-dimensional-cvmat-mat-or-matnd
    // copy this code in your own cvMat_To_Char_Array() function which really OpenCV should provide somehow...
    // keep in mind that even Mat::clone() aligns each row at a 4 byte boundary, so uneven sized images always have stepgaps
    size_t totalsize = sourceMat.step[sourceMat.dims-1];
    const size_t rowsize = sourceMat.step[sourceMat.dims-1] * sourceMat.size[sourceMat.dims-1];
    size_t coordinates[sourceMat.dims-1] = {0};
    std::cout << "Image dimensions: ";
    for (int t=0;t<sourceMat.dims;t++)
    {
        // calculate total size of multi dimensional matrix by multiplying dimensions
        totalsize*=sourceMat.size[t];
        std::cout << (t>0?" X ":"") << sourceMat.size[t];
    }
    // Allocate destination image buffer
    uint8_t * imagebuffer = new uint8_t[totalsize];
    size_t srcptr=0,dptr=0;
    std::cout << std::endl;
    std::cout << "One pixel in image has " << sourceMat.step[sourceMat.dims-1] << " bytes" <<std::endl;
    std::cout << "Copying data in blocks of " << rowsize << " bytes" << std::endl ;
    std::cout << "Total size is " << totalsize << " bytes" << std::endl;
    while (dptr<totalsize) {
        // we copy entire rows at once, so lowest iterator is always [dims-2]
        // this is legal since OpenCV does not use 1 dimensional matrices internally (a 1D matrix is a 2d matrix with only 1 row)
        std::memcpy(&imagebuffer[dptr],&(((uint8_t*)sourceMat.data)[srcptr]),rowsize);
        // destination matrix has no gaps so rows follow each other directly
        dptr += rowsize;
        // src matrix can have gaps so we need to calculate the address of the start of the next row the hard way
        // see *brief* text in opencv2/core/mat.hpp for address calculation
        coordinates[sourceMat.dims-2]++;
        srcptr = 0;
        for (int t=sourceMat.dims-2;t>=0;t--) {
            if (coordinates[t]>=sourceMat.size[t]) {
                if (t==0) break;
                coordinates[t]=0;
                coordinates[t-1]++;
            }
            srcptr += sourceMat.step[t]*coordinates[t];
        }
   }

   // this constructor assumes that imagebuffer is gap-less (if not, a complete array of step sizes must be given, too)
   cv::Mat destination=cv::Mat(sourceMat.dims, sourceMat.size, sourceMat.type(), (void*)imagebuffer);

   // and just to proof that sourceImage points to the same memory as origSource, we strike it through
   cv::line(sourceMat,cv::Point(0,0),cv::Point(sourceMat.size[1],sourceMat.size[0]),CV_RGB(255,0,0),3);

   cv::imshow("original image",origSource);
   cv::imshow("partial image",sourceMat);
   cv::imshow("copied image",destination);
   while (cv::waitKey(60)!='q');
}

回答by Melike

Instead of getting image row by row, you can put it directly to an array. For CV_8Utype image, you can use byte array, for other types check here.

您可以将其直接放入数组中,而不是逐行获取图像。对于CV_8U类型的图像,您可以使用字节数组,对于其他类型,请查看此处

Mat img; // Should be CV_8U for using byte[]
int size = (int)img.total() * img.channels();
byte[] data = new byte[size];
img.get(0, 0, data); // Gets all pixels

回答by Bogdan Ustyak

byte * matToBytes(Mat image)
{
   int size = image.total() * image.elemSize();
   byte * bytes = new byte[size];  //delete[] later
   std::memcpy(bytes,image.data,size * sizeof(byte));
}

回答by infoclogged

cv::Mat m;
m.create(10, 10, CV_32FC3);

float *array = (float *)malloc( 3*sizeof(float)*10*10 );
cv::MatConstIterator_<cv::Vec3f> it = m.begin<cv::Vec3f>();
for (unsigned i = 0; it != m.end<cv::Vec3f>(); it++ ) {
    for ( unsigned j = 0; j < 3; j++ ) {
        *(array + i ) = (*it)[j];
        i++;
    }
}

Now you have a float array. In case of 8 bit, simply change float to uchar and Vec3f to Vec3b and CV_32FC3 to CV_8UC3