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
Convert Mat to Array/Vector in OpenCV
提问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 mat
is 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::Mat
s 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
回答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:
扩展解释:
Mat
is stored as a contiguous block of memory, if created using one of its constructors or when copied to another Mat
using clone()
or similar methods. To convert to an array or vector
we need the address of its first block and array/vector length.
Mat
如果使用其构造函数之一创建或Mat
使用clone()
或类似方法复制到另一个构造函数,则存储为连续的内存块。要转换为数组,或者vector
我们需要它的第一个块的地址和数组/向量长度。
Pointer to internal memory block
指向内部存储块的指针
Mat::data
is a public uchar pointer to its memory.
But this memory may not be contiguous. As explained in other answers, we can check if mat.data
is 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*channels
right?
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::size
public 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