博客專欄

EEPW首頁 > 博客 > 深度解決添加復雜數(shù)據(jù)增強導致訓練模型耗時長的痛點(2)

深度解決添加復雜數(shù)據(jù)增強導致訓練模型耗時長的痛點(2)

發(fā)布人:計算機視覺工坊 時間:2022-12-22 來源:工程師 發(fā)布文章

4. C++ And CUDA Extensions 

For Python/ PyTorch


C++ 與 Python 或 PyTorch 的交互,業(yè)界主流做法是采用 pybind11,關于Pybind11 的更多詳細說明可以參看文獻 [15],其核心原理如下圖所示:


圖片

pybind11 pipeline


由于 PyTorch 的 C++ 拓展與純 Python 有一些區(qū)別,因為 PyTorch 的基礎數(shù)據(jù)類型是 torch.Tensor,該數(shù)據(jù)類型可以認為是 Pytorch 庫對 np.array 進行了更高一層的封裝。所以,在寫拓展程序時,其接口函數(shù)所需要的數(shù)據(jù)類型以及調(diào)用的庫會有些區(qū)別,下面會詳細解釋。


4.1. C++ Extensions For Python


首先我們看 Python 代碼,如下所示(scripts/test_warpaffine_opencv.py):





















import cv2import torch  # 不能刪掉, 因為需要動態(tài)加載torch的一些動態(tài)庫,后面會詳細說明.import numpy as npfrom orbbec.warpaffine import affine_opencv  # C++ interface
data_path = "./demo.png"img = cv2.imread(data_path, cv2.IMREAD_GRAYSCALE)
# python中的numpy.array()與 pybind中的py::array_t一一對應.src_point = np.array([[262.0, 324.0], [325.0, 323.0], [295.0, 349.0]], dtype=np.float32)dst_point = np.array([[38.29, 51.69], [73.53, 51.69], [56.02, 71.73]], dtype=np.float32)# python interface mat_trans = cv2.getAffineTransform(src_point, dst_point)res = cv2.warpAffine(img, mat_trans, (600,800))cv2.imwrite("py_img.png", res)
# C++ interfacewarpffine_img = affine_opencv(img, src_point, dst_point)cv2.imwrite("cpp_img.png", warpffine_img)


從上述代碼可以看到,Python 文件中調(diào)用了 affine_opencv 函數(shù),而 affine_opencv 的 C++ 實現(xiàn)在 orbbec/warpaffine/src/cpu/warpaffine_opencv.cpp 中,如下所示:








































#include<vector>#include<iostream>#include<pybind11/pybind11.h>#include<pybind11/numpy.h>#include<pybind11/stl.h>#include<opencv2/opencv.hpp>

namespace py = pybind11;
/* Python->C++ Mat */cv::Mat numpy_uint8_1c_to_cv_mat(py::array_t<unsigned char>& input){        ...}
cv::Mat numpy_uint8_3c_to_cv_mat(py::array_t<unsigned char>& input){        ...}
/* C++ Mat ->numpy */py::array_t<unsigned char> cv_mat_uint8_1c_to_numpy(cv::Mat& input){        ...}
py::array_t<unsigned char> cv_mat_uint8_3c_to_numpy(cv::Mat& input){        ...}
py::array_t<unsigned char> affine_opencv(py::array_t<unsigned char>& input,                                        py::array_t<float>& from_point,                                        py::array_t<float>& to_point){        ...}


由于本工程同時兼容了 PyTorch 的 C++/CUDA 拓展,為了更加規(guī)范,這里在拓展接口程序(orbbec/warpaffine/src/warpaffine_ext.cpp)中通過 PYBIND11_MODULE 定義好接口,如下所示:














































#include <torch/extension.h>#include<pybind11/numpy.h>
// python的C++拓展函數(shù)申明py::array_t<unsigned char> affine_opencv(py::array_t<unsigned char>& input,                                        py::array_t<float>& from_point,                                        py::array_t<float>& to_point);
// Pytorch的C++拓展函數(shù)申明(CPU)at::Tensor affine_cpu(const at::Tensor& input,          /*[B, C, H, W]*/                      const at::Tensor& affine_matrix,  /*[B, 2, 3]*/                      const int out_h,                      const int out_w);
// Pytorch的CUDA拓展函數(shù)申明(GPU)#ifdef WITH_CUDAat::Tensor affine_gpu(const at::Tensor& input,          /*[B, C, H, W]*/                      const at::Tensor& affine_matrix,  /*[B, 2, 3]*/                      const int out_h,                      const int out_w);#endif
// 通過WITH_CUDA宏進一步封裝Pytorch的拓展接口at::Tensor affine_torch(const at::Tensor& input,          /*[B, C, H, W]*/                                  const at::Tensor& affine_matrix,  /*[B, 2, 3]*/                                  const int out_h,                                  const int out_w){        if (input.device().is_cuda())          {#ifdef WITH_CUDA    return affine_gpu(input, affine_matrix, out_h, out_w);#else    AT_ERROR("affine is not compiled with GPU support");#endif          }          return affine_cpu(input, affine_matrix, out_h, out_w);}
// 使用pybind11模塊定義python/pytorch接口PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {  m.def("affine_opencv", &affine_opencv, "affine with c++ opencv");  m.def("affine_torch", &affine_torch,   "affine with c++ libtorch");}


從上面代碼可以看出,Python 中的 np.array 數(shù)組與 pybind11 的 py::array_t 相互對應,也即 Python 接口函數(shù)中,傳入的 np.array 數(shù)組,在 C++ 對應的函數(shù)中用 py::array_t 接收,操作 Numpy 數(shù)組,需要引入頭文件。


數(shù)組本質上在底層是一塊一維的連續(xù)內(nèi)存區(qū),通過 pybind11 中的 request() 函數(shù)可以把數(shù)組解析成 py::buffer_info 結構體,buffer_info 類型可以公開一個緩沖區(qū)視圖,它提供對內(nèi)部數(shù)據(jù)的快速直接訪問,如下代碼所示:










struct buffer_info {    void *ptr;                         // 指向數(shù)組(緩沖區(qū))數(shù)據(jù)的指針    py::ssize_t itemsize;              // 數(shù)組元素總數(shù)    std::string format;                // 數(shù)組元素格式(python表示的類型)    py::ssize_t ndim;                  // 數(shù)組維度信息    std::vector<py::ssize_t> shape;    // 數(shù)組形狀    std::vector<py::ssize_t> strides;  // 每個維度相鄰元素的間隔(字節(jié)數(shù)表示)};


在寫好 C++ 源碼以后,在 setup.py 中將相關 C++ 源文件,以及依賴的第三方庫:opencv、pybind11 的路徑寫入對應位置(本工程已經(jīng)寫好,請具體看 setup.py 文件),然后進行編譯和安裝:








# 切換工作路徑step 1: cd F:/code/python_cpp_extension# 編譯step 2: python setup.py develop# 安裝, 如果沒有指定--prefix, 則最終編譯成功的安裝包(.egg)文件會安裝到對應的python環(huán)境下的site-packages下.step 3: python setup.py install


【注】:關于工程文件中的 setup.py 相關知識可以參考文獻 [7]、[12]、[13],該三篇文獻對此有詳細的解釋。


執(zhí)行 step 2 和 step3 之后,如下圖所示,最終源碼文件會編譯成 .pyd 二進制文件(Linux 系統(tǒng)下編譯成 .so 文件),且會生成一個 Python 包文件:orbbec-0.0.1-py36-win-amd64.egg,包名取決于 setup.py 中規(guī)定的 name 和 version 信息,該安裝包會被安裝在當前 Python環(huán)境的 site-packages 文件夾下。


同時,在終端執(zhí)行命令:pip list,會發(fā)現(xiàn)安裝包以及對應的版本信息。安裝成功后,也就意味著,在該 Python環(huán)境(本工程的 Python環(huán)境是 cpp_extension)下,可以在任何一個 Python 文件中,導入 orbbec 安裝包中的接口函數(shù),比如上述 scripts/test_warpaffine_opencv.py 文件中的語句:from orbbec.warpaffine import affine_opencv。


圖片

編譯和安裝成功


圖片

pip list 顯示相關安裝包信息


編譯完成后,可以運行 tools/collect_env.py,查看當前一些必要工具的版本等一系列信息,輸出如下:


























sys.platform    : win32Python  : 3.6.13 |Anaconda, Inc.| (default, Mar 16 2021, 11:37:27) [MSC v.1916 64 bit (AMD64)]CUDA available  : TrueCUDA_HOME       : C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.1NVCC    : Not AvailableGPU 0   : NVIDIA GeForce GTX 1650OpenCV  : 3.4.0PyTorch : 1.5.0PyTorch compiling details       : PyTorch built with:  - C++ Version: 199711  - MSVC 191627039  - Intel(R) Math Kernel Library Version 2020.0.0 Product Build 20191125 for Intel(R) 64 architecture applications  - Intel(R) MKL-DNN v0.21.1 (Git Hash 7d2fd500bc78936d1d648ca713b901012f470dbc)  - OpenMP 200203  - CPU capability usage: AVX2  - CUDA Runtime 10.1  - NVCC architecture flags: -gencode;arch=compute_37,code=sm_37;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_61,code=sm_61;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_37,code=compute_37  - CuDNN 7.6.4  - Magma 2.5.2  - Build settings: BLAS=MKL, BUILD_TYPE=Release, CXX_FLAGS=/DWIN32 /D_WINDOWS  /GR  /w /EHa /bigobj -openmp -DNDEBUG -DUSE_FBGEMM, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, USE_CUDA=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=OFF, USE_NNPACK=OFF, USE_OPENMP=ON, USE_STATIC_DISPATCH=OFF,
TorchVision     : 0.6.0C/C++ Compiler  : MSVC 191627045CUDA Compiler   : 10.1


在運行 scripts/test_warpaffine_opencv.py 文件之前,由于 warpaffine_opencv.cpp 源碼用到相關 opencv 庫,因此,還需要配置動態(tài)庫路徑,Windows 系統(tǒng)配置如下:


圖片

Windows 相關環(huán)境配置(opencv 第三方庫)


Linux 系統(tǒng)同樣也需要配置進行配置,命令如下:




root@aistation:/xxx/code/python_cpp_extension# export LD_LIBRARY_PATH=/xxx/code/python_cpp_extension/3rdparty/opencv/linux/libroot@aistation:/xxx/code/python_cpp_extension# ldconfig


也可以通過修改 ~/.bashrc 文件,加入上述 export LD_LIBRARY_PATH=/...,然后命令:source ~/.bashrc。也可以直接修改配置文件 /etc/profile,與修改 .bashrc 文件 一樣,對所有用戶有效。


可以通過 tools 下的 Dependencies_x64_Release 工具(運行:DependenciesGui.exe),查看編譯好的文件(.pyd)依賴的動態(tài)庫是否都配置完好,如下圖所示:


圖片

檢查編譯好的動態(tài)庫依賴的動態(tài)庫路徑


可以發(fā)現(xiàn),該工具沒有找到 python36.dll、c10.dll、torch_cpu.dll、torch_python.dll 和 c10_cuda.dll 的路徑。


這里說明一下,Python 相關的 dll 庫以及 torch 相關的動態(tài)庫是動態(tài)加載的,也就是說,如果你在 Python 代碼中寫一句:import torch,只有在程序運行時才會動態(tài)加載 torch 相關庫。


所以,Dependencies_x64_Release工具檢查不到編譯好的 warpaffine_ext.cp36-win_amd64.pyd 文件依賴完好性。


這里還需要說明一下為什么 warpaffine_ext.cp36-win_amd64.pyd 需要依賴 torch 相關庫,這是因為源文件 orbbec/warpaffine/src/warpaffine_ext.cpp 兼容了 PyTorch 的 C++ 拓展,所以依賴 torch 和 cuda 相關動態(tài)庫文件,如果你單純只在 orbbec/warpaffine/src/warpaffine_ext.cpp 實現(xiàn)純粹 Python 的 C++拓展,則是不需要依賴 torch 和 cuda 相關動態(tài)庫。


配置好之后,還需要將 warpaffine_ext.cp36-win_amd64.pyd 無法動態(tài)加載的動態(tài)庫文件(opencv_world453.dll)放到 scripts/test_warpaffine_opencv.py 同路徑之下(Linux 系統(tǒng)也一樣),如下圖所示:


圖片

拷貝動態(tài)庫與測試腳本同一目錄


需要注意一個問題,有時候,如果在 docker 中進行編譯和安裝,其最終生成的 Python 安裝包(.egg)文件并不會安裝到當前 Python 環(huán)境下的 site-packages 中。


也就意味著,在 Python 文件中執(zhí)行:from orbbec.warpaffine import affine_opencv 會失敗。


原因是 orbbec.warpaffine 并不在其 Python 的搜索路徑中,這個時候有兩種解決辦法:一種是在執(zhí)行:python setup.py install 時,加上 --prefix='install path',但是經(jīng)過本人驗證,有時候不可行,另外一種辦法是在 Python 文件中,將 orbbec 文件夾路徑添加到 Python 的搜索路徑中,如下所示:











import cv2import torch  # 不能刪掉, 因為需要動態(tài)加載torch的一些動態(tài)庫.import numpy as np
# 添加下述兩行代碼,這里默認此python腳本所在目錄的上一層目錄路徑包含orbbec文件夾._FILE_PATH = os.path.dirname(os.path.abspath(__file__))sys.path.insert(0, os.path.join(_FILE_PATH, "../"))
from orbbec.warpaffine import affine_opencv  # C++ interface



*博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權請聯(lián)系工作人員刪除。



關鍵詞: AI

相關推薦

技術專區(qū)

關閉