OpenCV のその他のプリミティブなデータ型に対する「traits」クラステンプレート
template<typename _Tp> class DataType
{
// value_type は常に _Tp と等しくなります.
typedef _Tp value_type;
// _Tp の処理で利用される中間型です.
// uchar, signed char, unsigned char, signed short, int に対しては int ,
// float に対しては float , double に対しては double , ...
typedef <...> work_type;
// マルチチャンネルデータの場合,これは各チャンネルのデータ型になります.
typedef <...> channel_type;
enum
{
// CV_8U ... CV_64F
depth = DataDepth<channel_type>::value,
// 1 ...
channels = <...>,
// '1u', '4i', '3f', '2d' など.
fmt=<...>,
// CV_8UC3, CV_32FC2 ...
type = CV_MAKETYPE(depth, channels)
};
};
クラステンプレート DataType は,OpenCV の基本的なデータ型や以下の定義に従うその他の型に対する説明的なクラスです. OpenCV の基本的なデータ型は, unsigned char, bool, unsigned char, signed char, unsigned short, signed short, int, float, double の1つ,あるいは,1つの型の値のタプルで,その場合タプル内の値は全て同じ型になります. OpenCV の CvMat の表記法 CV _ 8U ... CV _ 32FC3, CV _ 64FC2 に慣れているならば, 基本的な型は CV_<bit-depth>{U|S|F}C<number_of_channels> の形式で一意な識別子を与えることができるもの,として定義できます. Vec は,この基本的なデータ型の1つのインスタンスを格納できる OpenCV の汎用的な構造です. また, std::vector , Mat , Mat_ , MatND , MatND_ , SparseMat , SparseMat_ や, Vec() のインスタンスを格納できるその他のコンテナは,このような型の複数のインスタンスを格納できます.
DataType クラスは基本的に,対象となるクラスにフィールドやメソッドを追加せずに(C/C++ の基本的なデータ型に何かを追加するのは実際に不可能),このような基本的なデータ型の説明を提供するために利用されます.このテクニックは,C++ では traits クラスとして知られています.使われるのは DataType 自身ではなく,これを特殊化したものです.例えば:
template<> class DataType<uchar>
{
typedef uchar value_type;
typedef int work_type;
typedef uchar channel_type;
enum { channel_type = CV_8U, channels = 1, fmt='u', type = CV_8U };
};
...
template<typename _Tp> DataType<std::complex<_Tp> >
{
typedef std::complex<_Tp> value_type;
typedef std::complex<_Tp> work_type;
typedef _Tp channel_type;
// DataDepth は別の traits クラス
enum { depth = DataDepth<_Tp>::value, channels=2,
fmt=(channels-1)*256+DataDepth<_Tp>::fmt,
type=CV_MAKETYPE(depth, channels) };
};
...
このクラスの主な目的は,コンパイル時の型情報を OpenCV と互換性のあるデータ型識別子に変換することです.例えば:
// 30x40 の浮動小数点型行列を確保します.
Mat A(30, 40, DataType<float>::type);
Mat B = Mat_<std::complex<double> >(3, 3);
// 以下の文は, 6, 2 を表示します /* つまり, depth == CV_64F, channels == 2 */
cout << B.depth() << ", " << B.channels() << endl;
つまり,この traits クラスは,ユーザがどんなデータ型を使用しているかを,たとえそのデータ型が OpenCV の組み込み型ではないとしても,OpenCV に教えるために利用されます (OpenCV は適切に特殊化されたクラステンプレート DataType<complex<_Tp> > を定義するので,上述の行列 B の初期化はコンパイルされます)また,この機構は generic algorithm の実装に役立ちます(OpenCV内でもそのように使われています).
2次元座標上の点のためのクラステンプレート.
template<typename _Tp> class Point_
{
public:
typedef _Tp value_type;
Point_();
Point_(_Tp _x, _Tp _y);
Point_(const Point_& pt);
Point_(const CvPoint& pt);
Point_(const CvPoint2D32f& pt);
Point_(const Size_<_Tp>& sz);
Point_(const Vec<_Tp, 2>& v);
Point_& operator = (const Point_& pt);
template<typename _Tp2> operator Point_<_Tp2>() const;
operator CvPoint() const;
operator CvPoint2D32f() const;
operator Vec<_Tp, 2>() const;
// 内積を計算します. (this->x*pt.x + this->y*pt.y)
_Tp dot(const Point_& pt) const;
// 倍精度の演算を用いて内積を計算します.
double ddot(const Point_& pt) const;
// この点が矩形 "r" 内に入って入れば真値を返します.
bool inside(const Rect_<_Tp>& r) const;
_Tp x, y;
};
このクラスは,その座標値 と によって指定される2次元の点を表現します.
このクラスのインスタンスは,Cの構造体 CvPoint や CvPoint2D32f と交換可能です.また,点の座標を指定の型に変換するためのキャスト演算子も存在します.浮動小数点型座標から整数座標への変換は,丸めることで行われます.通常は,それぞれの座標に対して 演算を行います.上述の宣言にあるクラスメンバに加えて,座標点に対する以下の処理が実装されています:
pt1 = pt2 + pt3;
pt1 = pt2 - pt3;
pt1 = pt2 * a;
pt1 = a * pt2;
pt1 += pt2;
pt1 -= pt2;
pt1 *= a;
double value = norm(pt); // L2 norm
pt1 == pt2;
pt1 != pt2;
利便性のために,以下のように型の別名が定義されています:
typedef Point_<int> Point2i;
typedef Point2i Point;
typedef Point_<float> Point2f;
typedef Point_<double> Point2d;
ここでは,短い例を示します:
Point2f a(0.3f, 0.f), b(0.f, 0.4f);
Point pt = (a + b)*10.f;
cout << pt.x << ", " << pt.y << endl;
3次元座標上の点のためのクラステンプレート.
template<typename _Tp> class Point3_
{
public:
typedef _Tp value_type;
Point3_();
Point3_(_Tp _x, _Tp _y, _Tp _z);
Point3_(const Point3_& pt);
explicit Point3_(const Point_<_Tp>& pt);
Point3_(const CvPoint3D32f& pt);
Point3_(const Vec<_Tp, 3>& v);
Point3_& operator = (const Point3_& pt);
template<typename _Tp2> operator Point3_<_Tp2>() const;
operator CvPoint3D32f() const;
operator Vec<_Tp, 3>() const;
_Tp dot(const Point3_& pt) const;
double ddot(const Point3_& pt) const;
_Tp x, y, z;
};
このクラスは,その座標値 , , によって指定される3次元の点を表現します.
このクラスのインスタンスは,Cの構造体 CvPoint2D32f と交換可能です. Point_ の場合と同様に,3次元点の座標を別の型に変換する事が可能で,さらにベクトル計算や比較演算もサポートされています.
以下の型の別名が利用可能です:
typedef Point3_<int> Point3i;
typedef Point3_<float> Point3f;
typedef Point3_<double> Point3d;
画像や矩形のサイズを表現するためのクラステンプレート.
template<typename _Tp> class Size_
{
public:
typedef _Tp value_type;
Size_();
Size_(_Tp _width, _Tp _height);
Size_(const Size_& sz);
Size_(const CvSize& sz);
Size_(const CvSize2D32f& sz);
Size_(const Point_<_Tp>& pt);
Size_& operator = (const Size_& sz);
_Tp area() const;
operator Size_<int>() const;
operator Size_<float>() const;
operator Size_<double>() const;
operator CvSize() const;
operator CvSize2D32f() const;
_Tp width, height;
};
Size_ クラスは,2つのメンバが x と y ではなく width と height という名前になっていることを除けば, Point_ と同じです.この構造は,古い OpenCV の構造体 CvSize や CvSize2D32f に対して相互に変換可能です.また,このクラスでも Point_ に対する計算や比較演算と同様のものが利用できます.
OpenCVでは,型の別名を以下の用に定義しています:
typedef Size_<int> Size2i;
typedef Size2i Size;
typedef Size_<float> Size2f;
2次元の矩形を表現するためのクラステンプレート.
template<typename _Tp> class Rect_
{
public:
typedef _Tp value_type;
Rect_();
Rect_(_Tp _x, _Tp _y, _Tp _width, _Tp _height);
Rect_(const Rect_& r);
Rect_(const CvRect& r);
// (x, y) <- org, (width, height) <- sz
Rect_(const Point_<_Tp>& org, const Size_<_Tp>& sz);
// (x, y) <- min(pt1, pt2), (width, height) <- max(pt1, pt2) - (x, y)
Rect_(const Point_<_Tp>& pt1, const Point_<_Tp>& pt2);
Rect_& operator = ( const Rect_& r );
// Point_<_Tp>(x, y) を返します
Point_<_Tp> tl() const;
// Point_<_Tp>(x+width, y+height) を返します
Point_<_Tp> br() const;
// Size_<_Tp>(width, height) を返します
Size_<_Tp> size() const;
// width*height を返します
_Tp area() const;
operator Rect_<int>() const;
operator Rect_<float>() const;
operator Rect_<double>() const;
operator CvRect() const;
// x <= pt.x && pt.x < x + width &&
// y <= pt.y && pt.y < y + height ? true : false
bool contains(const Point_<_Tp>& pt) const;
_Tp x, y, width, height;
};
矩形は,左上コーナの座標(OpenCV ではデフォルトで, Rect_::x と Rect_::y を左上コーナーと解釈しますが,ユーザのアルゴリズムによっては x と y を左下のコーナから数えるかもしれません)と,矩形の幅と高さによって表現されます.
OpenCV におけるもう1つの前提は,矩形の上側と左側の境界線は矩形に含まれますが,右側と下側の境界線はそうではないということです.例えば,以下のような場合, Rect_::contains メソッドは true を返します.
OpenCV では,画像の ROI (ROIは Rect_<int> によって指定されます)を処理するループは以下のように実装されます:
for(int y = roi.y; y < roi.y + rect.height; y++)
for(int x = roi.x; x < roi.x + rect.width; x++)
{
// ...
}
クラスのメンバに加えて,矩形に対する以下の処理が実装されています:
例.ここでは,矩形での partial ordering がどのように行われるかを示します(rect1 rect2):
template<typename _Tp> inline bool
operator <= (const Rect_<_Tp>& r1, const Rect_<_Tp>& r2)
{
return (r1 & r2) == r1;
}
利便性のため,以下のような型の別名が利用可能です:
typedef Rect_<int> Rect;
回転を考慮した矩形.
class RotatedRect
{
public:
// コンストラクタ
RotatedRect();
RotatedRect(const Point2f& _center, const Size2f& _size, float _angle);
RotatedRect(const CvBox2D& box);
// 回転した矩形を包含する,回転していない最小の矩形を返します.
Rect boundingRect() const;
// CvBox2D に対する後方互換性
operator CvBox2D() const;
// 矩形の重心
Point2f center;
// サイズ
Size2f size;
// degree で表現される回転角度
float angle;
};
RotatedRect クラスは,古い CvBox2D を置き換え,それと完全な互換性を保ちます.
反復アルゴリズムの停止基準.
class TermCriteria
{
public:
enum { COUNT=1, MAX_ITER=COUNT, EPS=2 };
// コンストラクタ
TermCriteria();
// type は, MAX_ITER, EPS, MAX_ITER+EPS の内の1つです.
// type = MAX_ITER の場合は,反復回数だけが問題になります.
// type = EPS の場合は,要求精度(イプシロン)だけが問題になります.
// (もっとも,大抵のアルゴリズムでは反復回数に何らかの制限を設けますが)
// type = MAX_ITER + EPS の場合は,反復回数が既定値に達するか,
// あるいは,要求精度が達成された場合にアルゴリズムが停止します.
TermCriteria(int _type, int _maxCount, double _epsilon);
TermCriteria(const CvTermCriteria& criteria);
operator CvTermCriteria() const;
int type;
int maxCount;
double epsilon;
};
TermCriteria は,古い CvTermCriteria を置き換え,それと完全な互換性を保ちます.
短い数値ベクトルのためのクラステンプレート.
template<typename _Tp, int cn> class Vec
{
public:
typedef _Tp value_type;
enum { depth = DataDepth<_Tp>::value, channels = cn,
type = CV_MAKETYPE(depth, channels) };
// デフォルトコンストラクタ:全ての要素は0にセットされます.
Vec();
// 最初の 10 要素までをパラメータにとるコンストラクタです.
Vec(_Tp v0);
Vec(_Tp v0, _Tp v1);
Vec(_Tp v0, _Tp v1, _Tp v2);
...
Vec(_Tp v0, _Tp v1, _Tp v2, _Tp v3, _Tp v4,
_Tp v5, _Tp v6, _Tp v7, _Tp v8, _Tp v9);
Vec(const Vec<_Tp, cn>& v);
// ベクトルの全要素を alpha にします.
static Vec all(_Tp alpha);
// 2 種類の内積
_Tp dot(const Vec& v) const;
double ddot(const Vec& v) const;
// 外積. cn == 3 の場合のみ有効です.
Vec cross(const Vec& v) const;
// 要素型の変換
template<typename T2> operator Vec<T2, cn>() const;
// CvScalar との相互変換( cn==4 の場合のみ有効です)
operator CvScalar() const;
// 要素アクセス
_Tp operator [](int i) const;
_Tp& operator[](int i);
_Tp val[cn];
};
このクラスは,短い数値ベクトルまたはタプルの汎用表現です. Vec<T,2> と Point_ , Vec<T,3> と Point3_ , Vec<T,4> と CvScalar の相互変換が可能です. Vec の要素には operator[] を用いてアクセスすることができます.期待されるベクトル演算もすべて実装されています:
利便性のため,以下の型の別名が導入されています:
typedef Vec<uchar, 2> Vec2b;
typedef Vec<uchar, 3> Vec3b;
typedef Vec<uchar, 4> Vec4b;
typedef Vec<short, 2> Vec2s;
typedef Vec<short, 3> Vec3s;
typedef Vec<short, 4> Vec4s;
typedef Vec<int, 2> Vec2i;
typedef Vec<int, 3> Vec3i;
typedef Vec<int, 4> Vec4i;
typedef Vec<float, 2> Vec2f;
typedef Vec<float, 3> Vec3f;
typedef Vec<float, 4> Vec4f;
typedef Vec<float, 6> Vec6f;
typedef Vec<double, 2> Vec2d;
typedef Vec<double, 3> Vec3d;
typedef Vec<double, 4> Vec4d;
typedef Vec<double, 6> Vec6d;
Vec クラスは,様々な数値オブジェクトを宣言するために利用可能です.例えば, Vec<double,9> は,3x3 の倍精度行列を格納するために利用できます.また,マルチチャンネル配列を宣言,処理する際にも便利です.詳しくは, Mat_ の説明を参照してください.
4-要素ベクトル.
template<typename _Tp> class Scalar_ : public Vec<_Tp, 4>
{
public:
Scalar_();
Scalar_(_Tp v0, _Tp v1, _Tp v2=0, _Tp v3=0);
Scalar_(const CvScalar& s);
Scalar_(_Tp v0);
static Scalar_<_Tp> all(_Tp v0);
operator CvScalar() const;
template<typename T2> operator Scalar_<T2>() const;
Scalar_<_Tp> mul(const Scalar_<_Tp>& t, double scale=1 ) const;
template<typename T2> void convertTo(T2* buf, int channels, int unroll_to=0) const;
};
typedef Scalar_<double> Scalar;
クラステンプレート Scalar_ および,その倍精度表現のインスタンス Scalar は,4要素のベクトルを表します.これらは Vec<_Tp, 4> から派生しているため,典型的な4-要素ベクトルとして利用でき,さらに CvScalar と相互に変換可能です. Scalar 型は,OpenCV でピクセル値を処理するために広く利用されており,以前のOpenCVで同様に利用されていた CvScalar を手軽に代替することができます.
シーケンス内の,連続した部分シーケンス(別名,スライス)を表す.
class Range
{
public:
Range();
Range(int _start, int _end);
Range(const CvSlice& slice);
int size() const;
bool empty() const;
static Range all();
operator CvSlice() const;
int start, end;
};
このクラスは,行列( Mat() )の行または列の区間を指定するため,またその他多くの目的で利用されます. Range(a,b) は基本的に, Matlab の a:b や Python の a..b などと等しいものです. Python での表現と同様に, start は範囲に含まれる左側の境界, end は範囲に含まれない右側の境界です.このような半開区間は,通常 と表記されます.
スタティックメソッド Range::all() は,丁度 Matlab での ” : ” や Python(numpy) での ” ... ” と同じように「シーケンス全体」あるいは「範囲全体」を意味する特別な変数を返します.
Range を扱う OpenCV のすべてのメソッドおよび関数は,この特別な値 Range::all() をサポートします.しかしもちろん,ユーザ独自の処理では明示的にそれをチェックして対処する必要があります:
void my_function(..., const Range& r, ....)
{
if(r == Range::all()) {
// 全データを処理
}
else {
// [r.start, r.end) を処理
}
}
参照カウント方式のスマートポインタクラステンプレート.
template<typename _Tp> class Ptr
{
public:
// デフォルトコンストラクタ
Ptr();
// オブジェクトポインタをラップするコンストラクタ
Ptr(_Tp* _obj);
// デストラクタ: release() を呼び出します
~Ptr();
// コピーコンストラクタ. ptr の参照カウントをインクリメントします.
Ptr(const Ptr& ptr);
// 代入演算子.( release() によって)自分の参照カウントをデクリメントし,
// ptr の参照カウンタをインクリメントします.
Ptr& operator = (const Ptr& ptr);
// 参照カウンタをインクリメントします.
void addref();
// 参照カウンタをデクリメントします. 0 になった場合,
// delete_obj() が呼ばれます.
void release();
// ユーザ指定のカスタムオブジェクト削除処理.
// デフォルトでは "delete obj" が呼ばれます.
void delete_obj();
// obj == 0 の場合 true を返します.
bool empty() const;
// オブジェクトのフィールドとメソッドにアクセスする手段を提供します.
_Tp* operator -> ();
const _Tp* operator -> () const;
// 内部のオブジェクトポインタを返します.
// このメソッドのおかげで, _Tp* の代わりに
// Ptr<_Tp> を利用できます.
operator _Tp* ();
operator const _Tp*() const;
protected:
// カプセル化されたオブジェクトポインタ
_Tp* obj;
// 参照カウンタ
int* refcount;
};
Ptr<_Tp> クラスは,対応する型へのポインタをラップするクラステンプレートです. これは,Boost ライブラリ( http://www.boost.org/doc/libs/1_40_0/libs/smart_ptr/shared_ptr.htm )や, C++0x 標準にある shared_ptr と同様のものです.
このクラスを利用すると,以下のような事が可能になります:
Ptr クラスは,ラッピングされたオブジェクトをブラックボックスとして扱います.参照カウンタは個別に確保,管理されます.ポインタクラスが知っておく必要があるのは,オブジェクトの解放の方法だけです.それは, Ptr::delete_obj() メソッドにカプセル化され,参照カウンタが 0 になったときに呼ばれます.このメソッドはデフォルトで delete obj; を呼ぶので,オブジェクトが C++ のクラスインスタンスである場合は,追加のコーディングは必要ありません.
しかし,オブジェクトが別の方法で解放される場合は,専用のメソッドを作る必要があります.例えば, FILE をラッピングしたい場合は, delete_obj を以下のような感じで実装します:
template<> inline void Ptr<FILE>::delete_obj()
{
fclose(obj); // さらにポインタをクリアする必要はありません.
// その処理,この外側で行われます.
}
...
// 使い方
Ptr<FILE> f(fopen("myfile.txt", "r"));
if(f.empty())
throw ...;
fprintf(f, ....);
...
// Ptr<FILE> のデストラクタによって, FILE は自動的に閉じられます.
注意 :参照のインクリメント/デクリメント処理は,不可分な処理として実装されているので,このクラスをマルチスレッドアプリケーションで利用しても通常は安全です.また,参照カウンタを利用する Mat() やその他の OpenCV のクラスも同様です.
OpenCV の C++ 行列クラス.
class CV_EXPORTS Mat
{
public:
// コンストラクタ
Mat();
// 指定したサイズ,型の行列を作成します.
// (_type is CV_8UC1, CV_64FC3, CV_32SC(12) など)
Mat(int _rows, int _cols, int _type);
Mat(Size _size, int _type);
// 行列を作成して,指定した値 _s で埋めます.
Mat(int _rows, int _cols, int _type, const Scalar& _s);
Mat(Size _size, int _type, const Scalar& _s);
// コピーコンストラクタ
Mat(const Mat& m);
// ユーザが確保したデータを指す行列ヘッダのコンストラクタ
Mat(int _rows, int _cols, int _type, void* _data, size_t _step=AUTO_STEP);
Mat(Size _size, int _type, void* _data, size_t _step=AUTO_STEP);
// より大きな行列の部分行列を作成します.
Mat(const Mat& m, const Range& rowRange, const Range& colRange);
Mat(const Mat& m, const Rect& roi);
// 古い形式の CvMat を新しい行列に変換します.デフォルトではデータはコピーされません.
Mat(const CvMat* m, bool copyData=false);
// 古い形式の IplImage を新しい行列に変換します.デフォルトではデータはコピーされません.
Mat(const IplImage* img, bool copyData=false);
// データコピーの有無を選択して, std::vector から行列を作成します.
template<typename _Tp> explicit Mat(const vector<_Tp>& vec, bool copyData=false);
// 行列表現 をコンパイルするための補助コンストラクタ
Mat(const MatExpr_Base& expr);
// デストラクタ - release() を呼び出します.
~Mat();
// 代入演算子
Mat& operator = (const Mat& m);
Mat& operator = (const MatExpr_Base& expr);
operator MatExpr_<Mat, Mat>() const;
// 指定した行に対する新しい行列ヘッダを返します.
Mat row(int y) const;
// 指定した列に対する新しい行列ヘッダを返します.
Mat col(int x) const;
// 指定した複数行に対する新しい行列ヘッダを返します.
Mat rowRange(int startrow, int endrow) const;
Mat rowRange(const Range& r) const;
// 指定した複数列に対する新しい行列ヘッダを返します.
Mat colRange(int startcol, int endcol) const;
Mat colRange(const Range& r) const;
// 指定した対角成分に対する新しい行列ヘッダを返します.
// (d=0 - 主対角成分,
// >0 - 主対角成分の下側の対角成分,
// <0 - 主対角成分の上側の対角成分 )
Mat diag(int d=0) const;
// 対角成分が "d" の正方対角行列を作成します.
static Mat diag(const Mat& d);
// 行列の深いコピーを返します.つまり,データがコピーされます.
Mat clone() const;
// 行列の内容を "m" にコピーします.
// これは, m.create(this->size(), this->type()) を呼び出します.
void copyTo( Mat& m ) const;
// 非 0 のマスク要素に対応する行列要素を "m" にコピーします.
void copyTo( Mat& m, const Mat& mask ) const;
// 行列をスケーリングして別のデータ型に変換します. cvConvertScale を参照してください.
void convertTo( Mat& m, int rtype, double alpha=1, double beta=0 ) const;
void assignTo( Mat& m, int type=-1 ) const;
// 全ての行列要素を s にします.
Mat& operator = (const Scalar& s);
// マスクに従って,行列要素を s にします.
Mat& setTo(const Scalar& s, const Mat& mask=Mat());
// データは同じでチャンネル数や行数が異なる,別の行列ヘッダを作成します.
// cvReshape を参照してください.
Mat reshape(int _cn, int _rows=0) const;
// 行列表現 を利用した転置行列
MatExpr_<MatExpr_Op2_<Mat, double, Mat, MatOp_T_<Mat> >, Mat>
t() const;
// 行列表現 を利用した逆行列
MatExpr_<MatExpr_Op2_<Mat, int, Mat, MatOp_Inv_<Mat> >, Mat>
inv(int method=DECOMP_LU) const;
MatExpr_<MatExpr_Op4_<Mat, Mat, double, char, Mat, MatOp_MulDiv_<Mat> >, Mat>
// 行列表現 を利用した要素毎の乗算
mul(const Mat& m, double scale=1) const;
MatExpr_<MatExpr_Op4_<Mat, Mat, double, char, Mat, MatOp_MulDiv_<Mat> >, Mat>
mul(const MatExpr_<MatExpr_Op2_<Mat, double, Mat, MatOp_Scale_<Mat> >, Mat>& m, double scale=1) const;
MatExpr_<MatExpr_Op4_<Mat, Mat, double, char, Mat, MatOp_MulDiv_<Mat> >, Mat>
mul(const MatExpr_<MatExpr_Op2_<Mat, double, Mat, MatOp_DivRS_<Mat> >, Mat>& m, double scale=1) const;
// 2 つの 3 次元ベクトルの外積
Mat cross(const Mat& m) const;
// 内積
double dot(const Mat& m) const;
// Matlab 形式の行列の初期化.
static MatExpr_Initializer zeros(int rows, int cols, int type);
static MatExpr_Initializer zeros(Size size, int type);
static MatExpr_Initializer ones(int rows, int cols, int type);
static MatExpr_Initializer ones(Size size, int type);
static MatExpr_Initializer eye(int rows, int cols, int type);
static MatExpr_Initializer eye(Size size, int type);
// 行列データを新たに確保します.ただし,現在のデータが既に,指定されたサイズ,型である場合は除きます.
// 必要ならば,以前のデータは参照されていない状態になります.
void create(int _rows, int _cols, int _type);
void create(Size _size, int _type);
// 参照カウンタを増やします.メモリリークに注意して使用してください.
void addref();
// 参照カウンタを減らします.
// 参照カウンタが 0 になると,データは解放されます.
void release();
// 親行列の内部に行列 ROI を作成します.以下を参照してください.
void locateROI( Size& wholeSize, Point& ofs ) const;
// 親行列内部の行列 ROI の移動やサイズ変更を行います.
Mat& adjustROI( int dtop, int dbottom, int dleft, int dright );
// 矩形の部分行列を取り出します.
// (これは, row, rowRange などを一般化したものです)
Mat operator()( Range rowRange, Range colRange ) const;
Mat operator()( const Rect& roi ) const;
// ヘッダを CvMat に変換します.データはコピーされません.
operator CvMat() const;
// ヘッダを IplImage に変換します;データはコピーされません.
operator IplImage() const;
// 行列データが連続である(つまり,行間にギャップが存在しない)
// 場合のみ, true を返します.
// CV_IS_MAT_CONT(cvmat->type) と等価です.
bool isContinuous() const;
// CV_ELEM_SIZE(cvmat->type) と同様に,
// バイト単位で表された要素サイズを返します.
size_t elemSize() const;
// returns the size of element channel in bytes.
size_t elemSize1() const;
// CV_MAT_TYPE(cvmat->type) と同様に,要素の型を返します.
int type() const;
// CV_MAT_DEPTH(cvmat->type) と同様に,要素のビット深度を返します.
int depth() const;
// CV_MAT_DEPTH(cvmat->type) と同様に,要素のチャンネル数を返します.
int channels() const;
// step/elemSize1() を返します.
size_t step1() const;
// 行列サイズを返します:
// width == 列数, height == 行数
Size size() const;
// 行列データが NULL の場合に true を返します.
bool empty() const;
// y 番目の行へのポインタを返します.
uchar* ptr(int y=0);
const uchar* ptr(int y=0) const;
// 上述のメソッドのテンプレート版です.
template<typename _Tp> _Tp* ptr(int y=0);
template<typename _Tp> const _Tp* ptr(int y=0) const;
// 読み書き,あるいは読み込みのみ,の要素アクセスを行うテンプレートメソッド
// _Tp は,実際の要素型と一致しなければならないことに注意してください -
// この関数は,処理中にいかなる型の変換も行いません.
template<typename _Tp> _Tp& at(int y, int x);
template<typename _Tp> _Tp& at(Point pt);
template<typename _Tp> const _Tp& at(int y, int x) const;
template<typename _Tp> const _Tp& at(Point pt) const;
// 行列要素を横断するテンプレートメソッド
// イテレータは,(もしあれば)行末のギャップをスキップします.
template<typename _Tp> MatIterator_<_Tp> begin();
template<typename _Tp> MatIterator_<_Tp> end();
template<typename _Tp> MatConstIterator_<_Tp> begin() const;
template<typename _Tp> MatConstIterator_<_Tp> end() const;
enum { MAGIC_VAL=0x42FF0000, AUTO_STEP=0, CONTINUOUS_FLAG=CV_MAT_CONT_FLAG };
// 数種類のビットフィールドを含みます:
// * マジックシグネチャ
// * 連続性フラグ
// * ビット深度
// * チャンネル数
int flags;
// 行数,列数
int rows, cols;
// バイト単位で表された,隣り合う行と行の距離.もしあれば,ギャップも含みます.
size_t step;
// データへのポインタ
uchar* data;
// 参照カウンタへのポインタ
// 行列データがユーザが確保したものであれば,このポインタは NULL です.
int* refcount;
// locateROI と adjustROI の内部で利用される補助フィールド
uchar* datastart;
uchar* dataend;
};
Mat クラスは,行列,画像,オプティカルフローマップなどとして振る舞う(そして,行列として参照される)2次元の数値配列を表現します.これは,前バージョンの OpenCV の CvMat 型と非常によく似ています.また, CvMat と同様にマルチチャンネルですが, IplImage と同様に ROI を完全にサポートしています.
Mat オブジェクトを作成する方法はたくさんありますが,ここではポピュラーなものをいくつか示します:
create(nrows, ncols, type) メソッドや,それと同様のコンストラクタ Mat(nrows, ncols, type[, fill_value]) を利用します.
指定されたサイズ,型の新しい行列が確保されます. ここで
type は, cvCreateMat の場合と同じ意味を持ちます.
例えば,
CV_8UC1 は8ビットでシングルチャンネルの行列,
CV_32FC2 は2チャンネル(つまり複素数)浮動小数点型の行列を表します.
// 1+3j で埋められた 7x7 の複素行列を作成します.
cv::Mat M(7,7,CV_32FC2,Scalar(1,3));
// そして,M を 100x60 15 チャンネル 8 ビットの行列に変更します.
// 行列の以前の内容は解放されます.
M.create(100,60,CV_8UC(15));
この章のイントロダクションでも述べたように, create() は,現在の行列の次元や型が指定されたものと異なる場合のみ,新しい行列を確保します.
コンストラクタや代入演算子を利用します.この場合,右辺は行列か matrix expression になります.詳しくは以下を参照してください.同じくイントロダクションで述べたように,行列の代入はヘッダをコピーして参照カウンタを増加させるだけなので O(1) の処理です.必要なときには Mat::clone() メソッドを使って,行列の完全なコピー(つまり,深いコピー)を得ることができます.
別の行列の一部分に対するヘッダを作成します.これは,行列の1行や1列,複数行や複数列,矩形領域(代数では小行列式と呼ばれます),対角要素などに対して可能です.新しいヘッダも同じデータを参照するので,この様な処理も O(1) です.この特徴を利用すると,例えば以下のように,行列の一部を実際に変更することができます.
// 5 行目 を 3 倍して, 3 行目に足します.
M.row(3) = M.row(3) + M.row(5)*3;
// 7 列目を 1 列目にコピーします.
// M.col(1) = M.col(7); // これは動作しません
Mat M1 = M.col(1);
M.col(7).copyTo(M1);
// 新たに 320x240 の画像を作成します.
cv::Mat img(Size(320,240),CV_8UC3);
// ROI を選択します.
cv::Mat roi(img, Rect(10,10,100,100));
// ROI を (0,255,0) ( RGB 空間での緑)で埋めます.
// 320x240 の元の画像は変更されます.
roi = Scalar(0,255,0);
datastart および dataend メソッドが追加されたおかげで, locateROI() を用いてメインの “コンテナ” 行列内の部分行列の相対位置を計算することができます.
Mat A = Mat::eye(10, 10, CV_32S);
// 1 列目(範囲に含む)から 3 列目(含まない)までを A として抽出します
Mat B = A(Range::all(), Range(1, 3));
// 5 行目(範囲に含む)から 9 行目(含まない)までを B として抽出します
// つまり, C ~ A(Range(5, 9), Range(1, 3))
Mat C = B(Range(5, 9), Range::all());
Size size; Point ofs;
C.locateROI(size, ofs);
// サイズは (width=10,height=10) , ofs は (x=1, y=5)
行列全体の場合と同様に,深いコピーが必要ならば,抽出された部分行列の clone() メソッドを利用します.
ユーザが確保したデータに対するヘッダを作成します.これは,以下の場合に有用です:
(例えば,DirectShow のフィルタや,gstreamer の処理モジュールなどを実装する場合)
void process_video_frame(const unsigned char* pixels,
int width, int height, int step)
{
cv::Mat img(height, width, CV_8UC3, pixels, step);
cv::GaussianBlur(img, img, cv::Size(7,7), 1.5, 1.5);
}
小さい行列の素早い初期化 および/または 超高速な要素アクセス
double m[3][3] = {{a, b, c}, {d, e, f}, {g, h, i}};
cv::Mat M = cv::Mat(3, 3, CV_64F, m).inv();
この「ユーザが確保したデータに対するヘッダを作成」で,一部で非常に頻繁に行われるケースが CvMat や IplImage から Mat への変換です.このために, CvMat や IplImage へのポインタをとる専用のコンストラクタと,データをコピーするか否かを指定するオプションフラグが存在します.
また,
Mat から CvMat や IplImage への後方変換は,キャスト演算子 Mat::operator CvMat() const と Mat::operator IplImage() によって提供されます.この演算子は,データをコピー しません .
IplImage* img = cvLoadImage("greatwave.jpg", 1);
Mat mtx(img); // IplImage* -> cv::Mat の変換
CvMat oldmat = mtx; // cv::Mat -> CvMat の変換
CV_Assert(oldmat.cols == img->width && oldmat.rows == img->height &&
oldmat.data.ptr == (uchar*)img->imageData && oldmat.step == img->widthStep);
MATLAB 形式 zeros(), ones(), eye() の行列初期化,つまり:
// 倍精度の単位行列を作成して,それを M に足します.
M += Mat::eye(M.rows, M.cols, CV_64F);
カンマ区切りの初期化子:
// 3x3 の倍精度単位行列を作成します.
Mat M = (Mat_<double>(3,3) << 1, 0, 0, 0, 1, 0, 0, 0, 1);
ここでは,まず(後で述べる) Mat_ クラスのコンストラクタに適切な行列を与えて呼び出し,さらに << 演算子に続いて,定数,変数,式などから成るカンマ区切りの値を与えます.また,コンパイルエラーを避けるために必要な括弧にも注意してください.
一度作成された行列は,参照カウント機構により自動的に管理されます(ただし,行列ヘッダがユーザが確保したデータに対して作成される場合を除きます.この場合,ユーザ自身がデータを処理しなければいけません).
行列データは,それを指し示すものがなくなった場合に解放されます.行列のデストラクタが呼ばれるよりも前に,その行列ヘッダによって指されるデータを解放したい場合は, Mat::release() を利用してください.
行列クラスに関して次に学ぶ重要事項は,要素へのアクセスです.ここでは,行列がどのようにデータを格納しているのかを述べます.要素は,行順に(行毎に)格納されています. Mat::data メンバは,この最初の行の最初の要素を指していおり, Mat::rows は行数を, Mat::cols は列数を表しています.さらに,実際に行列要素のアドレスを求めるために使われる Mat::step というメンバも存在します.ある行列は別の行列の一部であったり,アラインメントを揃えるために各行末に空白が存在したりするので,この Mat::step が必要になります.
これらのパラメータが与えられると,行列要素 のアドレスが以下のように計算されます:
addr( )=M.data + M.step*i + j*M.elemSize() もし行列要素の型が既知(例えば float )であるならば, at<>() メソッドを用いて以下のように書くことができます:
addr( )=&M.at<float>(i,j) (ここで & は, at によって返される参照をポインタに変換します)
行列の行全体を処理する必要がある場合に最も効率的な方法は,最初の行のポインタを取得して,純粋な C 言語の演算子 [] を利用することです:
// 値が正である要素の合計を求めます.
// (ここで M は倍精度行列と仮定します)
double sum=0;
for(int i = 0; i < M.rows; i++)
{
const double* Mi = M.ptr<double>(i);
for(int j = 0; j < M.cols; j++)
sum += std::max(Mi[j], 0.);
}
上述のような処理は,実際は行列の形には依存せず,行列要素を1つ1つ処理する(または,行列同士の足し算のように,複数の行列の同じ場所にある要素を処理する)だけです.このような処理は要素ワイズと呼ばれ,すべての入出力行列が連続であるか,つまり,各行末にギャップが存在しないかどうかをチェックするのが妥当です.もしそうならば,長く続く1つの行であるかのように処理できます.
// 値が正である要素の合計を求めます,最適化版
double sum=0;
int cols = M.cols, rows = M.rows;
if(M.isContinuous())
{
cols *= rows;
rows = 1;
}
for(int i = 0; i < rows; i++)
{
const double* Mi = M.ptr<double>(i);
for(int j = 0; j < cols; j++)
sum += std::max(Mi[j], 0.);
}
値が連続する行列の場合,外側のループは一度だけ実行されるのでオーバーヘッドが小さくなります.これは,特に小さい行列の場合に顕著です.
最後に,行と行の間のギャップをスキップする STL 形式の優れたイテレータの例を述べます:
// 値が正である要素の合計を求める,イテレータ利用版
double sum=0;
MatConstIterator_<double> it = M.begin<double>(), it_end = M.end<double>();
for(; it != it_end; ++it)
sum += std::max(*it, 0.);
行列のイテレータは,ランダムアクセスイテレータなので, std::sort() を含むすべての STL アルゴリズムに適用できます.
これは,実装されている行列演算の一覧であり,組み合わせて任意の複雑な表現を行うこともできます(ここで A と B は行列を表し, s はスカラ( Scalar )を, :math:`alpha` は実数値のスカラ( double )を表します).
加算,減算,符号反転:
スケーリング: A* , A/
要素毎の積,商: A.mul(B), A/B, /A
行列の積: A*B
転置: A.t()
逆行列,擬似逆行列,連立方程式,最小二乗問題の解: A.inv([method]) , A.inv([method])*B
比較: . 比較結果は,8ビットのシングルチャンネルマスクで,(特定の要素,あるいは要素の組が条件を満たしていれば)255か,(そうでなければ)0にセットされます.
ビット単位の論理演算: A & B, A & s, A | B, A | s, A ^ B, A ^ s, ~ A
要素単位の最小値,最大値: min(A, B), min(A, ), max(A, B), max(A, )
要素単位の絶対値: abs(A)
外積,内積: A.cross(B), A.dot(B)
行列かスカラを返す,行列,行列同士,スカラに対するあらゆる演算.例えば,
norm() , mean() , sum() , countNonZero() , trace() ,
determinant() , repeat() など.
行列の初期化子( eye(), zeros(), ones() ),行列のカンマ区切り初期化子,行列コンストラクタ,部分行列を抽出する演算子( Mat() の説明を参照してください).
結果を適切な型に変換するための verb “Mat_<destination_type>()” コンストラクタ.
また,カンマ区切りの初期化子,あるいはその他の演算は,曖昧さを回避するために明示的に Mat() や Mat_<T>() コンストラクタを呼び出す必要があるかもしれない事に注意してください.
以下は, Mat のメソッドの正式な説明です.
様々な行列コンストラクタ.
パラメタ: |
|
---|
これらは,行列を作成する様々なコンストラクタです. の注意書きにもあるように,多くの場合はデフォルトコンストラクタで十分であり,OpenCV の関数により適切な行列が確保されます.作成された行列は,さらに別の行列や matrix expression に代入することができ,その場合,古い内容に対しては参照外しが行われます.あるいは,行列をさらに Mat::create によって確保することもできます.
行列の代入演算子.
パラメタ: |
|
---|
これらは,すべて有効な代入演算子です.それぞれ大きく異なっているので,演算子の説明をきちんと読んでください.
このキャスト演算子は,明示的に呼ばれるべきものではありません. Matrix Expressions エンジンで,内部的に利用されます.
このメソッドは,行列の指定行に対する新しいヘッダを作成し,それを返します.これは,行列のサイズにかかわらず O(1) の処理です.新しい行列の内部データは,元の行列と共有されます.ここでは,LU やその他たくさんのアルゴリズムで利用されている,古典的で基本的な行列処理の1つである axpy の例を紹介します.
inline void matrix_axpy(Mat& A, int i, int j, double alpha)
{
A.row(i) += A.row(j)*alpha;
}
重大な注意 .現在の実装では,以下のコードは期待通りには動作しません.
Mat A;
...
A.row(i) = A.row(j); // 動作しません
動作しない理由は, A.row(i) が一時的なヘッダを作成し,そこにさらに別のヘッダに代入されるからです.これらの演算子は O(1) ,つまりデータのコピーは行われないことを忘れないでください.従って,j 番目の行が i 番目の行にコピーされると期待して上述の代入を行っても,期待通りには動きません.それを行うには,単純な代入を expression を利用したものに代えるか, Mat::copyTo メソッドを利用するかしてください.
Mat A;
...
// 動作しますが,少し分かり難いです.
A.row(i) = A.row(j) + 0;
// すこし長いですが,おすすめの方法です.
Mat Ai = A.row(i); M.row(j).copyTo(Ai);
このメソッドは,行列の指定列に対する新しいヘッダを作成し,それを返します.これは,行列のサイズにかかわらず O(1) の処理です.新しい行列の内部データは,元の行列と共有されます. Mat::row の説明も参照してください.
行列の指定された範囲行に対する行列ヘッダを作成します.
パラメタ: |
|
---|
このメソッドは,行列の指定範囲行に対する新しいヘッダを作成します.これは Mat::row() や Mat::col() と同様に,O(1) の処理です.
行列の指定された範囲列に対する行列ヘッダを作成します.
パラメタ: |
|
---|
このメソッドは,行列の指定範囲行に対する新しいヘッダを作成します.これは Mat::row() や Mat::col() と同様に,O(1) の処理です.
行列から対角成分を抜き出します.または対角行列を作成します.
パラメタ: |
|
---|
このメソッドは,指定された行列の対角成分に対する新しいヘッダを作成します.この新しい行列は,1列の行列として表現されます. Mat::row() や Mat::col() と同様に,これも O(1) の処理です.
このメソッドは,行列の完全なコピーを作成します.しかし,元の行列の step は考慮されません.この行列のコピーは,連続した cols*rows*elemSize() バイトの領域を占有します.
行列を別の行列にコピーします.
パラメタ: |
|
---|
このメソッドは,行列のデータを別の行列にコピーします.データをコピーする前に,このメソッドは以下を呼び出します.
m.create(this->size(), this->type);
これにより,必要ならばコピー先の行列が再配置されます. m.copyTo(m); が期待通りの動作を行います,つまり,行列は何も変化しません.しかし,この関数は,コピー元とコピー先の行列が部分的にオーバラップするような場合を扱うことはできません.
処理マスクが指定される場合,そして,上述の Mat::create 呼び出しが行列を再配置される場合,新しく確保された行列は,データコピーの前に0で初期化されます.
行列のデータを別の型に変換します.オプションで,スケーリングか可能です.
パラメタ: |
|
---|
このメソッドは,元行列のピクセル値を目的のデータ型に変換します.オーバフローを回避するために,処理の最後に saturate_cast<> が適用されます.
convertTo の関数形式.
パラメタ: |
|
---|
これは, Matrix Expressions エンジンによって内部的に利用されるメソッドです.
すべて,またはいくつかの行列の要素に指定された値をセットします.
パラメタ: |
|
---|
これは, Mat::operator=(const Scalar& s) 演算子の詳細版です.
データをコピーすることなく,行列の形状かチャンネル数,あるいはその両方を変更します
パラメタ: |
|
---|
このメソッドは, *this 要素に対する新しい行列ヘッダを作成します.新しい行列は,元の行列とは,サイズかチャンネル数,あるいはその両方が異なります.以下の条件内であれば,どのような組み合わせも可能です.
ここでは,簡単な例を示します.STL vector に保存された3次元の点群があり,これを 3xN の行列で表現したいとします.その処理は,次のようになります:
std::vector<cv::Point3f> vec;
...
Mat pointMat = Mat(vec). // vector から Mat への変換. O(1) の処理
reshape(1). // Nx1 3-チャンネル行列から,Nx3 1-チャンネル行列を作成します.
// これも O(1) の処理です.
t(); // 最後に,Nx3 行列を転置します.
// これは,全要素のデータコピーを含みます.
このメソッドは, matrix expressions を用いて行列の転置を行います.
つまりこのメソッドは,より複雑な matrix expression の一部として利用されたり,行列に代入されたりし得る,一時的な「転置行列」オブジェクトを返します.
Mat A1 = A + Mat::eye(A.size(), A.type)*lambda;
Mat C = A1.t()*A1; // (A + lambda*I)^t * (A + lamda*I) を計算します
逆行列を求めます.
Parameter: | method – 逆行列を求める方法.以下のうちの1つ
|
---|
このメソッドは,matrix expression を用いて逆行列を計算します.つまりこのメソッドは,より複雑な matrix expression の一部として利用されたり,行列に代入されたりし得る,一時的な「逆行列」オブジェクトを返します.
2つの行列の要素同士の掛け算,割り算を行います.
パラメタ: |
|
---|
このメソッドは,要素同士の掛け算,または割り算,オプションとしてのスケーリングを表す一時的なオブジェクトを返します.これは,単純な「*」演算子に対応する行列同士の積とは異なることに注意してください.
ここでは,このメソッドの3番目の形式が自動的に呼び出される例を示します:
Mat C = A.mul(5/B); // divide(A, B, C, 5) と等価
このメソッドは,3要素ベクトル同士の外積を計算します.この,ベクトルは同じ形状,同じサイズの3要素浮動小数点型ベクトルでなければいけません.計算結果は,入力ベクトルと同じ形状,同じ型の,別の3要素ベクトルとなります.
このメソッドは,2つの行列同士の内積を計算します.行列が1行または1列の行列でなかった場合,上から下,左から右の順番でスキャンを行い,それぞれを1次元ベクトルとして扱います.これらの2つベクトルは,同じサイズ,同じ型でなければいけません.行列が2つ以上のチャンネルを持つ場合,各チャンネルでの内積が足し合わされます.
指定されたサイズ,型の零行列を返します.
パラメタ: |
|
---|
このメソッドは,Matlab 形式の零行列イニシャライザを返します.これは,定数行列を作成する簡単な書式で,行列を,関数のパラメータ,matrix expression の一部,行列イニシャライザとして利用する場合に用いることができます.
Mat A;
A = Mat::zeros(3, 3, CV_32F);
上述のサンプルでは, A が 3x3 浮動小数点型行列ではない場合にのみ,新しい行列が確保されます.それ以外の場合は,既に存在する行列 A が0で埋められます.
指定されたサイズ,型の,すべての要素が1である行列を返します.
パラメタ: |
|
---|
このメソッドは Mat::zeros() と同様に,Matlab 形式の,1で埋められた行列イニシャライザを返します.このメソッドを利用する場合,以下の Matlab 的表現を用いると,行列を任意の値で初期化できることに注意してください:
Mat A = Mat::ones(100, 100, CV_8U)*3; // 3 で埋められた 100x100 行列
上述の処理は 1で埋められた 100x100 行列を作成して,それを3倍するわけではありません.スケールファクタ(この場合は3)を念頭に置き,行列イニシャライザを拡張する場合にはこれを利用してください.
指定されたサイズ,型の単位行列を返します.
パラメタ: |
|
---|
このメソッドは Mat::zeros() と同様に,Matlab 形式の単位行列イニシャライザを返します.このメソッドを利用する場合,必要なスケールファクタを掛けることで,定数倍された単位行列で行列を初期化できることに注意してください.
// 対角要素が 0.1 である 4x4 の対角行列を作成します.
Mat A = Mat::eye(4, 4, CV_32F)*0.1;
そして,これも非常に効率的に O(1) の処理を行います.
必要ならば,新しい行列データを確保します.
パラメタ: |
|
---|
これは, Mat のキーメソッドの1つです.行列を作成する関数やメソッドで,OpenCV の新しい形式のものの多くは,各出力行列毎にこのメソッドを呼び出します.このメソッドのアルゴリズムは,次のようになります:
ロバストかつ効率的なメモリ管理を同時に行うこのような機構は,ユーザのタイプ量をかなり削減します.つまり通常は,出力行列を明示的に確保する必要はない,とうことです.
このメソッドは,行列データに関連付けられた参照カウンタをインクリメントします.行列ヘッダが外部データを指し示している場合( Mat::Mat() を参照)は,参照カウンタは NULL です.その場合,このメソッドは何もしません.メモリリークを避けるため,通常このメソッドは明示的に呼ばれるべきではありません.行列の代入演算子においては,内部的に呼び出されています.また,参照カウンタの増加は,それをサポートするプラットフォーム上では不可分な操作なので,異なるスレッドで同じ行列を非同期に処理しても安全です.
このメソッドは,行列データに関連付けられた参照カウンタをデクリメントします.参照カウンタが0になると,行列データは解放され,そのデータと参照カウンタのポインタは NULL にセットされます.行列ヘッダが外部データを指し示している場合( Mat::Mat() を参照)は,参照カウンタは NULL です.その場合,このメソッドは何もしません.
行列データを強制的に解放するために,このメソッドを手動で呼び出すことができます.しかし,デストラクタ,そしてデータポインタを変更するあらゆるメソッドは自動的にこのメソッドを呼び出すので,通常その必要はありません.参照カウンタの減少とそれが0に到達したかどうかのチェックは,それをサポートするプラットフォーム上では不可分な操作なので,異なるスレッドで同じ行列を非同期に処理しても安全です.
親行列の内部の行列ヘッダを見つけます.
パラメタ: |
|
---|
Mat::row() , Mat::col() , Mat::rowRange() , Mat::colRange() などを利用して行列から部分行列を抽出した後,結果として得られる部分行列は元の大きな行列の一部を指し示すだけのものです.しかし,それぞれの部分行列は,( datastart と dataend フィールドによって表される)情報を持っています.これを用いることで,元の行列サイズと,抽出された部分行列が元の行列のどこに位置していたか,を復元できます.このメソッド locateROI は,まさにそれを行うものです.
親行列内の部分行列のサイズと位置を調整します.
パラメタ: |
|
---|
このメソッドは, Mat::locateROI() を補足するものです.実際,これらの関数の典型的な用法は,親行列内での部分行列の位置を検出して移動させる,というものです.ROI の外側のピクセルを考慮しなければならないフィルタリング処理は,このような処理が必要になる典型的な場合です.メソッドのすべてのパラメータは正値であり,これは,ROI がすべての方向に指定され量だけ増加しなければいけない,ということを意味します.つまり,以下の処理の場合:
A.adjustROI(2, 2, 2, 2);
これは,各方向に4要素分行列サイズを増加させ,それを2要素分だけ左と上に移動させます.これによって, 5x5 のカーネルでフィルタリングを行うために必要なすべてのピクセルを引き込むことになります.
adjustROI が親行列の境界を越えないようにするのはユーザの責任です.越えてしまった場合,この関数はエラーを発生させます.
この関数は, filter2D() やモルフォロジー演算などの,OpenCV のフィルタリング関数の内部で利用されています.
copyMakeBorder() も参照してください.
矩形部分行列を抽出します.
パラメタ: |
|
---|
この演算子は, *this 内部の指定された部分行列に対する新しいヘッダを作成します.これらは, Mat::row() , Mat::col() , Mat::rowRange() そして Mat::colRange() の最も一般化された形式です.例えば, A(Range(0, 10), Range::all()) は, A.rowRange(0, 10) と等価です.上述したものと同様に,この演算子も O(1) の処理であり,データはコピーされません.
この演算子は,内部データをコピーせずに,行列に対する CvMat ヘッダを作成します.この処理では,参照カウンタは考慮されないので, CvMat ヘッダが利用されている間は,元の行列が解放されないようにしなければいけません.この演算子は,OpenCV の新旧の API を混合して利用している場合に役立ちます.例えば:
Mat img(Size(320, 240), CV_8UC3);
...
CvMat cvimg = img;
my_old_cv_func( &cvimg, ...);
ここで my_old_cv_func は,OpenCV 1.x のデータ構造を処理するための何らかの関数を表します.
この演算子は,内部データをコピーせずに,行列に対する IplImage ヘッダを作成します. IplImage ヘッダが利用されている間は,元の行列が解放されないようにしなければいけません. Mat::operator CvMat と同様に,この演算子は,OpenCV の新旧の API を混合して利用している場合に役立ちます.
このメソッドは,行列の要素が連続して格納されていれば,つまり,各行の最後にギャップが存在しなければ true を返し,そうでなければ false を返します.明らかに, 1x1 や 1xN の行列は常に連続です. Mat::create() によって作成された行列は常に連続ですが, Mat::col() , Mat::diag() などによって部分行列が抜き出された場合,外部データに対して行列ヘッダが作成された場合など,そのような行列は連続とは限りません.
この連続性フラグは, Mat::flags フィールド内の1ビットとして保存され,行列ヘッダを作成する際に自動的に計算されます.従って,連続性のチェックは非常に高速であり,理論的には,以下のような処理になります:
// Mat::isContinuous() の別の実装
bool myCheckMatContinuity(const Mat& m)
{
//(m.flags & Mat::CONTINUOUS_FLAG) != 0; を返します
return m.rows == 1 || m.step == m.cols*m.elemSize();
}
このメソッドは,非常に多くの OpenCV の関数で利用されており,同様にユーザも自由に利用できるものです.(算術演算,論理演算,数学関数,アルファブレンディング,色空間変換などの)要素毎の処理は,画像の幾何的特徴に依存しないので,すべての入出力配列が連続であれば,この関数は,それらを非常に長い1行のベクトルとして扱うことができる,という事が重要な部分です.ここでは,アルファブレンディング関数の実装方法を示します.
template<typename T>
void alphaBlendRGBA(const Mat& src1, const Mat& src2, Mat& dst)
{
const float alpha_scale = (float)std::numeric_limits<T>::max(),
inv_scale = 1.f/alpha_scale;
CV_Assert( src1.type() == src2.type() &&
src1.type() == CV_MAKETYPE(DataType<T>::depth, 4) &&
src1.size() == src2.size());
Size size = src1.size();
dst.create(size, src1.type());
// 配列の連続性チェックを行い,
// 連続だった場合,
// 配列を1次元ベクトルとして扱います.
if( src1.isContinuous() && src2.isContinuous() && dst.isContinuous() )
{
size.width *= size.height;
size.height = 1;
}
size.width *= 4;
for( int i = 0; i < size.height; i++ )
{
// 配列が連続の場合,
// 外側のループは1度しか実行されません.
const T* ptr1 = src1.ptr<T>(i);
const T* ptr2 = src2.ptr<T>(i);
T* dptr = dst.ptr<T>(i);
for( int j = 0; j < size.width; j += 4 )
{
float alpha = ptr1[j+3]*inv_scale, beta = ptr2[j+3]*inv_scale;
dptr[j] = saturate_cast<T>(ptr1[j]*alpha + ptr2[j]*beta);
dptr[j+1] = saturate_cast<T>(ptr1[j+1]*alpha + ptr2[j+1]*beta);
dptr[j+2] = saturate_cast<T>(ptr1[j+2]*alpha + ptr2[j+2]*beta);
dptr[j+3] = saturate_cast<T>((1 - (1-alpha)*(1-beta))*alpha_scale);
}
}
}
この技は非常に単純ですが,単純な要素処理を 10-20 パーセント高速化できます.特に,画像が比較的小さく,処理が非常に単純な場合に有効です.
そして,この関数では OpenCV のもう一つの慣用的手法を用いていることに注意してください.つまり,出力配列が適切なサイズと型であるかをチェックする代わりに Mat::create() を呼び出しています.また,新たに確保された配列は常に連続ですが, create() が常に新しい行列を確保するとは限らないので,出力配列をチェックします.
このメソッドは,行列の要素サイズをバイト単位で返します.例えば,行列の型が CV_16SC3 である場合,このメソッドは 3*sizeof(short) または 6 を返します.
このメソッドは,行列要素のチャンネル毎のサイズをバイト単位で返します.つまり,チャンネル数が無視されます.例えば,行列の型が CV_16SC3 である場合,このメソッドは sizeof(short) または 2 を返します.
このメソッドは, CV_16SC3 または 16-ビット符号あり整数,3チャンネル配列 などの CvMat の型システムと互換性のある行列要素の型を ID で返します.
このメソッドは,行列要素のビット深度 ID ,つまり,各チャンネル毎の型を返します.例えば,16-ビット符号あり整数,3チャンネル配列の場合,これは CV_16S を返します.行列の型の完全なリストは以下のとおりです:
このメソッドは,行列のステップを Mat::elemSize1() で割った値を返します.これは,行列の任意の要素に高速にアクセスするのに役立ちます.
このメソッドは,行列データが NULL ポインタである場合にのみ true を返します.このメソッドは,STL vector との親和性を向上させるために導入されました.
行列の指定された行へのポインタを返します.
Parameter: | i – 0基準の行インデックス |
---|
このメソッドは,行列の指定行を指す uchar* または型付きポインタを返します.これらのメソッドの使用法の知るために, Mat::isContinuous() () 項にあるサンプルを参照してください.
行列の指定された要素への参照を返します.
パラメタ: |
|
---|
このテンプレートメソッドは,行列の指定要素への参照を返します.パフォーマンス向上のために,インデックスの範囲チェックは Debug モードでのみ行われます.
ここでは例として, 様々な数値アルゴリズムに対する standard poor-conditioned test matrices を, Mat::at メソッドを用いて作成する方法を示します.
Mat H(100, 100, CV_64F);
for(int i = 0; i < H.rows; i++)
for(int j = 0; j < H.cols; j++)
H.at<double>(i,j)=1./(i+j+1);
このメソッドは,読み込み専用,または読み書き可能なイテレータを返します.行列イテレータの利用法は, STL の双方向イテレータの利用法に非常によく似ています.ここでは,行列イテレータを用いて書かれたアルファブレンディング関数を紹介します:
template<typename T>
void alphaBlendRGBA(const Mat& src1, const Mat& src2, Mat& dst)
{
typedef Vec<T, 4> VT;
const float alpha_scale = (float)std::numeric_limits<T>::max(),
inv_scale = 1.f/alpha_scale;
CV_Assert( src1.type() == src2.type() &&
src1.type() == DataType<VT>::type &&
src1.size() == src2.size());
Size size = src1.size();
dst.create(size, src1.type());
MatConstIterator_<VT> it1 = src1.begin<VT>(), it1_end = src1.end<VT>();
MatConstIterator_<VT> it2 = src2.begin<VT>();
MatIterator_<VT> dst_it = dst.begin<VT>();
for( ; it1 != it1_end; ++it1, ++it2, ++dst_it )
{
VT pix1 = *it1, pix2 = *it2;
float alpha = pix1[3]*inv_scale, beta = pix2[3]*inv_scale;
*dst_it = VT(saturate_cast<T>(pix1[0]*alpha + pix2[0]*beta),
saturate_cast<T>(pix1[1]*alpha + pix2[1]*beta),
saturate_cast<T>(pix1[2]*alpha + pix2[2]*beta),
saturate_cast<T>((1 - (1-alpha)*(1-beta))*alpha_scale));
}
}
このメソッドは,最後の行列要素の後ろを指すようにセットされた,読み込み専用,または読み書き可能なイテレータを返します.
Mat() から派生する行列クラステンプレート.
template<typename _Tp> class Mat_ : public Mat
{
public:
typedef _Tp value_type;
typedef typename DataType<_Tp>::channel_type channel_type;
typedef MatIterator_<_Tp> iterator;
typedef MatConstIterator_<_Tp> const_iterator;
Mat_();
// Mat(_rows, _cols, DataType<_Tp>::type) と等価
Mat_(int _rows, int _cols);
// 上記コンストラクタの別の表現
Mat_(int _rows, int _cols, const _Tp& value);
explicit Mat_(Size _size);
Mat_(Size _size, const _Tp& value);
// コピー / 変換コンストラクタ. m が異なる型ならば,変換されます.
Mat_(const Mat& m);
// コピーコンストラクタ
Mat_(const Mat_& m);
// ユーザが確保したデータを持つ行列を作成します.
// step は型に関係なくバイト単位(!!)
Mat_(int _rows, int _cols, _Tp* _data, size_t _step=AUTO_STEP);
// 小行列式の選択
Mat_(const Mat_& m, const Range& rowRange, const Range& colRange);
Mat_(const Mat_& m, const Rect& roi);
// 複雑な matrix expression をサポートするため
Mat_(const MatExpr_Base& expr);
// Vec や std::vector から 1 列だけの行列を作成します.
template<int n> explicit Mat_(const Vec<_Tp, n>& vec);
Mat_(const vector<_Tp>& vec, bool copyData=false);
Mat_& operator = (const Mat& m);
Mat_& operator = (const Mat_& m);
// 全ての要素を s にセットします.
Mat_& operator = (const _Tp& s);
// 行末のギャップをスキップする優れたイテレータ
iterator begin();
iterator end();
const_iterator begin() const;
const_iterator end() const;
// Mat::create(_rows, _cols, DataType<_Tp>::type) と等価
void create(int _rows, int _cols);
void create(Size _size);
// 外積
Mat_ cross(const Mat_& m) const;
// 複雑な matrix expression をサポートするため
Mat_& operator = (const MatExpr_Base& expr);
// データ型の変換
template<typename T2> operator Mat_<T2>() const;
// Mat::row() などのオーバーライド
Mat_ row(int y) const;
Mat_ col(int x) const;
Mat_ diag(int d=0) const;
Mat_ clone() const;
// 転置,逆行列,要素同士の掛け算
MatExpr_<...> t() const;
MatExpr_<...> inv(int method=DECOMP_LU) const;
MatExpr_<...> mul(const Mat_& m, double scale=1) const;
MatExpr_<...> mul(const MatExpr_<...>& m, double scale=1) const;
// Mat::elemSize() などのオーバーライド
size_t elemSize() const;
size_t elemSize1() const;
int type() const;
int depth() const;
int channels() const;
size_t step1() const;
// step()/sizeof(_Tp) を返します.
size_t stepT() const;
// Mat::zeros() などのオーバーライド.もちろん,データ型の指定は省略されます.
static MatExpr_Initializer zeros(int rows, int cols);
static MatExpr_Initializer zeros(Size size);
static MatExpr_Initializer ones(int rows, int cols);
static MatExpr_Initializer ones(Size size);
static MatExpr_Initializer eye(int rows, int cols);
static MatExpr_Initializer eye(Size size);
// その他のオーバーライドメソッド
Mat_ reshape(int _rows) const;
Mat_& adjustROI( int dtop, int dbottom, int dleft, int dright );
Mat_ operator()( const Range& rowRange, const Range& colRange ) const;
Mat_ operator()( const Rect& roi ) const;
// 行と要素へのアクセス演算子の,より便利な形式
_Tp* operator [](int y);
const _Tp* operator [](int y) const;
_Tp& operator ()(int row, int col);
const _Tp& operator ()(int row, int col) const;
_Tp& operator ()(Point pt);
const _Tp& operator ()(Point pt) const;
// 複雑な matrix expression をサポートするため
operator MatExpr_<Mat_, Mat_>() const;
// ベクトルへの変換
operator vector<_Tp>() const;
};
Mat_<_Tp> クラスは, Mat クラスに被せる「薄い」テンプレートラッパーです.追加のデータフィールドは一切なく,これも Mat も仮想メソッドをまったく持たないので,これら2つのクラスへの参照やポインタは,互いに自由に変換できます.しかし,そうする場合には注意が必要です.例えば:
// 100x100 , 8 ビットの行列を作成する
Mat M(100,100,CV_8U);
// これは,コンパイル可能です.データ変換は行われません.
Mat_<float>& M1 = (Mat_<float>&)M;
// プログラムは,以下の文でクラッシュします.
M1(99,99) = 1.f;
大抵の場合 Mat で十分ですが,要素アクセス演算子をたくさん利用し,コンパイル時に行列の型が分かっている場合は, Mat_ の方がより便利です. Mat::at<_Tp>(int y, int x) および Mat_<_Tp>::operator ()(int y, int x) は完全に等価で同じ速度で動作しますが,後者の方が確実に短く書けることに注意してください:
Mat_<double> M(20,20);
for(int i = 0; i < M.rows; i++)
for(int j = 0; j < M.cols; j++)
M(i,j) = 1./(i+j+1);
Mat E, V;
eigen(M,E,V);
cout << E.at<double>(0,0)/E.at<double>(M.rows-1,0);
マルチチャンネルの画像/行列の場合は ``Mat_`` をどうやって使うのでしょうか? これは簡単 - Vec を Mat_ のパラメータとして渡すだけです:
// 320x240 のカラー画像を確保して,( RGB 空間の)緑色で埋めます.
Mat_<Vec3b> img(240, 320, Vec3b(0,255,0));
// 対角線上に白い線を描きます.
for(int i = 0; i < 100; i++)
img(i,i)=Vec3b(255,255,255);
// 各ピクセルの 2 番目(赤)のチャンネルをスクランブルします.
for(int i = 0; i < img.rows; i++)
for(int j = 0; j < img.cols; j++)
img(i,j)[2] ^= (uchar)(i ^ j);
n次元の密な配列.
class MatND
{
public:
// デフォルトコンストラクタ
MatND();
// 指定されたサイズとデータ型の配列を作成します.
MatND(int _ndims, const int* _sizes, int _type);
// 配列を作成して,指定された値で埋めます.
MatND(int _ndims, const int* _sizes, int _type, const Scalar& _s);
// コピーコンストラクタ.ヘッダのみがコピーされます.
MatND(const MatND& m);
// 部分配列の選択.ヘッダのみがコピーされます.
MatND(const MatND& m, const Range* ranges);
// 古い形式の ND配列 を MatND に変換します.オプションでデータをコピーできます.
MatND(const CvMatND* m, bool copyData=false);
~MatND();
MatND& operator = (const MatND& m);
// 行列の完全なコピーを作成します(すべてのデータがコピーされます).
MatND clone() const;
// 部分配列の選択.ヘッダのみがコピーされます.
MatND operator()(const Range* ranges) const;
// データを他の行列にコピーします.
// データをコピーする前に,
// m.create(this->size(), this->type()) を呼びます.
void copyTo( MatND& m ) const;
// 選択された要素のみを別の行列にコピーします.
void copyTo( MatND& m, const MatND& mask ) const;
// データを指定されたデータ型に変換します.
// 変換の前に m.create(this->size(), rtype) を呼びます.
void convertTo( MatND& m, int rtype, double alpha=1, double beta=0 ) const;
// 各配列要素に "s" を割り当てます.
MatND& operator = (const Scalar& s);
// 選択された配列要素に "s" を割り当てます.
// ( mask==MatND() の場合はすべての要素)
MatND& setTo(const Scalar& s, const MatND& mask=MatND());
// データをコピーせずに配列の形状を変えます.
MatND reshape(int _newcn, int _newndims=0, const int* _newsz=0) const;
// 現在の配列のサイズと型が,指定されたものと一致しない場合のみ,
// データ用の新しいバッファを確保します.
void create(int _ndims, const int* _sizes, int _type);
// 参照カウンタを手動でインクリメントします(注意して使用してください!!!)
void addref();
// 参照カウンタをデクリメントします.
// カウンタが 0 になったときにデータが解放されます.
void release();
// 行列を 2 次元の Mat あるいは 古い形式の CvMatND に変換します.
// どちらの場合も,データはコピーされません.
operator Mat() const;
operator CvMatND() const;
// 配列データが連続して格納されている場合に true を返します.
bool isContinuous() const;
// バイト単位で表された各要素サイズを返します.
size_t elemSize() const;
// バイト単位で表されたチャンネル毎の要素サイズを返します.
size_t elemSize1() const;
// OpenCV のデータ型 ID(CV_8UC1, ... CV_64FC4,...) を返します.
int type() const;
// ビット深度 (CV_8U ... CV_64F) を返します.
int depth() const;
// チャンネル数を返します.
int channels() const;
// step1() ~ step()/elemSize1()
size_t step1(int i) const;
// 要素へのポインタを返します( 1D, 2D, 3D, そして一般的な nD 版).
uchar* ptr(int i0);
const uchar* ptr(int i0) const;
uchar* ptr(int i0, int i1);
const uchar* ptr(int i0, int i1) const;
uchar* ptr(int i0, int i1, int i2);
const uchar* ptr(int i0, int i1, int i2) const;
uchar* ptr(const int* idx);
const uchar* ptr(const int* idx) const;
// 要素アクセスのための便利なテンプレートメソッド.
// _Tp は,実際の要素型と一致しなければならないことに注意してください -
// この関数は,処理中にいかなる型の変換も行われません.
template<typename _Tp> _Tp& at(int i0);
template<typename _Tp> const _Tp& at(int i0) const;
template<typename _Tp> _Tp& at(int i0, int i1);
template<typename _Tp> const _Tp& at(int i0, int i1) const;
template<typename _Tp> _Tp& at(int i0, int i1, int i2);
template<typename _Tp> const _Tp& at(int i0, int i1, int i2) const;
template<typename _Tp> _Tp& at(const int* idx);
template<typename _Tp> const _Tp& at(const int* idx) const;
enum { MAGIC_VAL=0x42FE0000, AUTO_STEP=-1,
CONTINUOUS_FLAG=CV_MAT_CONT_FLAG, MAX_DIM=CV_MAX_DIM };
// データ型,連続性フラグ,シグネチャ(マジックナンバー)の組み合わせ
int flags;
// 配列の次元
int dims;
// データの参照カウンタ
int* refcount;
// データへのポインタ
uchar* data;
// そして,データの実際の開始点と終了点
uchar* datastart;
uchar* dataend;
// 各次元の step と size ,最大で MAX_DIM
int size[MAX_DIM];
size_t step[MAX_DIM];
};
MatND クラスは,シングルチャンネル,あるいはマルチチャンネルで, n 次元の,密な数値配列を表します.これは,多次元ヒストグラム(極端に疎ではない場合.疎な場合は SparseMat の方が適しています),ボクセルボリューム,スタックモーションフィールドなどを表現するのに便利です.行列 のデータレイアウトは, M.step[] の配列によって定義され,要素 (ここで )のアドレスが次のように計算されます:
これは Mat() の場合の式を size[0] rows , size[1] cols とした,より一般的(高次元)な形です. step[0] は単に step と呼ばれ, step[1] は全く保存されませんが Mat::elemSize() として計算できます.
見方を変えれば MatND も Mat に非常に似てはいますが,以下ような制限と違いが存在します:
ここで,カラーの 8 ビット画像の NxNxN のヒストグラムを求めるために MatND をどのように利用するかを示します(つまり,各チャンネルの値は 0 から 255 までの範囲に分布しており,これを 0 から N-1 の範囲の値に量子化します):
void computeColorHist(const Mat& image, MatND& hist, int N)
{
const int histSize[] = {N, N, N};
// ヒストグラムが適切なサイズと型を確実にもつように.
hist.create(3, histSize, CV_32F);
// そして,それをクリアします.
hist = Scalar(0);
// 以下のループは,画像が 8 ビット 3 チャンネルであることを
// 仮定しているので,まずそれをチェックします.
CV_Assert(image.type() == CV_8UC3);
MatConstIterator_<Vec3b> it = image.begin<Vec3b>(),
it_end = image.end<Vec3b>();
for( ; it != it_end; ++it )
{
const Vec3b& pix = *it;
// セルの増加量 1.f の代わりに 1.f/(image.rows*image.cols)
// を利用して,ヒストグラムを正規化することもできたかも.
hist.at<float>(pix[0]*N/256, pix[1]*N/256, pix[2]*N/256) += 1.f;
}
}
つぎに, MatND の要素を反復処理で横断する方法を示します:
void normalizeColorHist(MatND& hist)
{
#if 1
// イテレータを初期化します(この形式は STL とは異なります).
// 初期化後のイテレータは,これから横断する
// スライス数 or 平面数を含みます.
MatNDIterator it(hist);
double s = 0;
// 行列を反復処理で横断します.各反復において,
// it.planes[*] (of type Mat) は,現在の平面の集合です.
for(int p = 0; p < it.nplanes; p++, ++it)
s += sum(it.planes[0])[0];
it = MatNDIterator(hist);
s = 1./s;
for(int p = 0; p < it.nplanes; p++, ++it)
it.planes[0] *= s;
#elif 1
// こちらは,MatND のビルトイン処理を利用した,
// 上記のものより短い実装です.
double s = sum(hist)[0];
hist.convertTo(hist, hist.type(), 1./s, 0);
#else
// そして,さらに短いバージョン.
// (ヒストグラムの要素が非負であることを仮定しています)
normalize(hist, hist, 1, 0, NORM_L1);
#endif
}
複数の行列が同じ形状(次元数とすべての次元サイズが等しい状態)である限り,同時に横断することができます.これは,この様な行列に対する 2 要素のあるいは n 要素の比較処理において役立ちます.そのためには,これらの行列を MatNDIterator に渡してください.すると,反復処理が行われる間, it.planes[0] , it.planes[1] , ... は,対応する行列のスライスを表します.
MatND() から派生した,n次元の密な配列のクラステンプレート.
template<typename _Tp> class MatND_ : public MatND
{
public:
typedef _Tp value_type;
typedef typename DataType<_Tp>::channel_type channel_type;
// 様々なコンストラクタ. MatND のものと同じですが,型の指定は省略されます.
MatND_();
MatND_(int dims, const int* _sizes);
MatND_(int dims, const int* _sizes, const _Tp& _s);
MatND_(const MatND& m);
MatND_(const MatND_& m);
MatND_(const MatND_& m, const Range* ranges);
MatND_(const CvMatND* m, bool copyData=false);
MatND_& operator = (const MatND& m);
MatND_& operator = (const MatND_& m);
// 別の初期化関数
// Scalar の代わりに _Tp をとります.
MatND_& operator = (const _Tp& s);
// 特別なデストラクタは不要です. MatND のものを利用します.
void create(int dims, const int* _sizes);
template<typename T2> operator MatND_<T2>() const;
MatND_ clone() const;
MatND_ operator()(const Range* ranges) const;
size_t elemSize() const;
size_t elemSize1() const;
int type() const;
int depth() const;
int channels() const;
// step[i]/elemSize()
size_t stepT(int i) const;
size_t step1(int i) const;
// MatND::at<_Tp> よりも短く書ける代替手段
_Tp& operator ()(const int* idx);
const _Tp& operator ()(const int* idx) const;
_Tp& operator ()(int idx0);
const _Tp& operator ()(int idx0) const;
_Tp& operator ()(int idx0, int idx1);
const _Tp& operator ()(int idx0, int idx1) const;
_Tp& operator ()(int idx0, int idx1, int idx2);
const _Tp& operator ()(int idx0, int idx1, int idx2) const;
_Tp& operator ()(int idx0, int idx1, int idx2);
const _Tp& operator ()(int idx0, int idx1, int idx2) const;
};
MatND_ と MatND の関係は, Mat_ と Mat の関係とほぼ同じです - つまり,多少便利な要素アクセス演算子を提供するだけで,基底クラスに対して仮想メソッドのメンバを追加したりはしません.従って, MatND_ と MatND への参照/ポインタは,容易に相互変換できます.例えば:
// 前述のヒストグラムを計算するループの別のバージョン
...
CV_Assert(hist.type() == CV_32FC1);
MatND_<float>& _hist = (MatND_<float>&)hist;
for( ; it != it_end; ++it )
{
const Vec3b& pix = *it;
_hist(pix[0]*N/256, pix[1]*N/256, pix[2]*N/256) += 1.f;
}
...
n次元の疎な配列.
class SparseMat
{
public:
typedef SparseMatIterator iterator;
typedef SparseMatConstIterator const_iterator;
// 内部構造体 - 疎な行列のヘッダ
struct Hdr
{
...
};
// 疎な行列のノード - ハッシュテーブルの要素
struct Node
{
size_t hashval;
size_t next;
int idx[CV_MAX_DIM];
};
////////// コンストラクタとデストラクタ //////////
// デフォルトコンストラクタ
SparseMat();
// 指定されたサイズと型の行列を作成します.
SparseMat(int dims, const int* _sizes, int _type);
// コピーコンストラクタ
SparseMat(const SparseMat& m);
// 2 次元の密な行列を,疎な形に変換します.
// try1d が true かつ,行列が 1 列の場合 (Nx1) は,
// 疎な行列も 1 次元になります.
SparseMat(const Mat& m, bool try1d=false);
// n 次元の密な行列を,疎な形に変換します.
SparseMat(const MatND& m);
// 古い形式の疎な行列を,新しい形式に変換します.
// 変換後に "m" を安全に解放できるように,
// すべてのデータがコピーされます.
SparseMat(const CvSparseMat* m);
// デストラクタ
~SparseMat();
///////// 代入演算子 ///////////
// これは O(1) の処理.データはコピーされません.
SparseMat& operator = (const SparseMat& m);
// ( try1d=false の場合のコンストラクタと等価)
SparseMat& operator = (const Mat& m);
SparseMat& operator = (const MatND& m);
// 行列の完全なコピーを作成します.
SparseMat clone() const;
// 出力行列にすべてのデータをコピーします.
// 必要ならば,出力行列は再割り当てされます.
void copyTo( SparseMat& m ) const;
// 1 次元あるいは 2 次元の疎な行列を, 2 次元の密な行列に変換します.
// 疎な行列が 1 次元である場合,結果として得られる密な行列は,
// 1 列の行列になります.
void copyTo( Mat& m ) const;
// 任意の疎な行列を密な行列に変換します.
// 使用メモリに注意してください!
void copyTo( MatND& m ) const;
// 行列のすべての要素に,指定したスカラを掛けます.
void convertTo( SparseMat& m, int rtype, double alpha=1 ) const;
// 疎な行列を,密な行列に変換します.型の変換やスケーリングなども可能です.
// rtype=-1 の場合,出力される密な行列の要素型は,
// 元の疎な行列の要素型と同じになります.
// それ以外の場合,ビット深度は rtype で指定された値に,
// チャンネル数は,疎な行列のものと同じになります.
void convertTo( Mat& m, int rtype, double alpha=1, double beta=0 ) const;
void convertTo( MatND& m, int rtype, double alpha=1, double beta=0 ) const;
// 今は利用されません.
void assignTo( SparseMat& m, int type=-1 ) const;
// 疎な行列を再割り当てします.既に適切なサイズと型の行列である場合,
// 単に clear() によってデータがクリアされます.そうでない場合,
// 古い行列は( release() によって)解放され,新しい行列が確保されます.
void create(int dims, const int* _sizes, int _type);
// すべての行列要素を 0 にします.これは,ハッシュテーブルのクリアを意味します.
void clear();
// ヘッダの参照カウンタを手動でインクリメントします.
void addref();
// ヘッダの参照カウンタをデクリメントします.カウンタが 0 になると
// ヘッダとすべての内部データが解放されます.
void release();
// 疎な行列を古い形式の表現に変換します.
// すべての要素がコピーされます.
operator CvSparseMat*() const;
// バイト単位で表された各要素サイズ
// (要素のインデックスや SparseMat::Node 要素が存在するので,
// 行列のノードサイズはもっと大きくなります)
size_t elemSize() const;
// elemSize()/channels()
size_t elemSize1() const;
// Mat や MatND のものと同じです.
int type() const;
int depth() const;
int channels() const;
// サイズの配列を返し,行列が確保されていない場合は 0 を返します.
const int* size() const;
// i 番目のサイズを返します(あるいは 0 ).
int size(int i) const;
// 行列の次元数を返します.
int dims() const;
// 0 ではない要素の個数を返します.
size_t nzcount() const;
// 要素のインデックスから,要素のハッシュ値を計算します:
// 1D の場合
size_t hash(int i0) const;
// 2D の場合
size_t hash(int i0, int i1) const;
// 3D の場合
size_t hash(int i0, int i1, int i2) const;
// n-D の場合
size_t hash(const int* idx) const;
// 低レベルな要素アクセス関数:
// 1D, 2D, 3D の場合の特別版と, n-D の場合の汎用版があります.
//
// 行列要素へのポインタを返します.
// 要素が存在する( 0 ではない)場合,そのポインタが返されます.
// 要素が存在せず createMissing=false の場合, NULL ポインタが返されます.
// 要素が存在せず createMissing=true の場合,新しい要素が作成されて
// 0 で初期化されます.そして,そのポインタが返されます.
// hashval ポインタが NULL でない場合,要素のハッシュ値は計算されずに,
// *hashval が代わりに利用されます.
uchar* ptr(int i0, bool createMissing, size_t* hashval=0);
uchar* ptr(int i0, int i1, bool createMissing, size_t* hashval=0);
uchar* ptr(int i0, int i1, int i2, bool createMissing, size_t* hashval=0);
uchar* ptr(const int* idx, bool createMissing, size_t* hashval=0);
// 高レベルな要素アクセス関数:
// ref<_Tp>(i0,...[,hashval]) - *(_Tp*)ptr(i0,...true[,hashval]) と等価.
// 常に要素への有効な参照を返します.
// 要素が存在しない場合は,新たに作成します.
// find<_Tp>(i0,...[,hashval]) - (_const Tp*)ptr(i0,...false[,hashval]) と等価.
// 要素が存在すればそのポインタ,存在しなければ NULL を返します.
// value<_Tp>(i0,...[,hashval]) -
// { const _Tp* p = find<_Tp>(i0,...[,hashval]); return p ? *p : _Tp(); } と等価.
// つまり, 要素が存在しなければ 0 を返します.
// _Tp は,実際の要素型と一致しなければならないことに注意してください -
// この関数は,処理中にいかなる型の変換も行いません.
// 1D の場合
template<typename _Tp> _Tp& ref(int i0, size_t* hashval=0);
template<typename _Tp> _Tp value(int i0, size_t* hashval=0) const;
template<typename _Tp> const _Tp* find(int i0, size_t* hashval=0) const;
// 2D の場合
template<typename _Tp> _Tp& ref(int i0, int i1, size_t* hashval=0);
template<typename _Tp> _Tp value(int i0, int i1, size_t* hashval=0) const;
template<typename _Tp> const _Tp* find(int i0, int i1, size_t* hashval=0) const;
// 3D の場合
template<typename _Tp> _Tp& ref(int i0, int i1, int i2, size_t* hashval=0);
template<typename _Tp> _Tp value(int i0, int i1, int i2, size_t* hashval=0) const;
template<typename _Tp> const _Tp* find(int i0, int i1, int i2, size_t* hashval=0) const;
// n-D の場合
template<typename _Tp> _Tp& ref(const int* idx, size_t* hashval=0);
template<typename _Tp> _Tp value(const int* idx, size_t* hashval=0) const;
template<typename _Tp> const _Tp* find(const int* idx, size_t* hashval=0) const;
// 指定された行列要素を削除します.
// 要素が存在しない場合は何もしません.
void erase(int i0, int i1, size_t* hashval=0);
void erase(int i0, int i1, int i2, size_t* hashval=0);
void erase(const int* idx, size_t* hashval=0);
// 行列のイテレータを返します.
// これは,疎な行列の最初の要素を指すか,
SparseMatIterator begin();
SparseMatConstIterator begin() const;
// ... あるいは,最後の要素の後ろを指します.
SparseMatIterator end();
SparseMatConstIterator end() const;
// 上述のメソッドのテンプレート版
// _Tp は,実際の行列型と一致しなければいけません.
template<typename _Tp> SparseMatIterator_<_Tp> begin();
template<typename _Tp> SparseMatConstIterator_<_Tp> begin() const;
template<typename _Tp> SparseMatIterator_<_Tp> end();
template<typename _Tp> SparseMatConstIterator_<_Tp> end() const;
// 疎な行列のノードに格納されている値を返します.
template<typename _Tp> _Tp& value(Node* n);
template<typename _Tp> const _Tp& value(const Node* n) const;
////////////// 内部で利用されるメソッド ///////////////
...
// 疎な行列のヘッダへのポインタ
Hdr* hdr;
};
SparseMat クラスは,多次元の疎な数値配列を表します.この疎な配列は, Mat() や MatND() が格納できるあらゆる型の要素を格納できます.「疎な」というのは,0 ではない要素だけが格納されていることを意味します(しかし,疎な行列を処理した結果,その行列の要素が実際には 0 になる可能性はあります.そのような要素を検出して, SparseMat::erase を用いて削除するかどうかはユーザ次第です).0 ではない要素は,ハッシュテーブルに格納されます.また,(要素が存在する・しないに関係なく)平均探索時間が O(1) となるように,多くの値が埋められるとハッシュテーブルは拡張されます.要素には以下のメソッドを利用してアクセスできます:
クエリ操作( SparseMat::ptr と,高レベル関数 SparseMat::ref , SparseMat::value , SparseMat::find ),つまり:
const int dims = 5;
int size[] = {10, 10, 10, 10, 10};
SparseMat sparse_mat(dims, size, CV_32F);
for(int i = 0; i < 1000; i++)
{
int idx[dims];
for(int k = 0; k < dims; k++)
idx[k] = rand()
sparse_mat.ref<float>(idx) += 1.f;
}
疎な行列のイテレータ. Mat() のイテレータと同様に,また MatND() のイテレータとは異なり,疎な行列のイテレータは STL 形式であり,この反復ループは C++ ユーザにとって馴染みあるものでしょう:
// 疎な浮動小数点型行列の要素と,
// その要素の合計を表示します.
SparseMatConstIterator_<float>
it = sparse_mat.begin<float>(),
it_end = sparse_mat.end<float>();
double s = 0;
int dims = sparse_mat.dims();
for(; it != it_end; ++it)
{
// 要素のインデックスと要素の値を表示します.
const Node* n = it.node();
printf("(")
for(int i = 0; i < dims; i++)
printf("
printf(":
s += *it;
}
printf("Element sum is
このループを走らせると,要素の表示順がいかなる論理的順序(辞書順,など)にも従わないことに気付くでしょう.これらは,ハッシュテーブルに格納された順番に,つまり,半乱数的に並んでいます.そこで,ノードへのポインタを集めて,適切な順序にソートしようとするかもしれません.しかしそうすると,後から行列に要素を加えたときに,バッファが再割り当てされて,ノードへのポインタが無効になってしまう可能性があることに注意しなければいけません.
2つ以上の疎な行列を同時に処理する必要がある場合の,上述の2つのメソッドの組み合わせ.例えば,次のようにすれば,2つの疎な浮動小数点型行列同士の正規化されていない相関を求めることができます:
double cross_corr(const SparseMat& a, const SparseMat& b)
{
const SparseMat *_a = &a, *_b = &b;
// b の要素数が a よりも少ない場合,
// b を反復横断した方が高速です.
if(_a->nzcount() > _b->nzcount())
std::swap(_a, _b);
SparseMatConstIterator_<float> it = _a->begin<float>(),
it_end = _a->end<float>();
double ccorr = 0;
for(; it != it_end; ++it)
{
// 最初の行列から,次の要素を取り出します.
float avalue = *it;
const Node* anode = it.node();
// 2 番目の行列で同じインデックスをもつ要素を見つけようとします.
// ハッシュ値は要素のインデックスにのみ依存するので,
// ノードに格納されたハッシュ値を再利用します.
float bvalue = _b->value<float>(anode->idx,&anode->hashval);
ccorr += avalue*bvalue;
}
return ccorr;
}
SparseMat() から派生した,疎なn次元配列のクラステンプレート.
template<typename _Tp> class SparseMat_ : public SparseMat
{
public:
typedef SparseMatIterator_<_Tp> iterator;
typedef SparseMatConstIterator_<_Tp> const_iterator;
// コンストラクタ;
// データ型が = DataType<_Tp>::type となる行列を作成します.
SparseMat_();
SparseMat_(int dims, const int* _sizes);
SparseMat_(const SparseMat& m);
SparseMat_(const SparseMat_& m);
SparseMat_(const Mat& m);
SparseMat_(const MatND& m);
SparseMat_(const CvSparseMat* m);
// 代入演算子.必要ならばデータ型を変換します.
SparseMat_& operator = (const SparseMat& m);
SparseMat_& operator = (const SparseMat_& m);
SparseMat_& operator = (const Mat& m);
SparseMat_& operator = (const MatND& m);
// 基底クラスの対応するメソッドと同じです.
SparseMat_ clone() const;
void create(int dims, const int* _sizes);
operator CvSparseMat*() const;
// データ型のチェックが加えられたオーバーライドメソッド
int type() const;
int depth() const;
int channels() const;
// より便利な要素アクセス演算子.
// ref() はそのまま(ただし, <_Tp> の指定は必要ありません)
// operator () は SparseMat::value<_Tp> と等価です.
_Tp& ref(int i0, size_t* hashval=0);
_Tp operator()(int i0, size_t* hashval=0) const;
_Tp& ref(int i0, int i1, size_t* hashval=0);
_Tp operator()(int i0, int i1, size_t* hashval=0) const;
_Tp& ref(int i0, int i1, int i2, size_t* hashval=0);
_Tp operator()(int i0, int i1, int i2, size_t* hashval=0) const;
_Tp& ref(const int* idx, size_t* hashval=0);
_Tp operator()(const int* idx, size_t* hashval=0) const;
// イテレータ
SparseMatIterator_<_Tp> begin();
SparseMatConstIterator_<_Tp> begin() const;
SparseMatIterator_<_Tp> end();
SparseMatConstIterator_<_Tp> end() const;
};
SparseMat_ は, Mat_ や MatND_ と同じように作成された, SparseMat() に被せる薄いラッパーです. これによって,いくつかの処理が次のように簡単に書けるようになります.
int sz[] = {10, 20, 30};
SparseMat_<double> M(3, sz);
...
M.ref(1, 2, 3) = M(4, 5, 6) + M(7, 8, 9);