1.7. OpenCV Python绑定¶
OpenCV Python绑定是如何工作的?
1.7.2. 如何生成OpenCV Python绑定?¶
在OpenCV,所有算法都是用C++实现的。但这些算法可以从不同的语言(如Python、Java等)中使用,这是由绑定生成器实现的。这些生成器在C++和Python之间创建了一个桥梁,使用户可以从Python调用C++函数。要全面了解后台发生的事情,需要对Python/C API有很好的了解。将C++函数扩展到Python的一个简单例子可以在官方Python文档中找到 [1] . 因此,通过手动编写包装器函数将OpenCV中的所有函数扩展到Python是一项耗时的任务。所以OpenCV用了一种更智能的方式。opencv使用C++中的一些Python脚本自动从C++头文件中生成这些包装器函数。 modules/python/src2
. 我们会调查他们的所作所为。
第一, modules/python/CMakeFiles.txt
是一个CMake脚本,它检查要扩展到Python的模块。它将自动检查所有要扩展的模块并获取它们的头文件。这些头文件包含该特定模块的所有类、函数、常量等的列表。
其次,这些头文件被传递给一个Python脚本, modules/python/src2/gen2.py
. 这是Python绑定生成器脚本。它调用另一个Python脚本 modules/python/src2/hdr_parser.py
. 这是头解析器脚本。这个头解析器将完整的头文件拆分成小的Python列表。因此,这些列表包含有关特定函数、类等的所有详细信息。例如,将对函数进行分析以获取包含函数名、返回类型、输入参数、参数类型等的列表。最终列表包含该头文件中所有函数、结构、类等的详细信息。
但是头解析器并不解析头文件中的所有函数/类。开发人员必须指定哪些函数应该导出到Python。为此,在这些声明的开头添加了一些宏,使头解析器能够识别要解析的函数。这些宏是由编写特定函数的开发人员添加的。简而言之,开发人员决定哪些函数应该扩展到Python,哪些不应该。这些宏的详细信息将在下一个会话中给出。
所以头解析器返回一个解析函数的最终大列表。我们的生成器脚本(gen2.py)将为头解析器解析的所有函数/类/枚举/结构创建包装函数(您可以在 build/modules/python/
文件夹为 pyopencv_generated_*.h
文件)。但是可能有一些基本的OpenCV数据类型,比如Mat、Vec4i、Size。它们需要手动扩展。例如,Mat类型应该扩展到Numpy数组,Size应该扩展到两个整数的元组等。同样,可能有一些复杂的结构/类/函数等需要手动扩展。所有这些手动包装函数都放在 modules/python/src2/pycv2.hpp
.
现在只剩下这些包装文件的编译 cv2 模块。所以当你调用一个函数时 res = equalizeHist(img1,img2)
在Python中,传递两个numpy数组,并期望另一个numpy数组作为输出。所以这些numpy数组被转换成 cv::Mat
然后打电话给 equalizeHist()
C++中的函数。最终结果, res
将转换回Numpy数组。总之,几乎所有的操作都是用C++完成的,这给了我们和C++一样的速度。
所以这是OpenCV Python绑定生成的基本版本。
1.7.3. 如何将新模块扩展到Python?¶
头解析器基于添加到函数声明中的一些包装宏解析头文件。枚举常量不需要任何包装宏。它们是自动包装的。但其余的函数、类等都需要包装宏。
函数的扩展使用 CV_EXPORTS_W
宏。下面是一个例子。
>>> CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );
头解析器可以理解来自以下关键字的输入和输出参数 InputArray, OutputArray
但有时,我们可能需要硬编码输入和输出。为此,宏 CV_OUT, CV_IN_OUT
使用等。
>>> CV_EXPORTS_W void minEnclosingCircle( InputArray points,
>>> CV_OUT Point2f& center, CV_OUT float& radius );
对于大型课程, CV_EXPORTS_W
使用。要扩展类方法, CV_WRAP
使用。同样地, CV_PROP
用于类字段。
>>> class CV_EXPORTS_W CLAHE : public Algorithm
>>> {
>>> public:
>>> CV_WRAP virtual void apply(InputArray src, OutputArray dst) = 0;
>>>
>>> CV_WRAP virtual void setClipLimit(double clipLimit) = 0;
>>> CV_WRAP virtual double getClipLimit() const = 0;
>>> }
重载函数可以使用 CV_EXPORTS_AS
. 但是我们需要传递一个新的名称,这样在Python中每个函数都将被这个名称调用。以下面的积分函数为例。有三个函数可用,因此每个函数在Python中都用后缀命名。类似地 CV_WRAP_AS
可用于包装重载方法。
>>> //! computes the integral image
>>> CV_EXPORTS_W void integral( InputArray src, OutputArray sum, int sdepth = -1 );
>>>
>>> //! computes the integral image and integral for the squared image
>>> CV_EXPORTS_AS(integral2) void integral( InputArray src, OutputArray sum,
>>> OutputArray sqsum, int sdepth = -1, int sqdepth = -1 );
>>>
>>> //! computes the integral image, integral for the squared image and the tilted integral image
>>> CV_EXPORTS_AS(integral3) void integral( InputArray src, OutputArray sum,
>>> OutputArray sqsum, OutputArray tilted,
>>> int sdepth = -1, int sqdepth = -1 );
使用 CV_EXPORTS_W_SIMPLE
. 这些结构通过值传递给C++函数。例如KeyPoint、Match等,它们的方法被扩展为 CV_WRAP
字段扩展为 CV_PROP_RW
.
>>> class CV_EXPORTS_W_SIMPLE DMatch
>>> {
>>> public:
>>> CV_WRAP DMatch();
>>> CV_WRAP DMatch(int _queryIdx, int _trainIdx, float _distance);
>>> CV_WRAP DMatch(int _queryIdx, int _trainIdx, int _imgIdx, float _distance);
>>>
>>> CV_PROP_RW int queryIdx; // query descriptor index
>>> CV_PROP_RW int trainIdx; // train descriptor index
>>> CV_PROP_RW int imgIdx; // train image index
>>>
>>> CV_PROP_RW float distance;
>>> };
其他一些小类/结构可以使用 CV_EXPORTS_W_MAP
将其导出到Python本机字典中。矩()就是一个例子。
>>> class CV_EXPORTS_W_MAP Moments
>>> {
>>> public:
>>> //! spatial moments
>>> CV_PROP_RW double m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
>>> //! central moments
>>> CV_PROP_RW double mu20, mu11, mu02, mu30, mu21, mu12, mu03;
>>> //! central normalized moments
>>> CV_PROP_RW double nu20, nu11, nu02, nu30, nu21, nu12, nu03;
>>> };
这些是OpenCV中可用的主要扩展宏。通常,开发人员必须在适当的位置放置适当的宏。其余由生成器脚本完成。有时,可能会出现生成器脚本无法创建包装器的例外情况。这些功能需要手动处理。但大多数情况下,根据OpenCV编码准则编写的代码将由生成器脚本自动包装。