OpenCV 編碼樣式指南

OpenCV 編碼樣式指南

目錄

 [隱藏]

前言

  本文檔是對OpenCV中代碼風格的簡短說明,因爲OpenCV的核心庫(cv,cvaux)是用C和C++編寫的,所以本文檔僅對用C和C++代碼的編寫有效。

文件命名

  所有cv和cvaux庫文件的命名必須服從於以下規則:

  1. 所有的CV庫文件名前綴爲cv
  2. 混合的C/C++接口頭文件擴展名爲 .h
  3. 純C++接口頭文件擴展名爲 .hpp
  4. 實現文件擴展名爲 .cpp
  5. 爲了與POSIX兼容,文件名都以小寫字符組成

文件結構

  每個文件以BSD兼容的許可聲明(模板在Contributors_BSD_Licsense.htm文件中可以找到)開頭;其它頭文件和實現文件的規則包括:

  1. 一行最多90個字符,不包括行結束符
  2. 不使用製表符
  3. 縮進爲4個空格符,所以製表符應該用1-4個空格替換(依據開始列確定)

  頭文件必須使用保護宏,防止文件被重複包含。混合C/C++接口頭文件用extern “C” { } 包含C語言定義。爲了使預編譯頭機制在Visual C++中工作正常,源文件必須在其它頭文件前包含precomp.h頭文件。   同時,請參見頭文件和實現文件的示例。

命名約定

  OpenCV中使用大小寫混合樣式來標識外部函數、數據類型和類方法。宏全部使用大寫字符,詞間用下劃線分隔。

所有的外部或內部名稱,若在多個文件中可見,則必須含有前綴:

  1. 外部函數使用前綴cv
  2. 內部函數使用前綴Icv
  3. 數據結構(C結構體、枚舉、聯合體、類)使用前綴Cv
  4. 外部或某些內部宏使用前綴CV_
  5. 內部宏使用前綴ICV_

函數接口設計

  爲了保持庫的一致性,以如下方式設計接口非常重要。函數接口元素包括:

  1. 功能
  2. 名稱
  3. 返回值
  4. 參數類型
  5. 參數順序
  6. 參數默認值

  函數功能必須定義良好並保持精簡。函數應該容易鑲入到使用其它OpenCV和IPL函數的不同處理過程。函數名稱應該簡單並能體現函數的功能。以下是OpenCV中的一些基本命名模式:

  1. 大多數函數名形式:cv<ActionName>[<Modifiers>],如:cvCalibrateCamera, cvCalcOpticalFlowPyrLK。特殊預定義情況下對象創建、消除、清理不包括消除分別用cvCreate<Object>, cvRelease<Object>和cvClear<Object>。
  2. 有時候函數以它實現的算法名或它產生的對象的名稱命名。如:cvSobel, cvCanny, cvRodrigues, cvSqrt, cvGoodFeaturesToTrack.
  3. 在對容器元素操作時,函數名以容器類型開頭,緊跟着是動作名;在這種情況下,函數名可以當作方法名。例如:SeqPush, GraphAddEdgeByIdx。

      返回值應該選擇能簡化功能的用法。通常一個函數創建一個對象並返回該對象。對於函數,處理動態數據結構或標量,這是一個好的方法。然而在圖片處理函數中會經常分配和回收大內存塊,所以圖片處理函數不能創建和返回圖像結果而是修改輸出一個作爲參數傳入的圖像。

      函數不應該用關於嚴重錯誤(例如空指針,0除數,錯誤參數範圍,不支持的圖像格式等)的信號作爲返回值。在這種情況下,可以用一種類似於IPL中特殊的錯誤處理機制。相反,使用期望的運行時情況信號作爲返回值比較好。(例如,跟蹤圖像移動到屏幕外)。

      參數類型選擇已經存在於OpenCV中的類型更適宜:IplImage用於光柵圖像,CvMat用於矩陣,CvSeq用於輪廓線等。建議不使用簡單指針和計數,因爲有許多函數參數,它降低了庫接口並使程序更難讀。

      一個一致的參數順序很重要,因爲它使參數易於記住順序並且幫助程序員避免錯誤和使用錯誤的參數順序聯接函數。

    1. 對於簡單過程函數(在命名模式列表中的第一種和第二種類型)典型的順序是:輸入參數,輸出參數,標記或可選參數。
    2. 對於容器元素方法,順序是:容器,元素位置,標記或可選參數。

      輸入參數經常用const修飾符。可選參數經常簡化函數用法。因爲C++允許在參數列表後跟隨可選參數,它也可能影響決定以參數順序,最重要的是標記位於最前,次重要的隨後。在函數聲明中用CV_DEFAULT宏指定可選參數的默認值.它使聲明與C相兼容。

      示例函數聲明請參見cvexample.h和cv.h、cvaux.h.

    函數實現

      本節主要關注以下幾點:

    1. 參數類型檢查
    2. 錯誤產生和處理
    3. 內存管理和資源回收
    4. 調用低級函數

      如前面所說,OpenCV函數廣泛使用高級數據類型傳送和返回參數。它簡化了函數的使用,但是增加了使用錯誤的參數組合調用函數的可能性(例如浮點圖像代替位圖,或兩個不同大小的圖像)。檢查標準類型參數存在標準的方法。

      IplImage圖像能通過CV_CHECK_IMAGE宏被檢查。該宏檢查傳給IplImage的指針和潛在的圖像數據指針不爲空,圖像有像素順序,沒有ROI掩碼或冗餘信息。

      CV_CHECK_MASK_IMAGE用於檢查掩碼圖像,二值圖和灰度圖。除了CV_CHECK_IMAGE檢查的條件外,它還能確保圖像有8位深度和單通道。並且,所有的輸入和輸出圖像在進行深度\通道數和尺寸組合前應該被檢查。隨後,應該在調用cvGetImageRawData函數返回的圖像ROI尺寸後應該被檢查。輸入等高線和其它動態數據結構能夠用CV_IS_CONTOUR和相關的宏進行檢查。

      任何時候,當傳入一個錯誤的參數或在函數執行時發生其它嚴重錯誤時,應該通過cvError函數拋出一個錯誤. OpenCV中與幾乎所有標準的低級C庫的IPL類似,有一個錯誤處理機制.那就是存在一個全局錯誤狀態代替返回錯誤碼:可以通過以下實現:

    1. 使用cvError函數設置
    2. 使用cvClearErrStatus清除
    3. 使用cvGetErrorStatus讀取

      除了設置錯誤狀態和指定值外,cvError還能進行額外的操作,依據錯誤處理模式而不同,錯誤處理模式可以通過cvSetErrorMode調整.在silent模式或parent模式下cvError立即返回.在子模式下,它打印出錯誤消息並終止應用程序。

      爲了更方便使用. 可以通過使用如下宏來代替以上函數:

    1. CV_ERROR和 OPENCV_ERROR代替cvError
    2. CV_CALL和OPENCV_CALL代替調用函數和檢查狀態

      CV_*宏需要在函數中定義”FuncName”字符串變量和”exit”標籤,OPENCV_*宏則不需要。

    在OpenCV中臨時緩存用cvAlloc和cvFree函數分配和回收.函數應注意適當對齊,對未釋放的內存保持跟蹤,檢查溢出。

      當程序運行出內存泛圍時,cvAlloc拋出一個錯誤.函數能夠調用能由用戶賦予完全控制內存分配的低級函數.因此強烈建議使用這些函數.以上描述僅對簡單緩存有效.臨時圖像,內存存儲和其它結構使用cvCreate<Object>和cvRelease<Object>的方式分配和回收.

      如果錯誤發生,並且CV_ERROR或CV_CALL宏被調用,控制轉到exit標籤處.同在在程序流中可以通過EXIT宏跳轉控制.標籤可以通過手動或__BEGIN__和宏被定義.此標籤引入是爲了資源回收.儘管執行分支,當程序流進行時,還是經常發生內存泄漏.這種情況通常發生在分支語句中使用返回語句.

      使用庫中的技術,可以幫助程序員避免大多數內存泄漏.在函數開始所有的指針被清除(通常在初始化中).在”exit”標籤後,對每個指針調用cvFree函數.cvFree函數可以安全處理空指針.在函數內部,返回語句用EXIT宏代替.這樣,我們可以確保內存的回收.當然,我們可能忘記對某些塊調用cvFree函數,函數執行時,僅僅只是內存泄漏發發生,並且易於捕捉.

      OpenCV中的低級函數象IPP中那樣主要是是C語言實現原始操作的.它們不同於前面討論的接口級高級函數(他們使用簡單的指針和數值,幾乎不用結構體)和錯誤處理方法(它們返回錯誤代碼而不是全局錯誤狀態).方便並安全的調用這些函數的方法是使用IPPI_CALL宏.

      函數實現示例請參見cvexample.cpp文件。

    代碼佈局

    在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 );
    }
    
    在符合以上樣式的前提下其它樣式也可能接受。也就是說,如果一個人修改別人的代碼,他應該使用相同的代碼樣式。

    移植性

    所要代碼必須符合以下標準:
    1. ANSI C 第一個語言標準ISO/IEC 9899:1990
    2. C9X (1999年修訂的新標準) – ISO/IEC 9899.
    3. C++ 標準 – ISO/IEC 14882-1998.
    你應該去除編譯器依賴或平臺依賴和系統調用,例如:
    1. 編譯器: pragma's
    2. 特定關鍵字: __stdcall, __inline, __int64(or long long).使用CV_INLINE, CV_STDCALL, int64分別代替。
    3. 編譯器擴展,例如?<和<?宏表示最小和最大,重載宏等。
    4. 內聯彙編
    5. Unix或Win32調用,如:bcopy, readdir, CreateFile, WaitForSingleObject 等。
    6. 用sizeof代替具體的數據大小(如sizeof(int)而不是4),字節順序(*(int*)"\x1\x2\x3\x4"是0x01020304或0x04030201或什麼?),用簡單的字符有符號字符或無符號字符處理數據(不是字符串)。使用短形式,uchar表示unsigned char和schar表示signed char。使用預處理指令包含非可移植性代碼片段。不要試圖使用標準元素,主要編譯器製造商幾乎不支持這些。

    函數文檔編寫

    文檔以HTML格式提供,因爲HTML格式提供文本格式化功能和超級鏈接,同時它非常簡單,易用和易於維護。每個函數的文檔或相關函數組的文檔放入不同文件中,該文可以從主頁鏈接到。
      這是一個包含鏈接到函數示例文檔的主頁原型,實現在cvexample.cpp中。
    看一下這些頁面和相應的HTML代碼,包括詳細的格式化註釋。你對拷貝函數文檔HTML,適當的更改和加入一個鏈接到索引頁中。
      函數文檔HTML文件包知以下基本元素(以如下順序):
    1. 頁面標題——顯示在瀏覽器標題欄中並且表示一個擴展的函數名或函數組名。
    2. 關鍵字列表——用於搜索引擎和各種工具檢索文檔。
    3. 可見的頁面標題——簡單的重複頁面標題。
    4. 函數名——真實的函數名,但是不包括前綴。它應該標籤化以便從文檔的其它位置引用它。
    5. Blurb——單行函數描述。
    6. 函數聲明——按它在頭文件中的形式,用<pre>和</pre>包圍。除了OPENCVAPI被忽略外,默認參數的普通C++語法用CV_DEFAULT宏代替。
    7. 函數參數描述——一個<參數名,描述>對的列表。
    8. 討論——描述函數功能的節。允許或支持的參數組合的限制,算法參考(鏈接或標題)。
    9. 使用示例——一個可選的代碼片斷或僞代碼段.幾個相關函數可以共享相同示例.
    10. 請參見——包含零個或多個相關函數鏈接。

    函數測試實現

      每個測試實現爲從文本文件輸入和輸出結果到另外一個文本文件的C/C++函數。這樣,函數就有如下接口:
    bool <TestName>( const char* inputfile, const char* output file );
      輸入輸出文件的格式沒有定義。然而,如果測試系統函數用於從文件中讀取或寫入高級數據(矩陣,文件名,輪廓等),那麼文件的格式應與函數兼容。
      外部或主測試函數執行所有或選擇的測試並且與標準結果相比較,它可以由其它程序或以前執行的相同測試創建。

    使用這種設計可以實現檢查在一個函數上檢查幾個特殊的數據集和測式比較函數在武斷的數據上的輸出和標準輸出。在這種情況下輸出文件能以不同的兩個輸出和從前面段的標準結果將全部是零。

      測試系統API使測試更容易,它包括:
    1. 系統測試內核(測試註冊,文件管理,用異常處理能力控制測試)
    2. 從文本文件取得矩陣、文件名和其它數據和寫入數據到文本文件的函數。
    3. 檢查數組中的特殊值。
    4. 內存管理函數幫助捕捉內存泄漏和緩衝區越界。
    5. 隨機數據生成。
    6. 簡單算法函數(矩陣操作)
    7. 可視化函數
    8. 很多功能實現在OpenCV和HighGUI API上的瘦層.
      以下是一步一步描述怎樣實現測試的示例:
    //創建一個測試體文件:
    //
    // skeleton_test.cpp
    //
    #include "opencv_tst.h"
    // 測試函數
    bool  test_skeleton( const char* input, const char* output )
    {
    // 從文本文件中加載一個圖片.
    IplImage* img = tstLoadImage( input );	
    // 運行函數(參見cvexample.cpp)
    cvRasterSkeleton( img, CV_SKEL_PAVLIDIS );
    //保存結果
    tstSaveImage( output );
    }
    // 註冊測試
    OPENCV_REGISTER_TEST( test_skeleton, "cvRasterSkeleton" )	
    // 以上宏擴展瞭如下代碼:
    //
    //     static  CvTstReg( test_skeleton, "cvRasterSkeleton",
    //                       "test_skeleton", "skeleton_test.cpp",
    //                       20 /* code line number */,
    //                       "test_skeleton.in" /* input data file name */
    //                       "test_skeleton.out"/* output data file name */ 
    //                       "test_skeleton.0"  /* etalon data file name */
    //                      );
    //
    // The line calls constructor of CvTstReg class that links the test to
    // the master test list.
    
    
      將測試文件加入到測試系統項目中.有main()函數的主測試已經包含在項目中,所以不需要再編寫執行代碼.
    1. 創建輸入和標準數據.首先你可能需要手工創建.標準數據能夠通過用-c(--create-etalon)傳輸測命令行選項標識生成。並且,這種情況可以在測試函數中用tstIsEtalonMode()函數(在這種情況下,給定可靠值給另外一個算法的變量)處理。
    2. 輸入輸出數據存放在opencv_tst/testdata文件夾中.
    3. 測試數據文件的名字由測試體文件和特定的擴展名構成.(輸入數據文件的擴展名爲”.in”,輸出數據文件的擴展名爲”.out”,標準數據文件的擴展名爲”.0”).
      隨後,測試系統將在不同的模式下執行.

    提示

      某些OpenCV函數使用小結構體作爲輸入.使結構體的名稱類型爲CvSomething.然後cvSomething通常是內聯的,從參數列表構成對象.使用這些內聯的構造器,使代碼更容易寫和讀.

      使用cvRound,cvFloor和cvCeil快速轉換浮點數爲相近的整形數或到負無窮在或正無窮大.在x86架構上,這些函數比簡單轉換操作運行更快.在C9X標準中有幾個標準的函數做相同的事情,但是它們現在很少被支持.當情況發生時,以上函數將轉換爲內聯宏.

    附錄

    附錄A: 參考

      本文不是完整的樣式參考,更多詳細和寫得更好相關主題列表如下:

    1. 推薦C樣式和標準編碼(Indian Hill C樣式和編碼標準更新版) . Henry Spencer et al. Rev. 6.0, 1990.
    2. C++編程規則與建議 Mats Henricson, Erik Nyquist. Ellemtel Communications Systems Laboratories. 1990-1992.
    3. GNU編碼樣式, Richard Stallman. GNU Project – Free Software Foundation. 2000
    4. 用C語言編寫可移植的程序 A. Dolenc, A. Lemmke, D. Keppel, G.V. Reilly. 1990。類似文檔可以在http://www.softpanorama.org/Lang/c.html 上找到。

    附錄B: 規則簡述列表

    1. 文件名以小寫的cv前綴開始.頭文件擴展名爲.h或.hpp
    2. 實現文件擴展名爲.cpp
    3. 每一個文件在開頭包含BSD兼容許可
    4. 頭文件有保護宏和extern “C” { } 保護C語言部分接口
    5. 源文件包含”precomp.h”作爲第一個頭文件
    6. 外部函數名和數據類型名寫成大小寫混合類型.外部宏寫作大寫字符
    7. 外部函數以cv前綴開始
    8. 內部函數前綴爲icv
    9. 數據類型前綴爲Cv
    10. 外部宏前綴爲CV
    11. 函數聲明形式爲:
    OPENCVAPI <returnType>	<functionName>( arguments );
    
    1. 函數實現形式爲:
    	CV_IMPL <returnType>
    	<functionName>( arguments )
    	{
    	CV_FUNCNAME("<functionName>");
    	...	
    	__BEGIN__;	
    	...
    	__END__;
    	
    	// cleanup ...
    	return ...;
    	}
    
    1. 使用CV_ERROR/OPENCV_ERROR拋出錯誤,使用CV_CALL/OPENCV_CALL調用高級函數,使用IPPI_CALL調用低級函數
    2. 使用cvAlloc和cvFree分配和回收臨時緩衝區
    3. 在每一個源文件中保持一致的格式
    4. 與C/C++標準兼容,避免編譯器依賴性,系統依賴性和平臺依賴性

    附錄C: 附加說明

      本文是hzy ([email protected]) 02:09 2006年8月17日 (CST)根據OpenCV Coding Style Guide翻譯。

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