OPENCV做爲一種開源的計算機視覺庫,咱們有必要去了解這個庫的一些編碼格式及文件結構。html
一、文檔命名規則
必須將全部功能放入一個或多個.cpp和.hpp文件到OpenCV的相應模塊中,或者若是貢獻的功能是至關大的代碼,或者若是它不適合任何現有代碼,則應建立新模塊模塊。python
- 全部文件名都以小寫字母書寫,以便更好地兼容POSIX和Windows。
- C ++接口頭的擴展名爲.hpp
- 實現文件的擴展名爲.cpp
- 實現被放到
opencv/modules/<module_name>/src
,接口被添加到頭文件中opencv/modules/<module_name>/include/opencv2/<module_name>
。沒有必要明確地將文件添加到模塊,只需從新運行CMake,它將自動添加文件。 - 樣本代碼在C ++中,若是有的話,被投入
opencv/samples/cpp
(具備異常gpu
和ocl
模塊,有專門的樣本目錄),在Python示例代碼-到opencv/samples/python2
。 - 將文檔
opencv/modules/<module_name>/doc
放入其中一個現有文件中或在其中添加新文件/章節。在後一種狀況下,文件名應列在TOC(內容表)文件中opencv/modules/<module_name>/doc/<module_name>.rst
- 進行測試
opencv/modules/<module_name>/test
。添加新的測試源文件後,從新運行CMake。若是測試須要一些數據,請將其放在單獨的頂級目錄中。對於ml
模塊,測試數據被放到opencv_extra/testdata/ml
子目錄中,對於其餘模塊 - 放到opencv_extra/testdata/cv
子目錄中。請將測試數據文件的大小限制在幾兆字節內,越小越好。若是某些現有測試數據可用於您的測試(如lena.jpg
等),請從新使用它。
二、文件結構
標頭和實現文件的其餘規則包括:ios
- 必須將全部功能放入
cv
命名空間,或者可能放入一些嵌套的命名空間,例如cv::vslam
- 代碼行不該該很長。一般,它們應限制爲100個字符。
- 不該使用製表。將編輯器設置爲使用空格。
- 縮進是4個空格。
- 僅容許使用英文文本(ASCII)。不要將註釋或字符串文字放在其餘語言中。
- 頭文件必須使用保護宏,保護文件不被重複包含:
#ifndef OPENCV_module_name_header_name_HPP #define OPENCV_module_name_header_name_HPP namespace cv { namespace mynamespace { ... }} #endif
- 源文件必須包含
precomp.hpp
其餘標頭以前的標頭,以便使Visual C ++中的預編譯標頭機制正常工做。
三、命名約定
- OpenCV對外部函數,類型和類方法使用混合大小寫樣式標識符。
- 班級名稱以大寫字母開頭。
- 方法'和函數'的名稱以小後者開頭,除非它們以算法的做者命名,例如
cv::Sobel()
,在這種狀況下,它們能夠以大寫字母開頭。 - 宏和枚舉常量用全都大寫字母書寫。單詞由下劃線分隔。
- 必須使用全部外部函數和類
CV_EXPORTS
,不然Windows上將存在連接錯誤。對於要在Python,Java等中公開的函數/類,您應該使用CV_EXPORTS_W
而不是CV_EXPORTS
,但請注意重載的函數和方法,非標準參數類型等。
四、設計功能和類接口
設計函數接口是一種重要的方式,與庫的其他部分一致。功能界面的元素包括:git
- 功能:功能必須定義良好且非冗餘。該功能應易於嵌入到使用其餘OpenCV功能的不一樣處理流水線中。
- 名稱
名稱應基本反映功能目的。OpenCV中有一些常見的命名模式:程序員
- 函數名的多數具備形式:
<actionName><Object><Modifiers>
例如calibrateCamera
,calcOpticalFlowPyrLK
。 - 有時功能能夠由它實現它產生的算法名或結果對象的名稱,例如被稱爲
Sobel
,Canny
,Rodrigues
,sqrt
,goodFeaturesToTrack
。
- 函數名的多數具備形式:
- 返回值
應該選擇它來簡化功能使用。一般,建立/計算值的函數應該返回它。對於返回標量值的函數,這是一個好習慣。可是,在圖像處理功能的狀況下,這將致使大內存塊的頻繁分配/釋放。圖像處理功能一般修改輸出圖像,輸出圖像做爲參數(經過引用)傳遞,而不是建立和返回結果圖像。github
函數不該該使用返回值來表示關鍵錯誤,例如空指針,除零,不良參數範圍,不支持的圖像格式等。相反,它們應該拋出異常,實例
cv::Exception
或其衍生類。另外一方面,建議使用返回值來報告在正常工做的系統中可能發生的很是正常的運行時狀況(例如,跟蹤的對象在圖像以外等)算法 - 參數類型
參數類型最好由已經存在的組的OpenCV類型的選擇:
Mat
對於光柵圖像和矩陣,vector<Mat>
進行圖像採集,vector<Point>
,vector<Point2f>
,vector<Point3f>
,vector<KeyPoint>
對點集,輪廓或關鍵點的集合,Scalar
爲1〜4元數值的元組(如顏色,四元數等。)不建議使用普通指針和計數器,由於它使接口更低級,意味着更可能的輸入錯誤,內存泄漏等。對於將複雜對象傳遞給函數,方法,請考慮Ptr<>
智能指針模板類。
數組一致的參數順序很重要,由於它更容易記住順序,它有助於程序員避免錯誤,鏈接錯誤的參數順序。一般的順序是:<輸入參數>,<輸出參數>,<標誌和可選參數>。框架
輸入參數一般具備
const
限定符。大對象一般經過常量引用傳遞; 原始類型和小結構(int, double, Point, Rect
)按值傳遞。機器學習可選參數一般簡化了函數使用。由於C ++僅在參數列表的末尾容許可選參數,因此它也可能影響參數順序的決策 - 最重要的標誌首先出現,而不過重要。
- 論證順序
- 某些參數的默認值
五、高級C ++接口算法
在某些狀況下,您可能但願將算法表示爲類,而不是函數。例如,算法能夠包括隨時間更新的特定狀態(例如,具備其背景統計的背景/前景減法器)。或者算法可能具備太多參數以將它們放入單個調用中。一些算法可能包括幾個步驟(例如,機器學習方法中的訓練和預測)等。
若是您決定將算法設爲類,則應遵循OpenCV算法概念。
基於算法的設計的基本原理和原理以下
- API在實現更改時保持穩定。
- 不只要保留源級兼容性,還要保留二進制級兼容性。
- 保持頭文件的清潔,並輕鬆跟蹤API中的更改。
- 保持咱們的工具可以簡單而強大地解析OpenCV標頭(例如doc checker和自動包裝生成器)
- 但願OpenCV快速構建。
爲了實現這些目標,opencv將C ++類的接口和實現部分分開。也就是說,opencv只公開接口,即沒有構造函數的類,沒有數據成員和全部純虛方法。實際的實現被放入從這些接口派生的類中。類的實際構造由外露函數(一般是靜態create
方法)完成。它返回指向接口的智能指針。能夠有多個「構造函數」或「工廠」函數,不必定放在同一個模塊中。用戶能夠添加本身的相同接口的實現,並提供相應的構造函數。(能夠參閱calib3d
模塊的寫法)。
使你的類遵循此風格的設計步驟
- 從算法或其直接或間接派生類派生您的類,例如,
StereoMatcher
若是您添加另外一個立體聲對應算法。 - 爲您的實際實現製做抽象基類,例如
... namespace cv { namespace mynamespace { class MyStereoMatcher : public StereoMatcher { public: virtual void setLambda(double lambda) = 0; virtual double getLambda() const = 0; ... // static method to construct the algorithm instance as a smart pointer to the interface class. // there can be several constructors static Ptr<MyStereoMatcher> create(<params> ...); }; }}
也就是說,放置算法將具備的額外方法和屬性。「Getters」應以「get」字樣開頭,「Setters」 - 以「set」開頭。你並不須要重複的虛擬方法的聲明StereoMatcher
,Algorithm
等等,由於你會在你的實際類反正實現它們。
- 在.cpp文件中放置一個實現接口和構造函數的類:
/* <OpenCV license with your copyright added> */ #include "precomp.hpp" namespace cv { namespace mynamespace { class MyStereoMatcherImpl : MyStereoMatcher { MyStereoMatcherImpl(...) { ... } virtual ~MyStereoMatcherImpl() { ... } ... double getLambda() const { return lambda; } // implement getters and setters void setLambda(double l) const { CV_Assert(l >= 0); lambda = l; } void compute(InputArray _left, InputArray _right, OutputArray _disp) // implement necessary methods from StereoMatcher, Algorithm etc. { Mat left = _left.getMat(), right = _right.getMat(); _disp.create(left.size(), CV_16S); Mat disp = _disp.getMat(); ... } ... double lambda; }; Ptr<MyStereoMatcher> MyStereoMatcher::create(<args>) { return makePtr<MyStereoMatcherImpl>(<args>); } }}
如此一來,你的類就建立完畢了
六、如何去擴展和修改算法
好比說,您爲OpenCV貢獻了新算法,如上所示實現,而且它已經集成。
而後,稍後您想要修改它。你應該作以下幾步:
- 只要您不修改標題,全部更改均可以。只需將它們做爲拉取請求提交。
- 若是你想爲算法添加一些新的屬性或修改方法的簽名,而且尚未包含你的代碼的官方OpenCV版本 - 那也不要緊; 進行修改並提交拉取請求。
- 若是您的算法的先前變體已經發布,那麼您實際上沒法修改接口。建立從前一個派生的新界面,在那裏添加更多屬性並添加新的構造函數:
namespace cv { namespace mynamespace { class MyStereoMatcher : public StereoMatcher {...}; CV_EXPORTS Ptr<MyStereoMatcher> createMyStereoMatcher(<params...>); // new extended interface class MyPyrStereoMatcher : public MyStereoMatcher { public: // more properties ... virtual void setNPyramidLevels(int nlevels) = 0; virtual double getNPyramidLevels() const = 0; ... }; // new contractor(s) CV_EXPORTS Ptr<MyPyrStereoMatcher> createMyPyrStereoMatcher(<new_params...>); }}
而後修改實現類,使其重新類派生(若是你想擁有一個實現類,而不是兩個),例如MyStereoMatcherImpl
將派生自MyPyrStereoMatcher
。其餘一切都保持不變,多虧了一些Ptr<>的方法
,你能夠經過你通過的Ptr<MyPyrStereoMatcher>
任何地方Ptr<MyStereoMatcher>
。
七、代碼佈局
OpenCV中有一個嚴格的編碼指南:每一個單個文件必須使用一致的格式化樣式。
目前在OpenCV中使用並推薦格式化樣式以下:
if( a > 5 ) { int b = a*a; c = c > b ? c : b + 1; } else if( abs(a) < 5 ) { c--; } else { printf( "a=%d is far to negative\n", a ); }
若是僅知足上述規則,也能夠接受其餘樣式。也就是說,若是被其餘代碼改寫了,他應該使用相同的編碼風格。
八、可移植性要求
形式上,代碼必須符合C ++ 98標準。建議不要在實現級別使用C ++ 11或TR1擴展,而且禁止在外部頭文件中使用它。
應該擺脫依賴於編譯器或平臺的構造和系統調用,例如:
- 編譯器pragma的
- 特定的關鍵字,例如
__stdcall
,__inline
,__int64
。相反,分別使用CV_INLINE
(或簡單inline
的C ++代碼),CV_STDCALL
(儘量避免使用)int64
。 - 編譯器擴展,例如min和max的特殊宏,重載宏等。
- 內聯彙編
- Unix或Win32的特定呼叫,如
bcopy
,readdir
,CreateFile
,WaitForSingleObject
等。 - 具體數據的大小,而不是@ @的sizeof的(
sizeof(int)
而不是4),字節順序(*(int*)"\x1\x2\x3\x4"
0×01020304或者0×04030201或者是什麼?),簡單char
的代替signed char
或unsigned char
任何地方,除了文本字符串。使用短形式uchar
爲unsigned char
和schar
的符號字符。使用預處理程序指令來處理不可移植的代碼片斷。
九、編寫有關函數的文檔
貢獻函數的文檔使用內聯Doxygen註釋編寫。該文檔每晚構建,並上傳到docs.opencv.org。
使用現有文檔做爲示例。您也能夠經過圖片,代碼示例等提供大型描述性文本塊的教程。
十、實施測試
- 對於測試,opencv使用GoogleTest框架。請檢查項目現場的文檔。
- 每一個測試源文件應
test_precomp.hpp
首先包含。 - 全部測試代碼都放在
opencv_test
命名空間中。 - 聲明你的Google測試以下:
TEST(<module_name> _ <tested_class_or_function>,<test_type>){<test_body>}
例如:
TEST(Imgproc_Watershed, regression) { ... }
- 要訪問測試數據,請使用
cvtest::TS::ptr()->get_data_path()
方法。例如,若是您將測試文件放入,則opencv_extra/testdata/cv/myfacetracker/clip.avi
可使用cvtest::TS::ptr()->get_data_path() + "myfacetracker/clip.avi"
獲取文件的完整路徑。要使其正常工做,請將環境變量設置OPENCV_TEST_DATA_PATH
爲<your_local_copy_of_opencv_extra>/testdata
- 避免包括C ++標準庫頭同樣
vector
,list
,map
,limits
,iostream
,等。 - 避免
using namespace std
。std::
必要時使用(將經常使用類型導入opencv_test
命名空間)。 - 不要使用
std::tr1
命名空間。 - 不要包含
core
/imgproc
/的OpenCV標頭highgui
。這些標題包括在內ts.hpp
。
十一、總結
- 文件名以小寫字母書寫。
- 標題擴展名爲.hpp。
- 實現文件的擴展名爲.cpp
- 每一個文件在開始時都包含與BSD兼容的許可證
- 不要使用製表。縮進是4個空格。行不該超過~100個字符。
- 在每一個特定的源文件中保持一致的格式化樣式(特別是若是您修改現有代碼)。
- 每一個源文件都包含
"precomp.hpp"
第一個標頭。 - 代碼放入cv或嵌套命名空間(cv :: vslam等)。測試代碼放入opencv_test命名空間。
- 在評論和字符串文字中只應使用英語。
- 外部函數名稱和數據類型名稱以大小寫混合寫入。類以大寫字母開頭,函數以小寫字母開頭。外部宏以大寫形式寫入。
CV_EXPORTS
在外部函數和類聲明中使用宏。使用CV_EXPORTS_W
了Python-和Java的可包裝的API。- 不要在標頭中使用條件編譯。
- 保持外部接口儘量緊湊。不要導出非必要且可隱藏的內部使用類或函數。
- 暴露的類應該是抽象的,派生自算法。實際的實現必須隱藏在.cpp中。
- 嘗試使您的代碼易於爲Python,Java等包裝。也就是說,儘可能不要引入新類型。限制回調的使用。
- 考慮將InputArray / InputOutputArray / OutputArray用於數組參數。
- 使用
CV_Error
報告有關不正確的參數和/或使用CV_Assert
來驗證一些條件,例如CV_Assert(inputImage.type() == CV_8UC3)
。 - 符合C / C ++標準。避免依賴於編譯器,依賴於操做系統和依賴於平臺的構造。不要使用C ++ 11和/或TR1擴展。
- 儘可能不要使用malloc / free,new / delete。使用
cv::Mat
,std::vector
,std::map
,cv::AutoBuffer
,cv::Ptr
來代替。這些類自動處理內存。 - 爲您的代碼提供基於GTest的測試。
- 爲Doxygen評論提供代碼文檔。歡迎使用教程。