OpenCV 编码样式指南
前言
本文档是对OpenCV中代码风格的简短说明,因为OpenCV的核心库(cv,cvaux)是用C和C++编写的,所以本文档仅对用C和C++代码的编写有效。
文件命名
所有cv和cvaux库文件的命名必须服从于以下规则:
- 所有的CV库文件名前缀为cv
- 混合的C/C++接口头文件扩展名为 .h
- 纯C++接口头文件扩展名为 .hpp
- 实现文件扩展名为 .cpp
- 为了与POSIX兼容,文件名都以小写字符组成
文件结构
每个文件以BSD兼容的许可声明(模板在Contributors_BSD_Licsense.htm文件中可以找到)开头;其它头文件和实现文件的规则包括:
- 一行最多90个字符,不包括行结束符
- 不使用制表符
- 缩进为4个空格符,所以制表符应该用1-4个空格替换(依据开始列确定)
头文件必须使用保护宏,防止文件被重复包含。混合C/C++接口头文件用extern “C” { } 包含C语言定义。为了使预编译头机制在Visual C++中工作正常,源文件必须在其它头文件前包含precomp.h头文件。 同时,请参见头文件和实现文件的示例。
命名约定
OpenCV中使用大小写混合样式来标识外部函数、数据类型和类方法。宏全部使用大写字符,词间用下划线分隔。
所有的外部或内部名称,若在多个文件中可见,则必须含有前缀:
- 外部函数使用前缀cv
- 内部函数使用前缀Icv
- 数据结构(C结构体、枚举、联合体、类)使用前缀Cv
- 外部或某些内部宏使用前缀CV_
- 内部宏使用前缀ICV_
函数接口设计
为了保持库的一致性,以如下方式设计接口非常重要。函数接口元素包括:
- 功能
- 名称
- 返回值
- 参数类型
- 参数顺序
- 参数默认值
函数功能必须定义良好并保持精简。函数应该容易镶入到使用其它OpenCV和IPL函数的不同处理过程。函数名称应该简单并能体现函数的功能。以下是OpenCV中的一些基本命名模式:
- 大多数函数名形式:cv<ActionName>[<Modifiers>],如:cvCalibrateCamera, cvCalcOpticalFlowPyrLK。特殊预定义情况下对象创建、消除、清理不包括消除分别用cvCreate<Object>, cvRelease<Object>和cvClear<Object>。
- 有时候函数以它实现的算法名或它产生的对象的名称命名。如:cvSobel, cvCanny, cvRodrigues, cvSqrt, cvGoodFeaturesToTrack.
- 在对容器元素操作时,函数名以容器类型开头,紧跟着是动作名;在这种情况下,函数名可以当作方法名。例如:SeqPush, GraphAddEdgeByIdx。
返回值应该选择能简化功能的用法。通常一个函数创建一个对象并返回该对象。对于函数,处理动态数据结构或标量,这是一个好的方法。然而在图片处理函数中会经常分配和回收大内存块,所以图片处理函数不能创建和返回图像结果而是修改输出一个作为参数传入的图像。
函数不应该用关于严重错误(例如空指针,0除数,错误参数范围,不支持的图像格式等)的信号作为返回值。在这种情况下,可以用一种类似于IPL中特殊的错误处理机制。相反,使用期望的运行时情况信号作为返回值比较好。(例如,跟踪图像移动到屏幕外)。
参数类型选择已经存在于OpenCV中的类型更适宜:IplImage用于光栅图像,CvMat用于矩阵,CvSeq用于轮廓线等。建议不使用简单指针和计数,因为有许多函数参数,它降低了库接口并使程序更难读。
一个一致的参数顺序很重要,因为它使参数易于记住顺序并且帮助程序员避免错误和使用错误的参数顺序联接函数。
- 对于简单过程函数(在命名模式列表中的第一种和第二种类型)典型的顺序是:输入参数,输出参数,标记或可选参数。
- 对于容器元素方法,顺序是:容器,元素位置,标记或可选参数。
输入参数经常用const修饰符。可选参数经常简化函数用法。因为C++允许在参数列表后跟随可选参数,它也可能影响决定以参数顺序,最重要的是标记位于最前,次重要的随后。在函数声明中用CV_DEFAULT宏指定可选参数的默认值.它使声明与C相兼容。
示例函数声明请参见cvexample.h和cv.h、cvaux.h.
函数实现
本节主要关注以下几点:
- 参数类型检查
- 错误产生和处理
- 内存管理和资源回收
- 调用低级函数
如前面所说,OpenCV函数广泛使用高级数据类型传送和返回参数。它简化了函数的使用,但是增加了使用错误的参数组合调用函数的可能性(例如浮点图像代替位图,或两个不同大小的图像)。检查标准类型参数存在标准的方法。
IplImage图像能通过CV_CHECK_IMAGE宏被检查。该宏检查传给IplImage的指针和潜在的图像数据指针不为空,图像有像素顺序,没有ROI掩码或冗馀信息。
CV_CHECK_MASK_IMAGE用于检查掩码图像,二值图和灰度图。除了CV_CHECK_IMAGE检查的条件外,它还能确保图像有8位深度和单通道。并且,所有的输入和输出图像在进行深度\通道数和尺寸组合前应该被检查。随后,应该在调用cvGetImageRawData函数返回的图像ROI尺寸后应该被检查。输入等高线和其它动态数据结构能够用CV_IS_CONTOUR和相关的宏进行检查。
任何时候,当传入一个错误的参数或在函数执行时发生其它严重错误时,应该通过cvError函数抛出一个错误. OpenCV中与几乎所有标准的低级C库的IPL类似,有一个错误处理机制.那就是存在一个全局错误状态代替返回错误码:可以通过以下实现:
- 使用cvError函数设置
- 使用cvClearErrStatus清除
- 使用cvGetErrorStatus读取
除了设置错误状态和指定值外,cvError还能进行额外的操作,依据错误处理模式而不同,错误处理模式可以通过cvSetErrorMode调整.在silent模式或parent模式下cvError立即返回.在子模式下,它打印出错误消息并终止应用程序。
为了更方便使用. 可以通过使用如下宏来代替以上函数:
- CV_ERROR和 OPENCV_ERROR代替cvError
- 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 ); }
-
- 在符合以上样式的前提下其它样式也可能接受。也就是说,如果一个人修改别人的代码,他应该使用相同的代码样式。
移植性
- 所要代码必须符合以下标准:
- ANSI C 第一个语言标准ISO/IEC 9899:1990
- C9X (1999年修订的新标准) – ISO/IEC 9899.
- C++ 标准 – ISO/IEC 14882-1998.
-
- 你应该去除编译器依赖或平台依赖和系统调用,例如:
- 编译器: pragma's
- 特定关键字: __stdcall, __inline, __int64(or long long).使用CV_INLINE, CV_STDCALL, int64分别代替。
- 编译器扩展,例如?<和<?宏表示最小和最大,重载宏等。
- 内联汇编
- Unix或Win32调用,如:bcopy, readdir, CreateFile, WaitForSingleObject 等。
- 用sizeof代替具体的数据大小(如sizeof(int)而不是4),字节顺序(*(int*)"\x1\x2\x3\x4"是0x01020304或0x04030201或什么?),用简单的字符有符号字符或无符号字符处理数据(不是字符串)。使用短形式,uchar表示unsigned char和schar表示signed char。使用预处理指令包含非可移植性代码片段。不要试图使用标准元素,主要编译器制造商几乎不支持这些。
函数文档编写
-
文档以HTML格式提供,因为HTML格式提供文本格式化功能和超级链接,同时它非常简单,易用和易于维护。每个函数的文档或相关函数组的文档放入不同文件中,该文可以从主页链接到。
-
这是一个包含链接到函数示例文档的主页原型,实现在cvexample.cpp中。
-
看一下这些页面和相应的HTML代码,包括详细的格式化注释。你对拷贝函数文档HTML,适当的更改和加入一个链接到索引页中。
- 函数文档HTML文件包知以下基本元素(以如下顺序):
- 页面标题——显示在浏览器标题栏中并且表示一个扩展的函数名或函数组名。
- 关键字列表——用于搜索引擎和各种工具检索文档。
- 可见的页面标题——简单的重复页面标题。
- 函数名——真实的函数名,但是不包括前缀。它应该标签化以便从文档的其它位置引用它。
- Blurb——单行函数描述。
- 函数声明——按它在头文件中的形式,用<pre>和</pre>包围。除了OPENCVAPI被忽略外,默认参数的普通C++语法用CV_DEFAULT宏代替。
- 函数参数描述——一个<参数名,描述>对的列表。
- 讨论——描述函数功能的节。允许或支持的参数组合的限制,算法参考(链接或标题)。
- 使用示例——一个可选的代码片断或伪代码段.几个相关函数可以共享相同示例.
- 请参见——包含零个或多个相关函数链接。
函数测试实现
- 每个测试实现为从文本文件输入和输出结果到另外一个文本文件的C/C++函数。这样,函数就有如下接口:
-
bool <TestName>( const char* inputfile, const char* output file );
-
输入输出文件的格式没有定义。然而,如果测试系统函数用于从文件中读取或写入高级数据(矩阵,文件名,轮廓等),那么文件的格式应与函数兼容。
- 外部或主测试函数执行所有或选择的测试并且与标准结果相比较,它可以由其它程序或以前执行的相同测试创建。
使用这种设计可以实现检查在一个函数上检查几个特殊的数据集和测式比较函数在武断的数据上的输出和标准输出。在这种情况下输出文件能以不同的两个输出和从前面段的标准结果将全部是零。
-
- 测试系统API使测试更容易,它包括:
- 系统测试内核(测试注册,文件管理,用异常处理能力控制测试)
- 从文本文件取得矩阵、文件名和其它数据和写入数据到文本文件的函数。
- 检查数组中的特殊值。
- 内存管理函数帮助捕捉内存泄漏和缓冲区越界。
- 随机数据生成。
- 简单算法函数(矩阵操作)
- 可视化函数
- 很多功能实现在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()函数的主测试已经包含在项目中,所以不需要再编写执行代码.
- 创建输入和标准数据.首先你可能需要手工创建.标准数据能够通过用-c(--create-etalon)传输测命令行选项标识生成。并且,这种情况可以在测试函数中用tstIsEtalonMode()函数(在这种情况下,给定可靠值给另外一个算法的变量)处理。
- 输入输出数据存放在opencv_tst/testdata文件夹中.
- 测试数据文件的名字由测试体文件和特定的扩展名构成.(输入数据文件的扩展名为”.in”,输出数据文件的扩展名为”.out”,标准数据文件的扩展名为”.0”).
-
- 随后,测试系统将在不同的模式下执行.
[ 编辑]提示
某些OpenCV函数使用小结构体作为输入.使结构体的名称类型为CvSomething.然后cvSomething通常是内联的,从参数列表构成对象.使用这些内联的构造器,使代码更容易写和读.
使用cvRound,cvFloor和cvCeil快速转换浮点数为相近的整形数或到负无穷在或正无穷大.在x86架构上,这些函数比简单转换操作运行更快.在C9X标准中有几个标准的函数做相同的事情,但是它们现在很少被支持.当情况发生时,以上函数将转换为内联宏.
[ 编辑]附录
[ 编辑]附录A: 参考
本文不是完整的样式参考,更多详细和写得更好相关主题列表如下:
- 推荐C样式和标准编码(Indian Hill C样式和编码标准更新版) . Henry Spencer et al. Rev. 6.0, 1990.
- C++编程规则与建议 Mats Henricson, Erik Nyquist. Ellemtel Communications Systems Laboratories. 1990-1992.
- GNU编码样式, Richard Stallman. GNU Project – Free Software Foundation. 2000
- 用C语言编写可移植的程序 A. Dolenc, A. Lemmke, D. Keppel, G.V. Reilly. 1990。类似文档可以在http://www.softpanorama.org/Lang/c.html 上找到。
[ 编辑]附录B: 规则简述列表
- 文件名以小写的cv前缀开始.头文件扩展名为.h或.hpp
- 实现文件扩展名为.cpp
- 每一个文件在开头包含BSD兼容许可
- 头文件有保护宏和extern “C” { } 保护C语言部分接口
- 源文件包含”precomp.h”作为第一个头文件
- 外部函数名和数据类型名写成大小写混合类型.外部宏写作大写字符
- 外部函数以cv前缀开始
- 内部函数前缀为icv
- 数据类型前缀为Cv
- 外部宏前缀为CV
- 函数声明形式为:
OPENCVAPI <returnType> <functionName>( arguments );
- 函数实现形式为:
CV_IMPL <returnType> <functionName>( arguments ) { CV_FUNCNAME("<functionName>"); ... __BEGIN__; ... __END__; // cleanup ... return ...; }
- 使用CV_ERROR/OPENCV_ERROR抛出错误,使用CV_CALL/OPENCV_CALL调用高级函数,使用IPPI_CALL调用低级函数
- 使用cvAlloc和cvFree分配和回收临时缓冲区
- 在每一个源文件中保持一致的格式
- 与C/C++标准兼容,避免编译器依赖性,系统依赖性和平台依赖性
附录C: 附加说明
本文是hzy (hzy@hzy.org.cn) 02:09 2006年8月17日 (CST)根据OpenCV Coding Style Guide翻译。
-