OpenCV-Python bindings是如何生成的(1)
翻譯自How OpenCV-Python Bindings Works?
目標
學習
- OpenCV-Python bindings是如何生成的
- 如何為Python擴展新的opencv模塊
OpenCV-Python bindings是如何生成的
在OpenCV里,所有算法都是用C++實現的。但是這些算法可以在別的語言里使用,比如Python,Java等。這就是通過bindings生成器實現的。這個生成器產生一個C++和Python之間的橋梁,讓用戶可以在Python里調用C++函數。要完整了解后臺發生了什么,需要Python/C API的知識,在Python官方文檔里有一個擴展C++的函數到Python里的簡單例子。通過手工寫包裝函數的方法在OpenCV里把所有函數擴展到Python是個很費時的任務,所以OpenCV用了個更智能的辦法。OpenCV用一些Python腳本從C++的頭文件自動生成這些包裝函數,這些腳本放在modules/python/src2。我們來看看他們做了什么。
首先,modules/python/CMakeFiles.txt是一個CMake腳本,用來檢查要擴展到Python的模塊。它會自動檢查所有要擴展的模塊,并抓取他們的頭文件。這些頭文件包含那個模塊的所有類,函數,常量的列表等。
然后,這些頭文件被傳給Python腳本modules/python/src2/gen2.py。這就是那個Python bindings生成腳本。它會調用另外一個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/cv2.cpp。
現在唯一剩下的事情是編譯這些包裝文件成cv2模塊。所以當你在Python里調用一個函數時,比如 res = equalizeHist(img1, img2)。你傳入兩個numpy數組,并希望輸出另一個numpy數組。這些numpy數組被轉換成了cv::Mat并在C++里調用equalizeHist()函數。最后的結果res被轉換回Numpy數組。簡單來說,就是幾乎所有的運算都是在C++里完成的,這使執行速度也和C++里幾乎一樣。
這就是OpenCV-Python binding 如何產生的基本介紹。
如何在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 indexCV_PROP_RW int trainIdx; // train descriptor indexCV_PROP_RW int imgIdx; // train image indexCV_PROP_RW float distance; };一些其他的小類/結構體可以用CV_EXPORTS_W_MAP來導出,導出到Python的原生字典。Moments()就是這樣一個例子:
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 momentsCV_PROP_RW double nu20, nu11, nu02, nu30, nu21, nu12, nu03; };所以這些是OpenCV里主要的擴展宏,一般來說,開發人員得在合適的位置放上合適的宏。剩下的工作就由生成器腳本完成。有時候可能有意外情況生成器腳本沒法創建包裝器,這樣的函數需要手工處理。但是大多數情況,用OpenCV編碼指導寫的代碼應該就能被生成器腳本自動包裝了。
總結
以上是生活随笔為你收集整理的OpenCV-Python bindings是如何生成的(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 常用的lucene分词器-笔记
- 下一篇: hadoop 二次开发DatanodeW