Я новичок в OpenCV. Недавно у меня возникли проблемы с поиском функций OpenCV для преобразования из Mat в Array. Я исследовал методы .ptr и .at, доступные в API OpenCV, но я не мог получить правильные данные. Я хотел бы иметь прямое преобразование из Mat в Array (если доступно, если не Vector). Мне нужны функции OpenCV, потому что код должен быть подвергнут высокоуровневому синтезу в Vivado HLS. Пожалуйста помоги.
Преобразование Mat в массив/вектор в OpenCV
Ответ 1
Если память Mat mat
непрерывна (все ее данные непрерывны), вы можете напрямую получить ее данные в одномерный массив:
std::vector<uchar> array(mat.rows*mat.cols);
if (mat.isContinuous())
array = mat.data;
В противном случае вы должны получать его данные построчно, например, в двумерный массив:
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);
ОБНОВЛЕНИЕ: Будет проще, если вы используете 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);
}
}
ps: Для cv::Mat
других типов, таких как 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);
}
}
Ответ 2
Вот еще одно возможное решение, предполагающее, что матрица имеет один столбец (вы можете преобразовать исходный Mat в один столбец Mat через reshape):
Mat matrix= Mat::zeros(20, 1, CV_32FC1);
vector<float> vec;
matrix.col(0).copyTo(vec);
Ответ 3
Ни один из приведенных здесь примеров не работает для общего случая, который является N-мерными матрицами. Все, что использует "rows", предполагает только столбцы и строки theres, размерная 4-мерная матрица может иметь больше.
Вот пример кода, копирующего не непрерывную 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');
}
Ответ 4
Вместо того, чтобы получать изображение подряд за строкой, вы можете поместить его непосредственно в массив. Для изображения типа 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
Ответ 5
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));
}
Ответ 6
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
Ответ 7
Можно сделать в две строчки :)
Мат в массив
uchar * arr = image.isContinuous()? image.data: image.clone().data;
uint length = image.total()*image.channels();
Мат для вектора
cv::Mat flat = image.reshape(1, image.total()*image.channels());
std::vector<uchar> vec = image.isContinuous()? flat : flat.clone();
Оба работают для любого общего cv::Mat
.
Пояснение с рабочим примером
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);
Расширенное объяснение:
Mat
хранится как непрерывный блок памяти, если он создан с использованием одного из его конструкторов или когда скопирован в другой Mat
с помощью clone()
или аналогичных методов. Для преобразования в массив или vector
нам нужен адрес его первого блока и длина массива/вектора.
Указатель на блок внутренней памяти
Mat::data
является публичным указателем uchar на свою память.
Но эта память не может быть смежной. Как объяснено в других ответах, мы можем проверить, mat.data
ли mat.data
на непрерывную память или не использует mat.isContinous()
. Если вам не нужна чрезвычайная эффективность, вы можете получить непрерывную версию мата, используя mat.clone()
за O (N) время. (N = количество элементов со всех каналов). Однако при работе с изображениями, читаемыми cv::imread()
мы редко когда-либо сталкиваемся с непрерывным ковриком.
Длина массива/вектора
Q: Должны быть row*cols*channels
верно?
A: не всегда. Это могут быть rows*cols*x*y*channels
.
Q: должно быть равно mat.total()?
A: Верно для одноканального мата. Но не для многоканального мата
Длина массива/вектора немного сложна из-за плохой документации OpenCV. У нас есть открытый член Mat::size
который хранит только размеры одного Mat без каналов. Для RGB-изображения Mat.size = [строки, столбцы], а не [строки, столбцы, каналы]. Mat.total()
возвращает общее количество элементов в одном канале мата, которое равно произведению значений в mat.size
. Для изображения RGB total() = rows*cols
. Таким образом, для любого общего Mat длина непрерывного блока памяти будет равна mat.total()*mat.channels()
.
Восстановление Mat из массива/вектора
Помимо массива/вектора нам также нужны оригинальные Mat mat.size
[array like] и mat.type()
[int]. Затем, используя один из конструкторов, которые получают указатель на данные, мы можем получить оригинальный Mat. Необязательный аргумент шага не требуется, потому что наш указатель данных указывает на непрерывную память. Я использовал этот метод для передачи Mat как Uint8Array между nodejs и C++. Это позволило избежать записи C++ привязок для cv :: Mat с помощью node-addon-api.