web-dev-qa-db-ja.com

OpenCVでMat​​を配列/ベクトルに変換

私はOpenCVの初心者です。最近、MatからArrayに変換するOpenCV関数を見つけるのに問題があります。 OpenCV APIで使用可能な.ptrおよび.atメソッドを調べましたが、適切なデータを取得できませんでした。 MatからArrayへの直接変換が必要です(可能であれば、Vectorには変換しません)。 Vivado HLSでコードを高レベル合成する必要があるため、OpenCV関数が必要です。助けてください。

34
Main

Mat matのメモリが連続的である(すべてのデータが連続的である)場合、そのデータを1D配列に直接取得できます。

std::vector<uchar> array(mat.rows*mat.cols);
if (mat.isContinuous())
    array = mat.data;

そうでなければ、データを行ごとに取得する必要があります。 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: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 .: cv::Matなどの他のタイプのCV_32Fsの場合、次のようにする必要があります。

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

マトリックスに1つの列があると仮定した場合の別の解決策は次のとおりです(元のマットを reshape を使用して1つの列マットに変形できます)。

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

行ごとに画像を取得する代わりに、配列に直接配置できます。 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
3
Melike

ここで提供されている例はいずれも、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');
}
3
CorvusCorax

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は、そのコンストラクタの1つを使用して作成された場合、またはclone()または同様のメソッドを使用して別のMatにコピーされた場合、メモリの連続ブロックとして保存されます。配列またはvectorに変換するには、最初のブロックのアドレスと配列/ベクトルの長さが必要です。

内部メモリブロックへのポインタ

Mat::dataは、そのメモリへのパブリックucharポインターです。
ただし、このメモリは連続していない場合があります。他の回答で説明したように、mat.dataが連続メモリを指しているか、mat.isContinous()を使用していないかを確認できます。極端な効率を必要としない限り、O(N)時間でmat.clone()を使用してマットの連続バージョンを取得できます。 (N =すべてのチャネルからの要素の数)。ただし、cv::imread()によって読み取られた画像を処理する場合、非連続マットに遭遇することはほとんどありません。

配列/ベクトルの長さ

Q:row*cols*channelsである必要がありますか?
A:常にではありません。 rows*cols*x*y*channelsにすることができます。
Q:mat.total()と等しくなければなりませんか?
A:シングルチャンネルマットに当てはまります。しかし、マルチチャンネルマット用ではありません
OpenCVのドキュメンテーションが不十分なため、配列/ベクトルの長さは少し注意が必要です。単一のMatwithoutチャンネルの次元のみを保存するMat::size publicメンバーがあります。 RGBイメージの場合、Mat.size = [rows、cols]であり、[rows、cols、channels]ではありません。 Mat.total()は、mat.sizeの値の積に等しいマットの単一チャネルの合計要素を返します。 RGB画像の場合、total() = rows*cols。したがって、一般的なマットの場合、連続メモリブロックの長さはmat.total()*mat.channels()になります。

配列/ベクトルからマットを再構築

配列/ベクトルとは別に、元のMatのmat.size [array like]およびmat.type() [int]も必要です。次に、データのポインターを受け取るコンストラクターの1つを使用して、元のMatを取得できます。データポインターが連続メモリを指すため、オプションのステップ引数は必要ありません。この方法を使用して、MatをnodejsとC++の間でUint8Arrayとして渡しました。これにより、node-addon-apiを使用したcv :: MatのC++バインディングの記述が回避されました。

参照:

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