【AI應用開發全流程】使用AscendCL開發板完成模型推理

华为云开发者联盟發表於2024-06-05

本文分享自華為雲社群《【昇騰開發全流程】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是前文控制皮膚中填寫的IP地址

輸入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)  # 轉為二值圖, 只包含 01

    # 從 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-推理應用開發
  • 影片教程
    昇騰官網->線上課程->昇騰推理應用開發及調優
gitee程式碼倉Ascend / samples
https://gitee.com/ascend/samples/tree/master/inference

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章