有時常常遇到兩種語言切換的問題,比如在C++中實現了一個圖像處理算法,將其封裝爲一個函數,該函數的輸入輸出類型爲OpenCV中常用的類型如cv::Mat等,需要在Python中調用,這時可以愉快地利用OpenCV生成Python包的方法。
原理
利用OpenCV生成Python包的原理,將我們寫的C++程序編譯成Python包(本文介紹的是在Windows環境下,使用VS,Python 64位,其他環境按需修改即可,主要是幾個文件)
原理參考 How OpenCV-Python Bindings Works?
參考learnopencv,本文介紹了cv2.cpp
的修改方法,與參考有些區別,改動少一些
配置方法
環境注意事項
- Python位數,和使用的VS編譯器位數要相同,本文測試的都是64位
- 本文介紹的OpenCV版本是4.0.1,其他版本按需修改(主要是幾個文件,支持生成Python包的OpenCV版本應該都是可以的)
配置步驟
-
將
cv2.cpp
,pycompat.hpp
,gen2.py
,hdr_parser.py
這幾個文件從OpenCV源碼中拷貝出來,大致路徑在moudules/python/src
-
目錄結構(要新建的後文介紹)
--cv2.cpp
--pycompat.hpp
--gen2.py
--hdr_parser.py
--headers.txt
--src
----pycvtest.h
----pycvtest.cpp
--pycvtest
pycvtest.h
中寫這些,注意命名空間必須使用cv,減少後續工作量,注意不要直接導入opencv.hpp
,因爲它導入了大量的庫,如果不需要不用導入,按需修改,本例中只是用了core.hpp
用來展示
#pragma once
//#include <opencv.hpp>
#include <opencv2/core.hpp>
namespace cv {
CV_EXPORTS_W void add_one_for_test(cv::InputArray t_in, cv::OutputArray t_out);
}
pycvtest.cpp
中寫這些
#include "pycvtest.h"
namespace cv {
void add_one_for_test(cv::InputArray t_in, cv::OutputArray t_out)
{
cv::add(t_in, 1, t_out);
}
- 修改
hdr_parser.py
中的列表opencv_hdr_list
,把我們要加的頭文件寫進去,其他都不要
opencv_hdr_list = [
"src/pycvtest.h"
]
- 註釋
gen2.py
中
if hdr.find('opencv2/') >= 0: #Avoid including the shadow files
self.code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('opencv2/'):]) )
改成
self.code_include.write( '#include "{0}"\n'.format(hdr[hdr.rindex('src/'):]) )
這個會影響好後生成的pyopencv_generated_include.h
文件導入頭文件的代碼,生成後可查看,按需修改~
- 新建headers.txt寫頭文件路徑
src/pycvtest.h
- 新建pycvtest文件夾,用於生成頭文件~
- 運行
python gen2.py pycvtest headers.txt
,這時看pycvtest
文件夾,應該已經生成好了pyopencv
開頭的文件了 - 在VS中新建DLL工程,注意事項(默認已經配置好了
OpenCV
)
– 添加cv2.cpp
,pycompat.h
,pycvtest.h
,pycvtest.cpp
– 添加include
路徑,就是剛剛生成以pyopencv_
文件名開頭的文件目錄pycvtest
– 屬性中C/C++ 預處理器添加宏定義CVAPI_EXPORTS
,必須寫出來,不然不會導出
– 添加Python庫lib
目錄,大致是Python安裝路徑\Python35\libs
– 添加Python包含include
目錄,大致是Python安裝路徑\Python35\include
– 添加Numpy包含目錄,大致是什麼什麼\Lib\site-packages\numpy\core\include
– 屬性常規選項卡,目標文件擴展名.pyd
– 屬性常規選項卡,目標文件名pycvtest
下面愉快地改cv2.cpp
文件
- 前面有個模板類
PyOpenCV_Converter
,裏面的兩個函數to
from
沒有寫,但是後面調用了,VS會編譯不通過,我改的方法是註釋取消,然後把函數定義寫出來,函數內容什麼都不用寫就是下面這樣,後面針對具體類型的模板函數會重載下面的pyopencv_to
和pyopencv_from
,所以不用擔心,有更好改法的朋友請告知,謝謝~
template<typename T, class TEnable = void> // TEnable is used for SFINAE checks
struct PyOpenCV_Converter
{
static inline bool to(PyObject* obj, T& p, const char* name) {
return true;
}
static inline PyObject* from(const T& src) {
return nullptr;
}
};
- 註釋掉
// #include "opencv2/opencv_modules.hpp"
因爲它導入了太多包,按需修改~ - 取消沒有用到的宏定義,按需修改!
#undef HAVE_OPENCV_HIGHGUI
#undef HAVE_OPENCV_DNN
- 註釋掉沒有使用的文件,按需修改~
//#include "pyopencv_custom_headers.h"
- 最後重要步驟!把
PyInit_cv2()
這個初始化包的函數名稱改成自己的PyInit_pycvtest()
,文件中的全都改一下,就改兩個地方就行了,就函數聲明那個位置
- 然後編譯吧~,因該主要地方都改了,有小錯誤按需修改。編譯好之後就會編出個
pycvtest.pyd
使用
拷貝需要的動態鏈接庫到pyd文件夾,或者添加動態鏈接庫的目錄到Python運行目錄,然後導入包,還有一種方法是先導入cv2
然後再導入pycvtest
,如下所示
import numpy as np
import cv2
import pycvtest as pct
a = np.array([[2, 3, 4]])
output = pct.add_one_for_test(a)
print(output)
好了~