opencv裡 函式引數型別: InputArray和OutputArray 解讀
概述
InputArray和OutputArray兩個類都是代理資料型別,用來接收Mat和Vector<>作為輸入引數,OutputArray繼承自InputArray。
InputArray作為輸入引數的時候,傳入的引數加了const限定符,即它只接收引數作為純輸入引數,無法更改輸入引數的內容。而OutputArray則沒有加入限定符,可以對引數的內容進行更改。
InputArray使用一系列的資料型別作為輸入例項化自身,通過設定一系列的建構函式來實現。
_InputArray::_InputArray(constMat&m) :flags(MAT),obj((void
_InputArray::_InputArray(constvector<Mat>&vec) : flags(STD_VECTOR_MAT),obj((void*)&vec) {}
_InputArray::_InputArray(constdouble&val) :flags(FIXED_TYPE +FIXED_SIZE +MATX +CV_64F),obj((void*)&val),sz(Size(1,1)) {}
.....
可以看到在構造的時候,同時指定了flags和obj,flags用於表明當前儲存的資料型別,而obj儲存的則是資料的記憶體地址。
除了這些基本的建構函式外,還有其他支援泛型的建構函式,如下
/////////////////////////////// Input/Output Arrays /////////////////////////////////
template<typename_Tp>inline_InputArray::_InputArray(constvector<_Tp>&vec)
: flags(FIXED_TYPE +STD_VECTOR +DataType<_Tp>::type),obj((void*)&vec) {}
template<typename_Tp>inline_InputArray::_InputArray(constvector<vector
: flags(FIXED_TYPE +STD_VECTOR_VECTOR +DataType<_Tp>::type),obj((void*)&vec) {}
template<typename_Tp>inline_InputArray::_InputArray(constvector<Mat_<_Tp> >& vec)
: flags(FIXED_TYPE +STD_VECTOR_MAT +DataType<_Tp>::type),obj((void*)&vec) {}
(這裡只列出了部分,更詳細的可以參見mat.hpp,或者全工程搜尋_InputArray::_InputArray),對於泛型的建構函式,可以看出flags除了存放了傳入的物件的型別,還存放了傳入物件內部資料的型別,也就是裡面的DataType<_Tp>::type。有了這個型別之後可以知道當前資料的型別,基於這個,可以實現將傳入資料打包成不同的資料,例如將vector資料打包成Mat型資料。
後面可以使用CV_MAT_MASK獲取資料元素的型別,在另一篇文章有解說opencv資料型別的位操作。總的來說,flags的低12位存放了資料元素的型別,包括通道channel和深度depth,而在本類裡新定義的物件型別,則是從高16位開始的(從下面的enum定義可知),這樣兩者就互不干擾了。
型別標記
這兩個類的作用是使Mat和Vector<>均可以作為統一的引數,將這些型別轉換成InputArray的時候,會自動生成對應的型別標記,標記為類裡的成員flag,然後在其他地方再根據這個標記解析出實際的型別。
支援這些型別的資料
enum {
KIND_SHIFT = 16,
FIXED_TYPE = 0x8000 <<KIND_SHIFT,
FIXED_SIZE = 0x4000 <<KIND_SHIFT,
KIND_MASK = ~(FIXED_TYPE|FIXED_SIZE) - (1 <<KIND_SHIFT) + 1,
NONE = 0 <<KIND_SHIFT,
MAT = 1 <<KIND_SHIFT,
MATX = 2 <<KIND_SHIFT,
STD_VECTOR = 3 <<KIND_SHIFT,
STD_VECTOR_VECTOR = 4 <<KIND_SHIFT,
STD_VECTOR_MAT = 5 <<KIND_SHIFT,
EXPR = 6 <<KIND_SHIFT,
OPENGL_BUFFER = 7 <<KIND_SHIFT,
OPENGL_TEXTURE = 8 <<KIND_SHIFT,
GPU_MAT = 9 <<KIND_SHIFT
};
獲取型別使用
int_InputArray::kind()const
{
returnflags &KIND_MASK;
}
該函式返回的就是NONE = 0 <<KIND_SHIFT一下的型別,這些型別在指定給flags的時候是單獨指定的,而其以上的FIXED_TYPE和FIXED_SIZE則可以組合使用。
資料轉換
為了從InputArray取出具體型別的資料,該類提供了一系列的介面函式,如下:
virtualMatgetMat(inti=-1)const;
virtualvoidgetMatVector(vector<Mat>&mv)const;
virtualGlBuffergetGlBuffer()const;
virtualGlTexturegetGlTexture()const;
virtualgpu::GpuMatgetGpuMat()const;
可以看出,這些型別分別對應於flags裡面的那些型別,具體的實現可以看裡面的程式碼,但是這裡面有比較有意思的地方,即傳入的型別可以解壓成其他的型別,就如開始時講到的將vector打包成Mat型別。
要實現不同型別的打包操作需要用到如下幾個函式
virtual Size size(int i=-1) const;
該函式返回當前型別的size。僅看該函式裡面的vector部分:
Size_InputArray::size(inti)const
{
intk =kind();
if( k == STD_VECTOR )
{
CV_Assert( i < 0 );
constvector<uchar>&v = *(constvector<uchar>*)obj;
constvector<int>&iv = *(constvector<int>*)obj;
size_tszb =v.size(),szi = iv.size();
returnszb ==szi ?Size((int)szb, 1) :Size((int)(szb/CV_ELEM_SIZE(flags)), 1);
}
}
可以看出,首先將將原始的資料轉換成最小單位uchar的vector,然後根據它的size就可以得到該資料所佔的位元組數,再除一個元素(舊事重提,opencv中一個元素對應於一個CV_MAKE_TYPE型別所指定的資料,例如三個通道的RGB型別是CV_MAKE_TYPE(CV_8U,3)即對應三個位元組)大小所佔的位元組即可以得到元素的個數。並且可以看出這裡的返回的size僅僅是指定了rows的,cols全部都設為1,也就是說,對於vector轉換為Mat,返回的是僅僅有一列的矩陣。
virtual Mat getMat(int i=-1) const;
該函式將資料打包成Mat返回,僅僅vector轉Mat的部分
Mat_InputArray::getMat(inti)const
{
intk =kind();
if( k == STD_VECTOR )
{
CV_Assert( i < 0 );
intt =CV_MAT_TYPE(flags);
constvector<uchar>&v = *(constvector<uchar>*)obj;
return !v.empty() ?Mat(size(),t, (void*)&v[0]) :Mat();
}
}
從這裡程式碼可以看出,首先是從flags裡面提取出元素型別編碼,然後使用基本單位uchar(計算機中最小的儲存單位是8位,即一個位元組,這裡uchar就是8位元組)來將初始資料的記憶體進行解引用(關於對vector所對應的指標進行解引用的問題,和人討論過應該是vector過載瞭解引用運算子*,但是不確定~~),然後就使用size、型別編碼以及記憶體地址就可以構造一個Mat了。