OpenCV 簡介(cxcore, ml)

這裏簡單的記錄一些 OpenCV 這個庫的使用。這是用 C/C++ 寫的一個和 computer vision 相關的庫,一共含有 5 個組件:

  • CXCORE 是 OpenCV 裏面使用的常用數據結構,以及處理這些數據結構的函數。
  • CV 是常用的 computer vision 相關的函數,比如計算 histogram、目標檢測、跟蹤的程序。
  • ML 是常用的 machine learning 相關的函數,如做分類的 naive Bayes、SVM 等等。
  • highgui 是一些比較高層函數,主要是做用戶界面。
  • 另外還有一個從攝像頭獲取數據的 CVCAM 庫,但是 1.1 裏面似乎並進 highgui 了。

最新的是 1.1pre1,但是 debian 裏面還是兩年前的 1.0 版本的,這就需要自己編譯一個了,後面有空學着 debian 的方式做個 deb 包好了。

OpenCV 的文檔也算是還不錯的了,除了函數的解釋,部分功能還有例程、例子可以參考,這對對函數功能不是很清楚的人是非常有用的,比如我 :-D 這裏不打算詳細的記錄 OpenCV 的每個功能,感興趣的看文檔吧。

 

先從 CXCORE 的基本結構講起,OpenCV 裏面提供了:

點 CvPoint(分 int 和 float 類型的,一般 int 的都沒有後綴,float 會寫 32f,double 是 64f;有 2D 和 3D 的版本),

大小 CvSize、

矩形 CvRect(用左下角的頂點座標和寬、高表示)、

向量 CvScalar(4 個 double 那麼大)。

更重要的就是存儲矩陣用的 CvMat、CvMatND(多維矩陣,一般圖片就是幾個 channel,每個 channel 是一個 CvMat),稀疏矩陣 CvSparseMat。注意這裏面有一個比較重要的成員,就是 int* refcount,這是允許多個“矩陣”(其實是 CvMatHeader 或者 CvMatNDHeader)對同一個矩陣進行引用,這時釋放的時候涉及到矩陣元素的屬主(ownership),只有當 refcount 爲 0 的時候,釋放的時候纔會釋放這些元素。數據的入口都是匿名 union,data 可以當作指針用。

圖片一般存放在一個 IplImage 結構裏面,這個結構裏面有很多參數決定圖片數據的類型,比如是 UINT8 還是 float 等。創建這些數據最基本的函數就是 cvCreate*,這是產生一個完整的結構,並且分配數據的內存,另有一種是僅僅分配一個 header,函數就是 cvCreate*Header,之後可以用 cvSet*Data 指定數據,釋放這些結構使用 cvRelease* 和 cvRelease*Header。另外可以通過 cvInit* 和 cvInit*Header 初始化這些數據結構。我們發現 OpenCV 的函數都是以 cv 開頭,而結構都是 Cv 開頭。另外,進行深層 copy 使用 cvClone* 函數。如果需要手工增加/減少對數據的 refcount,可以使用 cvIncRefData、cvDecRefData。OpenCV 裏面的數據用 CvArr,這是一個 void 類型,因此可以自由轉換成爲別的類型,如果需要訪問 CvArr 類型的數據,可以用 cvGetRawData,下面是一個例子:

cvGetRawData( array, (uchar**)&data, &step, &size );
step /= sizeof(data[0]);
for( y = 0; y < size.height; y++, data += step )
for( x = 0; x < size.width; x++ )
data[x] = (float)fabs(data[x]);

這裏的 data 是個 float*,因此每次遞增量是 cvGetRawData 返回的 step 除以 sizeof(float)。類似的,cvGet* 命令將會返回對應的 header,如 cvGetMat、cvGetImage 等。

 

對於一個 CvMat,可以用 cvGetSubMat、cvGetRow(s),cvGetCol(s)、cvGetDiag 獲得矩陣的部分,cvGetSize 獲得矩陣的大小。遍歷稀疏矩陣一般用 cvInitSparseMatIterator、cvGetNextSparseNode。另外可以用 cvGetElemType 獲得元素種類,cvGetDim(s) 獲得多維矩陣的大小。如果需要訪問某些元素,可以用 cvPtr?D 獲得指針,cvGet?D 獲得元素值,另外有快速版本 cvmGet(對 2D 的矩陣)。對應有 Set 版本,用於賦值,特別的有 cvSetZero 和 cvSetIdentity 用於產生 0 矩陣和單位矩陣。另外和 matlab 類似有個 cvRange,cvReshape 改變矩陣大小,cvRepeat 和 repmat 類似,cvFlip 和 flip* 類似,cvSplit 切分矩陣,cvMerge 是逆操作。針對圖片提供有 cvMixChannels。和 randperm 類似有一個 cvRandShuffle。

 

cvLUT 進行查表(某些圖片是 indexed color),cvConvertScale 可以將不同的類型的數據(尤其是圖片,有的用 float 的 0-1 表示,有的用 UINT8 表示)互相轉化,它的一個特殊情況就是 cvConvertScaleAbs,cvAdd 將兩個 CvArr 相加,cvAddS 加的是 scalar,cvAddWeighted 是加權和,cvSub 是相減,cvSubS 減標量,cvSubRS 是用標量減 CvArr,cvMul 是相乘,,cvDiv 是除,另外有 cvAnd(S)、cvOr(S)、cvXor(S)、cvNot、cvCmp(S);cvInRange(S) 可以檢查元素是不是位於上下界中,cvMax(S)、cvMin(S) 求最大最小,cvAbsDiff(S) 求差的絕對值,cvCountNonZero 數非零元素個數,cvSum 求和,cvAvg 求平均,cvAvgSdv 計算均值和標準差,cvMinMaxLoc 返回最小最大的位置,cvNorm 計算範數(包括 L_1L_2L_/infty)。cvReduce 將矩陣變成向量。

 

OpenCV 也實現了一些代數運算,比如 cvDotProduct 計算內積,cvNormalize 將向量依照某種範數歸一化,cvCrossProduct 計算叉積,cvScaleAdd 將標量相加,cvGEMM 是一個廣義的矩陣乘積,包括每個矩陣是不是轉置,然後相乘相加(用它定義了 cvMatMulAdd 和 cvMatMul),矩陣產生的變換 cvTransform、cvPerspectiveTransform,轉置相乘 cvMulTransposed,跡 cvTrace,轉置 cvTranspose、行列式 cvDet,cvInvert 求逆,cvSolve 求解線性方程組,cvSVD 計算奇異值分解,cvSVBkSb 用奇異值產生原來的矩陣,求對稱矩陣的特徵值 cvEigenVV,cvCovarMat 計算協方差矩陣,cvMahanobis 計算馬氏距離,cvCalcPCA 計算主成分分解,cvProjectPCA 進行投影,cvBackProjectPCA 重構,。

 

下面是 OpenCV 裏面常用的數學函數,如舍入的 cvRound、cvFloor 和 cvCeil,開方系列 cvSqrt、cvInvSqrt、cvCbrt,反正切 cvFastArctan、判斷 cvIsNaN、cvIsInf,座標轉換 cvCartToPolar 和 cvPolarToCart,冪函數 cvPow,指數 cvExp,對數 cvLog,求解三次方程 cvSolveCubic,一般多項式 cvSolvePoly。

另外有一些隨機數相關的函數,比如 cvRNG 產生一個 random number generator,cvRandArr 產生一個隨機 CvArr,cvRandInt 返回一個隨機整數,cvRandReal 產生 0-1 之間的隨機實數。

另外有一些和 discrete Fourier transform 相關的東西,比如 cvDFT,cvDCT 等。

 

OpenCV 提供了一些動態存儲的數據結構,這在很多操作中非常重要,比如需要進行檢測某些特徵點,那麼用戶並不知道將會發現多少個,所以最好的方法是使用 OpenCV 的動態數據結構,不過很遺憾的是,似乎這些結構不僅不能和 STL 的容器互相轉換,接口也並不統一。

比較重要的類結構是 CvMemStorage,一般說來我們使用 cvCreateMemStorage 創建一個,並最後用 cvReleaseMemStorage 釋放,OpenCV 裏面很多函數需要 workspace 往往就是一個 CvMemStorage,如果需要自己進行更細緻的操作,就需要了解一些比較基本的知識,比如它的結構是一個一個的 block 用雙向鏈表鏈接(CvMemBlock 結構)而成,通過 CvMemStoragePos 記錄位置,通過 cvCreateChildMemStorage 可以創建一個和 parent 相對獨立的存儲空間,臨時存放數據非常方便。通過 cvClearMemStorage 將該結構管理的內存減到最少。CvMemStorage 提供了一個較大尺度的內存管理機制,而 CvSeq 就成爲了一個容器,這個容器是一個雙向鏈表(另有 CvSeqBlock 是個循環鏈表)。這個容器可以用 cvCreateSeq 創建(有一些內部支持的類型),cvSeqPush/cvSeqPop 是當作一個 stack 來進行入棧操作,類似的操作是 cvSeqPush/PopFront(隊列常用操作),還可以一次插入多個 cvPush/PopMulti,插入 cvSeqInsert、刪除 cvSeqRemove、刪除所有元素 cvClearSeq、獲得其中一個元素 cvGetSeqElem、獲得一個元素的指標 cvGetElemIdx、轉換成 array cvCvtSeqToArray、爲 array 創建 Seq 的 header cvMakeSeqHeaderForArray,創建一個 CvSeq 的子序列 cvSlice、複製 cvCloneSeq、子序列的刪除插入 cvRemove/InsertSlice、反轉 cvSeqInvert、排序 cvSeqSort、搜索 cvSeqSearch。和 CvSeq 的輸入輸出相關的就是 CvSeqWriter 和 CvSeqReader 了,分別調用 cvStart/EndWriter/Reader 即可。

另外一個有用的數據結構是 CvSet,它是 CvGraph、SvSparseMat 和 CvSubdiv2D 的父類,其實現依賴於 CvSeq,創建一個 CvSet 和 CvSeq 類似爲 cvCreateSet,添加、刪除元素使用 cvSetAdd(cvSetNew 是一個 inline 版本)和 cvSetRemove(cvSetRemoveByPtr 使用指針刪除),cvGetSetElem 通過一個索引獲得元素,cvClearSet 清除裏面的元素。

CvGraph 是用來表示一個帶權有向或者無向圖的數據結構,用 cvCreateGraph 創建,cvGraphAdd/RemoveVxt 添加頂點(另有一個ByPtr 版本),可以用 cvGetGraphVxt 獲得頂點內容,cvGraphVxtIdx 獲得元素的索引,cvGraphAdd/RemoveEdge 添加/刪除邊(也有 ByPtr 版本),另外有搜索邊的 cvFindGraphEdge 以及 ByPtr 版本。通過 cvGraphVxtDegree 獲得頂點的 degree,清除一個 graph 可以用 cvClearGraph,複製用 cvCloneGraph。遍歷一個 graph 可以用 CvGraphScanner 這個數據結構,使用 cvCreateGraphScanner 創建深度優先便利的 scanner(用 Release 釋放),這個 scanner 可以用一個 mask 來決定哪些地方是值得停下來供程序員處理的,有了這個 scanner 後用 cvGraphNextItem 就可以迭代了。

 

OpenCV 裏面還有不少作圖函數,就是在圖片上畫一些簡單的幾何形狀,比如 Line、Rectangle、Circle、Ellipse、EllipseBox、FillPoly、FillConvexPoly、PolyLine。另外爲了渲染字體,有 cvInitFont,cvPutText、cvGetTextSize 供使用。另外提供了 cv 裏面 contour 的繪製函數(一般使用 cvFindContours 獲得等高線,然後)cvDrawContours。通過 cvInitLineIterator 可以獲得一條直線(指定起點和終點)上面的那些 pixel。cvClipLine 是將一條直線用一個矩形 clip。cvEllipse2Poly 將橢圓轉換成爲 polyline。

 

另外 cxcore 裏面還提供了一個一致的存儲數據的方式,這是通過 XML(或者 YAML)和 CvFileStorage 結構實現的,XML 本身是一個 tree 結構的東西,每個節點用 CvFileNode 表示其類型,用 CvAttrList 表示一個對象含有的屬性列表。創建/釋放一個 CvFileStorage 對象用 cvOpen/ReleaseFileStorage 函數。前者創建的 CvFileStorage 可用 cvStart/EndWriteStruct 或者 cvWriteInt/String/Real/Comment/RawData 進行寫入,另外有 cvWrite 適合寫入 CvSeq 等對象。讀取使用比較笨的就是直接 cvRead*ByName,或者 cvGetFileNodeByName,比較快的是創建 CvStringHashNode,使用它查詢 cvGetFileNode,然後從 CvFileNode 裏面獲得數據。另外,也有類似的 cvReadInt/Real/等等,以及 cvRead。

OpenCV 提供了一個基本的 RTTI 實現,這是利用 CvTypeInfo 實現的,利用 cvTypeOf 可以獲得對應的類型,如果不確定有沒有某個類型,可以用 cvFindType 搜索,通過 cvRegisterType 和 cvUnregisterType 註冊、註銷新類型。這樣我們可以直接用 cvRelease 釋放某個已知類型的對象、cvClone 複製一個,cvSave 和 cvLoad 實現該對象的存儲。

 

cxcore 裏面還有一些非常有用的功能,比如 cvCheckArr 檢查有沒有越界或者 NaN 類型。cvKMeans2 可以進行 kmeans 聚類,cvSeqPartition 用於將序列通過一個等價函數分成等價類。另外有一些用於出錯處理的函數,比如每個 OpenCV 的函數源代碼裏面寫的 CV_FUNCNAME( Name ) 就是用於報告出錯函數的宏,等價於聲明瞭一個靜態的字符串,常用 CV_ERROR 報錯,另有 CV_CHECK 檢查出錯代碼,CV_CALL 提供統一的調用 cxcore 函數方法(自動 CV_CHECK),CV_ASSERT 判斷條件(和 assert 類似)。另外有 cvGetErrStatus、cvSetErrStatus,設置出錯模式 cvGet/SetErrMode(分 leaf 出錯就退出,parent 調用 handler 後向上 unwind,silent 不調用 handler 直接向上 unwind),產生錯誤 cvError,可以用 cvErrorStr 獲得解釋。

 

和 OS 相關的函數,如 cvAlloc、cvFree,計算用時的 cvGetTickCount 和 cvgetTickFrequency,編寫模塊用的 cvRegisterModule、cvGetModuleInfo,另外還有獲得線程、以及並行運算信息的函數,這裏不加敘述了。

 

ml 這個庫的結構相對簡單,實現的 ML 的算法也僅僅那麼幾個最經典的,並不具有很好的擴展性。幾乎所有的 ml 的模型都是繼承 CvStatModel 這個類(其實是一個接口),主要有 clear() 清除模型,train() 訓練,predict() 預測,以及 IO 相關的 save/load() 或者 read/write()。實現的算法有樸素貝葉斯分類器 CvNormalBayesClassifier,k-近鄰 CvKNearest(不知道用了 kdtree 這些結構沒),支持向量機 CvSVM(不支持用戶自定義的核函數似乎,可以用 k-fold 交叉驗證選擇參數),決策樹 CART CvDTree,集成算法 AdaBoost CvBoost 以及 boosted tree CvBoostTree、隨機樹 CvRTree、EM 算法 CvEM(僅僅對某些特定模型可以用),神經網絡(兩層前饋神經網絡)CvANN,感覺就是可以用來做些簡單的問題,但是不要指望能用到最新的一些模型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章