2019-10-22 opencv和python绑定是如何实现的?

官网参见https://docs.opencv.org/3.4.1/da/d49/tutorial_py_bindings_basics.html

opencv和python绑定是如何生成的?

opencv中所有算法都是C++实现的。不过所有算法都可以用其他语言使用的,例如:python,Java。这是通过绑定生成器来实现的。这些生成器在C++和python之间创建了桥梁,让用户可以在python中调用C++函数。如果要了解整个过程,那就需要python/C的API知识了。python官方文档中有一个简单的例子说明如何扩展C++函数到python。如果要自己手工编写把所有opencv函数扩展到python的包装函数是很花费时间的。所以opencv采用了一种更加聪明的方法。opencv会使用一些modules/python/src2目录下的python脚本,来通过C++函数头文件自动生成包装函数。我们来看看是怎么做的。

首先,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 需要变换为2个整数的元组。同样,还有一些复杂的结构/类/函数也需要手工变换。所有的手工包装函数都在modules/python/src2/cv2.cpp中。

最后编译包装函数,生成从cv2模块。当你在python中调用一个函数,res = equalizeHist(img1,img2),传入2个numpy数组,输出1个numpy数组。这些numpy数组会转化为cv::Mat类,再调用C++中的equalizeHist() 函数。最终res会被再转化为numpy数组。也就是说,所有的操作都在C++中完成,速度和C++中运行完全一样。

以上就是opencv和python绑定生成的基本版本。

如何在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中函数可以通过这个名称来调用。以下面的积分函数为例。有3个重载函数,在python中每个都加入一个后缀。

CV_EXPORTS_W void integral( InputArray src, OutputArray sum, int sdepth = -1 );
CV_EXPORTS_AS(integral2) void integral( InputArray src, OutputArray sum,
                                        OutputArray sqsum, int sdepth = -1, int sqdepth = -1 );
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的字典。例如:Moments()

class CV_EXPORTS_W_MAP Moments
{
public:
    CV_PROP_RW double  m00, m10, m01, m20, m11, m02, m30, m21, m12, m03;
    CV_PROP_RW double  mu20, mu11, mu02, mu30, mu21, mu12, mu03;
    CV_PROP_RW double  nu20, nu11, nu02, nu30, nu21, nu12, nu03;
};

以上这些就是opencv中主要的宏。通常开发者需要在适当的位置放入适当宏,剩余的工作就由生成器脚本来完成了。有时候,生成器脚本不能创建包装器,此时就需要手工处理,编写你自己的pyopencv_*.hpp头文件,然后把它们放到模块的misc/python子目录中。大部分情况根据opencv编码准则编写的代码将由生成器脚本自动包装。

发布了122 篇原创文章 · 获赞 7 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_42555985/article/details/102675953