【问题标题】:Convert Mat to Array/Vector in OpenCV在 OpenCV 中将 Mat 转换为数组/向量
【发布时间】:2014-12-28 04:32:24
【问题描述】:

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

【问题讨论】:

    标签: c++ arrays opencv vector synthesis


    【解决方案1】:

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

    std::vector<uchar> array(mat.rows*mat.cols*mat.channels());
    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*mat.channels()];
    
    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()*mat.channels());
    } else {
      for (int i = 0; i < mat.rows; ++i) {
        array.insert(array.end(), mat.ptr<uchar>(i), mat.ptr<uchar>(i)+mat.cols*mat.channels());
      }
    }
    

    p.s.:对于其他类型的cv::Mats,比如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()*mat.channels());
    } else {
      for (int i = 0; i < mat.rows; ++i) {
        array.insert(array.end(), mat.ptr<float>(i), mat.ptr<float>(i)+mat.cols*mat.channels());
      }
    }
    

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

    • imread()clone() 或构造函数创建的矩阵将始终是连续的。
    • 矩阵不连续的唯一情况是它从现有矩阵(即根据大垫子的投资回报率创建)。

    请查看this code snippet 进行演示。

    【讨论】:

    • 如果你使用 std::vector 会更好。使用裸指针还必须释放内存。
    • @blackibiza 好点。更新了使用std::vector 的答案。 :-)
    • 我猜mat.cols在用array.insert复制数据时应该乘以mat.channels。当使用 float 作为模板参数时,也可以省略从 uchar*float* 的转换:mat.ptr&lt;float&gt;
    • 这行有一个隐藏的错误:array.assign(mat.datastart, mat.dataend); 给定一个随机的 2D (2X10) mat m,调用你的 'vec2mat' 函数,vector&lt;T&gt; array1 = mat2vec(mat.row(0)), array2 = mat2vec(mat.row(1)),你会发现 array1 和 array2大小为 20,而不是 10。
    • @WilderField 单行矩阵保证是连续的,但当它的数据从另一个大矩阵(由大矩阵的 ROI 创建)借用时,不能保证单列矩阵。我创建了一个code snippet 以便更好地演示。
    【解决方案2】:

    可以分两行完成:)

    垫到数组

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

    两者都适用于any一般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 存储为连续的内存块,如果使用其构造函数之一创建或使用clone() 或类似方法复制到另一个Mat 时。要转换为数组或vector,我们需要它的第一个块的地址和数组/向量长度。

    指向内部内存块的指针

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

    数组/向量的长度

    问:应该是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.size 中值的乘积。对于 RGB 图像,total() = rows*cols。因此,对于任何通用 Mat,连续内存块的长度将为 mat.total()*mat.channels()

    从数组/向量重构 Mat

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

    参考资料:

    【讨论】:

    • 如果矩阵不连续,“Mat to vector”建议将不起作用,因为 reshape 会抛出异常。
    • 你试过了还是失败了? .clone() 确保您获得一个连续的平面数组,您可以稍后对其进行整形。
    • 是的,我做到了。你对克隆返回一个连续数组是正确的,但关键是你建议在克隆它之前重塑垫子图像,所以如果图像不连续,它会抛出异常。无论如何,它帮助我找到了解决问题的方法。谢谢。
    【解决方案3】:

    这是另一种可能的解决方案,假设矩阵有一列(您可以通过reshape 将原始 Mat 重塑为一列 Mat):

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

    【讨论】:

    • 为什么this code不能正常运行?
    • @yode imgdouble 类型,而 vecint 类型。此外,vec 在您的情况下不是一维的。
    【解决方案4】:

    此处提供的示例均不适用于通用情况,即 N 维矩阵。任何使用“行”的东西都假定只有列和行,一个 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');
    }
    

    【讨论】:

      【解决方案5】:

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

      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
      

      【讨论】:

        【解决方案6】:
        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));
        }
        

        【讨论】:

        • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值。
        【解决方案7】:

        您可以使用迭代器:

        Mat matrix = ...;
        
        std::vector<float> vec(matrix.begin<float>(), matrix.end<float>());
        

        【讨论】:

          【解决方案8】:
          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++;
              }
          }
          

          现在你有了一个浮点数组。如果是 8 位,只需将 float 更改为 ucharVec3f 更改为 Vec3bCV_32FC3 更改为 CV_8UC3

          【讨论】:

          • 在这种情况下,你可以用这个重新创建opencv垫:A = Mat(10, 10, CV_32FC3, &amp;array); ?
          • 我不确定,但我认为你可以做到。我可以稍后检查。因此,您想通过再次将它们转换为 Mat 来验证对 cv::Mat m 创建的数组的转换?
          • 是的,我想我试过了(但那是很久以前的事了,我不确定),它奏效了!
          • 好的,您第一条评论末尾的问号看起来像是您在问问题。
          【解决方案9】:

          如果你知道你的 img 是 3 通道,那么你可以试试这个代码

           Vec3b* dados = new Vec3b[img.rows*img.cols];
              for (int i = 0; i < img.rows; i++)
                  for(int j=0;j<img.cols; j++)
                      dados[3*i*img.cols+j] =img.at<Vec3b>(i,j);
          

          如果你想检查 (i,j) vec3b 你可以写

          std::cout << (Vec3b)img.at<Vec3b>(i,j) << std::endl;
              std::cout << (Vec3b)dados[3*i*img.cols+j] << std::endl;
          

          【讨论】:

            猜你喜欢
            • 2012-11-21
            • 2013-09-02
            • 2013-10-05
            • 1970-01-01
            • 2016-09-04
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-02-18
            相关资源
            最近更新 更多