cv::Matの基本処理

cv::Matの概要

OpenCV 1.x には,基本的に C言語 および Python のインタフェースが用意されていましたが,OpenCV 2.0 以降では,新たに C++ インタフェースが追加されました. OpenCV 1.x では,画像を管理する構造体として IplImage が,その他の行列を管理する構造体として CvMat が用いられました. しかし,OpenCV 2.x の C++ インタフェースでは, cv::Mat クラスを利用して,これらを統一的に扱います. cv::Mat クラスは,実際のデータへのポインタと,様々なプロパティ(幅,高さ,ビット深度など)を保持します.

  1.0/1.1pre 2.0/2.1 2.2/2.3
Interface C C++ C C++ C C++
Image IplImage   IplImage Mat IplImage Mat
Matrix CvMat   CvMat Mat CvMat Mat
N-dimentional Matrix CvMatND   CvMatND MatND CvMatND Mat
Sparse Matrix CvSparseMat   CvSparseMat SparseMat CvSparseMat SparseMat

マルチチャンネルと多次元配列

OpenCV 2.2 以降,2次元配列用の cv::Mat クラスと,多次元配列用の cv::MatND クラスが統一されました.つまり,現在は, cv::Mat で任意次元の配列を表現します. しかし,今まで cv::Mat を引数に取っていた関数が,すべて「多次元(>=3次元)」の Mat に対応しているとは限らないので注意が必要です.

OpenCVの構造には,次元とチャンネルの概念があります.

  • チャンネル(channel):要素の次元
  • 次元(dimension):複数の要素からなる配列の次元

マルチチャンネル多次元配列の例(Dim=2 Ch=2, Dim=2 Ch=1, Dim=2 Ch=3, Dim=3 Ch=4):

_images/mat17c2.png _images/space.png _images/mat57c1.png _images/space.png _images/mat57c3.png _images/space.png _images/mat57xc4.png

上記の例のように, 1\times xx\times 1 の配列は,1次元ベクトルではなく,1行の2次元行列と見なされることに注意してください.つまり,Mat の次元は,必ず2以上になります.

次元数の最大値は CV_MAX_DIM (OpenCV 2.2/2.3 では =32)で定義されます. また,4チャンネル以上の Mat を作成する場合は,要素の型として CV_8UC(n), ..., CV_64FC(n) を指定してください. 作成された Mat の,チャンネル数や次元数,その他のプロパティを得る方法は, cv::Matの様々なプロパティ を参照してください.

_images/mat57gray.png _images/space.png _images/mat57color.png

Mat で画像を表現する場合,各チャンネルは「色平面」と呼ばれることもあります.画像の1つのチャンネルが1つの色(あるいは輝度や彩度など)を表すためです.カラー画像では,RGB表現が良く利用されますが,OpenCVのチャンネルの順番は「BGR」になっていることに注意してください.

1チャンネルを表現するためのビット数と,{U|S|F}の数値表現ID,チャンネル数を合わせて, CV_<bit-depth>{U|S|F}C<number_of_channels> の形式で CV_8UC1 (符号なし8ビット整数型,1チャンネル)や CV_32FC2 (32ビット浮動小数点型,2チャンネル)のような一意な識別子を与える事ができます.これが Mat のデータ型となります.

これらは types_c.h ヘッダで次の様に定義されています.

#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT))
...
#define CV_8UC1 CV_MAKETYPE(CV_8U,1)
#define CV_8UC2 CV_MAKETYPE(CV_8U,2)
...
#define CV_64FC1 CV_MAKETYPE(CV_64F,1)
#define CV_64FC2 CV_MAKETYPE(CV_64F,2)
...

上述のように,CV_8U や CV_32F などの CV_<depth> という表記と, CV_8UC1 や CV_32FC1 などの1チャンネルの表記 CV_<depth>C1 は一致するので,コンストラクタなどで,1チャンネルの型を指定する場合は, CV_8UC1CV_8U のどちらでも構いません.

以下に,マルチチャンネル多次元配列の作成の例を示します.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 64F, channels=10, 3x3 の2次元配列(行列)
  cv::Mat m1(3, 3, CV_64FC(10));
  cv::Mat m2(3, 3, CV_MAKETYPE(CV_64F, 10));
   
  CV_Assert(m1.type() == m2.type());
  std::cout << "mat1/mat2"<< std::endl;
  std::cout << "  dims: " << m1.dims << ", depth(byte/channel):" << m1.elemSize1() \
	    << ", channels: " << m1.channels() << std::endl;

  // 64F, channles=1, 2x2x3x4 の4次元配列
  const int sizes[] = {2, 2, 3, 4};
  cv::Mat m3(sizeof(sizes)/sizeof(int), sizes, CV_64FC1);
   
  CV_Assert(m3.dims == sizeof(sizes)/sizeof(sizes[0]));
  CV_Assert(m3.rows == -1); // 3次元以上のMatでは,
  CV_Assert(m3.cols == -1); // 常にrows==cols==-1
  
  std::cout << "mat3"<< std::endl;
  std::cout << "  dims: " << m3.dims << ", depth(byte/channel):" << m3.elemSize1() \
	    << ", channels: " << m3.channels() << std::endl;
}

実行結果:

mat1/mat2
  dims: 2, depth(byte/channel):8, channels: 10
mat3
  dims: 4, depth(byte/channel):8, channels: 1

浅いコピーと深いコピー

cv::Matを代入演算子でコピーすると,浅いコピー(shallow copy)が行われ,コピー元とコピー先とcv::Matでデータが共有されます.この場合,最も大きいメンバであるデータ自身が実際にはコピーされないので,cv::Matのコピー動作はサイズに依存せず高速に行われます.それぞれのcv::Matのdataポインタは,同じメモリアドレスを指すので,コピー元cv::Matのデータを書き換えると,コピー先cv::Matのデータも変わることになります.

コピー元とコピー先を別データとしてコピーするには,clone()メソッドを利用して,深いコピー(deep copy)を行います. clone()メソッド内部では,新たなcv::Matが作成され,そこにcopyTo()メソッドでデータ全体がコピーされます.

_images/shallow_copy.png _images/space.png _images/deep_copy.png
#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);

  // 浅いコピー
  cv::Mat m_shallow = m1;
  // 深いコピー(clone と copyTo)
  cv::Mat m_deep1 = m1.clone();
  cv::Mat m_deep2;
  m1.copyTo(m_deep2);

  std::cout << "m1=" << m1 << std::endl << std::endl;
  std::cout << "m_shallow=" << m_shallow << std::endl << std::endl;
  std::cout << "m_deep1=" << m_deep1 << std::endl << std::endl;
  std::cout << "m_deep2=" << m_deep2 << std::endl << std::endl;

  // 行列m1の(0,0)要素を書き換える
  m1.at<double>(0,0) = 100;

  std::cout << "m1=" << m1 << std::endl << std::endl;
  std::cout << "m_shallow=" << m_shallow << std::endl << std::endl;
  std::cout << "m_deep1=" << m_deep1 << std::endl << std::endl;
  std::cout << "m_deep2=" << m_deep2 << std::endl << std::endl;
}

実行結果:

m1=[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

m_shallow=[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

m_deep1=[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

m_deep2=[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

m1=[100, 2, 3;
  4, 5, 6;
  7, 8, 9]

m_shallow=[100, 2, 3;
  4, 5, 6;
  7, 8, 9]

m_deep1=[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

m_deep2=[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

連続データと不連続データ

通常,cv::Matのデータはメモリ上の連続した領域に割り当てられます. しかし,IplImageからのキャストを行った場合や,ROIを利用した場合など,このデータが不連続になる場合があります. ポインタを利用してデータにアクセスする場合,データの連続・不連続の問題を意識することが重要です.

cv::Matのデータがメモリ上で連続しているかを調べるには, cv::Matの様々なプロパティ を参照してください.

_images/continuous_discontinuous.png

cv::Mat の内容を std::cout に出力する

デフォルトの出力

OpenCV 2.2 から << 演算子で,std::cout への出力が可能になりました. MatExpr,Point_<_Tp>, Point3_<_Tp> や,マルチチャンネル配列なども出力可能です.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m1 = cv::Mat::eye(3,3,CV_32FC1);
  cv::Mat m2 = (cv::Mat_<cv::Vec2i>(2, 2) << cv::Vec2i(1,1), cv::Vec2i(2, 4), cv::Vec2i(3, 9), cv::Vec2i(4, 16));
  cv::Point_<int> p1(1,2);
  cv::Point3_<double> p2(1.5, 2.2, 3.9);
   
   // cv::Mat
   std::cout << "m1=" << m1 << std::endl << std::endl;
   // cv::Mat (multi-channel)
   std::cout << "m2=" << m2 << std::endl << std::endl;
   // cv::Point_, cv::Point3_
   std::cout << "p1=" << p1 << std::endl << std::endl;
   std::cout << "p2=" << p2 << std::endl << std::endl;
   // cv::MatExpr
   std::cout << "m1+m1+1=" << m1+m1+1 << std::endl;
}

実行結果:

m1=[1, 0, 0;
  0, 1, 0;
  0, 0, 1]

m2=[1, 1, 2, 4;
  3, 9, 4, 16]

p1=[1, 2]

p2=[1.5, 2.2, 3.9]

m1+m1+1=[3, 1, 1;
  1, 3, 1;
  1, 1, 3]

出力フォーマットの指定

std::cout への出力は,デフォルトスタイル以外にも,python,numpy,csv,c言語の配列スタイルの出力フォーマットを選択できます. フォーマットの選択には, cv::format 関数を利用します.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m(4, 2, CV_8UC3);
  cv::randu(m, cv::Scalar::all(0), cv::Scalar::all(255));
  
  std::cout << "m (default) = " << m << ";" << std::endl << std::endl;
  std::cout << "m (python) = " << cv::format(m,"python") << ";" << std::endl << std::endl;
  std::cout << "m (numpy) = " << cv::format(m,"numpy") << ";" << std::endl << std::endl;
  std::cout << "m (csv) = " << cv::format(m,"csv") << ";" << std::endl << std::endl; 
  std::cout << "m (c) = " << cv::format(m,"C") << ";" << std::endl << std::endl;
}

実行結果:

r (default) = [91, 2, 79, 179, 52, 205;
  236, 8, 181, 239, 26, 248;
  207, 218, 45, 183, 158, 101;
  102, 18, 118, 68, 210, 139];

r (python) = [[[91, 2, 79], [179, 52, 205]], 
  [[236, 8, 181], [239, 26, 248]], 
  [[207, 218, 45], [183, 158, 101]], 
  [[102, 18, 118], [68, 210, 139]]];

r (numpy) = array([[[91, 2, 79], [179, 52, 205]], 
  [[236, 8, 181], [239, 26, 248]], 
  [[207, 218, 45], [183, 158, 101]], 
  [[102, 18, 118], [68, 210, 139]]], type='uint8');

r (csv) = 91, 2, 79, 179, 52, 205
  236, 8, 181, 239, 26, 248
  207, 218, 45, 183, 158, 101
  102, 18, 118, 68, 210, 139
;

r (c) = {91, 2, 79, 179, 52, 205,
  236, 8, 181, 239, 26, 248,
  207, 218, 45, 183, 158, 101,
  102, 18, 118, 68, 210, 139};

cv::Matにおけるメモリの自動管理

まず,OpenCVの以前のバージョンと異なり,ほとんどの関数において,関数呼び出しの前に出力用の領域を確保しておく必要はありません. 多くの関数は,関数内で適切なサイズ,型,ビット深度の cv::Mat を確保します.

また, cv::Mat は,そのデータ領域がどこからも参照されなくなると,自動的にメモリを解放します. これは,データを参照する cv::Mat ヘッダの個数をカウントし,これが0になった場合にデータを解放する,参照カウント(Reference Count)方式によって実現されます. また,ユーザが自分自身でデータ領域を割り当てた場合は,参照カウンタは null のままで,参照カウントは動作しません.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 1000x1000x8 = 8(Mb) の大きな行列を作成します.
  cv::Mat A(1000, 1000, CV_64F);
  // ... 初期化など

  // A と同じデータを参照する,新しい行列ヘッダ B を作成します.
  // この処理は,行列のサイズによらず一瞬で行われます.
  cv::Mat B = A;
  // A の 3 行目に対するヘッダ C を作成します.データのコピーは行われません.
  cv::Mat C = B.row(3);
  // ここで,この行列の別のコピー D を作成します.
  // データは完全にコピーされ,複製が作られます.
  // A,B,Cは同じデータ(の一部)を参照しますが,Dは全く別のデータを参照します.
  cv::Mat D = B.clone();
  // B の 5 行目を C にコピー,つまり A の 5 行目を A の 3 行目にコピーします.
  // このデータを「変更されたAデータ」と呼びます.
  B.row(5).copyTo(C);
  // ここで, A と D が同じデータを参照するようにします.
  // これ以降も,「変更されたAデータ」 は BとCから参照されます.
  A = D;
  // B を(メモリバッファを参照しない)空の行列にしても,
  // 「変更されたAデータ」は,まだ C から参照されています.
  // Cは,単に元のAの(一部である)1つの行を表してます.
  B.release();
  
  // 最後に C の完全なコピーを作成し,それをCに代入します.
  // その結果,「変更されたAデータ」はどこからも参照されなくなるので,
  // 自動的に解放されます.
  C = C.clone();
}

参照カウント

参照カウンタの値は,データ領域と1対1で対応し,実データの先頭または末尾に置かれます. この値を見るには,refcount メンバを利用します.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
  int *refcount = m1.refcount;

  // m1を作成した時点での参照カウントは =1
  std::cout << "ref_count=" << *(refcount) << std::endl;

  {    
    cv::Mat m2 = m1;
    // m2からもm1.dataが参照されるようになったので,
    // この時点での参照カウントは =2
    std::cout << "ref_count=" << *(refcount) << std::endl;   
  }
  // スコープを抜けて、m2が解放される

  // m1.dataはm2から参照されなくなったので,
  // この時点での参照カウントは =1
  std::cout << "ref_count=" << *(refcount) << std::endl;   

  cv::Mat m3 = m1.clone();
  m1 = m3;

  // 以前のm1.dataは,別のアドレスにあるm3.dataで上書きされ,
  // どこからも参照されなくなったので,この時点での参照カウントは =0
  std::cout << "ref_count=" << *(refcount) << std::endl;   
}

実行結果:

ref_count=1
ref_count=2
ref_count=1
ref_count=0

cv::Matを初期化する

カンマ区切り初期化子

このカンマ区切り初期化子には,定数だけではなく,変数,MatExpr などを利用できます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 単位行列 (= cv::Mat::eye(3,3, CV_64F) )
  cv::Mat m = (cv::Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
  // 2x2 回転行列
  double angle = 30, a = std::cos(angle*CV_PI/180), b = std::sin(angle*CV_PI/180);
  cv::Mat r = (cv::Mat_<double>(2,2) << a, -b, b, a);
  
  std::cout << "m=" << m << std::endl << std::endl;
  std::cout << "r=" << r << std::endl;
}

実行結果:

m=[1, 0, 0;
  0, 1, 0;
  0, 0, 1]

r=[0.8660254037844387, -0.4999999999999999;
  0.4999999999999999, 0.8660254037844387]

MATLABスタイルの行列初期化関数

次のような,MATLABスタイルの行列初期化関数が存在します.

  • cv::Mat::zeros() :行列要素を0で埋めて初期化します.
  • cv::Mat::ones() :行列要素を1で埋めて初期化します.
  • cv::Mat::eye() :単位行列で初期化します.

ここで, cv::Mat::zeros() を2次元画像に対して用いた場合,黒色で画像を初期化することができます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
   // 要素がすべて 3 の 5x5 行列
   cv::Mat mat1 = cv::Mat::ones(5, 5, CV_8U)*3;
   // 要素がすべて 0 の 5x5 行列
   cv::Mat mat2 = cv::Mat::zeros(5, 5, CV_8U);
   // 5x5 の単位行列
   cv::Mat mat3 = cv::Mat::eye(5, 5, CV_8U);
   
   std::cout << "mat1=" << mat1 << std::endl << std::endl;
   std::cout << "mat2=" << mat2 << std::endl << std::endl;
   std::cout << "mat3=" << mat3 << std::endl;
}

実行結果:

mat1=[3, 3, 3, 3, 3;
  3, 3, 3, 3, 3;
  3, 3, 3, 3, 3;
  3, 3, 3, 3, 3;
  3, 3, 3, 3, 3]

mat2=[0, 0, 0, 0, 0;
  0, 0, 0, 0, 0;
  0, 0, 0, 0, 0;
  0, 0, 0, 0, 0;
  0, 0, 0, 0, 0]

mat3=[1, 0, 0, 0, 0;
  0, 1, 0, 0, 0;
  0, 0, 1, 0, 0;
  0, 0, 0, 1, 0;
  0, 0, 0, 0, 1]

配列の値で初期化する

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  float *data = new float[9] ;
  for(int i=0; i<9; ++i) 
    data[i] = i+1;

  {
    // この方法で確保したユーザデータは,参照カウントされません.
    // データの解放は,ユーザが責任を持ちます.
    cv::Mat m1(3, 3, CV_32F, data);

    // 参照カウンタは動作しません.
    CV_Assert(m1.refcount == NULL);

    std::cout << "m1=" << m1 << std::endl;
  }

  // ユーザの責任で解放します.
  delete[] data;

}

実行結果:

m1=[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

定数で初期化する

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m1(3, 3, CV_32F, cv::Scalar(5));
  std::cout << "m1=" << m1 << std::endl;

  cv::Mat m2(3, 3, CV_32F);
  m2 = cv::Scalar(5);
  std::cout << "m2=" << m2 << std::endl;

  cv::Mat m3 = cv::Mat::ones(3, 3, CV_32F)*5;
  std::cout << "m3=" << m3 << std::endl;

  cv::Mat m4 = cv::Mat::zeros(3, 3, CV_32F)+5;
  std::cout << "m4=" << m4 << std::endl;

  return 0;
}

実行結果:

m1=[5, 5, 5;
  5, 5, 5;
  5, 5, 5]
m2=[5, 5, 5;
  5, 5, 5;
  5, 5, 5]
m3=[5, 5, 5;
  5, 5, 5;
  5, 5, 5]
m4=[5, 5, 5;
  5, 5, 5;
  5, 5, 5]

乱数で初期化する

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat mat(3, 2, CV_8UC1);

  // 一様分布乱数,[0,256)
  cv::randu(mat, cv::Scalar(0), cv::Scalar(256));
  std::cout << mat << std::endl << std::endl;
  
  // 正規分布乱数,mean=128, stddev=10
  cv::randn(mat, cv::Scalar(128), cv::Scalar(10));
  std::cout << mat << std::endl << std::endl;  

  // RNG初期化
  cv::RNG gen(cv::getTickCount());

  // 一様分布乱数,[0,256)
  gen.fill(mat, cv::RNG::UNIFORM, cv::Scalar(0), cv::Scalar(256));
  std::cout << mat << std::endl << std::endl;

  // 正規分布乱数,mean=128, stddev=10
  gen.fill(mat, cv::RNG::NORMAL, cv::Scalar(128), cv::Scalar(10));
  std::cout << mat << std::endl << std::endl;
}

実行結果:

[246, 156;
  192, 7;
  165, 231]

[124, 140;
  125, 122;
  131, 133]

[161, 236;
  102, 177;
  15, 200]

[118, 138;
  113, 126;
  134, 148]

マルチチャンネル配列を初期化する

マルチチャンネル配列を初期化したい場合,初期化されたシングルチャンネル配列を reshape で変形します.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // CV32SC2, 3x3 行列
  // 初期化+reshapeを用いた変形
  cv::Mat m0 = (cv::Mat_<int>(3,6) << 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18);
  cv::Mat m1 = m0.reshape(2);
  
  // 一応,こういう風にもできる…
  cv::Vec2i data[] = {cv::Vec2i(1,2), cv::Vec2i(3,4),cv::Vec2i(5,6),cv::Vec2i(7,8),cv::Vec2i(9,10),
		      cv::Vec2i(11,12),cv::Vec2i(13,14),cv::Vec2i(15,16),cv::Vec2i(17,18)};
  cv::Mat m2 = cv::Mat(3, 3, CV_32SC2, data).clone();
  //
  cv::Mat m3 = (cv::Mat_<cv::Vec2i>(3,3) << cv::Vec2i(1,2),cv::Vec2i(3,4),cv::Vec2i(5,6),cv::Vec2i(7,8),
		cv::Vec2i(9,10),cv::Vec2i(11,12),cv::Vec2i(13,14),cv::Vec2i(15,16),cv::Vec2i(17,18));
  
  std::cout << "m1=" << m1 << std::endl << std::endl;
  std::cout << "m2=" << m1 << std::endl << std::endl;
  std::cout << "m3=" << m1 << std::endl << std::endl;
}

実行結果:

m1=[1, 2, 3, 4, 5, 6;
  7, 8, 9, 10, 11, 12;
  13, 14, 15, 16, 17, 18]

m2=[1, 2, 3, 4, 5, 6;
  7, 8, 9, 10, 11, 12;
  13, 14, 15, 16, 17, 18]

m3=[1, 2, 3, 4, 5, 6;
  7, 8, 9, 10, 11, 12;
  13, 14, 15, 16, 17, 18]

cv::Matの行数・列数・次元数を調べる

cv::Matの様々なプロパティ を参照してください.

cv::Matのビット深度を調べる

cv::Matの様々なプロパティ を参照してください.

cv::Matのデータがメモリ上で連続しているかを調べる

cv::Matの様々なプロパティ を参照してください.

cv::Matが部分配列かを調べる

cv::Matの様々なプロパティ を参照してください.

cv::Matの様々なプロパティ

シングルチャンネル,2次元配列

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m1(3, 4, CV_64FC1);
  
  // 行数
  std::cout << "rows:" << m1.rows <<std::endl;
  // 列数
  std::cout << "cols:" << m1.cols << std::endl;
  // 次元数
  std::cout << "dims:" << m1.dims << std::endl;
  // サイズ(2次元の場合)
  std::cout << "size[]:" << m1.size().width << "," << m1.size().height << std::endl;
  // ビット深度ID
  std::cout << "depth (ID):" << m1.depth() << "(=" << CV_64F << ")" << std::endl;
  // チャンネル数
  std::cout << "channels:" << m1.channels() << std::endl;
  // (複数チャンネルから成る)1要素のサイズ [バイト単位]
  std::cout << "elemSize:" << m1.elemSize() << "[byte]" << std::endl;
  // 1要素内の1チャンネル分のサイズ [バイト単位]
  std::cout << "elemSize1 (elemSize/channels):" << m1.elemSize1() << "[byte]" << std::endl;
  // 要素の総数
  std::cout << "total:" << m1.total() << std::endl;
  // ステップ数 [バイト単位]
  std::cout << "step:" << m1.step << "[byte]" << std::endl;
  // 1ステップ内のチャンネル総数
  std::cout << "step1 (step/elemSize1):" << m1.step1()  << std::endl;
  // データは連続か?
  std::cout << "isContinuous:" << (m1.isContinuous()?"true":"false") << std::endl;
  // 部分行列か?
  std::cout << "isSubmatrix:" << (m1.isSubmatrix()?"true":"false") << std::endl;
  // データは空か?
  std::cout << "empty:" << (m1.empty()?"true":"false") << std::endl;
}

実行結果:

rows:3
cols:4
dims:2
size[]:4,3
depth (ID):6(=6)
channels:1
elemSize:8[byte]
elemSize1 (elemSize/channels):8[byte]
total:12
step:32[byte]
step1 (step/elemSize1):4
isContinuous:true
isSubmatrix:false
empty:false

マルチチャンネル,2次元配列の部分配列

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m1(4, 5, CV_32FC(5));  //5x4
  cv::Rect roi_rect(0, 0, 3, 4); //4x3
  cv::Mat r1(m1, roi_rect);

  // 行数
  std::cout << "rows:" << r1.rows <<std::endl;
  // 列数
  std::cout << "cols:" << r1.cols << std::endl;
  // 次元数
  std::cout << "dims:" << r1.dims << std::endl;
  // サイズ(2次元の場合)
  std::cout << "size[]:" << r1.size().width << "," << r1.size().height << std::endl;
  // ビット深度ID
  std::cout << "depth (ID):" << r1.depth() << "(=" << CV_32F << ")" << std::endl;
  // チャンネル数
  std::cout << "channels:" << r1.channels() << std::endl;
  // (複数チャンネルから成る)1要素のサイズ [バイト単位]
  std::cout << "elemSize:" << r1.elemSize() << "[byte]" << std::endl;
  // 1要素内の1チャンネル分のサイズ [バイト単位]
  std::cout << "elemSize1 (elemSize/channels):" << r1.elemSize1() << "[byte]" << std::endl;
  // 要素の総数
  std::cout << "total:" << r1.total() << std::endl;
  // ステップ数 [バイト単位]
  std::cout << "step:" << r1.step << "[byte]" << std::endl;
  // 1ステップ内のチャンネル総数
  std::cout << "step1 (step/elemSize1):" << r1.step1()  << std::endl;
  // データは連続か?
  std::cout << "isContinuous:" << (r1.isContinuous()?"true":"false") << std::endl;
  // 部分行列か?
  std::cout << "isSubmatrix:" << (r1.isSubmatrix()?"true":"false") << std::endl;
  // データは空か?
  std::cout << "empty:" << (r1.empty()?"true":"false") << std::endl;
}

実行結果:

rows:4
cols:3
dims:2
size[]:3,4
depth (ID):5(=5)
channels:5
elemSize:20[byte]
elemSize1 (elemSize/channels):4[byte]
total:12
step:100[byte]
step1 (step/elemSize1):25
isContinuous:false
isSubmatrix:true
empty:false

マルチチャンネル,5次元配列

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 32S, channles=2, 2x3x3x4x6 の5次元配列
  const int sizes[] = {2, 3, 3, 4, 6};
  cv::Mat m1(sizeof(sizes)/sizeof(int), sizes, CV_32SC2);

  // 行数
  std::cout << "rows:" << m1.rows <<std::endl;
  // 列数
  std::cout << "cols:" << m1.cols << std::endl;
  // 次元数
  std::cout << "dims:" << m1.dims << std::endl;
  // 各次元のサイズ
  std::cout << "size[]:";
  for(int i=0; i<m1.dims; ++i)  std::cout << m1.size[i] << ",";
  std::cout << std::endl;
  // ビット深度ID
  std::cout << "depth (ID):" << m1.depth() << "(=" << CV_32S << ")" << std::endl;
  // チャンネル数
  std::cout << "channels:" << m1.channels() << std::endl;
  // (複数チャンネルから成る)1要素のサイズ [バイト単位]
  std::cout << "elemSize:" << m1.elemSize() << "[byte]" << std::endl;
  // 1要素内の1チャンネル分のサイズ [バイト単位]
  std::cout << "elemSize1 (elemSize/channels):" << m1.elemSize1() << "[byte]" << std::endl;
  // 要素の総数
  std::cout << "total:" << m1.total() << std::endl;
  // ステップ数 [バイト単位]
  std::cout << "step[]:";
  for(int i=0; i<m1.dims; ++i)  std::cout << m1.step[i] << ",";
  std::cout << "[byte]" << std::endl;
  // 1ステップ内のチャンネル総数
  std::cout << "step1 (step/elemSize1):" << m1.step1()  << std::endl;
  // データは連続か?
  std::cout << "isContinuous:" << (m1.isContinuous()?"true":"false") << std::endl;
  // 部分行列か?
  std::cout << "isSubmatrix:" << (m1.isSubmatrix()?"true":"false") << std::endl;
  // データは空か?
  std::cout << "empty:" << (m1.empty()?"true":"false") << std::endl;
}

実行結果:

rows:-1
cols:-1
dims:5
size[]:2,3,3,4,6,
depth (ID):4(=4)
channels:2
elemSize:8[byte]
elemSize1 (elemSize/channels):4[byte]
total:432
step[]:1728,576,192,48,8,[byte]
step1 (step/elemSize1):432
isContinuous:true
isSubmatrix:false
empty:false

cv::Matの型(ビット深度)を変換する

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1.1, 1.2, 1.3, 2.1, 2.2, 2.3, 3.1, 3.2, 3.3);
  std::cout << "m1(CV_64FC1)" << std::endl << m1 << std::endl << std::endl;
  
  cv::Mat m2, m3;
  // データはコピーされる
  // 出力行列,型(ビット深度)
  m1.convertTo(m2, CV_8U);
  std::cout << "m2(CV_8UC1)" << std::endl << m2 << std::endl << std::endl;
  
  // データはコピーされる
  // 出力行列,型(ビット深度),α(スケールファクタ),β(足される値)
  m1.convertTo(m3, CV_8U, 2, 10);
  std::cout << "m3(CV_8UC1)" << std::endl << m3 << std::endl << std::endl;
}

実行結果:

m1(CV_64FC1)
[1.1, 1.2, 1.3;
  2.1, 2.2, 2.3;
  3.1, 3.2, 3.3]

m2(CV_8UC1)
[1, 1, 1;
  2, 2, 2;
  3, 3, 3]

m3(CV_8UC1)
[12, 12, 13;
  14, 14, 15;
  16, 16, 17]

cv::Matをリサイズする

STL vector のように,Matのサイズを変更することができます.ただし,変更できるのは行単位でのみです. つまり,3x3の行列を2x3の行列に縮小したり,4x3の行列に拡大したりすることができます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 0, 3, 1, 4, 1, 2, 1);

  std::cout << "m1=" << m1 << std::endl << std::endl;  

  // 2x3 行列
  m1.resize(2);
  std::cout << "m1=" << m1 << std::endl << std::endl;

  // 4x3 行列
  m1.resize(4);
  std::cout << "m1=" << m1 << std::endl << std::endl;

  // 5x3 行列(拡大される行に入る値を指定)   
  m1.resize(5, cv::Scalar(100));
  std::cout << "m1=" << m1 << std::endl << std::endl;
}

実行結果:

m1=[1, 2, 0;
  3, 1, 4;
  1, 2, 1]

m1=[1, 2, 0;
  3, 1, 4]

m1=[1, 2, 0;
  3, 1, 4;
  0, 0, 0;
  0, 0, 0]

m1=[1, 2, 0;
  3, 1, 4;
  0, 0, 0;
  0, 0, 0;
  100, 100, 100]

cv::Matを変形する

前述のサイズ変更とは別に,Matの全体のサイズ(全次元サイズとチャンネルの積)を変えずに,その形状だけを変更することができます. この変形は,「1チャンネル3x3の行列」を「1チャンネル1x9の行列(行ベクトル)」に変形するような「行数の変形操作」と, 「3チャンネル1x3の行列」を「1チャンネル1x9の行列」に変形するような「チャンネル数の変形操作」から成り立ちます. これらを行うメソッド reshape では,変形後のチャンネル数と行数を指定します.

このメソッドは,マルチチャンネル行列を初期化したり,座標点群のベクトルを Mat 形式にしたりする際に非常に役立ちます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 1チャンネル,3x4 行列
  cv::Mat m1 = (cv::Mat_<double>(3,4) << 1,2,3,4,5,6,7,8,9,10,11,12);

  // 2チャンネル,3x2 行列
  cv::Mat m2 = m1.reshape(2);

  // 1チャンネル, 2x6 行列
  cv::Mat m3 = m1.reshape(1,2);

  // 1チャンネル, 2x6 行列
  cv::Mat m4 = m1.reshape(1,2);

  std::cout << "m1=" << m1 << std::endl << "ch=" << m1.channels() << std::endl << std::endl;
  std::cout << "m2=" << m2 << std::endl << "ch=" << m2.channels() << std::endl << std::endl; 
  std::cout << "m3=" << m3 << std::endl << "ch=" << m3.channels() << std::endl << std::endl;
  std::cout << "m4=" << m4 << std::endl << "ch=" << m4.channels() << std::endl << std::endl;
}

実行結果:

m1=[1, 2, 3, 4;
  5, 6, 7, 8;
  9, 10, 11, 12]
ch=1

m2=[1, 2, 3, 4;
  5, 6, 7, 8;
  9, 10, 11, 12]
ch=2

m3=[1, 2, 3, 4, 5, 6;
  7, 8, 9, 10, 11, 12]
ch=1

m4=[1, 2, 3, 4, 5, 6;
  7, 8, 9, 10, 11, 12]
ch=1

ちなみに,多次元 Mat に対する reshape は,下記の様にまだ実装されていません.

matrix.cpp

Mat Mat::reshape(int, int, const int*) const
{
    CV_Error(CV_StsNotImplemented, "");
    // TBD
    return Mat();
}

cv::Matの特定の行/列を取り出す

行列の特定の行/列,または特定の範囲の複数行/列,を取り出すことができます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);

  // すべての行(=rawRange(0,3))
  std::cout << m1.rowRange(cv::Range::all()) << std::endl << std::endl;
  // [0,2) 行の範囲
  std::cout << m1.rowRange(cv::Range(0,2)) << std::endl << std::endl;
  // 0行目
  std::cout << m1.row(0) << std::endl << std::endl;
  
  // すべての列(=colRange(0,3))
  std::cout << m1.colRange(cv::Range::all()) << std::endl << std::endl;
  // [0,2) 列の範囲
  std::cout << m1.colRange(cv::Range(0,2)) << std::endl << std::endl;
  // 0列目
  std::cout << m1.col(0) << std::endl << std::endl;
}

実行結果:

[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

[1, 2, 3;
  4, 5, 6]

[1, 2, 3]

[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

[1, 2;
  4, 5;
  7, 8]

[1; 4; 7]

cv::Matの部分行列を取り出す

行列の部分行列,を取り出すことができます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
  std::cout << m1 << std::endl << std::endl;

  // 行[0,2) 列[0,2) の範囲の部分行列(Range)
  std::cout << m1(cv::Range(0,2), cv::Range(0,2)) << std::endl << std::endl;
  // 行[0,2) 列[0,2) の範囲の部分行列(Rect)
  std::cout << m1(cv::Rect(0,0,2,2)) << std::endl << std::endl;
}

実行結果:

[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

[1, 2;
  4, 5]

[1, 2;
  4, 5]

cv::Mat のチャンネルの合成と分離

例えば,個別の行列として存在する実数部と虚数部を1つの複素行列に合成する場合や,画像のR,G,Bのチャンネルを個別に処理したい場合などを考えると, チャンネルの合成や分離といった操作が必要になります.

OpenCVでは,複数チャンネルを合成する関数として cv::mergecv::mixChannels が用意されています. cv::merge は,複数の1チャンネル cv::Mat を,1つの cv::Mat に合成する関数です. 一方で cv::mixChannels は,入力としてマルチチャンネルの cv::Mat を与えることができ,出力の cv::Mat も複数指定することができます. 以下では,それぞれの関数の使い方を示します.

単純な合成:merge

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 2x2 の行列
  cv::Mat m1 = (cv::Mat_<double>(2,2)<<1.0, 2.0, 3.0, 4.0);
  cv::Mat m2 = (cv::Mat_<double>(2,2)<<1.1, 2.1, 3.1, 4.1);
  cv::Mat m3 = (cv::Mat_<double>(2,2)<<1.2, 2.2, 3.2, 4.2);

  // mergeに与えるのは,cv::Matのvectorでも配列でも構わない
  std::vector<cv::Mat> mv;
  mv.push_back(m1);
  mv.push_back(m2);
  mv.push_back(m3);
  
  // マージ
  cv::Mat m_merged;
  cv::merge(mv, m_merged);

  std::cout << "m_merged=" << m_merged << std::endl << std::endl;
}

実行結果:

m_merged=[1, 1.1, 1.2, 2, 2.1, 2.2;
  3, 3.1, 3.2, 4, 4.1, 4.2]

複雑な合成:mixChannels

cv::mixChannels では,入出力の cv::Mat の他に,どの「入力チャンネル」から,どの「出力チャンネル」に値がコピーされるかを示す配列を渡す必要があります. 例えば,入力が3チャンネル cv::Mat 1つ(src),出力が2チャンネル cv::Mat 2つ(dst1, dst2)である場合に,

// src[0] => dst1[0]
// src[1] => dst1[1]
// src[1] => dst2[1]
// src[2] => dst2[0]

というようにチャンネルをコピーするとします.この場合,入力チャンネルのインデックスは,そのまま 0〜2 が利用されますが, 出力先が複数あるので,これらのインデックスは, dst1[0]0dst1[1]1dst2[0]2dst2[1]3 という様にインクリメントされます. つまり,

// {入力1,出力1, 入力2,出力2, ..., 入力n,出力n}
{0,0, 1,1, 1,3, 2,2}

というペアを指定します.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 2x2 の行列
  cv::Mat m1 = (cv::Mat_<double>(2,2)<<1.0, 2.0, 3.0, 4.0);
  cv::Mat m2 = (cv::Mat_<double>(2,2)<<1.1, 2.1, 3.1, 4.1);
  cv::Mat m3 = (cv::Mat_<double>(2,2)<<1.2, 2.2, 3.2, 4.2);

  // mixChannlesに与えるのは,cv::Matのvectorでも配列でも構わない
  std::vector<cv::Mat> mv;
  mv.push_back(m1);
  mv.push_back(m2);
  mv.push_back(m3);
  
  // 出力用 Mat は,必ず割り当てる必要がある
  cv::Mat m_mixed1(2,2, CV_64FC2);
  cv::Mat m_mixed2(2,2, CV_64FC2);
  int fromTo[] = {0,0, 1,1, 1,3, 2,2};
  // mixChannlesに与えるのは,cv::Matのvectorでも配列でも構わない
  std::vector<cv::Mat> mixv;
  mixv.push_back(m_mixed1);
  mixv.push_back(m_mixed2);

  // ミックス
  cv::mixChannels(mv, mixv, fromTo, 4);

  std::cout << "m_mixed1=" << m_mixed1 << std::endl << std::endl;
  std::cout << "m_mixed2=" << m_mixed2 << std::endl << std::endl;
}

実行結果:

m_mixed1=[1, 1.1, 2, 2.1;
  3, 3.1, 4, 4.1]

m_mixed2=[1.2, 1.1, 2.2, 2.1;
  3.2, 3.1, 4.2, 4.1]

分離

合成とは逆に,マルチチャンネルの cv::Mat を,各チャンネル毎に分離して,1チャンネルの cv::Mat をもとのチャンネル数分だけ作ることもできます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m0 = (cv::Mat_<int>(3,6) << 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18);
  // channels=3, 3x2 の行列
  cv::Mat m1 = m0.reshape(3,2);
  std::cout << "m1" << std::endl << m1 << std::endl << std::endl;;

  std::vector<cv::Mat> planes;
  // チャンネル毎に分離
  cv::split(m1, planes);
  std::vector<cv::Mat>::const_iterator it = planes.begin();
  for(; it!=planes.end(); ++it) {
    std::cout << *it << std::endl << std::endl;;
  }
}

実行結果:

[1, 4, 7;
  10, 13, 16]

[2, 5, 8;
  11, 14, 17]

[3, 6, 9;
  12, 15, 18]

cv::Matの列/行毎の合計,平均値,最小,最大値を求める

例えば,1行が1つの特徴ベクトルである行列(行数は特徴ベクトルの個数)があるとして,それらのベクトルの平均を求めるにはどうすれば良いでしょうか. 1行1行足し合わせて個数で割ることも出来ますが, reduce メソッドを用いると,行列の行毎(または列毎)の,合計値や平均値などを簡単に計算することができます.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/core/core_c.h>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 5, 3, 4, 2, 6, 7, 8, 9);

  cv::Mat v1, v2, v3 ,v4;
  // 3x3の行列を1行に縮小
  cv::reduce(m1, v1, 0, CV_REDUCE_SUM); // 各列の合計値
  cv::reduce(m1, v2, 0, CV_REDUCE_AVG); // 各列の平均値
  cv::reduce(m1, v3, 0, CV_REDUCE_MIN); // 各列の最小値
  cv::reduce(m1, v4, 0, CV_REDUCE_MAX); // 各列の最大値
  
  std::cout << "m1=" << m1 << std::endl << std::endl;
  std::cout << "v1(sum)=" << v1 << std::endl;
  std::cout << "v2(avg)=" << v2 << std::endl;
  std::cout << "v3(min)=" << v3 << std::endl;
  std::cout << "v4(max)=" << v4 << std::endl;

  // 3x3の行列を1列に縮小
  cv::reduce(m1, v1, 1, CV_REDUCE_SUM);
  cv::reduce(m1, v2, 1, CV_REDUCE_AVG);
  cv::reduce(m1, v3, 1, CV_REDUCE_MIN);
  cv::reduce(m1, v4, 1, CV_REDUCE_MAX);
  
  std::cout << "m1=" << m1 << std::endl << std::endl;
  std::cout << "v1(sum)=" << v1 << std::endl;
  std::cout << "v2(avg)=" << v2 << std::endl;
  std::cout << "v3(min)=" << v3 << std::endl;
  std::cout << "v4(max)=" << v4 << std::endl;
}

実行結果:

m1=[1, 5, 3;
  4, 2, 6;
  7, 8, 9]

v1(sum)=[12, 15, 18]
v2(avg)=[4, 5, 6]
v3(min)=[1, 2, 3]
v4(max)=[7, 8, 9]
m1=[1, 5, 3;
  4, 2, 6;
  7, 8, 9]

v1(sum)=[9; 12; 24]
v2(avg)=[3; 4; 8]
v3(min)=[1; 2; 7]
v4(max)=[5; 6; 9]

cv::Matの親行列に対する部分行列の位置を求める

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
  std::cout << "m1" << std::endl << m1 << std::endl << std::endl;

  // 行[0,2) 列[0,2) の範囲の部分行列(Rect)
  cv::Mat m2 = m1(cv::Rect(0,0,2,2));
  std::cout << "m2" << std::endl << m2 << std::endl << std::endl;

  cv::Size wholeSize;
  cv::Point ofs;
  // 部分行列m2の親行列サイズと,その中での位置を求める
  m2.locateROI(wholeSize, ofs);
  std::cout << "wholeSize:" << wholeSize.height << "x" << wholeSize.width << std::endl; //3x3
  std::cout << "offset:" << ofs.x << ", " << ofs.y << std::endl; // 0,0
}

実行結果:

m1
[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

m2
[1, 2;
  4, 5]

wholeSize:3x3
offset:0, 0

cv::Matの部分行列の範囲を拡大する

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
  std::cout << "m1" << std::endl << m1 << std::endl << std::endl;

  // 行[0,2) 列[0,2) の範囲の部分行列(Rect)
  cv::Mat m2 = m1(cv::Rect(0,0,2,2));
  std::cout << "m2" << std::endl;
  std::cout << "rows=" << m2.rows << ", cols=" << m2.cols << std::endl;
  std::cout << m2 << std::endl << std::endl;

  // ROIの範囲を拡大する
  // 上,下,左,右への増加量
  m2.adjustROI(0, 1, 0, 1);
  std::cout << "m2" << std::endl;
  std::cout << "rows=" << m2.rows << ", cols=" << m2.cols << std::endl;
  std::cout << m2 << std::endl << std::endl;
}

実行結果:

m1
[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

m2
rows=2, cols=2
[1, 2;
  4, 5]

m2
rows=3, cols=3
[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

cv::Matの要素をシャッフルする

実際には,「行数x列数x反復ファクタ」回だけ要素の入れ替えが行われます.

シングルチャンネル行列のシャッフル

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 4x5 の行列
  cv::Mat m1 = (cv::Mat_<double>(4,5) << 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20);
  std::cout << "m1(original)" << std::endl << m1 << std::endl << std::endl;
  
  cv::RNG gen(cv::getTickCount());
  // 行列,反復ファクタ,乱数生成器
  cv::randShuffle(m1, 2.0, &gen);
  // シャッフルされた行列を表示
  std::cout << "m1(shuffle)" << std::endl << m1 << std::endl << std::endl;

  // 部分行列のシャッフル
  cv::Mat m2 = m1(cv::Rect(1,1,3,2));
  std::cout << "m2(sub-matrix)" << std::endl << m2 << std::endl << std::endl;
  cv::randShuffle(m2, 2.0, &gen);
  std::cout << "m2(shuffle sub-matrix)" << std::endl << m2 << std::endl << std::endl;
  std::cout << "m1" << std::endl << m1 << std::endl << std::endl;
}

実行結果:

m1(original)
[1, 2, 3, 4, 5;
  6, 7, 8, 9, 10;
  11, 12, 13, 14, 15;
  16, 17, 18, 19, 20]

m1(shuffle)
[15, 3, 14, 11, 17;
  1, 8, 6, 2, 10;
  19, 18, 20, 7, 5;
  4, 16, 13, 12, 9]

m2(sub-matrix)
[8, 6, 2;
  18, 20, 7]

m2(shuffle sub-matrix)
[20, 2, 7;
  6, 18, 8]

m1
[15, 3, 14, 11, 17;
  1, 20, 2, 7, 10;
  19, 6, 18, 8, 5;
  4, 16, 13, 12, 9]

マルチチャンネル行列のシャッフル

マルチチャンネルの場合でも,1要素単位(=複数チャンネルから成る要素自身は保たれたまま)で入れ替えが行われます. つまり,1つのMat要素を校構成する複数のチャンネルの値は,シャッフル後も保たれたままになります. RGB画像の場合で考えると,1画素を構成するRGB値の値と順序は保たれるので,シャッフルで画素の位置は変わりますが,色は変わりません.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // channels=2, 4x2の行列
  cv::Mat m0 = (cv::Mat_<double>(4,4) << 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16);
  cv::Mat m1 = m0.reshape(2);
  std::cout << "m1(original)" << std::endl << m1 << std::endl << std::endl;
  
  cv::RNG gen(cv::getTickCount());
  // 行列,反復ファクタ,乱数生成器
  cv::randShuffle(m1, 2.0, &gen);
  // シャッフルされた行列を表示
  std::cout << "m1(shuffle)" << std::endl << m1 << std::endl << std::endl;
}

実行結果:

m1(original)
[1, 2, 3, 4;
  5, 6, 7, 8;
  9, 10, 11, 12;
  13, 14, 15, 16]

m1(shuffle)
[3, 4, 1, 2;
  11, 12, 7, 8;
  13, 14, 5, 6;
  15, 16, 9, 10]

2つのcv::Matをスワップする

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat eye = cv::Mat::eye(3, 3, CV_8UC1);
  cv::Mat ones = cv::Mat::ones(3, 3, CV_8UC1);
  
  std::cout << "eye=" << eye << std::endl;
  std::cout << "ones=" << ones << std::endl << std::endl;
  
  cv::swap(eye, ones);
  
  std::cout << "eye=" << eye << std::endl;
  std::cout << "ones=" << ones << std::endl << std::endl;
}

実行結果:

eye=[1, 0, 0;
  0, 1, 0;
  0, 0, 1]
ones=[1, 1, 1;
  1, 1, 1;
  1, 1, 1]

eye=[1, 1, 1;
  1, 1, 1;
  1, 1, 1]
ones=[1, 0, 0;
  0, 1, 0;
  0, 0, 1]

cv::Matの行/列の要素をソートする

ソートした値を出力

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/legacy/compat.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat mat(5, 5, CV_8UC1);

  // 一様分布乱数,[0,25)
  cv::randu(mat, cv::Scalar(0), cv::Scalar(25));
  std::cout << mat << std::endl << std::endl;

  cv::Mat dst_mat;

  // 各行を昇順にソート
  cv::sort(mat, dst_mat, CV_SORT_EVERY_ROW|CV_SORT_ASCENDING);
  std::cout << "ROW|ASCENDING" << std::endl << dst_mat << std::endl << std::endl;

  // 各行を降順にソート
  cv::sort(mat, dst_mat, CV_SORT_EVERY_ROW|CV_SORT_DESCENDING);
  std::cout << "ROW|DESCENDING" << std::endl << dst_mat << std::endl << std::endl;
  
  // 各列を昇順にソート
  cv::sort(mat, dst_mat, CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING);
  std::cout << "COLUMN|ASCENDING" << std::endl << dst_mat << std::endl << std::endl;

  // 各列を降順にソート
  cv::sort(mat, dst_mat, CV_SORT_EVERY_COLUMN|CV_SORT_DESCENDING);
  std::cout << "COLUMN|DESCENDING" << std::endl << dst_mat << std::endl << std::endl;
}

実行結果:

[6, 22, 14, 4, 22;
  10, 11, 18, 1, 4;
  1, 13, 7, 13, 0;
  23, 18, 1, 17, 3;
  3, 18, 0, 14, 23]

ROW|ASCENDING
[4, 6, 14, 22, 22;
  1, 4, 10, 11, 18;
  0, 1, 7, 13, 13;
  1, 3, 17, 18, 23;
  0, 3, 14, 18, 23]

ROW|DESCENDING
[22, 22, 14, 6, 4;
  18, 11, 10, 4, 1;
  13, 13, 7, 1, 0;
  23, 18, 17, 3, 1;
  23, 18, 14, 3, 0]

COLUMN|ASCENDING
[1, 11, 0, 1, 0;
  3, 13, 1, 4, 3;
  6, 18, 7, 13, 4;
  10, 18, 14, 14, 22;
  23, 22, 18, 17, 23]

COLUMN|DESCENDING
[23, 22, 18, 17, 23;
  10, 18, 14, 14, 22;
  6, 18, 7, 13, 4;
  3, 13, 1, 4, 3;
  1, 11, 0, 1, 0]

ソートしたインデックスを出力

同値の場合,元の順序が保存される保証はありません.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/legacy/compat.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat mat(5, 5, CV_8UC1);

  // 一様分布乱数,[0,25)
  cv::randu(mat, cv::Scalar(0), cv::Scalar(25));
  std::cout << mat << std::endl << std::endl;

  cv::Mat dst_mat;

  // 各行を昇順にソート
  cv::sortIdx(mat, dst_mat, CV_SORT_EVERY_ROW|CV_SORT_ASCENDING);
  std::cout << "ROW|ASCENDING" << std::endl << dst_mat << std::endl << std::endl;

  // 各行を降順にソート
  cv::sortIdx(mat, dst_mat, CV_SORT_EVERY_ROW|CV_SORT_DESCENDING);
  std::cout << "ROW|DESCENDING" << std::endl << dst_mat << std::endl << std::endl;
  
  // 各列を昇順にソート
  cv::sortIdx(mat, dst_mat, CV_SORT_EVERY_COLUMN|CV_SORT_ASCENDING);
  std::cout << "COLUMN|ASCENDING" << std::endl << dst_mat << std::endl << std::endl;

  // 各列を降順にソート
  cv::sortIdx(mat, dst_mat, CV_SORT_EVERY_COLUMN|CV_SORT_DESCENDING);
  std::cout << "COLUMN|DESCENDING" << std::endl << dst_mat << std::endl << std::endl;
}

実行結果:

[6, 22, 14, 4, 22;
  10, 11, 18, 1, 4;
  1, 13, 7, 13, 0;
  23, 18, 1, 17, 3;
  3, 18, 0, 14, 23]

ROW|ASCENDING
[3, 0, 2, 1, 4;
  3, 4, 0, 1, 2;
  4, 0, 2, 1, 3;
  2, 4, 3, 1, 0;
  2, 0, 3, 1, 4]

ROW|DESCENDING
[4, 1, 2, 0, 3;
  2, 1, 0, 4, 3;
  3, 1, 2, 0, 4;
  0, 1, 3, 4, 2;
  4, 1, 3, 0, 2]

COLUMN|ASCENDING
[2, 1, 4, 1, 2;
  4, 2, 3, 0, 3;
  0, 3, 2, 2, 1;
  1, 4, 0, 4, 0;
  3, 0, 1, 3, 4]

COLUMN|DESCENDING
[3, 0, 1, 3, 4;
  1, 4, 0, 4, 0;
  0, 3, 2, 2, 1;
  4, 2, 3, 0, 3;
  2, 1, 4, 1, 2]

cv::Matの行列要素を,イテレータを介して扱う

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);

  // Iterator
  // cv::MatIterator_<double> it = m1.begin<double>(); と同じ
  cv::Mat_<double>::iterator it = m1.begin<double>();
  for(; it!=m1.end<double>(); ++it) {
    *it -= 0.5;
  }

  // Const Iterator
  // cv::MatConstIterator_<double> cit = m1.begin<double>(); と同じ
  cv::Mat_<double>::const_iterator cit = m1.begin<double>();
  std::cout << "m1=" << std::endl;
  for(; cit!=m1.end<double>(); ++cit) {
    std::cout << *cit << ", ";
  }
  std::cout << std::endl << std::endl;

  // 3x3 の行列
  cv::Mat_<double> m2(m1);
  
  // Iterator
  // cv::MatIterator_<double> it = m1.begin(); と同じ
  cv::Mat_<double>::iterator it2 = m2.begin();
  std::cout << "m2=" << std::endl;
  for(; it2!=m2.end(); ++it2) {
    *it2 -= 0.5;
  }

  // Const Iterator
  // cv::MatConstIterator_<double> cit = m1.begin(); と同じ
  cv::Mat_<double>::const_iterator cit2 = m2.begin();
  for(; cit2!=m2.end(); ++cit2) {
    std::cout << *cit2 << ", ";
  }
  std::cout << std::endl;
}

実行結果:

m1=
0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 

m2=
0, 1, 2, 3, 4, 5, 6, 7, 8, 

cv::Mat_<_Tp>を使う

cv::Mat_<_Tp> は, cv::Mat を継承したクラステンプレートです. cv::Mat_<_Tp> にも cv::Mat にも,virtual なメソッドは存在せず,データフィールドも同一なので,互いのポインタを自由に変換することができます. とは言っても,互いの data メンバの型が変換されるわけではないので,以下のような場合に注意が必要です.

// 100x100 , 8 ビットの行列を作成
Mat M(100,100,CV_8U);
// これは,コンパイル可能です.データ変換は行われません.
Mat_<float>& M1 = (Mat_<float>&)M;
// uchar型配列の最後の要素にfloatを書きこむことで,範囲外のアクセスが発生します.
M1(99,99) = 1.f;

また,コンパイル時に型が既知であるならば,cv::Mat_<_Tp> を利用すると,様々な処理を短く書くことができます.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
  cv::Mat_<double> m2 = m1.clone();
  
  /// 行列要素の直接指定
  // Mat の場合
  std::cout << "m1(1,1) = " << m1.at<double>(1,1) << std::endl;
  // Mat_<_Tp> の場合
  std::cout << "m2(1,1) = " << m2(1,1) << std::endl;

  /// 行列要素の,ポインタを介した指定
  // Mat の場合
  std::cout << "m1(1,2) = " << m1.ptr<double>(1)[2] << std::endl;
  // Mat_<_Tp> の場合
  std::cout << "m2(1,2) = " << m2[1][2] << std::endl;
}

実行結果:

m1(1,1) = 5
m2(1,1) = 5
m1(1,2) = 6
m2(1,2) = 6

cv::Matxを使う

このクラスは,コンパイル時に型とサイズが既知である小さい行列を表します.より柔軟な型が必要な場合は cv::Mat を利用してください. 行列の形状によっては,値を指定して初期化可能なコンストラクタが用意されています.

template<typename T, int m, int n> class Matx
{
public:
    typedef T value_type;
    enum { depth = DataDepth<T>::value, channels = m*n,
           type = CV_MAKETYPE(depth, channels) };

    // ...
Tp val[m*n];
};

typedef Matx<float, 1, 2> Matx12f;
typedef Matx<double, 1, 2> Matx12d;
// ...
typedef Matx<float, 1, 6> Matx16f;
typedef Matx<double, 1, 6> Matx16d;

typedef Matx<float, 2, 1> Matx21f;
typedef Matx<double, 2, 1> Matx21d;
//...
typedef Matx<float, 6, 1> Matx61f;
typedef Matx<double, 6, 1> Matx61d;

typedef Matx<float, 2, 2> Matx22f;
typedef Matx<double, 2, 2> Matx22d;
//...
typedef Matx<float, 6, 6> Matx66f;
typedef Matx<double, 6, 6> Matx66d;

行列 M の要素には, cv::Mat_<_Tp> の場合と同様に M(i,j) という表記でアクセス可能です. また,一般的な行列演算の大部分が利用可能です.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // float型,1x2 の行列
  cv::Matx21f m1(1,2,10,20);
  // double型,3x3 の行列
  cv::Matx33d m2(1,3,5,2,4,6,7,8,9);
  // double型,5x1 の行列
  cv::Matx<double,5,1> m3(10,11,12,13,14);
  // float型, 2x11 の行列
  cv::Matx<float,2,11> m4;
  for(int j=0; j<2; ++j) 
    for(int i=0; i<11; ++i) 
      m4(j,i) = i+j;
  
  // Matに変換してstdに出力することができる
  std::cout << "m1=" << cv::Mat(m1) << std::endl << std::endl;
  std::cout << "m2=" << cv::Mat(m2) << std::endl << std::endl;
  std::cout << "m3=" << cv::Mat(m3) << std::endl << std::endl;
  std::cout << "m4=" << cv::Mat(m4) << std::endl << std::endl;
}

実行結果:

m1=[1; 2]

m2=[1, 3, 5;
  2, 4, 6;
  7, 8, 9]

m3=[10; 11; 12; 13; 14]

m4=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10;
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

cv::Vecを使う

Vec は, Matx の特別なケースと言えます. Matx は行列を表現しますが, Vec はベクトルを1行の行列として表現します.

template<typename T, int cn> class Vec : public Matx<T, cn, 1>
{
public:
    typedef T value_type;
    enum { depth = DataDepth<T>::value, channels = cn,
           type = CV_MAKETYPE(depth, channels) };
    //...
};

typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
//...
typedef Vec<double, 6> Vec6d;

Vec<T,2>Point_ は相互に変換可能で, Vec<T,3>Point3_ も同様です.また, Vec<T,4>CvScalarScalar に変換することができます. Vec の要素にアクセスするには, operator[] を利用します.

Vec クラスは通常,マルチチャンネル配列のピクセル型を記述するために利用されます.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  // 320x240 の3チャンネルカラー画像を確保して,緑色で埋めます.
  cv::Mat_<cv::Vec3b> img(300, 300, cv::Vec3b(0,200,0));

  // 対角線上に白い線を描きます.
  for(int i=0; i<img.cols; ++i)
    img(i,i)=cv::Vec3b(255, 255, 255);
  
  cv::namedWindow("image", CV_WINDOW_AUTOSIZE|CV_WINDOW_FREERATIO);
  cv::imshow("image", img);
  cv::waitKey(0);
}

実行結果:

_images/sample_vec.png

cv::MatとIplImageの相互変換

#include <iostream>
#include <iostream>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  // IplImage -> Mat(コンストラクタ)
  cv::Ptr<IplImage> ipl_img1 = cvLoadImage("../../image/lenna.png", CV_LOAD_IMAGE_COLOR);
  cv::Mat mat_img1a(ipl_img1);       // データを共有する
  CV_Assert(reinterpret_cast<uchar*>(ipl_img1->imageData) == mat_img1a.data);
  cv::Mat mat_img1b(ipl_img1, true); // データをコピーする
  CV_Assert(reinterpret_cast<uchar*>(ipl_img1->imageData) != mat_img1b.data);

  // IplImage -> Mat(関数で変換)
  cv::Mat mat_img1c = cv::cvarrToMat(ipl_img1);  // データをコピーする
  CV_Assert(reinterpret_cast<uchar*>(ipl_img1->imageData) != mat_img1b.data);

  // Mat -> IplImage 
  cv::Mat mat_img2 = cv::imread("../../image/lenna.png", 1);
  IplImage ipl_img2 = mat_img2; // データを共有する
  CV_Assert(reinterpret_cast<uchar*>(ipl_img2.imageData) == mat_img2.data);
}

cv::MatとCvMatの相互変換

#include <iostream>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  double data[] = { 1, 2, 3, 4, 5, 6, 7, 8,  9, 10, 11, 12 };
  CvMat cv_mat1;
  cvInitMatHeader(&cv_mat1, 3, 4, CV_64FC1, data);

  // CvMat -> cv::Mat
  cv::Mat mat1(&cv_mat1); // データを共有する
  CV_Assert(cv_mat1.data.ptr == mat1.data);
  cv::Mat mat2(&cv_mat1, true); // データをコピーする
  CV_Assert(cv_mat1.data.ptr != mat2.data);

  std::cout << "mat1=" << mat1 << std::endl << std::endl;

  // cv::Mat -> CvMat
  CvMat cv_mat2 = mat2; // データを共有する
  CV_Assert(cv_mat2.data.ptr == mat2.data);
}

cv::MatとSTL vectorの相互変換

#include <iostream>
#include <opencv2/core/core.hpp>

#define OPENCV_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
#define OPENCV_VERSION_CODE OPENCV_VERSION(CV_MAJOR_VERSION, CV_MINOR_VERSION, CV_SUBMINOR_VERSION)

int
main(int argc, char *argv[])
{
   cv::Mat m1 = (cv::Mat_<double>(3,3) << 1, 2, 0, 3, 1, 4, 1, 2, 1);
   std::vector<double> v1;

   // Mat -> vector
   m1 = m1.reshape(0, 1); // 1行の行列に変形
#if OPENCV_VERSION_CODE<OPENCV_VERSION(2,3,0)
   m1.copyTo<double>(v1); // データをコピー
#else
   m1.copyTo(v1); // データをコピー
#endif

   std::cout << "m1=" << m1 << std::endl << std::endl;
   for(std::vector<double>::iterator it=v1.begin(); it!=v1.end(); ++it)
     std::cout << *it << ", ";
   std::cout << std::endl;
   
   // vector -> Mat
   cv::Mat m2(v1); // データを共有
   cv::Mat m3(v1, true); // データをコピー
}

cv::Matとcv::Matxの相互変換

Matx から Mat への変換コンストラクタは,データをコピーするフラグのデフォルト値が true であり, CvMatvector などとは異なることに注意してください.

cv::Matxを使う も参照してください.

#include <iostream>
#include <opencv2/core/core.hpp>

int
main(int argc, char *argv[])
{
  // double型,3x3 の行列
  cv::Matx33d mx1(1,3,5,2,4,6,7,8,9);

  // Matx -> Mat
  cv::Mat m1(mx1, false); // データを共有
  cv::Mat m2(mx1); // データをコピー
    
  // Mat -> Matx
  cv::Matx33d mx2 = m1; // データを共有
}

cv::Matとcv::SparseMatの相互変換

要素の大部分が0である疎な行列を効率的に保持するために,OpenCVには SparseMat クラスが用意されています. Mat から SparseMat に変換すると,値が0ではない要素のみがハッシュテーブルに保存されます. SparseMat から Mat に変換する場合,最初に0で埋められた Mat のインスタンスが作成され,そこに SparseMat のノードに保存された値が上書きされます.

#include <iostream>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat mat1 = (cv::Mat_<double>(3,3) << 1, 2, 0, 0, 0, 6, 7, 8, 9);

  // Mat -> SparseMat
  cv::SparseMat sparse_mat1(mat1);
  cv::SparseMat sparse_mat2;
  sparse_mat2 = mat1;

  // SparseMat -> Mat
  cv::Mat mat2;
  sparse_mat1.copyTo(mat2);
  CV_Assert(mat1.data != mat2.data);

  // 表示
  std::cout << "mat1" << mat1 << std::endl << std::endl;
  std::cout << "sparse_mat1" << std::endl;
  cv::SparseMatConstIterator it1 = sparse_mat1.begin();
  for(; it1!=sparse_mat1.end(); ++it1)
    std::cout << it1.value<double>() << ", ";
  std::cout << std::endl;
  std::cout << "sparse_mat2" << std::endl;
  cv::SparseMatConstIterator it2 = sparse_mat2.begin();
  for(; it2!=sparse_mat2.end(); ++it2)
    std::cout << it2.value<double>() << ", ";
  std::cout << std::endl << std::endl;
  std::cout << "mat2" << mat2 << std::endl << std::endl;
}

実行結果:

mat1[1, 2, 0;
  0, 0, 6;
  7, 8, 9]

sparse_mat1
1, 2, 7, 8, 9, 6, 
sparse_mat2
1, 2, 7, 8, 9, 6, 

mat2[1, 2, 0;
  0, 0, 6;
  7, 8, 9]

cv::MatとEigen::Matrixの相互変換

cv::MatからEigen::Matrixへの変換

#include <iostream>
#include <Eigen/Core>
#include <opencv2/core/core.hpp>
#include <opencv2/core/eigen.hpp>

int
main(int argc, char *argv[])
{
  // 3x3, double の行列
  Eigen::Matrix3d eigen_mat;
  eigen_mat << 1.1, 1.2, 1.3,
               2.1, 2.2, 2.3,
               3.1, 3.2, 3.3;
  cv::Mat cv_mat;
  // convert from Eigen::Matrix to cv::Mat
  cv::eigen2cv(eigen_mat, cv_mat);

  std::cout << "cv_mat:\n" << cv_mat << std::endl << std::endl;
  std::cout << "eigen_mat:\n" << eigen_mat << std::endl << std::endl;
}

実行結果:

cv_mat:
[1.1, 1.2, 1.3;
  2.1, 2.2, 2.3;
  3.1, 3.2, 3.3]

eigen_mat:
1.1 1.2 1.3
2.1 2.2 2.3
3.1 3.2 3.3

Eigen::Matrixからcv::Matへの変換

#include <iostream>
#include <Eigen/Core>
#include <opencv2/core/core.hpp>
#include <opencv2/core/eigen.hpp>

int
main(int argc, char *argv[])
{
  // 3x3 の行列
  cv::Mat cv_mat = (cv::Mat_<double>(3,3) << 1, 2, 3, 4, 5, 6, 7, 8, 9);
  Eigen::Matrix<double, 3, 3> eigen_mat1; // コンパイル時に行列サイズが既知の場合
  // convert from cv::Mat to Eigen::Matrix
  cv::cv2eigen(cv_mat, eigen_mat1);

  std::cout << "cv_mat:\n" << cv_mat << std::endl << std::endl;
  std::cout << "eigen_mat1:\n" << eigen_mat1 << std::endl << std::endl;

  Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> eigen_mat2; // 動的な行列
  // convert from cv::Mat to Eigen::Matrix
  cv::cv2eigen(cv_mat, eigen_mat2);
  std::cout << "eigen_mat2:\n" << eigen_mat2 << std::endl << std::endl;
}

実行結果:

cv_mat:
[1, 2, 3;
  4, 5, 6;
  7, 8, 9]

eigen_mat1:
1 2 3
4 5 6
7 8 9

eigen_mat2:
1 2 3
4 5 6
7 8 9

cv::Mat データの行アライメントを調整する

OpenCV によって割り当てられるメモりは,必ず16バイト境界にアライメントが調整されます.ここで,注意が必要なのは,アライメントが調整されるのはメモリ割り当て時のアドレスに対して,という点です.画像の行毎のメモリアドレスの話ではありません.

IplImage を利用した場合は,さらに,確保された画像の行毎のアラインメントも調整されます. この,アライメントが調整されるバイト数は, IplImage->align によって決まり,デフォルト値は 4 [byte] と定義されています.

modules/core/include/opencv2/core/internal.hpp:

/* default image row align (in bytes) */
#define  CV_DEFAULT_IMAGE_ROW_ALIGN  4

しかし,cv::Mat によって確保されるデータは,デフォルトで連続データとなります.つまり,行毎のアライメント調整は行われません. この値についても,以下の様に 1 [byte] で定義されていますが,このマクロは(OpenCV2.2〜2.3では)実際には利用されていないので,この値を変更しても意味はありません.

modules/core/include/opencv2/core/internal.hpp:

/* matrices are continuous by default */
#define  CV_DEFAULT_MAT_ROW_ALIGN  1

データを行毎にコピーしてアライメントを調整する

cv::MatとIplImageの相互変換 でも述べた様に,Mat を IplImage にキャストする場合はデータが共有されるので,結果的に行毎のアライメントが調整されていない IplImage データが作成されます. このような場合にアライメントを調整する最も単純な方法は,ユーザがデータを1行ずつコピーすることです.

_images/align_copy.png
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_mat = cv::imread("../../image/lenna333x333.png", 1);
  if(src_mat.empty()) return -1; 
  int cols = 333, rows = 333;

  cv::Ptr<IplImage> ipl_img = cvCreateImage(cvSize(cols, rows), 8, 3);
  int ws = ipl_img->widthStep;
  int s = src_mat.step;
  // コピー
  for(int i=0; i<ipl_img->height; ++i)
    memcpy(&ipl_img->imageData[ws*i], &src_mat.data[s*i], s);

  std::cout << "src_mat" << std::endl;
  std::cout << "  size=(" << src_mat.cols << ", " << src_mat.rows << ")" << std::endl;
  std::cout << "  step=" << src_mat.step << std::endl;
  std::cout << "  isContinuous=" << (src_mat.isContinuous()?"true":"false") << std::endl;
  std::cout << "ipl_img" << std::endl;
  std::cout << "  size=(" << ipl_img->width << ", " << ipl_img->height << ")" << std::endl;
  std::cout << "  widthStep=" << ipl_img->widthStep << std::endl;
}

実行結果:

src_mat
  size=(333, 333)
  step=999
  isContinuous=true
ipl_img
  size=(333, 333)
  widthStep=1000

ROIを利用してアライメントを調整する

上記の様にデータを1行ずつコピーする方法は,単純ですがあまり見た目の良いものではありません. また,IplImageではなく cv::Mat データ自体の行毎のアライメントを調整したい場合もあるでしょう. その場合,IplImage の場合と同様に1行ずつデータをコピーすることもできますが,コピー先にROIを適用すると copyTo メソッドを利用できます.

_images/align_roi.png
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

int
main(int argc, char *argv[])
{
  cv::Mat src_mat = cv::imread("../../image/lenna333x333.png", 1);
  if(src_mat.empty()) return -1; 
  int cols = 333, rows = 333;

  int alignment = 4;
  int new_cols = cv::alignSize(cols, alignment);
  // ROI を利用してコピー
  cv::Mat p_mat(rows, new_cols, CV_8UC3);
  cv::Mat roi_mat(p_mat, cv::Rect(0,0,cols,rows));
  src_mat.copyTo(roi_mat);
  IplImage ipl_img = roi_mat; // データを共有

  std::cout << "src_mat" << std::endl;
  std::cout << "  size=(" << src_mat.cols << ", " << src_mat.rows << ")" << std::endl;
  std::cout << "  step=" << src_mat.step << std::endl;
  std::cout << "  isContinuous:" << (src_mat.isContinuous()?"true":"false") << std::endl;
  std::cout << "  isSubmatrix=" << (src_mat.isSubmatrix()?"true":"false") << std::endl;
  std::cout << "roi_mat" << std::endl;
  std::cout << "  size=(" << roi_mat.cols << ", " << roi_mat.rows << ")" << std::endl;
  std::cout << "  step=" << roi_mat.step << std::endl;
  std::cout << "  isContinuous=" << (roi_mat.isContinuous()?"true":"false") << std::endl;
  std::cout << "  isSubmatrix=" << (roi_mat.isSubmatrix()?"true":"false") << std::endl;
  std::cout << "ipl_img" << std::endl;
  std::cout << "  size=(" << ipl_img.width << ", " << ipl_img.height << ")" << std::endl;
  std::cout << "  widthStep=" << ipl_img.widthStep << std::endl;
}

実行結果:

src_mat
  size=(333, 333)
  step=999
  isContinuous:true
  isSubmatrix=false
roi_mat
  size=(333, 333)
  step=1008
  isContinuous=false
  isSubmatrix=true
ipl_img
  size=(333, 333)
  widthStep=1008

また,画像サイズと型が既知であれば,この様な ROI を設定した Mat に直接画像を読み込むこともできます.

cv::Mat roi_img = cv::imread("image_name.png", 1);

この方法では,複数チャンネルから成る1要素単位でしか親行列のサイズを変更できないため,上記の図ように無駄な領域が増えています. また,ROIを設定できるのは2次元の行列のみ,という制約もあります.

カスタムアロケータを利用してアライメントを調整する

cv::Mat では,任意の方法で領域を割り当てることができるカスタムアロケータを指定することができます. ここでは,最初から行アライメントが調整された領域を割り当てるアロケータの例を示します.

#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

// カスタムアロケータ
class alignMatAllocator: public cv::MatAllocator
{
public:
  alignMatAllocator(size_t a=4):alignment(a) {}
  virtual ~alignMatAllocator() {}

  // 割り当て
  void allocate(int dims, const int* sizes, int type, int*& refcount,
                uchar*& datastart, uchar*& data, size_t* step)
  {
    // ステップ幅のアライメント調整
    for(int i=0; i<dims-1; ++i) {
      step[i] = cv::alignSize(sizes[i+1]*CV_ELEM_SIZE(type), alignment);
    }
    // 最後は必ず要素サイズ
    step[dims-1] = CV_ELEM_SIZE(type);
    // 参照カウンタ分を追加して,領域を割り当て
    size_t total = cv::alignSize(step[0]*sizes[0], static_cast<int>(sizeof(*refcount)));
    data = datastart = (uchar*)cv::fastMalloc(total + static_cast<int>(sizeof(*refcount)));
    refcount = reinterpret_cast<int*>(data + total);
    // カウンタの値を1に
    *refcount = 1;
  }
    
  // 解放
  void deallocate(int* refcount, uchar* datastart, uchar* data)
  {
    if(!refcount) // ユーザが割り当てたデータの場合
      return;
    cv::fastFree(datastart);
  }
private:
  size_t alignment;
};


int
main(int argc, char *argv[])
{
  cv::Mat src_mat = cv::imread("../../image/lenna333x333.png", 1);
  if(src_mat.empty()) return -1; 
  int cols = 333, rows = 333;

  // カスタムアロケータを利用する.
  alignMatAllocator alc(4); // 4 byte border
  cv::Mat dst_mat;
  dst_mat.allocator = &alc;
  src_mat.copyTo(dst_mat);

  std::cout << "src_mat" << std::endl;
  std::cout << "  size=(" << src_mat.cols << ", " << src_mat.rows << ")" << std::endl;
  std::cout << "  step=" << src_mat.step << std::endl;
  std::cout << "  isContinuous:" << (src_mat.isContinuous()?"true":"false") << std::endl;
  std::cout << "  isSubmatrix=" << (src_mat.isSubmatrix()?"true":"false") << std::endl;
  std::cout << "dst_mat" << std::endl;
  std::cout << "  size=(" << dst_mat.cols << ", " << dst_mat.rows << ")" << std::endl;
  std::cout << "  step=" << dst_mat.step << std::endl;
  std::cout << "  isContinuous=" << (dst_mat.isContinuous()?"true":"false") << std::endl;
  std::cout << "  isSubmatrix=" << (dst_mat.isSubmatrix()?"true":"false") << std::endl;
}

実行結果:

src_mat
  size=(333, 333)
  step=999
  isContinuous:true
  isSubmatrix=false
dst_mat
  size=(333, 333)
  step=1000
  isContinuous=false
  isSubmatrix=false

ここで示した例は2次元の行列に対するものですが,多次元に対応したアロケータを指定することも可能です.

cv::Matでカスタムアロケータを利用する

カスタムアロケータを利用してアライメントを調整する を参照してください.

目次

前のトピックへ

OpenCV.jp : OpenCV逆引きリファレンス

次のトピックへ

線形代数

このページ