本文分享自華為雲社群《【昇騰開發全流程】AscendCL開發板模型推理》,作者:沉迷sk。
前言
學會如何安裝配置華為雲ModelArts、開發板Atlas 200I DK A2。
並打通一個Ascend910訓練到Ascend310推理的全流程思路。
在本篇章,我們繼續進入推理階段!
推理階段
B. 環境搭建
AscendCL 開發板 模型推理
Step1 準備硬體
基礎硬體
- 開發者套件
- Micro SD 卡(TF卡):容量推薦不小於64GB
- 讀卡器
- PC(筆記本或桌上型電腦)
所需配件
用於後續連線啟動&登入開發者套件。
這裡以遠端登入模式為例
- RJ45網線
注:
這裡使用Windows系統,透過網線以遠端登入模式連線啟動登入開發者套件。
詳細內容or選擇其他系統其他模式的使用者可參考昇騰官網文件-快熟開始
Step2 制卡
PC下載並安裝制卡工具“Ascend-devkit-imager_latest_win-x86_64.exe”
將SD卡插入讀卡器的卡槽中,接著一起插入PC的USB介面中
開啟制卡工具
-> 線上制卡(映象版本選擇Ubuntu)
-> 選擇SD卡(燒錄映象時會自動將SD卡格式化,需要提前檢查SD卡是否有資料需要提前備份)
-> 燒錄映象(大概20min)
燒錄成功後,將SD卡從讀卡器中取出。
Step3 連線啟動開發者套件
將燒錄好的SD卡插入開發者套件的SD插槽,並確保完全推入插槽底部。(推到底是有類似彈簧的觸感)
確保開發者套件的撥碼開關2、3、4的開關值如圖所示:使用網線連線開發板套件和PC。
Step4 登入開發者套件
透過PC共享網路聯網(Windows):
控制皮膚 -> 網路和共享中心 -> 更改介面卡設定 ->
右鍵“WLAN” -> 屬性 ->
進入“共享”介面
右鍵“乙太網” -> 屬性 ->
進入“網路”介面 -> 雙擊“Internet 協議版本 4(TCP/IPv4)” -> 修改IP地址與子網掩碼
(PC需設定IP與開發板處於同一網段。這裡使用192.168.137.102為例)
確認儲存。PC下載並解壓SSH遠端登入工具“MobaXterm_Personal_22.2.exe”(或者進入官網下載)
開啟SSH遠端登入工具“MobaXterm_Personal_22.2.exe” -> Session -> SSH
-> 填寫實際與PC連線的開發者套件網口IP(制卡中配置的IP地址,預設為192.168.137.100)
-> 勾選“Specify username”選項,填寫使用者名稱(這裡使用root)
-> Accept
-> 輸入root使用者名稱登入密碼(預設為Mind@123)
輸入密碼時,介面不會顯示密碼和輸入位數,輸入密碼後在鍵盤按Enter鍵即可
-> 介面會出現儲存密碼提示,可以單擊“No”,不儲存密碼直接登入開發者套件。
Step5 確認開發者套件成功聯網
透過能否ping通進行檢驗網路
輸入ping 8.8.8.8或者ping www.baidu.com
若回顯如圖所示,則說明開發者套件還未成功聯網。
請繼續後續命令配置操作。
輸入ip ro回顯如下
刪除多餘的路由
輸入ip ro del default via 192.168.137.1新增丟失的路由
輸入sudo ip route add default via 192.168.137.102 dev eth1 metric 1
輸入ip ro回顯如下
透過能否ping通進行檢驗網路
輸入ping 8.8.8.8或者ping www.baidu.com
若回顯如圖所示,則說明開發者套件已經成功聯網。
(若正確配置網路後仍無法聯網,請參考昇騰官網文件-正確配置網路後仍無法聯網)Step6 為開發者套件新增推理階段專案工程檔案
上傳
將推理階段專案工程檔案壓縮包上傳到開發者套套件
(可以透過拖拽檔案的方法上傳到MobaXterm)
解壓
開啟“Terminal”命令列終端介面 ->
執行以下命令,解壓專案工程檔案壓縮包
unzip unet_sdk.zip
unzip unet_cann.zip
模型轉換工程目錄結構如下:
├── unet_sdk ├── model │ ├──air2om.sh // air模型轉om指令碼 │ ├──xxx.air //訓練階段匯出的air模型 │ ├──aipp_unet_simple_opencv.cfg // aipp檔案 │ ├──xxx.om //訓練轉換產生的om模型
推理階段工程目錄結構如下:
├── unet_cann ├── main.py // 推理檔案 ├── image.png //圖片資料 ├── mask.png //標籤資料
注:
接下來就可以繼續旅程,進入推理階段。
若中途暫停或完成實驗,記得將開發者套件關機和下電;
若之後返回或繼續實驗,再次將開發者套件開機。
如果開發板下電斷開連線,重新上電後PC不會主動再次連線,
需要更新狀態(例如取消網路共享+再次共享)
七. 執行推理
Step1 acl推理指令碼
開啟unet_cann/main.py檔案
內容如下,可根據實際開發情況進行修改。
#!/usr/bin/python # -*- coding: utf-8 -*- import cv2 # 圖片處理三方庫,用於對圖片進行前後處理 import numpy as np # 用於對多維陣列進行計算 from albumentations.augmentations import transforms # 資料增強庫,用於對圖片進行變換 import acl # acl 推理檔案庫 def sigmoid(x): y = 1.0 / (1 + np.exp(-x)) # 對矩陣的每個元素執行 1/(1+e^(-x)) return y def plot_mask(img, msk): """ 將推理得到的 mask 覆蓋到原圖上 """ msk = msk + 0.5 # 將畫素值範圍變換到 0.5~1.5, 有利於下面轉為二值圖 msk = cv2.resize(msk, (img.shape[1], img.shape[0])) # 將 mask 縮放到原圖大小 msk = np.array(msk, np.uint8) # 轉為二值圖, 只包含 0 和 1 # 從 mask 中找到輪廓線, 其中第二個引數為輪廓檢測的模式, 第三個引數為輪廓的近似方法 # cv2.RETR_EXTERNAL 表示只檢測外輪廓, cv2.CHAIN_APPROX_SIMPLE 表示壓縮水平方向、 # 垂直方向、對角線方向的元素, 只保留該方向的終點座標, 例如一個矩形輪廓只需要4個點來儲存輪廓資訊 # contours 為返回的輪廓(list) contours, _ = cv2.findContours(msk, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 在原圖上畫出輪廓, 其中 img 為原圖, contours 為檢測到的輪廓列表 # 第三個參數列示繪製 contours 中的哪條輪廓, -1 表示繪製所有輪廓 # 第四個參數列示顏色, (0, 0, 255)表示紅色, 第五個參數列示輪廓線的寬度 cv2.drawContours(img, contours, -1, (0, 0, 255), 1) # 將輪廓線以內(即分割區域)覆蓋上一層紅色 img[..., 2] = np.where(msk == 1, 255, img[..., 2]) return img # 初始化變數 pic_input = 'image.png' # 單張圖片 model_path = "../unet_sdk/model/unet_hw960_bs1.om" # 模型路徑 num_class = 2 # 類別數量, 需要根據模型結構、任務類別進行改變; device_id = 0 # 指定運算的Device print("init resource stage:") # acl初始化 ret = acl.init() ret = acl.rt.set_device(device_id) # 指定運算的Device context, ret = acl.rt.create_context(device_id) # 顯式建立一個Context,用於管理Stream物件 stream, ret = acl.rt.create_stream() # 顯式建立一個Stream, 用於維護一些非同步操作的執行順序,確保按照應用程式中的程式碼呼叫順序執行任務 print("Init resource success") # 載入模型 model_id, ret = acl.mdl.load_from_file(model_path) # 載入離線模型檔案, 返回標識模型的ID model_desc = acl.mdl.create_desc() # 初始化模型描述資訊, 包括模型輸入個數、輸入維度、輸出個數、輸出維度等資訊 ret = acl.mdl.get_desc(model_desc, model_id) # 根據載入成功的模型的ID, 獲取該模型的描述資訊 print("Init model resource success") img_bgr = cv2.imread(pic_input) # 讀入圖片 img = cv2.resize(img_bgr, (960,960)) # 將原圖縮放到 960*960 大小 img = transforms.Normalize().apply(img) # 將畫素值標準化(減去均值除以方差) img = img.astype('float32') / 255 # 將畫素值縮放到 0~1 範圍內 img = img.transpose(2, 0, 1) # 將形狀轉換為 channel first (3, 96, 96) # 準備輸入資料集 input_list = [img, ] # 初始化輸入資料列表 input_num = acl.mdl.get_num_inputs(model_desc) # 得到模型輸入個數 input_dataset = acl.mdl.create_dataset() # 建立輸入資料 for i in range(input_num): input_data = input_list[i] # 得到每個輸入資料 # 得到每個輸入資料流的指標(input_ptr)和所佔位元組數(size) size = input_data.size * input_data.itemsize # 得到所佔位元組數 bytes_data=input_data.tobytes() # 將每個輸入資料轉換為位元組流 input_ptr=acl.util.bytes_to_ptr(bytes_data) # 得到輸入資料指標 model_size = acl.mdl.get_input_size_by_index(model_desc, i) # 從模型資訊中得到輸入所佔位元組數 # if size != model_size: # 判斷所分配的記憶體是否和模型的輸入大小相符 # print(" Input[%d] size: %d not equal om size: %d" % (i, size, model_size) + ", may cause inference result error, please check model input") dataset_buffer = acl.create_data_buffer(input_ptr, size) # 為每個輸入建立 buffer _, ret = acl.mdl.add_dataset_buffer(input_dataset, dataset_buffer) # 將每個 buffer 新增到輸入資料中 print("Create model input dataset success") # 準備輸出資料集 output_size = acl.mdl.get_num_outputs(model_desc) # 得到模型輸出個數 output_dataset = acl.mdl.create_dataset() # 建立輸出資料 for i in range(output_size): size = acl.mdl.get_output_size_by_index(model_desc, i) # 得到每個輸出所佔記憶體大小 buf, ret = acl.rt.malloc(size, 2) # 為輸出分配記憶體。 dataset_buffer = acl.create_data_buffer(buf, size) # 為每個輸出建立 buffer _, ret = acl.mdl.add_dataset_buffer(output_dataset, dataset_buffer) # 將每個 buffer 新增到輸出資料中 if ret: # 若分配出現錯誤, 則釋放記憶體 acl.rt.free(buf) acl.destroy_data_buffer(dataset_buffer) print("Create model output dataset success") # 模型推理, 得到的輸出將寫入 output_dataset 中 ret = acl.mdl.execute(model_id, input_dataset, output_dataset) # 解析 output_dataset, 得到模型輸出列表 model_output = [] # 模型輸出列表 for i in range(output_size): buf = acl.mdl.get_dataset_buffer(output_dataset, i) # 獲取每個輸出buffer data_addr = acl.get_data_buffer_addr(buf) # 獲取輸出buffer的地址 size = int(acl.get_data_buffer_size(buf)) # 獲取輸出buffer的位元組數 byte_data = acl.util.ptr_to_bytes(data_addr, size) # 將指標轉為位元組流資料 dims = tuple(acl.mdl.get_output_dims(model_desc, i)[0]["dims"]) # 從模型資訊中得到每個輸出的維度資訊 output_data = np.frombuffer(byte_data, dtype=np.float32).reshape(dims) # 將 output_data 以流的形式讀入轉化成 ndarray 物件 model_output.append(output_data) # 新增到模型輸出列表 x0 = 2200 # w:2200~4000; h:1000~2800 y0 = 1000 x1 = 4000 y1 = 2800 ori_w = x1 - x0 ori_h = y1 - y0 def _process_mask(mask_path): # 手動裁剪 mask = cv2.imread( mask_path , cv2.IMREAD_GRAYSCALE ) # [y0:y1, x0:x1] return mask[y0:y1, x0:x1] # 後處理 model_out_msk = model_output[0] # 取出模型推理結果, 推理結果形狀為 (1, 1, 96, 96),即(batchsize, num_class, height, width) model_out_msk = _process_mask("mask.png") # 摳圖後的shape, hw # model_out_msk = sigmoid(model_out_msk[0][0]) # 將模型輸出變換到 0~1 範圍內 img_to_save = plot_mask(img_bgr, model_out_msk) # 將處理後的輸出畫在原圖上, 並返回 # 儲存圖片到檔案 cv2.imwrite('result.png', img_to_save) # 釋放輸出資源, 包括資料結構和記憶體 num = acl.mdl.get_dataset_num_buffers(output_dataset) # 獲取輸出個數 for i in range(num): data_buf = acl.mdl.get_dataset_buffer(output_dataset, i) # 獲取每個輸出buffer if data_buf: data_addr = acl.get_data_buffer_addr(data_buf) # 獲取buffer的地址 acl.rt.free(data_addr) # 手動釋放 acl.rt.malloc 所分配的記憶體 ret = acl.destroy_data_buffer(data_buf) # 銷燬每個輸出buffer (銷燬 aclDataBuffer 型別) ret = acl.mdl.destroy_dataset(output_dataset) # 銷燬輸出資料 (銷燬 aclmdlDataset型別的資料) # 解除安裝模型 if model_id: ret = acl.mdl.unload(model_id) # 釋放模型描述資訊 if model_desc: ret = acl.mdl.destroy_desc(model_desc) # 釋放 stream if stream: ret = acl.rt.destroy_stream(stream) # 釋放 Context if context: ret = acl.rt.destroy_context(context) # 釋放Device acl.rt.reset_device(device_id) acl.finalize() print("Release acl resource success")
Step2 執行指令碼
開啟Terminal命令列終端介面:確保是否在工程目錄unet_cann/路徑下
輸入cd /root/project/unet_cann
執行示例,輸入python3 main.py
輸出結果:
注:
到此我們就已經走過了從Ascend910訓練到Ascend310推理的昇騰開發全流程。
更多內容深入參考下方學習資源推薦
學習資源推薦
昇騰官網
- 文件教程
昇騰官網文件-CANN-推理應用開發 - 影片教程
昇騰官網->線上課程->昇騰推理應用開發及調優
https://gitee.com/ascend/samples/tree/master/inference
點選關注,第一時間瞭解華為雲新鮮技術~