1 摘要
OpenCV使用一系列基礎數(shù)據(jù)類型作為模塊,特例化這些模版能夠得到大量的數(shù)據(jù)類型,另外你也能夠聯(lián)系你的使用場景對其擴展從而更靈活的完成自己的應用。OpenCV嚴重依賴標準模版庫(STL),如很多函數(shù)的參數(shù)都要求傳入向量類的實例。除此之外OpenCV自己的數(shù)據(jù)類型分為三類,
- 基本類型,它直接對C++基本數(shù)據(jù)類型組裝,如向量、矩陣以及表示點浴捆、矩形等幾何概念的數(shù)據(jù)類型忿族。
- 輔助類型碉考,它們表示如垃圾收集指針類串纺,用于切片的范圍對象以及終止條件抽象等更抽象的概念糊啡。
- 大數(shù)組類型河闰,它們是包含數(shù)組或者其他基本數(shù)據(jù)類型集合的集合數(shù)據(jù)類型多柑,它也也能包含基礎數(shù)據(jù)類型。如類
cv::Mat
,它用于表示任意維度包含基礎元素的數(shù)組荞怒,圖片是該類的一個特例化類愉择,和OpenCV 2.1之前的版本不同蔚润,特例化的類型仍使用原名粪小。如稀疏矩陣類cv::SparseMat
描述更稀疏的數(shù)據(jù),如直方圖趁冈。
2 基本類型
下表列出OpenCV的基本數(shù)據(jù)類型,在OpenCV中僧家,盡管幾乎所有的基本數(shù)據(jù)類型都是模版類,但是我們很少直接使用他們來定義變量钟鸵,通常我們都使用其別名形式叼风。
模版 | 別名 | 示例 |
---|---|---|
cv::Vec<> | cv::Vec{2,3,4,6}{b,w,s,i,f,d} | cv::Vec2i |
cv::Matx<> | cv::Matx{1,2,3,4,6}{1,2,3,4,6}{f,d} | cv::Vec33f |
cv::Point<> | cv::Matx{2,3}{i,f,d} | cv::Point2i |
- | - | cv::Scalar |
cv::Size | cv::Size2{i,l,d,f} | cv::Size2i |
cv::Rect | cv:: Rect2{i,d,f} | cv:: Rect2i |
- | - | cv:: RotatedRect |
- | - | cv::Complexd cv::Complexd |
相比于STL的向量類伏尼,模版類cv::Vec<>
具有固定的長度猿规,被稱為固定向量向量類(FIxed Vector Class)褐耳,這意味著編譯時就知道向量的大小,從而使得處理此類問題的代碼更高效。另外該類的元素個數(shù)是有限的,如在OpenCV2.2中,該類的元素個數(shù)不能超過9個。如果需要處理更復雜的數(shù)據(jù),請使用類cv::Mat
。盡管該類是一個模版類闷串,但是我們通常不使用模版類的實例化方式先较,相反我們通常使用別名的方式去實例花一個向量對象,其所有的別名類型及示例如上表。
和向量類類似史煎,cv::Matx<>
是輕量級矩陣模塊類,這意味著所包含的數(shù)據(jù)尺寸也受限制璧眠,通常在計算機視覺中串绩,2??2关划、3??3和4??4的矩陣用于大多數(shù)變換操作,而cv::Matx<>
就是設計于這些數(shù)據(jù)類型的,它最多支持6??6矩陣。同意的程序在編譯時就知道容器的數(shù)據(jù)大小掠哥,不需要在運行時動態(tài)分布內存雀瓢,這會使得代碼執(zhí)行效率更高牙瓢。其所有的別名及示例如上表劫拗。
模版類cv::Point<>
表示一個點,和向量及矩陣使用下標不同矾克,它使用x页慷、y、z
變量訪問其中包含的元素胁附。類cv::Scalar
繼承于cv::Vec<double,4>
酒繁,它表示一個由四個雙精度元素組成的集合。
模版類cv::Size<>
和cv::Rect<>
分別表示一個尺寸和一個矩形控妻,需要注意的是cv::Size
和cv::Rect
分別是cv::Size2i
和cv::Rect2i
的別名州袒。類cv::RotatedRect
表示非軸對其的矩陣,它包含一個額外的浮點型角度值弓候。
2.1 Point
Point是最簡單的基本數(shù)據(jù)類型郎哭,它的開銷很小。盡管該類定義的接口并不多菇存,但是在需要的時候可以很方便的轉換為更復雜的類型夸研,如矩陣和向量類,其所有的接口如下表依鸥。
操作 | 示例 |
---|---|
默認構造函數(shù) | cv::Point2i p; cv::Point3f p; |
拷貝構造函數(shù) | cv::Point3f p2( p1 ); |
值構造函數(shù) | cv::Point2i p( x0, x1 ); cv::Point3d p( x0, x1, x2 ); |
轉換為固定向量類實例 | (cv::Vec3f) p; |
成員訪問 | p.x; p.y; p.z |
點乘 | float x = p1.dot( p2 ) |
雙精度點乘 | double x = p1.ddot( p2 ) |
叉乘 | p1.cross( p2 ) // 僅支持3元素實例 |
檢測是否位于矩形內 | p.inside( r ) // 僅支持2元素實例 |
2.2 Scalar
cv::Scalar<>
是一個模版類亥至,它是Vec<_Tp, 4>
的子類,同時cv::Scalar
又是一個別名贱迟,它包含了4個雙精度的元素姐扮。在四元素相關的運算中常會使用到該類,其支持的操作如下关筒。
操作 | 示例 |
---|---|
默認構造函數(shù) | cv::Scalar s; |
拷貝構造函數(shù) | cv::Scalar s2( s1 ); |
值構造函數(shù) | cv::Scalar s( x0 ); cv::Scalar s( x0, x1, x2, x3 ); |
乘法運算 | s1.mul( s2 ); |
共軛四元素 | s.conj(); |
四元素真值測試 | s.isReal(); |
2.3 Size
Size類實例不能轉化為向量類實例叶撒,但是向量類和Point類實例可以轉化為Size類實例戒洼,其支持的操作如下衫画。
操作 | 示例 |
---|---|
默認構造函數(shù) | cv::Size sz; cv::Size2i sz; cv::Size2f sz; |
拷貝構造函數(shù) | cv::Size sz2( sz1 ); |
值構造函數(shù) | cv::Size2f sz( w, h ); |
成員訪問 | sz.width; sz.height; |
計算面積 | sz.area(); |
2.4 Rect
Rect類表示一個矩形场航,其包含成員變量x
和y
,分別表示矩形左上角頂點和矩形的大小袍榆,其直接支持的操作如下胀屿。
操作 | 示例 |
---|---|
默認構造函數(shù) | cv::Rect r; |
拷貝構造函數(shù) | cv::Rect r2( r1 ); |
值構造函數(shù) | cv::Rect( x, y, w, h ); |
使用頂點和大小構造 | cv::Rect( p, sz ); |
使用兩個頂點構造 | cv::Rect( p1, p2 ); |
成員訪問 | r.x; r.y; r.width; r.height; |
面積計算 | r.area(); |
提取左上角頂點 | r.tl(); |
提取右下角頂點 | r.br(); |
判斷是否包含某點 | r.contains( p ); |
此外Rect類還重載了一些運算符,用于應對一些常見的幾何運算包雀。
操作 | 示例 |
---|---|
計算矩形重疊部分 | cv::Rect r3 = r1 & r2; r1 &= r2; |
包含兩個矩形的最小矩形 | cv::Rect r3 = r1|r2; r1 |= r2; |
平移矩形 | cv::Rect rx = r + x; r += x; |
增加矩形大小 | cv::Rect rs = r + s; |
比較兩個矩形是否相同 | bool eq = (r1 == r2); |
比較兩個矩形是否不同 | bool ne = (r1 != r2); |
2.5 RotatedRect
cv::RotatedRect
是OpenCV底層少有的幾個未使用模版的C++接口類之一宿崭,它包含一個cv::Point2f
實例屬性定義矩形中心,一個cv::Size2f
實例屬性定義矩陣大小才写,一個浮點型數(shù)據(jù)定義矩形繞中心旋轉的角度葡兑。其支持的操作如下奖蔓。
操作 | 示例 |
---|---|
默認構造函數(shù) | cv::RotatedRect rr(); |
拷貝構造函數(shù) | cv::RotatedRect rr2( rr1 ); |
使用兩個頂點構建 | cv::RotatedRect( p1, p2 ); |
使用值構建 | cv::RotatedRect rr( p, sz, theta ) ; |
成員訪問 | rr.center; rr.size; rr.angle; |
獲取頂點列表 | rr.points( pts[4] ); |
2.6 固定矩陣
固定矩陣類是大多數(shù)OpenCV的C++接口基本類型核心,如固定向量類繼承于該類讹堤,而Scalar類又繼承于固定向量類吆鹤。它在編譯階段就能夠確定變量的內存大小,因此在程序運行時該變量在棧上創(chuàng)建和釋放洲守,有著較高的效率疑务。它適用于6維及以下的矩陣,如果需要表示更多的元素梗醇,需要使用類cv::Mat。其支持的操作如下叙谨。
操作 | 示例 |
---|---|
默認構造函數(shù) | cv::Matx33f m33f; cv::Matx43d m43d; |
拷貝構造函數(shù) | cv::Matx22d m22d( n22d ); |
值構造函數(shù) | cv::Matx21f m(x0,x1); cv::Matx44d m(x0,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15); |
含相同元素矩陣 | m33f = cv::Matx33f::all( x ); |
全零矩陣 | m23d = cv::Matx23d::zeros(); |
全一矩陣 | m16f = cv::Matx16f::ones(); |
單位矩陣 | m33f = cv::Matx33f::eye(); |
提取矩陣對角線 | m31f = cv::Matx33f::diag(); |
創(chuàng)建均勻分布矩陣 | m33f = cv::Matx33f::randu( min, max ); |
創(chuàng)建正態(tài)分布矩陣 | m33f = cv::Matx33f::nrandn( mean, variance ); |
成員訪問 | m( i, j ), m( I ); |
矩陣代數(shù)運算 | m1 = m0; m0 * m1; m0 + m1; m0 – m1; |
和標量的代數(shù)運算 | m * a; a * m; m / a; |
比較 | m1 == m2; m1 != m2; |
點乘 | m1.dot( m2 ); //返回值精度和參數(shù)一致 m1.ddot( m2 ); //返回值為雙精度類型 |
改變矩陣結構 | m91f = m33f.reshape<9,1>(); |
類型轉換 | m44f = (Matx44f) m44d |
提取指定位置指定大小的子矩陣 | m44f.get_minor<2, 2>( i, j ); |
提取某行 | m14f = m44f.row( I ); |
提取某列 | m41f = m44f.col( j ); |
轉置矩陣 | n44f = m44f.t(); |
逆矩陣 | n44f = m44f.inv( method ); // 默認方法是cv::DECOMP_LU |
解線性系統(tǒng) | m31f = m33f.solve( rhs31f, method ) m32f = m33f.solve<2>( rhs32f, method ); //模版類型温鸽,默認方法是DECOMP_LU |
逐元素乘法 | m1.mul( m2 ); |
2.7 固定向量類
固定向量類繼承于固定矩陣類,即模版類cv::Vec<>
可以理解為列數(shù)為1的cv::Matx<>
類唉俗。其別名中的w
后綴表示無符號短整形嗤朴。其支持的操作如下。
操作 | 示例 |
---|---|
默認構造函數(shù) | Vec2s v2s; Vec6f v6f;等 |
拷貝構造函數(shù) | Vec3f u3f( v3f ); |
值構造函數(shù) | Vec2f v2f(x0,x1); Vec6d v6d(x0,x1,x2,x3,x4,x5); |
成員訪問 | v4f[ i ]; v3w( j ); |
向量點乘 | v3f.cross( u3f ); |
2.8 復數(shù)類
OpenCV的復數(shù)類和STL里面的模版復數(shù)類complex<>
有一定區(qū)別虫溜,它們相互兼容,并可互相轉換股缸,其支持的操作如下衡楞。
操作 | 示例 |
---|---|
默認構造函數(shù) | cv::Complexf z1; cv::Complexd z2; |
拷貝構造函數(shù) | cv::Complexf z2( z1 ); |
值構造函數(shù) | cv::Complexd z1(re0); cv::Complexd(re0,im1) ; |
成員訪問 | z1.re; z1.im; |
共軛復數(shù) | z2 = z1.conj(); |
3 輔助類型
輔助類型包含控制大量算法(如終止條件)行為,以及在容器內執(zhí)行諸如范圍確定和切片等操作的基礎類型敦姻,以及從C++集成到OpenCV中的智能指針類型cv::Ptr
等瘾境。
3.1 終止條件
大多數(shù)算法都需要一個終止條件,通常這個條件是算法迭代的次數(shù)達到了上線镰惦,或者算法計算出的誤差在被允許的范圍內迷守。大多數(shù)場景下,我們會同時從這兩個方面去限制算法的行為旺入,因為計算的結果可能永遠無法達到可接受的誤差范圍兑凿。類cv::TermCriteria
封裝了這兩個條件,它包含類型茵瘾、最大迭代數(shù)和可接受誤差三個參數(shù)礼华,可以通過cv::TermCriteria( int type, int maxCount, double epsilon )
方式構建實例。參數(shù)type可選的值為cv::TermCriteria::COUNT
和TermCriteria::EPS
拗秘,當你需要同時限制這兩個條件時圣絮,使用邏輯或符合|
,即cv::TermCriteria::COUNT|TermCriteria::EPS
雕旨。
3.2 范圍
類cv::Range
用于表示連續(xù)的整數(shù)序列扮匠,通過構造函數(shù)cv::Range( int start, int end )
初始化捧请,需要注意的是這個序列是一個半開半閉區(qū)間,其取值范圍為[start, end)棒搜,即調用cv::Range( 1, 3 )
得到的序列是1疹蛉、2。函數(shù)cv::Range::size()
用于獲取范圍對象的容量帮非,cv::Range::empty()
用于判斷范圍對象是否為空氧吐,cv::Range::all()
用于獲取對象的取值范圍。
3.3 指針和垃圾收集
C++中一個非常有用的類是智能指針末盔,它也被集成到了OpenCV中筑舅。智能指針使我們可以創(chuàng)建一個變量的多個引用,并將它們任意傳遞陨舱,每創(chuàng)建一個引用都會增加原始對象的引用計數(shù)翠拣,當某個引用出作用域后其引用計數(shù)就會減1,當最后一個引用出作用域后游盲,元素對象的引用就會為0误墓,此時其析構函數(shù)會被隱式自動調用,程序員就不需要再擔心內存問題益缎。
使用智能指針時谜慌,需要包裝一個具體類型,可以通過代碼cv::Ptr<Matx33f> p( new cv::Matx33f )
或者cv::Ptr<Matx33f> p = makePtr<cv::Matx33f>()
創(chuàng)建一個指向類cv::Matx33f
實例的指針對象莺奔。它可以像正常指針一樣被傳遞欣范,以及支持常用的操作符,如*()
和->()
令哟。在創(chuàng)建出智能指針對象p后恼琼,可以使用賦值符號將創(chuàng)建一個新的智能指針實例,如Ptr<Mat33f> q = p
;屏富,和直接使用裸指針(原始數(shù)據(jù)類型的指針一樣)晴竞,堆上的cv::Matx33f
實例僅存在一份。不同的是智能指針會記錄對該實例的引用計數(shù)狠半,當q
被釋放時噩死,p
對原始數(shù)據(jù)的引用計數(shù)減1,當p
被釋放時典予,引用計數(shù)變?yōu)?甜滨,則原始數(shù)據(jù)被銷毀,內存被回收瘤袖。
類cv::Ptr<>
還提供了一些接口用于管理職能指針的引用計數(shù)衣摩,函數(shù)addref()
和release()
分別增加和減少引用計數(shù),這是一組帶風險的函數(shù),建議只有在當不得不對引用計數(shù)進行操作時才調用這兩個函數(shù)艾扮。函數(shù)empty()
適用于兩種場景既琴,第一是判斷一個智能指針指向的實例是否已經(jīng)被釋放。第二在調用某些能夠返回null
函數(shù)初始化智能指針時泡嘴,判斷這些方法返回值是否為null
甫恩,如使用c語言函數(shù)cvLoadImage()
和fopen()
等。
該類還提供了一個函數(shù)delete_obj()
酌予,該函數(shù)會在引用計數(shù)變?yōu)?時自動調用磺箕。默認情況下該函數(shù)內部未實現(xiàn)任何功能。當智能指針指向的實例在銷毀時需要額外操作時我們需要重載該函數(shù)并實現(xiàn)自己的邏輯抛虫。例如使用老版本OpenCV的C語言接口IplImage
時松靡,需要調用函數(shù)cvLoadImage()
從磁盤中加載圖片數(shù)據(jù),使用C語言編程代碼如下建椰。
IplImage* img_p = cvLoadImage( ... );
// 這里省略內存管理相關邏輯
而使用智能指針后雕欺,代碼應該是這樣的。
cv::Ptr<IplImage> img_p = cvLoadImage( "an_image" );
cv::Ptr<IplImage> img_p( cvLoadImage( "an_image" ) );
該類型的智能指針對應的delete_obj()
函數(shù)已經(jīng)被重載棉姐,其被定義在OpenCV的頭文件中屠列。其中obj
是智能指針內部實際指向具體實例的成員變量,該實現(xiàn)在其引用計數(shù)為0時釋放了內存資源伞矩。
template<> inline void cv::Ptr<IplImage>::delete_obj() {
cvReleaseImage(&obj);
}
在更多的情況下笛洛,如果我們希望包裝的實例類型在釋放時需要定制邏輯,我們需要自己去重載該模版的delete_obj()
方法乃坤。例如使用智能指針包裝FILE來處理文件相關操作時撞蜂,我們需要以如下方式重載模版cv::Ptr<FILE>
的delete_obj()
方法。
template<> inline void cv::Ptr<FILE>::delete_obj() {
fclose(obj);
}
在如下代碼中侥袜,當智能指針實例f
出作用域后,該實例被銷毀溉贿,同時檢測到指向的FILE實例的引用計數(shù)為0時枫吧,文件句柄就會自動關閉。這里僅為舉例說明宇色,通常情況下我們建議文件關閉邏輯顯示調用九杂,而不是重載delete_obj()
方法隱式調用。
{
cv::Ptr<FILE> f(fopen("myfile.txt", "r"));
if(f.empty())
fprintf(f, ...);
...
}
需要注意的是智能指針內部的引用計數(shù)以及OpenCV中其他使用引用計數(shù)的類都是線程安全的宣蠕。
3.4 異常類和異常處理
cv::Exception
是OpenCV內部負責處理異常的類例隆,它繼承于STL的異常處理類std::exception
,除了改變命名空間外抢蚀,它沒有做任何其他處理镀层。其屬性code
表示錯誤碼,err
表示錯誤描述皿曲,func
表示拋出異常的函數(shù)唱逢,而file
和line
具體表示具體的文件和行號吴侦。
你也可以使用一些內置宏來拋出自己的錯誤。CV_Error( errorcode, description )
和CV_Error_( errorcode, printf_fmt_str, [printf-args] )
可以拋出自定義的異常坞古,區(qū)別在于后者可以使用格式化的字符串作為錯誤描述备韧。另外CV_Assert( condition )
可以CV_DbgAssert( condition )
用于條件斷言,后者只在Debug環(huán)境中生效痪枫。這些宏會自己處理異常的函數(shù)织堂、文件和行號信息,我們不需要做額外操作奶陈。
3.5 類型
在OpenCV內部需要傳遞類型時需要使用到模版類cv::DataType<>
易阳,在實際使用的時候我們需要使用到其特例化類。模版類cv::DataType<>
的定義如下尿瞭,其中包含了編譯時能得到的信息闽烙,它們主要通過typedef
的方式聲明,也包含了一些運行時才能確定的信息声搁,它們通過枚舉的方式提供黑竞。該模版類可以實現(xiàn)一些復雜的交互,如能夠使得一些算法實現(xiàn)不依賴于特定的數(shù)據(jù)類型疏旨。
template<typename _Tp> class DataType
{
typedef _Tp value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum {
generic_type = 1,
depth = -1,
channels = 1,
fmt = 0,
type = CV_MAKETYPE(depth, channels)
};
};
在模版的定義中很魂,通過typedef在編譯時確定的4個類型都是相同的,但是在模版的特例化實現(xiàn)中它們通常是不一樣的檐涝。為了更深入了解這些變量的含義遏匆,參考如下在core.hpp
頭文件中該模版的特例化實現(xiàn)。在下面的實例中谁榜,通過typedef定義的幾個類型都是float
類型幅聘。枚舉部分的常量中,generic_type
被定義為0窃植,對于在core.hpp
頭文件中所有該模版的特例化實現(xiàn)該值都被定義為0帝蒿。depth
表示實際的基本數(shù)據(jù)類型標識符,這里是常量CV_32F
巷怜。channel
表示該數(shù)據(jù)類型所包含的基礎數(shù)據(jù)類型數(shù)量葛超,這里是1。fmt
通過一個單一字符的方式表示數(shù)據(jù)的格式延塑,這里是f
绣张。type
通過關聯(lián)基礎數(shù)據(jù)類型和通道數(shù)表示了當前的數(shù)據(jù)類型,這里是CV_32FC1
关带。
template<> class DataType<float>
{
public:
typedef float value_type;
typedef value_type work_type;
typedef value_type channel_type;
typedef value_type vec_type;
enum {
generic_type = 0,
depth = DataDepth<channel_type>::value,
channels = 1,
fmt = DataDepth<channel_type>::fmt,
type = CV_MAKETYPE(depth, channels)
};
};
模版類cv::DataType<>
也能夠以更復雜的方式使用侥涵,如下面的例子,該模版類的特例化結果仍然包含范型。首先該模版類包裝的數(shù)據(jù)類型cv::Rect_<>
本身也是一個模版類独令,在使用時它還需要特例化為具體的類型端朵,如cv::DataType<Rect>
或者cv::DataType< Rect_<float> >
。
template<typename _Tp> class DataType<Rect_<_Tp> >
{
public:
typedef Rect_<_Tp> value_type;
typedef Rect_<typename DataType<_Tp>::work_type> work_type;
typedef _Tp channel_type;
typedef Vec<channel_type, channels> vec_type;
enum {
generic_type = 0,
depth = DataDepth<channel_type>::value,
channels = 4,
fmt = ((channels-1)<<8) + DataDepth<channel_type>::fmt,
type = CV_MAKETYPE(depth, channels)
};
};
對于cv::DataType<Rect>
燃箭,此處范型_Tp
的類型被編譯為int
冲呢,即value_type
類型為Rect
,它是模版類cv::DataType<>
包裝的數(shù)據(jù)類型的描述招狸。work_type
此時也為Rect敬拓,表示數(shù)據(jù)在計算過程中的類型。channel_type
為int
裙戏,表示最小單元數(shù)據(jù)類型乘凸。vec_type
為cv::Vec<int,4>
,它表示單個模版類cv::DataType<>
所包裝的對象的數(shù)據(jù)類型組成累榜。在枚舉部分营勤,generic_type
仍然是0,depth
為宏定義CV_32S
壹罚,channels
為4葛作,因為一個Rect數(shù)據(jù)需要4個單元組成,fmt
為0x3069猖凛,type
為CV_32SC4
赂蠢。
3.6 輸入和輸出
大多數(shù)OpenCV的函數(shù)都需要數(shù)組作為輸入?yún)?shù),或者會返回一個數(shù)組辨泳,但是在OpenCV內部數(shù)組類的數(shù)據(jù)類型有很多虱岂,如cv::Scalar
、cv::Vec
菠红、cv::Matx
和cv::Mat
等第岖,以及STL提供的std::vector<>
等。為了使接口不太復雜试溯,OpenCV定義了類型cv::InputArray
和cv::OutputArray
绍傲。實際上這兩個類型可以支持上述的任意數(shù)據(jù)類型。它們的區(qū)別是前者是一個常量耍共,不能修改。另外還定義了類型cv::InputOutputArray
用于就地的計算任務猎塞。
在OpenCV內部實現(xiàn)中我們呈远粒看到這些數(shù)組類型的使用,盡管我們很少在自己的函數(shù)中使用這類參數(shù)荠耽。當我們使用OpenCV提供的帶這類參數(shù)函數(shù)時钩骇,我們可以使用任意的數(shù)組類型,如cv::Scalar
。另外還有一個重要的函數(shù)cv::noArray()
會返回一個cv::InputArray
的實例倘屹,當它作為參數(shù)傳入的時候表示沒有任何輸入數(shù)據(jù)需要被處理银亲。對于一些具有可選輸出數(shù)組的函數(shù)時,當我們不需要任何輸出結果時可以傳入該實例纽匙。
4 工具函數(shù)
OpenCV提供了如下系列工具函數(shù)用于處理數(shù)學計算务蝠,測試,錯誤生成烛缔,內存和線程操作和優(yōu)化等馏段。
函數(shù)/宏 | 描述 |
---|---|
cv::alignPtr() | 以指定的字節(jié)對齊指針 |
cv::alignSize() | 以指定的字節(jié)對齊緩存大小 |
cv::allocate() | 分配C語言的數(shù)組 |
cvCeil() | 對某個變量向上取整 |
cv::cubeRoot() | 計算某個數(shù)的立方根 |
cv::CV_Assert() | 斷言,條件不滿足時拋出異常践瓷,生產(chǎn)和工作環(huán)境都有效 |
cv::CV_DbgAssert() | 斷言院喜,條件不滿足時拋出異常,僅在工作環(huán)境生效 |
cv::deallocate() | 釋放C語言的數(shù)組 |
cv::error() | 提示錯誤并拋出異常 |
cv::fastAtan2() | 使用反三角函數(shù)計算角度 |
cv::fastFree() | 釋放緩存對象的內存資源 |
cv::fastMalloc() | 分配一塊大小對齊的緩存 |
cvFloor() | 對某個變量向下取整 |
cv::format() | 以STL中的sprintf()函數(shù)類似的方式創(chuàng)建STL字符串 |
cv::getCPUTickCount() | 獲取CPU時鐘的滴答數(shù) |
cv::getNumThreads() | 獲取OpenCV當前使用的線程數(shù) |
cv::getOptimalDFTSize() | 在將一個數(shù)組傳遞給函數(shù)cv::DFT()晕翠,獲取該數(shù)組的最佳大小 |
cv::getThreadNum() | 獲取當前線程的索引 |
cv::getTickCount() | 獲取系統(tǒng)的滴答數(shù) |
cv::getTickFrequency() | 獲取每秒的tick數(shù) |
cvIsInf() | 檢查某個浮點型數(shù)是否是無窮大 |
cvIsNaN() | 檢查某個浮點型變量是否是無效值 |
cvRound() | 計算某個變量的最接近整數(shù) |
cv::setNumThreads() | 設置OpenCV使用的線程數(shù) |
cv::setUseOptimized() | 啟用/禁用代碼優(yōu)化喷舀,如SSE2等 |
cv::useOptimized() | 獲取代碼優(yōu)化功能的狀態(tài) |
函數(shù)cv::alignPtr()
定義如下。需要注意的是在某些架構上訪問多字節(jié)格式數(shù)據(jù)時淋肾,如果訪問的地址不能被對象的大小整除硫麻,則這個操作無法完成。如從被4整除的地址中讀取32位整型數(shù)據(jù)巫员。盡管在x86等架構上可以通過多次讀取最后組裝數(shù)據(jù)庶香,但是這種方式會產(chǎn)生嚴重的性能成本。
/**
根據(jù)范型T的具體類型简识,對齊ptr指針赶掖,并返回對齊后的指針。
其計算公式為(T*)(((size_t)ptr + n+1) & -n)
@param ptr 需要被計數(shù)的指針
@param n 需要對齊的大小
*/
template<T> T* cv::alignPtr(T* ptr, int n = sizeof(T));
函數(shù)cv::alignSize()
定義如下七扰。
/**
根據(jù)指定的元素大小奢赂,對齊一個尺寸
其計算公式為(sz + n-1) & -n
@param sz 需要對齊的尺寸
@param n 單位元素大小
*/
size_t cv::alignSize(size_t sz, int n);
函數(shù)cv::AutoBuffer::allocate()
定義如下。
/**
分配一塊可以容納n個T類型元素的緩存空間
為每個對象都調用默認的構造函數(shù)颈走,并把指向第一個元素的地址返回
@param sz 需要分配的內存空間大小
*/
void allocate(size_t _size);
該方式使用示例如下膳灶。
cv::AutoBuffer<cv::VideoCapture> buffer;
buffer.allocate(10);
也可以使用定義在頭文件cvstd.hpp
中的cv::Allocator
類相關函數(shù)來完成類似的任務,其使用實例如下立由。
cv::Allocator<cv::VideoCapture> allocator;
allocator.allocate(100);
函數(shù)cv::AutoBuffer::deallocate()
可以釋放通過cv::AutoBuffer::allocate()
分配的緩存轧钓,同樣的也可以使用定義在頭文件cvstd.hpp
中的cv::Allocator
類相關函數(shù)來完成類似的任務,函數(shù)cv::Allocator::deallocate()
定義如下锐膜。
/**
釋放通過函數(shù)cv::allocate()分配的內存空間毕箍,會調用數(shù)組內每個實例的析構函數(shù)
需要注意的是它釋放的內存空間大小需要和分配的大小相同
@param ptr 指向需要釋放的內存空間地址
@param sz 需要釋放的內存空間大小
*/
void deallocate(pointer p, size_t sz);
函數(shù)cv::fastAtan2()
需要兩個浮點型參數(shù)x
和y
,它使用反三角公式求y/x表示的角度道盏,其返回值的取值區(qū)間為[0.0, 360.0)而柑,其定義如下文捶。
// 使用反正切函數(shù)求y/x的對應的角度
float cv::fastAtan2(float y, float x);
函數(shù)cv::cvCeil()
原型如下。如果輸出參數(shù)x
超過32位整型能夠表示的范圍媒咳,則返回值是未定義的粹排。
// 計算不小于x的整數(shù)
int cvCeil(float x);
函數(shù)cv::cubeRoot()
定義如下。如果返回值的正負和參數(shù)x
相同涩澡。
// 返回x的立方根
float cv::cubeRoot(float x );
函數(shù)cv::error()
定義如下顽耳。宏CV_Error()
和CV_Error_()
內部生成一個異常,然后會調用該函數(shù)筏养,通常我們不需要直接調用斧抱。在生產(chǎn)環(huán)境下,該函數(shù)會拋出指定的異常渐溶,在工作環(huán)境中它會故意引發(fā)一個違規(guī)的內存訪問使得在調試時能夠查看堆和參數(shù)信息辉浦。
void cv::error(const cv::Exception& ex);
函數(shù)cv::fastFree()
釋放由函數(shù)cv::fastMalloc()
創(chuàng)建的內存資源,其定義如下茎辐。
void cv::fastFree(void* ptr);
函數(shù)cv::fastMalloc()工作原理和malloc()類似宪郊,但是它的效率更高,并且返回的緩存大小是對齊的拖陆。這意味著當你分配的緩存等于或者超過16個字節(jié)時弛槐,得到的內存大小會按16字節(jié)對齊。
void* cv::fastMalloc(size_t size);
函數(shù)cvFloor()
用于計算不大于某個浮點型數(shù)據(jù)的整數(shù)依啰,同樣的如果傳入的參數(shù)轉換的結果超出了32位整型數(shù)能夠表示的范圍乎串,則返回值是未定義的昼浦。
int cvFloor(float x);
函數(shù)cv::format()
和STL中的sprintf()
函數(shù)功能類似柔纵,但是它不需要調用方傳入一個字符緩存碗誉,它會構建并返回一個STL字符串對象御雕,這在使用Exception()
的構造函數(shù)時非常便利,因為該構造函數(shù)需要STL字符串類型的參數(shù)磷蛹。其定義如下瞳别。
string cv::format(const char* fmt, ... );
函數(shù)cv::getCPUTickCount()
可以在支持的架構上(包括但是不限于x86架構)直接獲取CPU時鐘的滴答數(shù)轩触。需要注意大多數(shù)情況下這個函數(shù)的返回值都很難解釋忙灼,這是因為在多核系統(tǒng)中匠襟,一個線程可能在某個核內休眠,卻在另外一個核中被喚醒该园,兩處該該函數(shù)的調用結果可能會誤導我們的判斷酸舍,甚至說這個結果完全沒有意義。因此除非有十足的把握里初,盡量不要調用這個函數(shù)父腕,相反可以調用函數(shù)cv::getTickCount()
。調用函數(shù)cv::getCPUTickCount()
對于初始化隨機數(shù)生成器很有幫助青瀑。該函數(shù)原型如下璧亮。
int64 cv::getCPUTickCount( void );
函數(shù)cv::getNumThreads()
獲取OpenCV正在使用的線程數(shù)量,其定義如下斥难。
int cv::getNumThreads( void );
在調用函數(shù)cv::dft()
時枝嘶,其內部計算變換的算法對傳遞到該函數(shù)的數(shù)組參數(shù)的大小很敏感。最佳的數(shù)組大小應該遵從其內部的一些規(guī)定哑诊,這些規(guī)定較復雜群扶,因此我們自己計算會很麻煩。調用函數(shù)cv::getOptimalDFTSize()
能夠獲取最佳的數(shù)組大小镀裤,它會根據(jù)我們傳入的參數(shù)n
竞阐,即我們想要傳遞到函數(shù)cv::dft()
中的數(shù)組大小,計算一個最佳的數(shù)組大小并返回暑劝。這樣OpenCV會創(chuàng)建一個更大的數(shù)組骆莹,并將額外部分都填充為0。
int cv::getOptimalDFTSize( int n );
如果在編譯OpenCV庫時候添加了OpenMP的支持担猛,則可以使用函數(shù)cv::getThreadNum()
獲取當前線程的標識幕垦,其定義如下。
int cv::getThreadNum( void );
函數(shù)cv::getTickCount()
獲取與一些架構相關的時鐘滴答數(shù)傅联,時鐘滴答的頻率也和架構和操作系統(tǒng)相關先改。每秒的滴答數(shù)可以通過函數(shù)cv::getTickFrequency()獲得。使用函數(shù)cv::getTickCount()
在大多數(shù)場景下比使用函數(shù)cv::getCPUTickCount()
更加方便蒸走。因為它不會受到一些如當前線程具體是在哪個核上工作仇奶,以及現(xiàn)代處理器處于能耗控制原因會對CPU頻率節(jié)流等這些底層問題的影響。該函數(shù)定義如下比驻。
int64 cv::getTickCount( void );
函數(shù)cv::getTickFrequency()
返回系統(tǒng)美秒的滴答數(shù)该溯,該值和系統(tǒng)及架構相關,該函數(shù)原型如下嫁艇。
double cv::getTickFrequency( void );
函數(shù)cvIsInf()
用于確定某個浮點型數(shù)據(jù)是否是正無窮或者負無窮大朗伶,如果是則返回1,反之則返回0步咪。
int cvIsInf( double x );
函數(shù)cvIsNaN()
判斷某個浮點型數(shù)據(jù)是否是無效值论皆,如果是則返回1,反正則返回0猾漫。
int cvIsNan( double x );
函數(shù)cvRound()
用于獲取最接近某個浮點數(shù)的整數(shù)点晴,同樣的如果轉換后的結果超出了32為整型數(shù)據(jù)的表示范圍,則返回的值是未定義的悯周。
int cvRound( double x );
如果在編譯OpenCV庫時加入了OpenMP支持粒督,則可以使用函數(shù)cv::setNumThreads()
指定OpenCV在并行OpenMP領域使用的線程數(shù)量。默認的線程數(shù)量和CPU的邏輯核心相同禽翼,如對于一個有4個核心屠橄,每個核心有2個超線程的GPU族跛,該默認值為8。如果參數(shù)nthreads傳入0锐墙,則使用的線程數(shù)會被重置為默認值礁哄。
void cv::setNumThreads( int nthreads );
早期版本的OpenCV依賴于外部庫(如IPP)來獲得如SSE2指令集等高性能優(yōu)化,在后續(xù)的版本中溪北,這部分功能都會被默認遷移到OpenCV內部了桐绒,除非你在編譯OpenCV時明確的禁用了這些優(yōu)化。在程序中之拨,我們也可以通過調用函數(shù)cv::setUseOptimized()
來選擇禁用或者開啟這些優(yōu)化茉继。但是需要注意的是不用在程序運行過程中去調用該函數(shù),我們應該在自己應用的高抽象層蚀乔,確切的知道哪些函數(shù)會運行哪些不會時去調用這個函數(shù)烁竭。其原型如下。
void cv::setUseOptimized( bool on_off );
函數(shù)cv::useOptimized()
檢測當前是否已經(jīng)啟用了性能優(yōu)化乙墙,其原型如下颖变。
bool cv::useOptimized( void );
5 模版結構
到目前為止我們已將介紹了幾乎所有基本數(shù)據(jù)結結構的模版形式。OpenCV 2.1和之后的版本采用了和STL听想、Boost等庫相似的模版元編程風格腥刹。這種設計使得代碼能夠擁有更高的質量和效率,同時也給予了開發(fā)者更多自由度汉买。另外它還使得算法能夠以一種更抽象的衔峰,不依賴C++和OpenCV中具體基礎類型的方式去實現(xiàn)。
在我們實例化一個cv::Point
對象時蛙粘,實際上我們實例化的是模版特例化后的cv::Point_<int>
對象垫卤。其使用的模版也可以使用其他具體類型特例化,只要這些類型支持和int類型一樣的運算集合出牧,如加法穴肘、減法和乘法等。例如舔痕,可以使用類cv::Complex
來特例化模版類评抚,甚至你可以使用自己定義的類型。同樣的伯复,這些規(guī)則和定義對于其他模版類慨代,如cv::Scalar_<>
、cv::Rect_<>
啸如、cv::Matx_<>
以及cv::Vec_<>
也適用侍匙。
在實例化這些模版類時,你需要指定范型的具體類型叮雳,以及模版的尺寸想暗,常見的模版類及特例化時需要使用的參數(shù)如下表妇汗。
模版類定義 | 描述 |
---|---|
cv::Point_<Type T> | 由兩個范型對象組成的點 |
cv::Rect_<Type T> | 由一個頂點,寬和高組成的矩形说莫,類型均為范型T |
cv::Vec<Type T, int H> | H個范型數(shù)據(jù)的集合 |
cv::Matx<Type T, int H, int W> | H*W個范型數(shù)據(jù)的集合 |
cv::Scalar_<Type T> | 四個范型數(shù)據(jù)的集合铛纬,類似于cv::Vec<T, 4> |
6 圖片和大數(shù)組類型
在大數(shù)組類型中,最重要的類是cv::Mat
唬滑,它表示任意維度的稠密數(shù)組。這里稠密的意思指的是數(shù)組中的每個成員都有對應的內存空間存儲數(shù)據(jù)棺弊,例如大多數(shù)圖片都使用稠密數(shù)組的方式存儲晶密,即使存在了值為0的像素值,它們仍占據(jù)內存空間模她。與之相對的是稀疏數(shù)組稻艰,即cv::SparseMat
,該類型與稠密數(shù)組的區(qū)別在于它只會在內存中保存非零的成員侈净。需要注意的是對于稠密的數(shù)據(jù)尊勿,使用稀疏數(shù)組消耗的內存空間比稠密數(shù)組更大。一個常見的使用稀疏數(shù)組的例子是統(tǒng)計直方圖畜侦,它的大多數(shù)成員都是0元扔。
6.1 稠密矩陣
稠密數(shù)組中的數(shù)據(jù)可以被認為是按刪格掃描順序存儲的。對于一維數(shù)組旋膳,其存儲的數(shù)據(jù)是連續(xù)的澎语。對于二維數(shù)組,數(shù)據(jù)按行的方式組織验懊,下一行的數(shù)據(jù)存在該行數(shù)據(jù)之后擅羞。對于三維數(shù)據(jù),每個面都是逐行填充义图,然后再存儲下一個面的數(shù)據(jù)减俏。
每個實例對象包含一個flags屬性表示數(shù)組內容信息。dims
屬性表示數(shù)組的維度碱工。rows
和cols
屬性表示數(shù)組的行列數(shù)娃承,當數(shù)組維度大與2時這兩個屬性是無效的。data
指針屬性指向了數(shù)據(jù)的真實內存地址痛垛。refcount
屬性表示該部分數(shù)據(jù)的引用計數(shù)草慧,它的工作原理和智能指針cv::Ptr<>
使用引用計數(shù)類似。屬性step[]
記錄了數(shù)據(jù)的內存布局信息匙头,數(shù)組中每個元素可以用如下索引表示漫谷。
則數(shù)組中每個元素的內存地址可以通過如下公式計算得出。
對于二維稠密數(shù)組蹂析,這個公式可以簡寫為如下形式舔示。
cv::Mat
中不僅可以存儲最基本的數(shù)據(jù)類型碟婆,也可以存儲其他數(shù)據(jù)類型。該實例中的每個元素可以時一個數(shù)字或者多個數(shù)組惕稻,后者被稱為多通道數(shù)組竖共。稠密數(shù)組存儲的數(shù)據(jù)在內存中布局時會考慮內存對齊問題,如對于一個2維3通道數(shù)組俺祠,每個通道都是32位的浮點數(shù)據(jù)公给,這意味著每個元素需要占據(jù)12個字節(jié)的內存空間,在內存布局時蜘渣,首先每行數(shù)據(jù)內部一定是連續(xù)的淌铐,但是在行間可能會存在一個很小的間隔使得每行的首地址以一定的規(guī)則對齊,從而提升數(shù)據(jù)讀取效率蔫缸。
6.1.1 創(chuàng)建稠密矩陣
你可以通過實例化一個cv::Mat的對象來創(chuàng)建一個數(shù)組腿准,但是得到的數(shù)組對象是沒有大小和數(shù)據(jù)類型的∈奥担或者你也可以使用如下方式來創(chuàng)建數(shù)組對象吐葱,需要注意的是數(shù)據(jù)類型同時指定了數(shù)據(jù)的格式,以及通道數(shù)校翔。它可以理解為形如格式CV_{8U,16S,16U,32S,32F,64F}C{1,2,3}弟跑,其中第一個數(shù)字表示的是每個通道的數(shù)據(jù)類型,而第二個數(shù)字表示的是通道數(shù)展融,如CV_32FC3表示3通道的32位浮點型數(shù)據(jù)窖认。
cv::Mat m;
// 創(chuàng)建一個包含3通道32位浮點型數(shù)據(jù)的3行10列數(shù)組
m.create(3, 10, CV_32FC3);
// 設置其中每個元素的初始值為(1.0,0.0告希,1.0)
m.setTo(cv::Scalar(1.0f, 0.0f, 1.0f));
// 也可以通過如下方式創(chuàng)建
cv::Mat m(3, 10, CV_32FC3, cv::Scalar(1.0f, 0.0f, 1.0f));
需要注意的是cv::Mat
實例和其真正存儲的數(shù)據(jù)是分離開的扑浸,cv::Mat
實例僅僅包含數(shù)據(jù)的一些描述信息,以及指向真是數(shù)據(jù)的內存地址燕偶。其包含的真實數(shù)據(jù)也是通過類似智能指針的方式管理的喝噪,這意味著當你有兩個數(shù)組對象m和n時,如果你執(zhí)行了賦值操作m=n
指么,則首先所有與m共享真實數(shù)據(jù)的實例對應的引用計數(shù)會-1酝惧,如果其引用計數(shù)已經(jīng)為0,則真實的數(shù)據(jù)會被釋放伯诬,內存被回收晚唇。然后m的data指針會指向n所包含的真實數(shù)據(jù),并從n中讀取該數(shù)據(jù)段段引用計數(shù)盗似,將其加1并更新所有共享這部分真實數(shù)據(jù)的數(shù)組對象中的引用計數(shù)哩陕。最后m對象再從n中更新對于真實數(shù)據(jù)的描述信息。
下面列舉了所有可用的基本數(shù)組構造方法。
// 默認構造方法
cv::Mat;
// 二維數(shù)組悍及,指定行列闽瓢,數(shù)據(jù)類型的構造方法
cv::Mat(int rows, int cols, int type);
// 二維數(shù)組,指定行列心赶,數(shù)據(jù)類型和默認值的構造方法
cv::Mat(int rows, int cols, int type,
const Scalar& s);
// 二維數(shù)組扣讼,指定行列,數(shù)據(jù)類型缨叫,使用已有數(shù)據(jù)的構造方法
cv::Mat(int rows, int cols, int type,
void* data, size_t step=AUTO_STEP);
// 二維數(shù)組椭符,指定尺寸,數(shù)據(jù)類型的構造方法
cv::Mat(cv::Size sz, int type);
// 二維數(shù)組耻姥,指定尺寸艰山,數(shù)據(jù)類型和默認值的構造方法
cv::Mat(cv::Size sz, int type,
const Scalar& s);
// 二維數(shù)組,指定尺寸咏闪,數(shù)據(jù)類型,使用已有數(shù)據(jù)的構造方法
cv::Mat(cv::Size sz, int type,
void* data, size_t step=AUTO_STEP);
// 多維數(shù)組摔吏,指定尺寸鸽嫂,數(shù)據(jù)類型的構造方法
cv::Mat(int ndims, const int* sizes, int type);
// 多維數(shù)組,指定尺寸征讲,數(shù)據(jù)類型和默認值的構造方法
cv::Mat(int ndims, const int* sizes, int type,
const Scalar& s);
// 多維數(shù)組据某,指定尺寸,數(shù)據(jù)類型诗箍,使用已有數(shù)據(jù)的構造方法
cv::Mat(int ndims, const int* sizes, int type,
void* data, size_t step=AUTO_STEP);
下面列舉了所有可用的拷貝數(shù)組構造方法癣籽。
// 拷貝構造函數(shù)
cv::Mat(const Mat& mat);
// 拷貝另外一個矩陣的指定行列
cv::Mat(const Mat& mat,
const cv::Range& rows, const cv::Range& cols);
// 拷貝另外一個矩陣的指定區(qū)域
cv::Mat(const Mat& mat,
const cv::Rect& roi);
// 拷貝另外一個n維矩陣的指定區(qū)域,通過數(shù)組指定每個維度的范圍
cv::Mat(const Mat& mat,
const cv::Range* ranges);
// 使用另外一個數(shù)組的線性代數(shù)結構來構造一個新的數(shù)組
cv::Mat(const cv::MatExpr& expr);
如果你還在維護2.1版本之前的OpenCV滤祖,你可以使用如下方法將過時的數(shù)據(jù)結構轉換成新的數(shù)據(jù)結構筷狼。下面代碼中的參數(shù)copyData設
置為false
時將創(chuàng)建矩陣描述信息,并使用已存在的數(shù)據(jù)匠童。當該參數(shù)設置為true
時埂材,將會分配一片新的內存空間,并拷貝原始數(shù)據(jù)汤求。
// 使用CvMat構造數(shù)組對象
cv::Mat(const CvMat* old, bool copyData=false);
// 使用IplImage構造數(shù)組對象
cv::Mat(const IplImage* old, bool copyData=false);
實際上這些構造函數(shù)隱藏了很多額外的操作俏险,特別是它允許表達式混合C++和C數(shù)據(jù)類型,另外隱式的構造函數(shù)會生成需要的C++數(shù)據(jù)類型扬绪。這樣竖独,在需要cv::Mat
類型的地方簡單的傳入C結構體的指針也能夠使得函數(shù)正常工作,這也是參數(shù)copyData為什么默認值是false的原因挤牛。此外莹痢,也有一些轉換符號能夠將cv::Mat
對象轉換為CvMat
或者IplImage
,這些轉換也不會拷貝數(shù)據(jù)。
此外還可以使用一些模版類型來構造cv::Mat
對象格二,這些模版構造函數(shù)如下劈彪。
// 構造1維數(shù)組,使用類型為T顶猜,尺寸為N的有限向量
cv::Mat(const cv::Vec<T,n>& vec, bool copyData=true);
// 構造2維數(shù)組沧奴,使用類型為T,尺寸為m*n的有限矩陣
cv::Mat(const cv::Matx<T,m,n>& vec, bool copyData=true);
// 構造1維數(shù)組长窄,使用類型為T的STL向量
cv::Mat(const std::vector<T>& vec, bool copyData=true);
類cv::Mat
也提供一些靜態(tài)方法來創(chuàng)建一些特殊的矩陣滔吠,如0矩陣、1矩陣和單位矩陣挠日。
// 構建元素全為0的矩陣
cv::Mat::zeros(rows, cols, type);
// 構建元素全為1的矩陣
cv::Mat::ones(rows, cols, type);
// 構建單位矩陣疮绷,即該矩陣和某個符合乘法規(guī)律的向量結果仍然為原向量
cv::Mat::eye(rows, cols, type);
6.1.2 訪問稠密矩陣元素
OpenCV中可以通過位置或者迭代器訪問矩陣中的元素。使用位置訪問是最簡單也是最直接的嚣潜,類cv::Mat
提供了模版函數(shù)at<>()
及大量有著不同參數(shù)組合的變體來訪問矩陣的中某個元素冬骚,其使用方式如下。
// 訪問單通道的矩陣元素
cv::Mat m1 = cv::Mat::eye(10, 10, CV_32FC1);
printf("Element (3,3) is %f\n", m1.at<float>(3,3));
// 訪問多通道的矩陣元素
cv::Mat m2 = cv::Mat::eye(10, 10, CV_32FC2);
printf("Element (3,3) is (%f,%f)\n",
m2.at<cv::Vec2f>(3,3)[0], m2.at<cv::Vec2f>(3,3)[1]);
當然你也可以使用更復雜的數(shù)據(jù)類型來實例化矩陣對象懂算,如使用復數(shù)只冻。這里需要注意在構建矩陣實例時,需要傳入一個int
類型的參數(shù)確定基本數(shù)據(jù)類型计技,這里基本數(shù)據(jù)類型是cv::Complexf
喜德,但這是編譯時的數(shù)據(jù)結構,可以通過cv::traits::Type<cv::Complexf>::value
生成其對應的運行時類型標識垮媒。在OpenCV3.3以前舍悯,需要使用cv::DataType<cv::Complexf>::type
生成該類型。
cv::Mat m = cv::Mat::eye(10, 10, cv::traits::Type<cv::Complexf>::value);
printf("Element (3,3) is %f + i%f\n",
m.at<cv::Complexf>(3,3).re, m.at<cv::Complexf>(3,3).im);
所有可用的模版函數(shù)at<>()
的變體列舉如下睡雇。
// 獲取一維整型矩陣中的某個元素
M.at<int>(i);
// 獲取二維浮點型矩陣中的某個元素
M.at<float>(i, j);
// 獲取二維整型矩陣中的某個元素萌衬,(pt.x,pt.y)表示二維某個點
M.at<int>(pt);
// 獲取三維浮點型矩陣中的某個元素
M.at<float>(i, j, k);
// 獲取n維無符號字符型矩陣中的某個元素它抱,idx[]表示n維空間中的某個點
M.at<uchar>(idx);
訪問2維矩陣時奄薇,通過cv::Mat
內部定義的模版函數(shù)ptr<>()
,可以使用C語音風格的指針訪問矩陣內的某一行元素抗愁。需要注意的是矩陣是按行存儲數(shù)據(jù)的馁蒂,因此無法通過該方式獲取某一列的數(shù)據(jù),稍后會講到獲取某列數(shù)據(jù)的正確方法蜘腌。該函數(shù)的返回值是指向矩陣構成的最基本元素的指針沫屡,也就是說如果矩陣的每個元素類型是CV_32FC3
,則該函數(shù)的返回值是float*類型的變量撮珠。即對于由該數(shù)據(jù)類型構成的矩陣mtx
沮脖,通過mtx.ptr<Vec3f>(3)
的到的是第4行的第一個元素的第一個通道的數(shù)據(jù)地址。
使用函數(shù)at<>()
和指針訪問的性能差異取決于編譯器的優(yōu)化。如果優(yōu)化足夠好勺届,則通過函數(shù)at<>()
訪問元素的效率僅僅比通過指針訪問的方式差一點驶俊,如果編譯優(yōu)化被關閉(如使用在調試環(huán)境編譯),則其效率將會相對于使用指針的方式差上一個數(shù)量級以上免姿。
獲取矩陣中某個數(shù)據(jù)的地址方式除了使用函數(shù)ptr<>()
外饼酿,還可以使用整個數(shù)據(jù)段的指針即data
屬性,然后通過保存內部布局信息的step[]
屬性計算胚膊。由于函數(shù)at<>()
和ptr<>()
故俐,以及迭代器的存在,這種方式通常不被推薦紊婉,但是不得不承認药版,直接計算地址的方式是最有效率的,特別是當你在處理一個2維以上的矩陣時喻犁。
在訪問矩陣內部所有元素時槽片,通常我們需要逐行訪問,這是因為矩陣內部每行數(shù)據(jù)在內存中的存儲可能是連續(xù)的肢础,也可能是不連續(xù)的筐乳。當然我們也可以使用函數(shù)isContinuous()
判斷內存布局,如果其布局是連續(xù)的乔妈,那么我們也可以拿到第一個元素的指針,然后再循環(huán)遍歷氓皱。但是這種方式是不推薦的路召,我們推薦使用后面即將講到的迭代器方式來訪問矩陣內部元素。
連續(xù)訪問矩陣內部的數(shù)據(jù)可以使用cv::Mat
內置的迭代器機制波材。該機制是建立在STL內部提供的迭代器機制股淡,但是有一定的差異。OpenCV提供了一對迭代器模版分別用于處理常量矩陣和非常量矩陣廷区,它們分別是cv::MatIterator<>
和cv::MatConstIterator<>
唯灵。cv::Mat
的方法begin()和end()將會返回對應迭代器類型的實例。迭代器內部會處理多維數(shù)組和內存不連續(xù)布局的情況隙轻,我們不需要再處理這部分邏輯埠帕。
迭代器在聲明時都必須指定矩陣內部的元素類型,下面是一個計算3維3通道矩陣中最長元素(各通道平方和最大)的示例玖绿。
int sz[3] = {4, 4, 4};
// 3維3通道敛瓷,基礎數(shù)據(jù)類型為32位float的矩陣
cv::Mat m(3, sz, CV_32FC3);
// 填充[-1.0, 1.0]的隨機數(shù)
cv::randu(m, -1.0f, 1.0f);
float max = 0.0f;
cv::MatConstIterator_<cv::Vec3f> it = m.begin<cv::Vec3f>();
while (it != m.end<cv::Vec3f>()) {
float len2 = (*it)[0]*(*it)[0]+(*it)[1]*(*it)[1]+(*it)[2]*(*it)[2];
if (len2 > max) {
max = len2;
}
it++;
}
矩陣數(shù)組迭代器
OpenCV還提供了矩陣數(shù)組迭代器類cv::NAryMatIterator
,盡管它不能很好的處理矩陣內部內存不連續(xù)布局的問題斑匪,但是它能夠很方便的對多個矩陣數(shù)組同步迭代呐籽。它不像普通的矩陣迭代器類返回指向某個元素的實例,而是返回指向某段數(shù)據(jù)的實例。這段數(shù)據(jù)被稱為一個平面狡蝶,它可能是任意維度庶橱,這取決于被迭代矩陣的維度,它們是相同的贪惹。每個平面內部的所有元素在內存中是連續(xù)分布的苏章,也就是說我們可以通過數(shù)組操作符去訪問該平面內的所有元素馍乙,或者通過一般遍歷方式去訪問它們刨啸,而不需要在考慮內存間隔的問題。需要注意的是菇爪,矩陣數(shù)組迭代器只能處理具有相同幾何結構的矩陣碴萧,也就是說它們的維度和每個維度的具體長度都必須相同。一個簡單的使用該類的例子如下显蝌。程序SummationOfNDArray
// 創(chuàng)建5*5*5的矩陣预伺,每個元素類型為單通道32位浮點型
const int n_mat_size = 5;
const int n_mat_sz[] = { n_mat_size, n_mat_size, n_mat_size };
cv::Mat n_mat = cv::Mat(3, n_mat_sz, CV_32FC1);
// 使用均勻分布類型隨機數(shù)填充矩陣,隨機數(shù)取值區(qū)間為[0.0f, 1.0f]
cv::RNG rng = cv::RNG();
rng.fill(n_mat, cv::RNG::UNIFORM, 0.0f, 1.0f);
// 創(chuàng)建數(shù)組矩陣迭代器
const cv::Mat* arrays[] = {&n_mat, 0};
// 這里的參數(shù)my_planes是用于迭代器保存多個數(shù)組當前被迭代的平面數(shù)組曼尊,即每次迭代
// 后該數(shù)組內部的值都會更新
cv::Mat my_planes[1];
cv::NAryMatIterator it = cv::NAryMatIterator(arrays, my_planes);
// 求矩陣中所有元素的和
float s = 0.0f;
for (int p = 0; p < it.nplanes; p++, ++it) {
// 這里使用it.planes和my_planes等價酬诀,均表示多個數(shù)組的當前平面組成的數(shù)組
// 由于只迭代了1個數(shù)組,因此取索引值0的平面
s += cv::sum(it.planes[0])[0];
}
在上面的例子中骆撇,我們只遍歷了單個矩陣瞒御,下面是使用數(shù)組矩陣迭代器遍歷多個矩陣的例子。程序SummationOfNDArrays
// 創(chuàng)建2個5*5*5的矩陣對象
const int n_mat_size = 5;
const int n_mat_sz[] = {n_mat_size, n_mat_size, n_mat_size};
cv::Mat n_mat0(3, n_mat_sz, CV_32FC1);
cv::Mat n_mat1(3, n_mat_sz, CV_32FC1);
// 使用區(qū)間[0,1]的浮點型隨機數(shù)填充著兩個矩陣
cv::RNG rng;
rng.fill( n_mat0, cv::RNG::UNIFORM, 0.f, 1.f );
rng.fill( n_mat1, cv::RNG::UNIFORM, 0.f, 1.f );
// 創(chuàng)建矩陣數(shù)組迭代器
const cv::Mat* arrays[] = {&n_mat0, &n_mat1, 0};
cv::Mat my_planes[2];
cv::NAryMatIterator it(arrays, my_planes);
// 遍歷所有平面并求兩個矩陣所有元素的和
float s = 0.f;
for (int p = 0; p < it.nplanes; p++, ++it) {
// planes[]中每個元素依次為每個被遍歷的數(shù)組當前平面的元素集合
s += cv::sum(it.planes[0])[0];
s += cv::sum(it.planes[1])[0];
}
除了使用函數(shù)cv::sum()
求某個平面的和之外神郊,還可以使用指針訪問平面中的每個元素肴裙,并累加求和。此時還需要使用到另外一個cv::NAryMatIterator
的實例屬性size
涌乳,它表示每個平面的元素個數(shù)蜻懦,但是需要注意的是它不包含元素的通道數(shù)。一個使用指針訪問平面內元素并進行數(shù)學計算的例子如下夕晓。
// 創(chuàng)建3個矩陣
cv::Mat src1, src2;
cv::Mat dst;
// 構建數(shù)組矩陣迭代器
const cv::Mat* arrays[] = {&src1, &src2, &dst, 0};
float* ptrs[3];
cv::NAryMatIterator it(arrays, (uchar**)ptrs);
// 使用指針的方式訪問元素并執(zhí)行數(shù)學運算
for (size_t i = 0; i < it.nplanes; i++, ++it) {
for (size_t j = 0; j < it.size; j++) {
ptrs[2][j] = std::pow(ptrs[0][j], ptrs[1][j]);
}
}
6.1.3 獲取子矩陣
在前面的小節(jié)中介紹了如何獲取數(shù)組中的每個元素宛乃,此外類cv::Mat提供了如下一系列的實例方法獲取一個矩陣的子矩陣。
函數(shù) | 描述 |
---|---|
m.row( i ); | 獲取某行數(shù)據(jù) |
m.col( j ); | 獲取某列數(shù)據(jù) |
m.rowRange( i0, i1 ); | 獲取從i0行到i1-1行的數(shù)據(jù) |
m.rowRange( cv::Range( i0, i1 ) ); | 獲取從i0行到i1-1行的數(shù)據(jù) |
m.colRange( j0, j1 ); | 獲取從j0行到j1-1列的數(shù)據(jù) |
m.colRange( cv::Range( j0, j1 ) ); | 獲取從j0行到j1-1列的數(shù)據(jù) |
m.diag( d ); | 獲取距離主對角線距離為d的對角線數(shù)據(jù) |
m( cv::Range(i0,i1), cv::Range(j0,j1) ); | 獲取由頂點(i0, j0)和(i1-1, j1-1)包圍的矩形區(qū)域數(shù)據(jù) |
m( cv::Rect(i0,i1,w,h) ); | 獲取由頂點(i0, j0)和(i0+w-1, j1+h-1)包圍的矩形區(qū)域數(shù)據(jù) |
m( ranges ); | 獲取由數(shù)組ranges確定的子矩陣數(shù)據(jù) |
需要注意的是使用上述方法獲取到子矩陣的時候蒸辆,得到的類cv::Mat
的實例和原矩陣共享數(shù)據(jù)段征炼,也就是說如果在類似m2 = m.row(3)
表達示之后修改m
中對應的數(shù)據(jù),則m2
所包含的數(shù)據(jù)也會改變躬贡。如果確實需要拷貝數(shù)據(jù)柒室,需要使用函數(shù)copyTo()
。這種設計的好處是避免了數(shù)據(jù)拷貝時的性能消耗逗宜,這種消耗并不是很低雄右,它和原始矩陣和即將創(chuàng)建的矩陣的大小相關空骚。在通過指定范圍截取子矩陣時需要注意該區(qū)間是包含起始點,不包含終止點的半閉半開區(qū)間擂仍。在使用函數(shù)diag()截取子矩陣時囤屹,如果參數(shù)為正,表示向上偏移取對角線逢渔,如果為負則表示向下偏移取對角線肋坚。最后一個函數(shù)是唯一的一個取高維矩陣子矩陣的方法。
6.1.4 矩陣表達式
OpenCV從2.1版本開始引入C++后獲得的一個能力就是運算符的重載肃廓,這種機制使得我們能夠用簡單有意義的表達式來完成復雜的操作智厌。在這些運算符的背后OpenCV利用了很多矩陣類自身的特性來完成這些操作。例如矩陣頭在需要的時候會被自動創(chuàng)建盲赊,工作區(qū)數(shù)據(jù)只有在需要的時候才會分配內存空間铣鹏,并且在不需要的時候數(shù)據(jù)區(qū)域會自動釋放內存,最終計算的結果會被通過運算符“=表達式“放到左側的矩陣中哀蘑。需要注意的是這種形式并不是賦值一個cv::Mat
實例诚卸,盡管表面上看上去似乎是這樣,實際上這種形式更像是一個cv::MatExpr
绘迁,即矩陣表達式合溺。cv::MatExpr
的底層機制很復雜,當前我們并不需要了解缀台,可以簡單的將其理解為等式右側的代數(shù)符合表達棠赛,它的好處是能夠移除或者修改一些不必要的表達式,如求矩陣轉置的轉置膛腐,加0矩陣睛约,乘逆矩陣等。最重要的區(qū)別是這種矩陣表達式的運算符一定會創(chuàng)建一片新的內存區(qū)域用于存儲數(shù)據(jù)依疼,并得到一個新的矩陣實例。
考慮代碼m2=m1
而芥,這里只是將m2
實例的引用指向m1
律罢。再考慮代碼m2=m1+m0
,這里m1+m0
是矩陣表達式棍丐,將會計算出一個新的矩陣误辑,并分配一片新的內存區(qū)域來存儲對應數(shù)據(jù),將新矩陣的引用賦值給m2
歌逢。
下表列出了可用的矩陣表達式及一些方法的示例巾钉,這里出了簡單的表達式之外,還例舉了一些方法來執(zhí)行一些如計算轉置和逆矩陣等高級操作秘案,其中有些是之前已經(jīng)遇到的砰苍,如cv::Mat::eye()
潦匈。使用下表的一些表達式的關鍵在于如果使用一行簡潔清晰的代碼來應對你實際遇到的計算機視覺問題。
示例 | 描述 |
---|---|
m0 + m1, m0 – m1; | 矩陣的加法和減法 |
m0 + s; m0 – s; s + m0, s – m1; | 矩陣和標量的加減法運算 |
-m0; | 矩陣求負 |
s * m0; m0 * s; | 使用標量縮放矩陣 |
m0.mul( m1 ); m0/m1; | 矩陣逐元素相乘和相除 |
m0 * m1; | 矩陣的乘法運算 |
m0.inv( method ); | 求逆矩陣(默認使用DECOMP_LU) |
m0.t(); | 求轉置矩陣赚导,無數(shù)據(jù)拷貝 |
m0>m1; m0>=m1; m0==m1; m0<=m1; m0<m1; | 逐元素比較茬缩,返回的矩陣元素數(shù)據(jù)類型為nchar,取值為0或者255 |
m0&m1; m0與m1; m0^m1; ~m0; m0&s; s&m0; m0與s; s與m0; m0^s; s^m0; |
在矩陣之間或者矩陣和標量之間逐元素執(zhí)行按位邏輯運算 |
min(m0,m1); max(m0,m1); min(m0,s); min(s,m0); max(m0,s); max(s,m0); |
在矩陣之間或者矩陣和標量之間逐元素取最小或者最大值 |
cv::abs( m0 ); | 逐元素取絕對值 |
m0.cross( m1 ); m0.dot( m1 ); | 向量的叉乘和點乘運算吼旧,僅適用于對3??1矩陣 |
cv::Mat::eye( Nr, Nc, type ); cv::Mat::zeros( Nr, Nc, type ); cv::Mat::ones( Nr, Nc, type ); |
返回類型為type的Nr??Nc矩陣凰锡,類方法 |
函數(shù)inv()
在求逆矩陣時可用指定使用的數(shù)學方法。第一種是cv::DECOMP_LU
圈暗,即使用LU分解掂为,該方法對所有的非奇異矩陣都有效。第二種方式是cv::DECOMP_CHOLESKY
员串,它使用Cholesky分解求逆矩陣勇哗,這種方式只對半正定矩陣有效,在處理大矩陣時昵济,其效率明顯高于LU分解智绸。最后一種是cv::DECOMP_SVD
,他使用奇異值分解(Singular Value Decomposition, SVD)求逆矩陣访忿,在處理奇異和非方陣的矩陣時瞧栗,它是唯一可用的方法,此時得到的是矩陣的偽逆海铆。
盡管上表中并未包含如cv::norm()
迹恐、cv::mean()
、cv::sum()
等函數(shù)卧斟,但是你仍可以在矩陣表達式中使用殴边。
6.1.5 飽和轉換
在OpenCV中,在執(zhí)行數(shù)學運算時珍语,特別是對無符號類型數(shù)據(jù)執(zhí)行減法運算時锤岸,會有數(shù)據(jù)溢出的風險,OpenCV依賴飽和轉換(Satruration Casting)的機制來處理這個問題板乙。也就是說OpenCV內部的算法以及對數(shù)據(jù)的其他操作時會自定的檢查數(shù)據(jù)溢出情況是偷。在數(shù)據(jù)溢出時,OpenCV的函數(shù)會使用該類型最大或者最小的值來替換運算結果募逞,需要注意的是這種操作并不是C語言本身提供的蛋铆。
另外OpenCV也提供了模版函數(shù)cv::saturate_cast<>()
使得我們可以指定某個數(shù)據(jù)類型來對某個計算結果執(zhí)行飽和轉換操作,如下面的示例放接。
uchar& Vxy = m0.at<uchar>( y, x );
Vxy = cv::saturate_cast<uchar>((Vxy-128)*2 + 128);
在上面的例子中刺啦,假定Vxy
的值為10,則按照C語言的規(guī)則計算的結果將會是-108纠脾,顯然這已經(jīng)超出了無符號字符型能夠表達的范圍玛瘸,因此飽和轉換函數(shù)會將計算結果替代為0蜕青。
6.1.6 其余的矩陣操作
到目前為止已經(jīng)介紹了大部分類cv::Mat
的操作,剩余的一些無法分到前面所討論的特定主題下的操作如下表捧韵。
示例 | 描述 |
---|---|
m1 = m0.clone(); | 深拷貝一個矩陣市咆,會實際拷貝真正的數(shù)據(jù)段,得到的矩陣是連續(xù)的 |
m0.copyTo( m1 ); | 將矩陣m0的內容拷貝到矩陣m1再来,如果m1指向的數(shù)據(jù)引用計數(shù)變?yōu)?會自動釋放其內存 等同于m1=m0.clone() |
m0.copyTo( m1, mask ); | 在上一個函數(shù)的基礎上蒙兰,只復制mask指示的區(qū)域 |
m0.convertTo( m1, type, scale, offset ); |
轉換矩陣m0中的元素為類型type(如CV_32F),并在此基礎上乘以縮放系數(shù)scale(默認1)芒篷,再偏移offset(默認0)并將最后的結果賦值給m1 |
m0.assignTo( m1, type ); | 只能在內部使用搜变,集成在函數(shù)convertTo中 |
m0.setTo( s, mask ); | 將矩陣m0中的所有元素賦值為s,如果存在mask针炉,則只操作mask對應的區(qū)域(即mask中的非0元素) |
m0.reshape( chan, rows ); | 改變二維數(shù)組的有效形狀挠他,參數(shù)chan和rows為0的時候表示不需要更改,矩陣的數(shù)據(jù)不會被拷貝 |
m0.push_back( s ); | 擴展一個由元素s組成的m??1的矩陣 |
m0.push_back( m1 ); | 將矩陣m1點數(shù)據(jù)拷貝到矩陣m0的最后一行后篡帕,需要注意矩陣m1和m0必須有相同的列數(shù) |
m0.pop_back( n ); | 移除矩陣m0點末尾n行數(shù)據(jù)殖侵,參數(shù)n默認值為1 |
m0.locateROI( size, offset ); | 將矩陣m0的尺寸寫入到cv::Size類型的參數(shù)size中,如果m0只是一個大矩陣的部分镰烧,則還會將其在所屬大矩陣中的偏移值寫入到參數(shù)offset中 |
m0.adjustROI( t, b, l, r ); | 向上擴展t個拢军,向左擴展l個,向下擴展b個怔鳖,向右擴展r個像素 |
m0.total(); | 矩陣矩陣的元素個數(shù)茉唉,不包含通道數(shù) |
m0.isContinuous(); | 判斷矩陣內部的數(shù)據(jù)段是否是緊密包裝的連續(xù)數(shù)據(jù) |
m0.elemSize(); | 返回矩陣的元素大小,如3通道的浮點型元素將會返回12字節(jié) |
m0.elemSize1(); | 返回矩陣的基本元素大小结执,如3通道的浮點型元素會返回4字節(jié) |
m0.type(); | 返回元素類型的標識符度陆,如CV_32FC3 |
m0.depth(); | 返回元素的單通道數(shù)據(jù)類型的標示符,如CV_32 |
m0.channels(); | 返回元素的通道數(shù) |
m0.size(); | 返回一個cv::Size的數(shù)據(jù)描述矩陣的大小 |
m0.empty(); | 判斷矩陣中是否包含元素 |
6.2 稀疏矩陣
稀疏矩陣cv::SparseMat
用于在某個矩陣內部非零元素占比很小的場景献幔,通常見于高維矩陣中描述直方圖等數(shù)據(jù)懂傀,因為在實際的應用的這種場景中大部分數(shù)據(jù)都是空的。稀疏矩陣僅存儲需要展示的數(shù)據(jù)蜡感,因此能夠節(jié)省大量的內存蹬蚁。但是當稀疏矩陣執(zhí)行逐元素的計算邏輯時,其效率要低于稠密矩陣铸敏。需要注意的是這種計算方式并不一定非常慢缚忧,如果能夠提前知道哪些操作可以避免能夠節(jié)約很多計算時間悟泵。
稀疏矩陣在很多地方都與稠密矩陣類似杈笔,它們具有類似的定義,支持很多相同的操作糕非,能夠容納同樣類型的數(shù)據(jù)蒙具。但是在矩陣內部球榆,數(shù)據(jù)以完全不同的方式組織。類cv::Mat
的數(shù)據(jù)存儲方式和C語言的數(shù)組類似禁筏,數(shù)據(jù)以連續(xù)的方式存儲持钉,并且每個元素的地址可以通過元素下標計算得到。但是在類cv::SparseMat
中篱昔,使用了哈希表的方式來存儲非零元素每强。(實際上當某些元素經(jīng)過計算后為0時仍然會被存儲,如果想要清理這些元素必須手動處理州刽,稍后會介紹函數(shù)SparseMat::erase()
)OpenCV會自動維護該哈希表空执,當非零元素的數(shù)量變得太大不能夠執(zhí)行高效查詢時,該哈希表會自動增長穗椅。
6.2.1 訪問稀疏矩陣內的元素
稀疏矩陣和稠密矩陣之間最大的區(qū)別是訪問內部元素的方式辨绊,稀疏矩陣提供了四種方式,分別為cv::SparseMat::ptr()
匹表、cv::SparseMat::ref()
门坷、cv::SparseMat::value()
、和cv::SparseMat::find()
袍镀。方法cv::SparseMat::ptr()
包含多個變體默蚌,其中最簡單的一個如下。
uchar* cv::SparseMat::ptr( int i0, bool createMissing, size_t* hashval=0 );
該方法可以訪問一維稀疏數(shù)組中的元素流椒,參數(shù)i0
是需要訪問元素的索引敏簿。參數(shù)createMissing
表示如果訪問的元素不被包含在稀疏數(shù)組中,是否需要創(chuàng)建這個元素宣虾。前面已經(jīng)講過惯裕,稀疏數(shù)組底層實現(xiàn)是哈希表,在哈希表中查數(shù)據(jù)包含計算哈希值和查找和該值關聯(lián)的列表绣硝,通常情況下這個關聯(lián)的列表會很短蜻势,理想情況是只有1個元素。其中計算哈希值是需要耗費性能的鹉胖,如果已經(jīng)通過如函數(shù)cv::SparseMat::hash()
計算過其哈希值握玛,則在下次查找時可以省略這個過程。因此在函數(shù)cv::SparseMat::ptr()
中第四個參數(shù)hashval
表示的就是這個哈希值甫菠,當其為NULL時會計算這個值挠铲,否則使用外部提供的值。
該函數(shù)的其他變體還通過提供多個索引參數(shù)的方式訪問多維數(shù)組寂诱,甚至還可以通過一個索引數(shù)組的方式去訪問高維數(shù)組拂苹。在所有的變體中該函數(shù)的返回值都指向類型uchar
的指針,需要根據(jù)實際情況做類型強轉痰洒。
函數(shù)SparseMat::ref<>()
返回指定元素的引用瓢棒,它接收和函數(shù)SparseMat::ptr()
相同的參數(shù)浴韭,該函數(shù)是模版函數(shù),其使用示例如下脯宿。
a_sparse_mat.ref<float>( i0, i1 ) += 1.0f;
模版函數(shù)cv::SparseMat::value<>()
和SparseMat::ref<>()
基本相同念颈,區(qū)別在于前者返回的是具體的值,并且該值為常量连霉,禁止修改榴芳,而后者返回的是引用。模版函數(shù)cv::SparseMat::find<>()
和前兩個函數(shù)也類似跺撼,區(qū)別在于它返回的是指向特定元素和特定類型的指針翠语,為例代碼整潔,能夠使用該函數(shù)時不要使用函數(shù)cv::SparseMat::ptr()
财边。另外該函數(shù)的返回值是一個常量肌括,不可更改,因此這兩個函數(shù)并不總是可以互換的酣难。
除了直接訪問外谍夭,還可以使用迭代器的方式訪問某個元素。和稠密矩陣類似憨募,OpenCV提供了模版迭代器類cv::SparseMa?t?Iterator_<>
和cv::SparseMatConstIterator_<>
紧索,同時稀疏矩陣類也提供了成員變量cv::SparseMat::begin<>()
和cv::SparseMat::end<>()
來獲取指向第一個元素后末尾元素的迭代器。另外非模版類的稀疏矩陣返回的是非模版類的迭代器類cv::SparseMatIterator
和cv::SparseMatConstIterator
菜谣。示例程序PrintSparseArray打印了一個稀疏矩陣中的所有非零元素珠漂。
int size[] = {10,10};
cv::SparseMat sm( 2, size, CV_32F );
for( int i=0; i<10; i++ ) {
int idx[2];
idx[0] = size[0] * rand();
idx[1] = size[1] * rand();
sm.ref<float>( idx ) += 1.0f;
}
cv::SparseMatConstIterator_<float> it = sm.begin<float>();
cv::SparseMatConstIterator_<float> it_end = sm.end<float>();
for(; it != it_end; ++it) {
const cv::SparseMat::Node* node = it.node();
printf(" (%3d,%3d) %f\n", node->idx[0], node->idx[1], *it);
}
在上面的例子中,迭代器的方法node()
返回了該迭代器指向的數(shù)組中某個元素的節(jié)點信息類cv::SparseMat::Node
的實例尾膊,其定義如下媳危。其中對于同一個元素,成員變量hashval
和使用函數(shù)SparseMat::ptr()
冈敛、SparseMat::ref()
待笑、SparseMat::value()
和SparseMat::find()
時指定的哈希值相同。cv::SparseMat::Node
定義如下抓谴。
struct Node {
size_t hashval;
size_t next;
int idx[cv::MAX_DIM];
};
6.2.2 稀疏矩陣獨有函數(shù)
稀疏矩陣獨有函數(shù)如下暮蹂。
示例 | 描述 |
---|---|
cv::SparseMat sm; | 聲明一個稀疏矩陣 |
cv::SparseMat sm( 3, sz, CV_32F ); | 創(chuàng)建一個三維的稀疏矩陣 |
cv::SparseMat sm( sm0 ); | 使用已有稀疏矩陣sm0創(chuàng)建新的實例 |
cv::SparseMat( m0, try1d ); | 使用已有稠密矩陣m0創(chuàng)建稀疏矩陣,如果參數(shù)try1d為true癌压,則會講1??n或者n??1的稠密矩陣壓縮為1維稀疏矩陣 |
cv::SparseMat( &old_sparse_mat ); | 使用OpenCV2.1之前的版本中C語言風格的稀疏矩陣CvSparseMat創(chuàng)建新版本的矩陣實例 |
CvSparseMat* old_sm = (cv::SparseMat *) sm; | 將新版本的稀疏矩陣實例轉換為OpenCV2.1之前的版本中C語言版本CvSparseMat實例 |
size_t n = sm.nzcount(); | 查詢矩陣中非0元素的個數(shù) |
size_t h = sm.hash( i0 ); size_t h = sm.hash( i0, i1 ); size_t h = sm.hash( i0, i1, i2 ); size_t h = sm.hash( idx ); |
返回矩陣中某個元素的哈希值 |
sm.ref<float>( i0 ) = f0; sm.ref<float>( i0, i1 ) = f0; sm.ref<float>( i0, i1, i2 ) = f0; sm.ref<float>( idx ) = f0; |
為矩陣中的某個元素賦值 |
f0 = sm.value<float>( i0 ); f0 = sm.value<float>( i0, i1 ); f0 = sm.value<float>( i0, i1, i2 ); f0 = sm.value<float>( idx ); |
讀取矩陣中某個元素的值 |
p0 = sm.find<float>(i0); p0 = sm.find<float>(i0, i1); p0 = sm.find<float>( i0, i1, i2 ); p0 = sm.find<float>( idx ); |
讀取矩陣中某個元素的值 |
sm.erase( i0, i1, &hashval ); sm.erase( i0, i1, i2, &hashval ); sm.erase( idx, &hashval ); |
移除矩陣中的某個元素仰泻,如果參數(shù)hashval不為NULL,會直接使用該值定位元素 |
cv::SparseMatIterator<float> it = sm.begin<float>(); | 獲取指向矩陣第一個元素的迭代器 |
cv::SparseMatIterator<uchar> it_end = sm.end<uchar>(); | 獲取指向矩陣最后一個元素的迭代器 |
6.2.3 模版結構和大數(shù)組類型
矩陣類cv::Mat
和cv::Mat_<>
同樣使用了模版結構滩届,盡管類cv::Mat
已經(jīng)有了表示任意類型數(shù)據(jù)的能力集侯,但是在構建的時候仍需要顯示的聲明其元素數(shù)據(jù)類型。類cv::Mat_<>
是類cv::Mat
的特例化實現(xiàn),這樣就能簡化其方法的使用和成員變量的訪問方式浅悉。如下面的例子,cv::SparseMat_<>
的函數(shù)可以不再提供模版方法訪問內部元素券犁。
// 定義一個cv::Mat類型的矩陣
cv::Mat m( 10, 10, CV_32FC2 );
// 使用模版函數(shù)為內部元素賦值
m.at< Vec2f >( 0, 0 ) = cv::Vec2f( x, y );
// 定義模版類矩陣
cv::Mat_<Vec2f> m( 10, 10 );
// 使用非模版函數(shù)為內部元素賦值
m.at( i0, i1 ) = cv::Vec2f( x, y );
m( i0, i1 ) = cv::Vec2f( x, y );
需要注意的是术健,上述兩種聲明矩陣的方式,以及使用的.at
方法效率時一樣的粘衬,但是第二種方式被認為是更準確的荞估,因為在某些方法要求傳入一個矩陣參數(shù)時,這種方式使編譯器能夠做類型檢查稚新。
// 初始化一個矩陣實例
cv::Mat m(10, 10, CV_32FC2 );
// 將其傳入到如下函數(shù)中
void foo((cv::Mat_<char> *)myMat);
// 則這段代碼將會在運行時崩潰勘伺,因為參數(shù)類型并不匹配
// 初始化一個模版矩陣實例
cv::Mat_<Vec2f> m( 10, 10 );
// 將其傳入到如下函數(shù)中
void foo((cv::Mat_<char> *)myMat);
// 此時編譯器會直接檢測到類型不匹配錯誤
在函數(shù)中使用模版可以使函數(shù)能夠處理不同類型的數(shù)據(jù),如前文打印某個稀疏數(shù)組中的非零元素代碼如下褂删。
void print_matrix( const cv::SparseMat* sm ) {
cv::SparseMatConstIterator_<float> it = sm.begin<float>();
cv::SparseMatConstIterator_<float> it_end = sm.end<float>();
for(; it != it_end; ++it) {
const cv::SparseMat::Node* node = it.node();
printf(" (%3d,%3d) %f\n", node->idx[0], node->idx[1], *it );
}
}
為使該函數(shù)更加通用飞醉,可以使用模版改造如下。程序PrintMatrixWithTemplate
// 這里使用指針而不是引用的目的是方便調用者使用類型轉換來處理本質上
// 具有相同的數(shù)據(jù)類型的不同引用屯阀,如下面的方法calling_function
template <class T>
void print_matrix( const cv::SparseMat_<T>* sm ) {
cv::SparseMatConstIterator_<T> it = sm->begin();
cv::SparseMatConstIterator_<T> it_end = sm->end();
for(; it != it_end; ++it) {
// 此處typename關鍵字告訴編譯器cv::SparseMat_<T>::Node是類型而不是變量
const typename cv::SparseMat_<T>::Node* node = it.node();
cout <<"( " <<node->idx[0] <<", " <<node->idx[1]
<<" ) = " <<*it <<endl;
}
}
void calling_function( void ) {
...
cv::SparseMat sm( ndim, size, CV_32F );
...
print_matrix<float>( (cv::SparseMat_<float>*) &sm );
}
7 小結
在本文中詳細覆蓋了OpenCV庫在操作緊湊集合時所使用到的基本數(shù)據(jù)類型缅帘,這些集合包括點,以及通常用于表示顏色和坐標的小型向量难衰,用于轉換坐標和顏色的小型矩陣钦无。同樣也講到了這些數(shù)據(jù)類型的模版類,以及它們的特例化實現(xiàn)盖袭。在進行OpenCV的開發(fā)工作是失暂,你將會經(jīng)常用到這些特例化類。此外鳄虱,本文還覆蓋了一些表示如終止條件的異常處理工具類弟塞,表示范圍的工具類,以及智能指針類拙已。接下來本文介紹了一些實用函數(shù)宣肚,這些函數(shù)提供了計算機視覺程序經(jīng)常處理的任務的優(yōu)化實現(xiàn)。其中重要的例子包含特殊運算和內存管理工具悠栓。
在第二部分內容中霉涨,本文接著介紹了最重要的數(shù)據(jù)結構cv::Mat,它可以用于表示矩陣惭适、圖片和多維數(shù)組笙瑟。該類可以包含任意基本數(shù)據(jù)類型,如數(shù)字癞志,向量等往枷。對于圖片而言,通常包含的元素類型是固定長度的向量,如Vec3b错洁。另外本文還講到了只存儲非0元素的稀疏矩陣cv::SparseMat秉宿。最后本文討論了這兩個矩陣的模版結構。