1. 程式人生 > >opencv裡 函式引數型別: InputArray和OutputArray 解讀

opencv裡 函式引數型別: InputArray和OutputArray 解讀

概述

InputArray和OutputArray兩個類都是代理資料型別,用來接收Mat和Vector<>作為輸入引數,OutputArray繼承自InputArray。

InputArray作為輸入引數的時候,傳入的引數加了const限定符,即它只接收引數作為純輸入引數,無法更改輸入引數的內容。而OutputArray則沒有加入限定符,可以對引數的內容進行更改。

InputArray使用一系列的資料型別作為輸入例項化自身,通過設定一系列的建構函式來實現。

_InputArray::_InputArray(constMat&m) :flags(MAT),obj((void

*)&m) {}

_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

<_Tp> >& vec)

: 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_TYPEFIXED_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了。