opencv源码编写规则
OPENCV作为一种开源的计算机视觉库,我们有必要去了解这个库的一些编码格式及文件结构。
1、文档命名规则
必须将所有功能放入一个或多个.cpp和.hpp文件到OpenCV的相应模块中,或者如果贡献的功能是相当大的代码,或者如果它不适合任何现有代码,则应创建新模块模块。
- 所有文件名都以小写字母书写,以便更好地兼容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
等),请重新使用它。
2、文件结构
标头和实现文件的其他规则包括:
- 必须将所有功能放入
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 ++中的预编译标头机制正常工作。
3、命名约定
- OpenCV对外部函数,类型和类方法使用混合大小写样式标识符。
- 班级名称以大写字母开头。
- 方法'和函数'的名称以小后者开头,除非它们以算法的作者命名,例如
cv::Sobel()
,在这种情况下,它们可以以大写字母开头。 - 宏和枚举常量用全都大写字母书写。单词由下划线分隔。
- 必须使用所有外部函数和类
CV_EXPORTS
,否则Windows上将存在链接错误。对于要在Python,Java等中公开的函数/类,您应该使用CV_EXPORTS_W
而不是CV_EXPORTS
,但请注意重载的函数和方法,非标准参数类型等。
4、设计功能和类接口
设计函数接口是一种重要的方式,与库的其余部分一致。功能界面的元素包括:
- 功能:功能必须定义良好且非冗余。该功能应易于嵌入到使用其他OpenCV功能的不同处理流水线中。
- 名称
名称应基本反映功能目的。OpenCV中有一些常见的命名模式:
- 函数名的多数具有形式:
<actionName><Object><Modifiers>
例如calibrateCamera
,calcOpticalFlowPyrLK
。 - 有时功能可以由它实现它产生的算法名或结果对象的名称,例如被称为
Sobel
,Canny
,Rodrigues
,sqrt
,goodFeaturesToTrack
。
- 函数名的多数具有形式:
- 返回值
应该选择它来简化功能使用。通常,创建/计算值的函数应该返回它。对于返回标量值的函数,这是一个好习惯。但是,在图像处理功能的情况下,这将导致大内存块的频繁分配/释放。图像处理功能通常修改输出图像,输出图像作为参数(通过引用)传递,而不是创建和返回结果图像。
函数不应该使用返回值来表示关键错误,例如空指针,除零,不良参数范围,不支持的图像格式等。相反,它们应该抛出异常,实例
cv::Exception
或其衍生类。另一方面,建议使用返回值来报告在正常工作的系统中可能发生的非常正常的运行时情况(例如,跟踪的对象在图像之外等) - 参数类型
参数类型最好由已经存在的组的OpenCV类型的选择:
Mat
对于光栅图像和矩阵,vector<Mat>
进行图像采集,vector<Point>
,vector<Point2f>
,vector<Point3f>
,vector<KeyPoint>
对点集,轮廓或关键点的集合,Scalar
为1〜4元数值的元组(如颜色,四元数等。)不建议使用普通指针和计数器,因为它使接口更低级,意味着更可能的输入错误,内存泄漏等。对于将复杂对象传递给函数,方法,请考虑Ptr<>
智能指针模板类。
一致的参数顺序很重要,因为它更容易记住顺序,它有助于程序员避免错误,连接错误的参数顺序。通常的顺序是:<输入参数>,<输出参数>,<标志和可选参数>。
输入参数通常具有
const
限定符。大对象通常通过常量引用传递; 原始类型和小结构(int, double, Point, Rect
)按值传递。可选参数通常简化了函数使用。因为C ++仅在参数列表的末尾允许可选参数,所以它也可能影响参数顺序的决策 - 最重要的标志首先出现,而不太重要。
- 论证顺序
- 某些参数的默认值
5、高级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>); } }}
如此一来,你的类就创建完毕了
6、如何去扩展和修改算法
比如说,您为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>
。
7、代码布局
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 ); }
如果仅满足上述规则,也可以接受其他样式。也就是说,如果被其他代码改写了,他应该使用相同的编码风格。
8、可移植性要求
形式上,代码必须符合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
的符号字符。使用预处理程序指令来处理不可移植的代码片段。
9、编写有关函数的文档
贡献函数的文档使用内联Doxygen注释编写。该文档每晚构建,并上传到docs.opencv.org。
使用现有文档作为示例。您也可以通过图片,代码示例等提供大型描述性文本块的教程。
10、实施测试
- 对于测试,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
。
11、总结
- 文件名以小写字母书写。
- 标题扩展名为.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评论提供代码文档。欢迎使用教程。
参考资料:
1、OpenCV官方手册
2、Coding_Style_Guide