1.7. OpenCV Python绑定

OpenCV Python绑定是如何工作的?

1.7.1. 目标

学习:

  • 如何生成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编码准则编写的代码将由生成器脚本自动包装。