Je suis novice en OpenCV. Récemment, j'ai eu du mal à trouver des fonctions OpenCV pour convertir Mat en Array. J'ai étudié les méthodes .ptr et .at disponibles dans les API OpenCV, mais je ne pouvais pas obtenir les données appropriées. Je voudrais avoir une conversion directe de Mat à Array (si disponible, sinon à Vector). J'ai besoin de fonctions OpenCV car le code doit être soumis à une synthèse de haut niveau dans Vivado HLS. S'il vous plaît aider.
Si la mémoire du Mat mat
est continu (toutes ses données sont continues), vous pouvez directement obtenir ses données dans un tableau 1D:
std::vector<uchar> array(mat.rows*mat.cols);
if (mat.isContinuous())
array = mat.data;
Sinon, vous devez obtenir ses données ligne par ligne, par exemple. à un tableau 2D:
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);
PDATE: Ce sera plus facile si vous utilisez std::vector
, où vous pouvez faire comme ceci:
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 .: Pour cv::Mat
s d’autres types, comme CV_32F
, tu devrais faire comme ça:
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);
}
}
Voici une autre solution possible en supposant que les matrices ont une colonne (vous pouvez redéfinir le mat d'origine en un mat de colonne via refaçonner ):
Mat matrix= Mat::zeros(20, 1, CV_32FC1);
vector<float> vec;
matrix.col(0).copyTo(vec);
Au lieu d’obtenir une image rangée par rangée, vous pouvez la placer directement dans un tableau. Pour CV_8U type image, vous pouvez utiliser un tableau d'octets, pour les autres types, vérifiez ici .
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
Aucun des exemples fournis ici ne fonctionne pour le cas générique, qui est une matrice à N dimensions. Tout ce qui utilise "lignes" suppose qu'il n'y ait que des colonnes et des lignes, une matrice à 4 dimensions pourrait en avoir plus.
Voici un exemple de code copiant une matrice N-dimensionnelle non continue dans un flux de mémoire continu - puis la reconvertit en 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');
}
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();
Les deux fonctionnent pour tous les types généraux 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
est stocké sous forme de bloc de mémoire contigu, s'il a été créé à l'aide de l'un de ses constructeurs ou copié dans un autre Mat
à l'aide de clone()
ou de méthodes similaires. Pour convertir en tableau ou vector
, nous avons besoin de l'adresse de son premier bloc et de la longueur du tableau/vecteur.
Mat::data
Est un pointeur public uchar vers sa mémoire.
Mais cette mémoire peut ne pas être contiguë. Comme expliqué dans d'autres réponses, nous pouvons vérifier si mat.data
Pointe vers la mémoire contiguë ou n'utilise pas mat.isContinous()
. Sauf si vous avez besoin d'une efficacité extrême, vous pouvez obtenir une version continue du tapis en utilisant mat.clone()
in O(N) time. (N = nombre d'éléments de tous les canaux). Cependant, lorsqu’il s’agit de traiter des images lues par cv::imread()
, nous ne rencontrerons que rarement un tapis non continu.
Q: Est-ce que row*cols*channels
Devrait être correct?
R: Pas toujours. Ce peut être rows*cols*x*y*channels
.
Q: Devrait être égal à mat.total ()?
A: Vrai pour un tapis monocanal. Mais pas pour les tapis multicanaux
La longueur du tableau/vecteur est légèrement délicate à cause de la mauvaise documentation d'OpenCV. Nous avons un membre public Mat::size
Qui stocke uniquement les dimensions d'un seul tapis sans canaux. Pour les images RVB, Mat.size = [lignes, colonnes] et non [lignes, colonnes, canaux]. Mat.total()
renvoie le total des éléments dans un seul canal du tapis, ce qui correspond au produit des valeurs dans mat.size
. Pour une image RVB, total() = rows*cols
. Ainsi, pour tout mat général, la longueur du bloc de mémoire continue serait de mat.total()*mat.channels()
.
En plus du tableau/vecteur, nous avons également besoin des fichiers mat.size
[Array like] et mat.type()
[int] de Mat d'origine. Ensuite, en utilisant l’un des constructeurs prenant le pointeur des données, nous pouvons obtenir l’objet Mat original. L'argument optionnel step n'est pas requis car notre pointeur de données pointe en mémoire continue. J'ai utilisé cette méthode pour passer Mat en tant que Uint8Array entre nodejs et C++. Cela évitait d'écrire des liaisons C++ pour cv :: Mat avec node-addon-api.
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));
}
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