天天看點

OpenCV-Python Bindings 如何工作 | 六十四

作者|OpenCV-Python Tutorials

編譯|Vincent

了解:

  • 如何生成OpenCV-Python bindings?
  • 如何将新的OpenCV子產品擴充到Python?

OpenCV-Python bindings如何生成?

在OpenCV中,所有算法均以C ++實作。但是這些算法可以從不同的語言(例如Python,Java等)中使用。綁定生成器使這成為可能。這些生成器在C ++和Python之間建立了橋梁,使使用者能夠從Python調用C ++函數。為了全面了解背景發生的事情,需要對Python / C API有充分的了解。在官方Python文檔中可以找到一個有關将C ++函數擴充到Python的簡單示例[1]。是以,通過手動編寫包裝函數将OpenCV中的所有函數擴充到Python是一項耗時的任務。是以,OpenCV以更智能的方式進行操作。 OpenCV使用位于​

​modules/python/src2​

​中的一些Python腳本,從C ++頭自動生成這些包裝器函數。我們将調查他們的工作。

首先,​

​modules/python / CMakeFiles.txt​

​是一個CMake腳本,用于檢查要擴充到Python的子產品。它将自動檢查所有要擴充的子產品并擷取其頭檔案。這些頭檔案包含該特定子產品的所有類,函數,常量等的清單。

其次,将這些頭檔案傳遞到Python腳本​

​modules/python/src2/gen2.py​

​。這是Python Binding生成器腳本。它調用另一個Python腳本​

​module/python/src2/hdr_parser.py​

​。這是标頭解析器腳本。此标頭解析器将完整的标頭檔案拆分為較小的Python清單。是以,這些清單包含有關特定函數,類等的所有詳細資訊。例如,将對一個函數進行解析以擷取一個包含函數名稱,傳回類型,輸入參數,參數類型等的清單。最終清單包含所有函數,枚舉的詳細資訊,頭檔案中的structs,classs等。

但是标頭解析器不會解析标頭檔案中的所有函數/類。開發人員必須指定應将哪些函數導出到Python。為此,在這些聲明的開頭添加了某些宏,這些宏使标頭解析器可以辨別要解析的函數。這些宏由對特定功能進行程式設計的開發人員添加。簡而言之,開發人員決定哪些功能應該擴充到Python,哪些不應該。這些宏的詳細資訊将在下一個會話中給出。

是以頭解析器将傳回已解析函數的最終大清單。我們的生成器腳本(gen2.py)将為标頭解析器解析的所有函數/類/枚舉/結建構立包裝函數(你可以在編譯期間在​

​build/modules/python/​

​檔案夾中以pyopencv_genic_*.h檔案找到這些标頭檔案)。但是可能會有一些基本的OpenCV資料類型,例如Mat,Vec4i,Size。它們需要手動擴充。例如,Mat類型應擴充為Numpy數組,Size應擴充為兩個整數的元組,等等。類似地,可能會有一些複雜的結構/類/函數等需要手動擴充。所有這些手動包裝函數都放在​

​modules/python/src2/cv2.cpp​

​中。

是以現在剩下的就是這些包裝檔案的編譯了,這給了我們cv2子產品。是以,當你調用函數時,例如在Python中說​

​res = equalizeHist(img1,img2)​

​,你将傳遞兩個numpy數組,并期望另一個numpy數組作為輸出。是以,将這些numpy數組轉換為cv::Mat,然後在C++中調用equalizeHist()函數。最終結果将res轉換回Numpy數組。簡而言之,幾乎所有操作都是在C++中完成的,這給了我們幾乎與C++相同的速度。

是以,這是OpenCV-Python bindings生成方式的基本形式。

如何擴充新的子產品到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​

​可用于包裝重載方法。

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編碼指南編寫的代碼将由生成器腳本自動包裝。

更進階的情況涉及為Python提供C ++接口中不存在的其他功能,例如額外的方法,類型映射或提供預設參數。稍後,我們将以​

​UMat​

​資料類型為例。首先,要提供特定于Python的方法,​

​CV_WRAP_PHANTOM​

​的用法與​

​CV_WRAP​

​相似,不同之處在于它以方法标頭作為參數,并且你需要在自己的​

​pyopencv_*.hpp​

​擴充名中提供方法主體。 ​

​UMat::queue()​

​和​

​UMat::context()​

​是此類幻象方法的示例,這些幻象方法在C++接口中不存在,但在Python端處理OpenCL功能時需要使用。其次,如果一個已經存在的資料類型可以映射到你的類,則最好使用​

​CV_WRAP_MAPPABLE​

​以源類型作為其參數來訓示這種容量,而不是精心設計自己的綁定函數。從Mat映射的UMat就是這種情況。最後,如果需要預設參數,但本機C++接口中未提供,則可以在Python端将其作為​

​CV_WRAP_DEFAULT​

​的參數提供。按照下面的​

​UMat::getMat​

​示例:

class CV_EXPORTS_W UMat
{
public:
    // 你需要提供 `static bool cv_mappable_to(const Ptr<Mat>& src, Ptr<UMat>& dst)`
    CV_WRAP_MAPPABLE(Ptr<Mat>);
    /! returns the OpenCL queue used by OpenCV UMat.
    // 你需要在資料夾代碼中提供方法主體
    CV_WRAP_PHANTOM(static void* queue());
    // 你需要在資料夾代碼中提供方法主體
    CV_WRAP_PHANTOM(static void* context());
    CV_WRAP_AS(get) Mat getMat(int flags CV_WRAP_DEFAULT(ACCESS_RW)) const;
};