【2023 · CANN訓(xùn)練營(yíng)第一季】模型的加載與推理
【2023 · CANN訓(xùn)練營(yíng)第一季】模型的加載與推理
文檔參考:CANN文檔社區(qū)版: 6.0.RC1.alpha001
在模型轉(zhuǎn)換,數(shù)據(jù)預(yù)處理后,我的學(xué)習(xí)終于終于來到了模型的加載與推理。是時(shí)候用我預(yù)處理后的圖片加上我ATC轉(zhuǎn)換的模型進(jìn)行一個(gè)推理和目標(biāo)檢測(cè)的步驟了!
一、接口調(diào)用流程
首次進(jìn)行代碼學(xué)習(xí)的時(shí)候,其實(shí)我們可以參考官方的文檔。
模型加載:
以下為使用不同模型的加載接口的示意圖:

如果我們要使用同一個(gè)模型加載接口,還可以創(chuàng)建一個(gè)config的形式來固定加載接口:

note:
1.當(dāng)由用戶管理內(nèi)存時(shí),為確保內(nèi)存不浪費(fèi),在申請(qǐng)工作內(nèi)存、權(quán)值內(nèi)存前,需要調(diào)用aclmdlQuerySize接口查詢模型運(yùn)行時(shí)所需工作內(nèi)存、權(quán)值內(nèi)存的大小。
2.如果模型輸入數(shù)據(jù)的Shape不確定,則不能調(diào)用aclmdlQuerySize接口查詢內(nèi)存大小,在加載模型時(shí),就無法由用戶管理內(nèi)存,因此需選擇由系統(tǒng)管理內(nèi)存的模型加載接口(例如,aclmdlLoadFromFile、aclmdlLoadFromMem)。
模型執(zhí)行:
模型執(zhí)行的參考流程如下:

輸入/輸出數(shù)據(jù)類型結(jié)構(gòu)準(zhǔn)備:
這里還用到了一個(gè)數(shù)據(jù)類型集合的使用:
使用aclmdlDataset類型的數(shù)據(jù)描述模型的輸入/輸出數(shù)據(jù),因?yàn)槟P涂赡艽嬖诙鄠€(gè)輸入、多個(gè)輸出
調(diào)用aclmdlDataset類型下的操作接口添加aclDataBuffer類型的數(shù)據(jù)、獲取aclDataBuffer的個(gè)數(shù)等
每個(gè)輸入/輸出的內(nèi)存地址、內(nèi)存大小用aclDataBuffer類型的數(shù)據(jù)來描述。調(diào)用aclDataBuffer類型下的操作接口獲取內(nèi)存地址、內(nèi)存大小等。

上述數(shù)據(jù)類型結(jié)構(gòu)準(zhǔn)備流程:

模型卸載:
模型卸載主要就是要注意不要有漏網(wǎng)之魚,畢竟我們創(chuàng)建的東西太多了,沒有釋放會(huì)導(dǎo)致內(nèi)存泄漏等一系列問題,可以參考以下內(nèi)容檢查是否有釋放完全:
1.卸載模型
2.釋放模型描述信息
3.釋放模型運(yùn)行的工作內(nèi)存
4.釋放模型運(yùn)行的權(quán)值內(nèi)存
二、接口簡(jiǎn)介
以下簡(jiǎn)單介紹一下我用到了的幾個(gè)API接口:
note:模型加載、模型執(zhí)行、模型卸載的操作必須在同一個(gè)Context下。這就意味著,我們必須在同一個(gè)線程下進(jìn)行使用,因?yàn)閏ontext無法跨線程使用
創(chuàng)建輸入輸出類型結(jié)構(gòu)的相關(guān)接口可以參考手冊(cè)了解,太多了這里就不寫了。
三、代碼驗(yàn)證與學(xué)習(xí)
代碼思路:
1.初始化相關(guān)通道,創(chuàng)建輸入輸出所需的一些內(nèi)容,加載模型
2.讀取圖片,將圖片整合進(jìn)輸入流,執(zhí)行模型
3.讀出結(jié)果,處理模型輸出的數(shù)據(jù)
4.依次銷毀所有創(chuàng)建的環(huán)境
接下來我們直接進(jìn)入我們的代碼運(yùn)行過程?。。?/p>
進(jìn)行代碼編譯,編譯成功!

首先,我們先進(jìn)行代碼驗(yàn)證,在之前的文章中,我自己用ATC工具,轉(zhuǎn)了一個(gè)om模型,首次驗(yàn)證,我們先不使用它,因?yàn)槲覠o法保證我自己的模型是否正常,所以我的思路是,先找了一個(gè)確定能行的我用過的om模型,進(jìn)行代碼驗(yàn)證。如果我要是用我自己的模型,這樣排查是我的模型加載相關(guān)代碼異常還是我的模型本身就有異常會(huì)造成一定的彎路。
代碼驗(yàn)證如下:

結(jié)果是識(shí)別到了兩個(gè)小車車,一個(gè)小停止路牌標(biāo)志,讓我們回顧一下,我的resize文章中輸出的640x640的原圖。

能識(shí)別出兩個(gè)車,效果還算不錯(cuò)了,雖然擋住的那個(gè)不太好識(shí)別。各位有興趣的小伙伴也可以用cv畫出bounding box看看識(shí)別情況
接下來!我們就使用我之前ATC的文章中轉(zhuǎn)好的yolov5s_bs.om進(jìn)行驗(yàn)證吧?。。。。?!
結(jié)果如下:

哦豁!有錯(cuò)誤,讓我們看看這個(gè)錯(cuò)誤碼是什么意思!

查看昇騰的C++應(yīng)用文檔對(duì)比錯(cuò)誤碼,發(fā)現(xiàn)是參數(shù)校驗(yàn)失敗,根據(jù)我們上面的驗(yàn)證思路,問題可能出在了模型上。
將兩個(gè)模型用netron打開進(jìn)行對(duì)比: 這是正常模型的輸入這里,可以看到還是進(jìn)行了AIPP的,但是不知道為啥沒有顯示這個(gè)算子

這是我自己轉(zhuǎn)的om的情況:

接下來我們?cè)俨榭摧敵?,這是運(yùn)行正常的模型:

以下這是我的異常模型:

同志們?。。?!我們發(fā)現(xiàn)了問題,這怎么是三個(gè)特征圖在輸出呢!
這里我們需要用到Y(jié)oloPreDetection和YoloV5DetectionOutput兩個(gè)算子,對(duì)輸出的onnx模型進(jìn)行調(diào)整。
以下參考昇騰社區(qū)modelZoo的his版本中的yolov5案例
首先,
(1)我們修改models/common.py文件,對(duì)其中Focus的forward函數(shù)做修改
(2)修改models/yolo.py腳本,使后處理部分不被導(dǎo)出
(3)修改models/experimental.py文件,將其中的attempt_download()所在行注釋掉
(4)修改models/export.py文件,將轉(zhuǎn)換的onnx算子版本設(shè)為11,v6.0無需修改
修改完成,我們把模型放在yolov5文件夾下,使用官方的export.py運(yùn)行如下命令
這里我使用的是v6.0版本的git版本,在windows下運(yùn)行也不用export那個(gè)pwd獲取的路徑

添加后處理算子
在這里生成了一個(gè)yolov5s_t.onnx


使用atc命令進(jìn)行轉(zhuǎn)換:
轉(zhuǎn)換結(jié)果如下:

生成了yolov5s_t_bs1.om

將該模型使用我們代碼進(jìn)行加載和執(zhí)行:

看到推理成功并且輸出了很多很多很多的結(jié)果!說明我們終于終于終于成功了。
但是保留一個(gè)我自己的小疑問是不知道為啥推理的結(jié)果有這么多,而且結(jié)果不太對(duì),等后面有空我在討論一下這個(gè)模型本身的問題。本文我們要解決的是如何讓我們的模型順利的加載并推理。我們完成了我們的目標(biāo)。
以下附上我的測(cè)試的代碼。寫的有些粗糙,因?yàn)樽罱鼤r(shí)間比較緊張。
runInfer.cpp
inference.cpp(推理功能實(shí)現(xiàn)文件)
inference.h
本文實(shí)現(xiàn)了模型的加載,推理,和后處理結(jié)果,如果有同學(xué)有需要,也可以使用cv,將后處理后的推理結(jié)果的boundingbox畫出來看看效果哦。
ps:該文僅是為了記錄CANN訓(xùn)練營(yíng)的學(xué)習(xí)過程所用,不參與任何商業(yè)用途,有任何代碼問題可以和我一起討論修改哦!