web-dev-qa-db-fra.com

Convertir Mat en Matrice / Vecteur en OpenCV

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.

34
Main

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::Mats 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);
  }
}
76
herohuyongtao

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

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
3
Melike

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');
}
3
CorvusCorax

Peut être fait en deux lignes :)

Mat au tableau

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

Mat au vecteur

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.

Explication avec un exemple de travail

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

Explication étendue:

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.

Pointeur sur le bloc de mémoire interne

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.

Longueur du tableau/vecteur

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().

Reconstruire Mat à partir d'un tableau/vecteur

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.

Les références:

2
sziraqui
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));
}
1
Bogdan Ustyak
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
0
infoclogged