背景php
當你在開源平臺上看到一個優質的深度學習模型並想使用它時,不少時候會遇到一個棘手的問題,就是這個模型所使用的深度學習框架與你所熟悉的框架並不相同,致使你難以快速的使用這個模型。python
深度學習模型跨框架遷移一直是一件不太容易的事情,面對這個問題時通常有兩個選擇,一是手動轉換代碼至你所熟悉的框架並從新訓練模型;二是使用各類模型轉換工具對模型進行直接一鍵的轉換。前者難度高、耗時長並且還須要算力的支持;然後者使用方便快捷,可是普適性不強,對於一些特殊的模型直接使用工具轉換多是行不通的。git
今天就經過一個實例:使用飛槳X2Paddle將Caffe框架訓練的OpenPose 手部關鍵點檢測模型的遷移至PaddlePaddle框架上,並實現推理部署,介紹一下如何使用模型轉換工具來解決深度學習模型跨框架遷移的問題。github
本文包含了模型從轉換到部署預測的相關代碼實現,並以教程的形式開放在AI Studio平臺上,你們能夠直接在AI Studio平臺上直接運行本文中的完整代碼。(AI Studio連接:app
https://aistudio.baidu.com/aistudio/projectdetail/1150796)框架
下載安裝命令 ## CPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu
X2Paddle簡介ide
X2Paddle是飛槳官方開發的一個模型轉換工具,支持將其它深度學習框架訓練獲得的模型,轉換至PaddlePaddle模型。目前X2Paddle支持以下三種模型轉換方式,即:TensorFlow、Caffe和ONNX。上述的三種轉換方式基本可以直接或者間接地覆蓋大部分主流框架,如:TensorFlow、Caffe和其餘支持導出ONNX模型的框架(Pytorch、MXNet等)。函數
X2Paddle安裝:通常使用pip的方式來安裝X2Paddle,命令以下:工具
pip install x2paddle --index https://pypi.Python.org/simple/
模型介紹性能
手部關鍵點檢測,旨在找出給定圖片中手指上的關節點及指尖關節點,其相似於面部關鍵點檢測(Facial Landmark Detection) 和人體關鍵點檢測(Human Body Pose Estimation)。手部關鍵點檢測的應用場景包括:手勢識別、手語識別與理解和手部的行爲識別等。
模型檢測效果以下圖:
模型轉換
轉換模型首先須要下載源模型,對於一個Caffe模型,通常包括以下兩個文件,即模型計算圖文件(* .prototxt)和模型權重文件(* .caffemodel),本次轉換的模型就包含以下文件:pose_deploy.prototxt和pose_iter_102000.caffemodel。
模型準備好以後,就可使用X2Paddle進行模型轉換。只須要經過下面的命令,就能夠將上述的Caffe模型轉換成PaddlePaddle的模型了:
x2paddle --framework=caffe \ # 源模型類型 (tensorflow、caffe、onnx) --prototxt=pose_deploy.prototxt \ # 指定caffe模型的proto文件路徑 --weight=pose_iter_102000.caffemodel \ # 指定caffe模型的參數文件路徑 --save_dir=pd_model \ # 指定轉換後的模型保存目錄路徑 --params_merge # 當指定該參數時,轉換完成後,inference_model中的全部模
執行完上述命令,等待轉換完成後,你將會在指定的模型保存目錄下看到可供PaddlePaddle框架調用的模型,該目錄下包含以下兩個文件夾:model_with_code和inference_model。前者包含Paddle模型代碼和訓練可加載模型權重文件;後者則爲Paddle推理模型。由於本次只介紹推理部署,因此主要關注於轉換生成的推理模型。
在生成的推理模型文件夾中一樣包含模型計算圖文件(__model__)和模型權重文件(__params__)兩個文件,這樣的推理模型可直接被Paddle Inference高性能推理引擎調用,完成推理部署的操做。
模型部署
PaddlePaddle推理模型部署通常使用Paddle Inference高性能推理引擎進行部署,下面就經過代碼講解一下如何Paddle Inference的使用方法,須要以下十個步驟。
1. 導入必要的包。 import os import numpy as np from paddle.fluid.core import AnalysisConfig, PaddleTensor from paddle.fluid.core import create_paddle_predictor 2. 設置模型路徑。 modelpath = 'pd_model/inference_model' model = os.path.join(modelpath, "__model__") params = os.path.join(modelpath, "__params__") 3. 使用AnalysisConfig對模型進行配置。 config = AnalysisConfig(model, params) config.disable_gpu() # 關閉GPU,使用CPU進行推理 config.enable_mkldnn() # 啓用MKLDNN加速 config.disable_glog_info() # 禁用預測中的glog日誌 config.switch_use_feed_fetch_ops(False) # 刪去 feed 和 fetchops op config.switch_specify_input_names(True) 4. 使用create_paddle_predictor建立模型預測器。 predictor = create_paddle_predictor(config) 5. 獲取模型的輸入輸出向量。 input_names = predictor.get_input_names() output_names = predictor.get_output_names() input_tensor = predictor.get_input_tensor(input_names[0]) output_tensor = predictor.get_output_tensor(output_names[0]) 6. 準備輸入數據。 input_shape = (1, 3, 224, 224) img = np.zeros(input_shape).astype('float32') print('input_shape:', input_shape) print(img) 7. 將輸入數據拷貝到輸入向量中。 input_tensor.copy_from_cpu(img) 8. 執行模型預測。 predictor.zero_copy_run() 9. 將輸出數據從輸出向量中拷貝出來。 output = output_tensor.copy_to_cpu() 10. 打印輸出。 print('output_shape:', output.shape) print(output)
經過上述的步驟,就能夠完成一個簡單的部署推理操做,但上述的操做只驗證了模型可以正常的完成前向計算,要實現其完整的功能還須要在代碼中加入數據預處理和輸出後處理的代碼。
對於這個模型來說,它的輸入應該是一張手部圖像,而後須要通過縮放、歸一化,才能最終轉換成模型所能接受的輸入數據,輸入的數據形狀應該爲[batch_size, 3, h, w],具體的預處理代碼以下:
# 設置輸入圖像高度 inHeight = 368 # 讀取圖像 img_cv2 = cv2.imread(imgfile) # 獲取圖像寬高 img_height, img_width, _ = img_cv2.shape # 計算長寬比 aspect_ratio = img_width / img_height # 計算輸入圖像寬度 inWidth = int(((aspect_ratio * inHeight) * 8) // 8) # 對輸入圖像進行縮放、歸一化 inpBlob = cv2.dnn.blobFromImage(img_cv2, 1.0 / 255, (inWidth, inHeight), (0, 0, 0), swapRB=False, crop=False)
而這個模型的輸出數據的形狀爲[batch_size, 22, h_out, w_out],輸出的數據是22張分辨率爲w_out*h_out的熱力圖,表明每一個關鍵點的在不一樣位置的機率,可經過縮放至輸入尺寸來可視化這些熱力圖,具體可視化代碼以下:
def vis_heatmaps(imgfile, net_outputs): # 讀取輸入圖像 img_cv2 = cv2.imread(imgfile) # 建立畫布 plt.figure(figsize=[10, 10]) # 遍歷每張熱力圖 for pdx in range(22): probMap = net_outputs[0, pdx, :, :] # 縮放至原圖尺寸 probMap = cv2.resize(probMap, (img_cv2.shape[1], img_cv2.shape[0])) # 設置繪圖規格 plt.subplot(5, 5, pdx+1) # 繪製輸入圖像 plt.imshow(cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)) # 繪製熱力圖併疊加到輸入圖像上 plt.imshow(probMap, alpha=0.6) # 顯示熱力圖顏色標尺 plt.colorbar() # 關閉座標軸 plt.axis("off") # 顯示總體圖像 plt.show()
繪製出來的熱力圖樣式以下:
固然也能夠經過這些熱力圖來解算出各個關鍵的的具體座標,只須要對每張熱力圖求全局最大值的位置,便可找出對應關鍵的位置,具體代碼以下:
# 遍歷熱力圖 points = [] for idx in range(22): probMap = output[0, idx, :, :] # 縮放熱力圖到輸入圖像尺寸 probMap = cv2.resize(probMap, (img_width, img_height)) # 尋找熱力圖中全局最大值的位置 minVal, prob, minLoc, point = cv2.minMaxLoc(probMap) # 若是該位置機率大於閾值則將其加入關鍵的列表 # 不然就填充None佔位 if prob > 0.1: points.append((int(point[0]), int(point[1]))) else: points.append(None) return points
獲得了具體的關鍵點座標以後,就能夠經過可視化代碼對關鍵點進行可視化,具體代碼以下:
# 設置好須要繪製連線的關鍵點對, point_pairs = [[0,1],[1,2],[2,3],[3,4], [0,5],[5,6],[6,7],[7,8], [0,9],[9,10],[10,11],[11,12], [0,13],[13,14],[14,15],[15,16], [0,17],[17,18],[18,19],[19,20]] def vis_pose(imgfile, points): # 讀取輸入圖像 img_cv2 = cv2.imread(imgfile) # 複製一份圖像 img_cv2_copy = np.copy(img_cv2) # 遍歷關鍵的座標繪製關鍵點 for idx in range(21): if points[idx]: cv2.circle(img_cv2_copy, points[idx], 8, (0, 255, 255), thickness=-1, lineType=cv2.FILLED) cv2.putText(img_cv2_copy, "{}".format(idx), points[idx], cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, lineType=cv2.LINE_AA) # 遍歷關鍵點對,繪製之間的連線 for pair in point_pairs: partA = pair[0] partB = pair[1] if points[partA] and points[partB]: cv2.line(img_cv2, points[partA], points[partB], (0, 255, 255), 3) cv2.circle(img_cv2, points[partA], 8, (0, 0, 255), thickness=-1, lineType=cv2.FILLED) # 設置繪圖畫布 plt.figure(figsize=[10, 10]) plt.subplot(1, 2, 1) # 繪製圖像 plt.imshow(cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)) plt.axis("off") plt.subplot(1, 2, 2) plt.imshow(cv2.cvtColor(img_cv2_copy, cv2.COLOR_BGR2RGB)) plt.axis("off") # 顯示圖像 plt.show()
使用上述代碼就能將關鍵點座標繪製到輸入圖像上,效果圖以下,能夠看到模型輸出的21個手部關鍵點的位置仍是比較準確:
介紹完全部步驟,接下來就是將上述的模型部署,數據先後處理結合起來,就能夠組成一個完整的圖像手部關鍵點檢測的程序了,完整代碼以下:
#!/usr/bin/python3 #!--*-- coding: utf-8 --*-- from __future__ import division import os import cv2 import time import numpy as np import matplotlib.pyplot as plt from paddle.fluid.core import AnalysisConfig, PaddleTensor from paddle.fluid.core import create_paddle_predictor class general_pose_model(object): # 初始化 def __init__(self, modelpath): self.num_points = 21 self.inHeight = 368 self.threshold = 0.1 self.point_pairs = [[0,1],[1,2],[2,3],[3,4], [0,5],[5,6],[6,7],[7,8], [0,9],[9,10],[10,11],[11,12], [0,13],[13,14],[14,15],[15,16], [0,17],[17,18],[18,19],[19,20]] self.hand_net = self.get_hand_model(modelpath) self.input_names = self.hand_net.get_input_names() self.output_names = self.hand_net.get_output_names() self.input_tensor = self.hand_net.get_input_tensor(self.input_names[0]) self.output_tensor = self.hand_net.get_output_tensor(self.output_names[0]) # 模型加載 def get_hand_model(self, modelpath): model = os.path.join(modelpath, "__model__") params = os.path.join(modelpath, "__params__") config = AnalysisConfig(model, params) config.disable_gpu() config.enable_mkldnn() config.disable_glog_info() config.switch_ir_optim(True) config.switch_use_feed_fetch_ops(False) config.switch_specify_input_names(True) predictor = create_paddle_predictor(config) return predictor # 模型推理預測 def predict(self, imgfile): # 圖像預處理 img_cv2 = cv2.imread(imgfile) img_height, img_width, _ = img_cv2.shape aspect_ratio = img_width / img_height inWidth = int(((aspect_ratio * self.inHeight) * 8) // 8) inpBlob = cv2.dnn.blobFromImage(img_cv2, 1.0 / 255, (inWidth, self.inHeight), (0, 0, 0), swapRB=False, crop=False) # 模型推理 self.input_tensor.copy_from_cpu(inpBlob) self.hand_net.zero_copy_run() output = self.output_tensor.copy_to_cpu() # 可視化熱力圖 self.vis_heatmaps(imgfile, output) # 關鍵點計算 points = [] for idx in range(self.num_points): # confidence map probMap = output[0, idx, :, :] probMap = cv2.resize(probMap, (img_width, img_height)) # Find global maxima of the probMap. minVal, prob, minLoc, point = cv2.minMaxLoc(probMap) if prob > self.threshold: points.append((int(point[0]), int(point[1]))) else: points.append(None) return points # 熱力圖可視化函數 def vis_heatmaps(self, imgfile, net_outputs): img_cv2 = cv2.imread(imgfile) plt.figure(figsize=[10, 10]) for pdx in range(self.num_points+1): probMap = net_outputs[0, pdx, :, :] probMap = cv2.resize(probMap, (img_cv2.shape[1], img_cv2.shape[0])) plt.subplot(5, 5, pdx+1) plt.imshow(cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)) plt.imshow(probMap, alpha=0.6) plt.colorbar() plt.axis("off") plt.show() # 手部姿式可視化函數 def vis_pose(self, imgfile, points): img_cv2 = cv2.imread(imgfile) img_cv2_copy = np.copy(img_cv2) for idx in range(len(points)): if points[idx]: cv2.circle(img_cv2_copy, points[idx], 8, (0, 255, 255), thickness=-1, lineType=cv2.FILLED) cv2.putText(img_cv2_copy, "{}".format(idx), points[idx], cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2, lineType=cv2.LINE_AA) # Draw Skeleton for pair in self.point_pairs: partA = pair[0] partB = pair[1] if points[partA] and points[partB]: cv2.line(img_cv2, points[partA], points[partB], (0, 255, 255), 3) cv2.circle(img_cv2, points[partA], 8, (0, 0, 255), thickness=-1, lineType=cv2.FILLED) plt.figure(figsize=[10, 10]) plt.subplot(1, 2, 1) plt.imshow(cv2.cvtColor(img_cv2, cv2.COLOR_BGR2RGB)) plt.axis("off") plt.subplot(1, 2, 2) plt.imshow(cv2.cvtColor(img_cv2_copy, cv2.COLOR_BGR2RGB)) plt.axis("off") plt.show() if __name__ == '__main__': print('Hand pose estimation') # 設置測試圖片目錄 imgs_path = path to imgs img_files = [os.path.join(imgs_path, img_file) for img_file in os.listdir(imgs_path) if 'jpg' in img_file] # 加載模型 start = time.time() modelpath = "pd_model/inference_model" pose_model = general_pose_model(modelpath) print("Model loads time: ", time.time() - start) # 模型推理和結果輸出 for img_file in img_files: start = time.time() res_points = pose_model.predict(img_file) print("Model predicts time: ", time.time() - start) pose_model.vis_pose(img_file, res_points) print("Done.")
運行上述代碼,能夠看到輸出了以下圖像:
這樣一個完整的模型部署流程就走完了,能夠看出邏輯仍是比較清晰的,對於大部分的模型來講模型推理的代碼都是類似的,不一樣的點通常在於輸入數據的預處理還有輸出結果的後處理上,因此在部署模型是應該將關注點多放在這兩部分,完成了這兩部分也就意味着模型的部署基本完成了。
總結
使用X2Paddle模型轉換工具能夠很方便地將其餘框架上訓練的模型遷移至飛槳平臺,利用飛槳高性能的推理引擎進行推理預測。假如你想在飛槳框架上使用其餘框架訓練的模型時能夠嘗試使用這種低成本的轉換方式。
另外根據飛槳團隊的同事透露的消息,X2Paddle隨飛槳開源框架2.0即將發佈新的版本,支持將TensorFlow、Caffe和ONNX的模型轉換爲基於飛槳動態圖的模型組網代碼,同時也會新增PyTorch模型的原生轉換支持。有需求或感興趣的同窗能夠在GitHub上關注X2Paddle的項目。
Openpose項目地址:
https://github.com/CMU-Perceptual-Computing-Lab/openpose
下載安裝命令 ## CPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/cpu paddlepaddle ## GPU版本安裝命令 pip install -f https://paddlepaddle.org.cn/pip/oschina/gpu paddlepaddle-gpu