本案例將使用YOLOX、SCNet兩個模型,實現一個簡單的隔空作畫趣味應用

語言: CN / TW / HK

摘要:本案例將使用YOLOX、SCNet兩個模型,實現一個簡單的隔空作畫趣味應用

本文分享自華為雲社群《​ ​ModelBox開發案例 - 隔空作畫​ ​》,作者:吳小魚。

本案例將使用 YOLOX、SCNet 兩個模型,實現一個簡單的隔空作畫趣味應用,最終效果如下所示:

案例所需資源(程式碼、模型、測試資料等)均可從obs桶下載。

模型訓練

我們使用面向開發者的一站式AI開發平臺ModelArts進行模型的訓練:

ModelArts提供了包括資料標註,訓練環境,預置演算法在內的豐富的功能,甚至可以通過訂閱預置演算法實現0程式碼的模型訓練工作。當然你也可以在本地訓練自己的模型。我們假設你現在已經擁有了訓練好的模型,接下來我們需要將訓練好的模型轉換成為可以在開發板上執行的模型。

模型轉換

我們釋出了開發板模型轉換案例, 參見 ​RK3568模型轉換驗證案例 ​ :

在這個案例中我們演示了從環境適配到模型的轉換驗證的全流程樣例程式碼,開發者可以通過“Run in ModelArts”一鍵將Notebook案例在ModelArts控制檯快速開啟、執行以及進行二次開發等操作。

開發環境部署

使用開發板進行ModelBox AI應用開發有兩種方式,一是開發板連線顯示器和鍵盤滑鼠,安裝Ubuntu桌面,直接在開發板上進行開發;二是使用遠端連線工具(如VS Code中的Remote-SSH)從PC端登入開發板進行開發。這裡我們推薦第二種方式,因為PC端可以使用功能更豐富、介面更友好的IDE。

1.配置網路

PC連線開發板需要知道開發板的ip,但是開發板預設沒有固定ip,我們提供了ModelBox PC Tool,可以自動為開發板配置ip,也可以在推理階段很方便的進行視訊推流拉流。

PC Tool位於SDK的connect_wizard目錄中:

雙擊connect_wizard.exe,在頁面中可以看到有兩種開發板連線方式,我們使用網線連線開發板的方式:

按照指引斷開或連線網線:

等待一小段時間,可以看到來到了第三步,此時開發板已設定為預設ip:192.168.2.111,PC使用該ip即可SSH登入:

2. 遠端連線開發板

我們推薦在PC端使用VS Code遠端連線開發板來對裝置操作。

使用VS Code連線開發板可以參考我們釋出的 ​ModelBox 端雲協同AI開發套件(RK3568)上手指南​ 同時,上手指南也介紹瞭如何將開發板註冊到HiLens管理控制檯進行更方便的線上管理。

應用開發

接下來我們會以隔空作畫demo為例,介紹如何使用ModelBox開發一個AI應用。

1.建立工程

SDK提供了工程指令碼create.py,可以使用./create.py -h檢視指令碼幫助:

ModelBox提供了視覺化圖編排工具:Editor,可以使用./create.py -t editor開啟圖編排服務:

服務預設ip即為192.168.2.111,如需配置其他ip或埠,可以通過-i ip:port引數進行配置。

點選連結即可進入視覺化編輯介面,我們點選編排進入工程開發介面,如果進一步瞭解ModelBox相關內容,可以點選右上角幫助:

進入編排介面,點選右上角新建專案:

專案路徑填寫workspace,專案名稱填寫hand_painting, 確認:

可以看到我們已經擁有了一個帶有http收發單元的預設圖:

其中,區域1為SDK預置的高效能通用流單元,區域2為視覺化編排介面,區域3為對應的圖配置檔案內容。同時,VS Code對應目錄下也出現了hand_painting專案:

2.建立推理功能單元

接下來,我們建立推理流單元:

對於手檢測模型,我們將流單元命名為hand_detection,模型檔名即為轉換好的檢測模型名:yolox_hand.rknn,此模型輸入為image,輸出為feature map,所以我們新增int型別的輸入埠與float型別的輸出埠。關於開發板的推理流單元建立,在處理型別時我們選擇cuda,即為npu推理,推理引擎可選任意一款,目前開發板SDK可以自動進行識別轉換。最後將功能單元分組修改為inference,點選確認,即可看到,在右側inference頁簽下出現了:

同時,在VS Code工程model目錄下可以看到建立好的推理流單元:

同樣的,我們建立pose_detection推理流單元:

3.建立後處理功能單元

除了推理流單元外,隔空作畫demo還需要一些通用功能單元:檢測後處理單元、感興趣區域提取單元、作畫單元,我們新建三個python功能單元來滿足上述需求。

對於檢測後處理單元,我們希望通過原圖和hand_detection的輸出解碼出手檢測框,所以該單元應該有兩個輸入。此外,對於畫幅中有手或者沒有檢測到手兩種狀態,我們希望該功能單元分情況處理,檢測到手時,將檢測結果送入感興趣區域提取單元,沒有檢測到手時,直接返回,因此功能單元型別選擇:IF_ELSE。新建單元如下:

同樣的,根據輸入輸出與功能單元狀態,我們建立extract_roi和painting兩個功能單元:

4.流程圖編排

拖拽

需要的功能單元全部建立好後,我們可以著手編排流程圖,我們編排一個視訊處理的圖,暫時不需要http收發單元,可以刪除不需要的單元:

在Generic列表下將虛擬輸入單元input和我們剛剛建立的三個功能單元拖入畫布:

在Image列表下將模型推理需要用到的預處理單元resize拖入畫布,因為我們需要兩個resize單元,所以重複拖入:

值得注意的是,resize單元需要配置引數,需要點選該單元進行配置:

在Input列表下拖入輸入解析單元data_source_parser:

在Video列表下拖入視訊處理需要的單元video_demuxer、video_decoder、video_out:

最後,在Inference列表下拖入我們建立的兩個推理單元:

編排

將功能單元按照處理邏輯進行連線:虛擬輸入input連線輸入解析data_source_parser,解析後送入視訊解包與解碼單元:

解碼輸出送入預處理後可直接進行推理:

推理後處理需要輸入原圖與推理結果,沒有結果則直接連線視訊輸入單元,有結果則連線感興趣區域提取單元:

提取結果送入預處理與推理:

最後,得到的關鍵點結果與原圖送入作畫單元,作畫結果送入視訊輸出單元進行儲存:

這樣,我們就完成了流程圖的編排,可以看到在GraphViz區域也出現了完整的圖表述:

儲存專案,轉到VS Code進行每個單元的程式碼實現:

5.程式碼補全

視覺化編排中,建立的推理單元位於專案的model目錄下,通用單元位於etc/flowunit目錄下,流程圖位於graph目錄下,可以看到建立的單元與圖都已同步過來:

其中,video_decoder需要指定型別:

video_decoder7 [ type=flowunit flowunit=video_decoder device=rknpu, deviceid="0", pix_fmt=bgr label="{{<in_video_packet> in_video_packet}|video_decoder7|{<out_video_frame> out_video_frame}}" ]

推理單元

首先完善推理單元,對於推理功能單元,只需要提供獨立的toml配置檔案,指定推理功能單元的基本屬性即可,目錄結構為:

[flowunit-name]
 |---[flowunit-name].toml #推理功能單元配置
 |---[model].rknn #模型檔案
 |---[infer-plugin].so       #推理自定義外掛
ModelBox框架在初始化時,會掃描目錄中的toml字尾的檔案,並讀取相關的推理功能單元資訊。[infer-plugin].so是推理所需外掛,推理功能單元支援載入自定義外掛,開發者可以實現自定義運算元。

將模型拷入對應資料夾,以hand_detection為例我們看一下推理功能單元配置檔案:

配置檔案中有一些單元型別、模型名稱、輸入輸出的基本配置,可以酌情修改。

通用單元

Python通用單元需要提供獨立的toml配置檔案,指定python功能單元的基本屬性。一般情況,目錄結構為:

[FlowUnitName]
 |---[FlowUnitName].toml
 |---[FlowUnitName].py
 |---xxx.py

相較於推理單元而言,通用單元不但有配置檔案,還需要完善具體的功能程式碼,以yolox_post為例,首先是功能單元配置檔案:

# Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.
# Basic config
[base]
name = "yolox_post" # The FlowUnit name
device = "cpu" # The flowunit runs on cpu
version = "1.0.0" # The version of the flowunit
type = "python" # Fixed value, do not change
description = "description" # The description of the flowunit
entry = "yolox_post@yolox_postFlowUnit" # Python flowunit entry function
group_type = "generic"  # flowunit group attribution, change as input/output/image ...
# Flowunit Type
stream = false # Whether the flowunit is a stream flowunit
condition = true # Whether the flowunit is a condition flowunit
collapse = false # Whether the flowunit is a collapse flowunit
collapse_all = false # Whether the flowunit will collapse all the data
expand = false #  Whether the flowunit is a expand flowunit
# The default Flowunit config
[config]
item = "value"
# Input ports description
[input]
[input.input1] # Input port number, the format is input.input[N]
name = "in_image" # Input port name
type = "uint8"  # input port data type ,e.g. float or uint8
device = "cpu"  # input buffer type
[input.input2] # Input port number, the format is input.input[N]
name = "in_feat" # Input port name
type = "uint8"  # input port data type ,e.g. float or uint8
device = "cpu"  # input buffer type
# Output ports description
[output]
[output.output1] # Output port number, the format is output.output[N]
name = "has_hand" # Output port name
type = "float"  # output port data type ,e.g. float or uint8
[output.output2] # Output port number, the format is output.output[N]
name = "no_hand" # Output port name
type = "float"  # output port data type ,e.g. float or uint8

Basic config是一些單元名等基本配置,Flowunit Type是功能單元型別,yolox_post是一個條件單元,所以可以看到condition為true,此外還有一些展開、歸攏等性質,可以在AI Gallery ModelBox)板塊下看到更多案例。

config為單元需要配置的一些屬性,如本單元需要一些特徵圖size、閾值等資訊,所以在配置檔案中修改config為:

[config]
net_h = 320
net_w = 320
num_classes = 2
conf_threshold = 0.5
iou_threshold = 0.5

此外,輸入輸出type根據實際邏輯可能進行一些修改:

# Input ports description
[input]
[input.input1] # Input port number, the format is input.input[N]
name = "in_image" # Input port name
type = "uint8"  # input port data type ,e.g. float or uint8
device = "cpu"  # input buffer type
[input.input2] # Input port number, the format is input.input[N]
name = "in_feat" # Input port name
type = "float"  # input port data type ,e.g. float or uint8
device = "cpu"  # input buffer type
# Output ports description
[output]
[output.output1] # Output port number, the format is output.output[N]
name = "has_hand" # Output port name
type = "uint8"  # output port data type ,e.g. float or uint8
[output.output2] # Output port number, the format is output.output[N]
name = "no_hand" # Output port name
type = "uint8"  # output port data type ,e.g. float or uint8

接下來,我們檢視yolox_post.py,可以看到建立單元時已經生成了基本介面:

# Copyright (c) Huawei Technologies Co., Ltd. 2022. All rights reserved.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import _flowunit as modelbox
class yolox_postFlowUnit(modelbox.FlowUnit):
 # Derived from modelbox.FlowUnit
 def __init__(self):
 super().__init__()
 def open(self, config):
 # Open the flowunit to obtain configuration information
 return modelbox.Status.StatusCode.STATUS_SUCCESS
 def process(self, data_context):
 # Process the data
 in_data = data_context.input("in_1")
 out_data = data_context.output("out_1")
 # yolox_post process code.
 # Remove the following code and add your own code here.
 for buffer in in_data:
            response = "Hello World " + buffer.as_object()
            result = response.encode('utf-8').strip()
 add_buffer = modelbox.Buffer(self.get_bind_device(), result)
 out_data.push_back(add_buffer)
 return modelbox.Status.StatusCode.STATUS_SUCCESS
 def close(self):
 # Close the flowunit
 return modelbox.Status()
 def data_pre(self, data_context):
 # Before streaming data starts
 return modelbox.Status()
 def data_post(self, data_context):
 # After streaming data ends
 return modelbox.Status()
 def data_group_pre(self, data_context):
 # Before all streaming data starts
 return modelbox.Status()
 def data_group_post(self, data_context):
 # After all streaming data ends
 return modelbox.Status()

如果功能單元的工作模式是stream = false時,功能單元會呼叫open、process、close介面;如果功能單元的工作模式是stream = true時,功能單元會呼叫open、data_group_pre、data_pre、process、data_post、data_group_post、close介面;使用者可根據實際需求實現對應介面。

根據單元性質,我們主要需要完善open、process介面:

import _flowunit as modelbox
import numpy as np 
from yolox_utils import postprocess, expand_bboxes_with_filter, draw_color_palette
class yolox_postFlowUnit(modelbox.FlowUnit):
 # Derived from modelbox.FlowUnit
 def __init__(self):
 super().__init__()
 def open(self, config):
 self.net_h = config.get_int('net_h', 320)
 self.net_w = config.get_int('net_w', 320)
 self.num_classes = config.get_int('num_classes', 2)
 self.num_grids = int((self.net_h / 32) * (self.net_w / 32)) * (1 + 2*2 + 4*4)
 self.conf_thre = config.get_float('conf_threshold', 0.3)
 self.nms_thre = config.get_float('iou_threshold', 0.4)
 return modelbox.Status.StatusCode.STATUS_SUCCESS
 def process(self, data_context):
 modelbox.info("YOLOX POST")
 in_image = data_context.input("in_image")
 in_feat = data_context.input("in_feat")
 has_hand = data_context.output("has_hand")
 no_hand = data_context.output("no_hand")
 for buffer_img, buffer_feat in zip(in_image, in_feat):
            width = buffer_img.get('width')
            height = buffer_img.get('height')
            channel = buffer_img.get('channel')
 img_data = np.array(buffer_img.as_object(), copy=False)
 img_data = img_data.reshape((height, width, channel))
 feat_data = np.array(buffer_feat.as_object(), copy=False)
 feat_data = feat_data.reshape((self.num_grids, self.num_classes + 5))
            ratio = (self.net_h / height, self.net_w / width)
 bboxes = postprocess(feat_data, (self.net_h, self.net_w), self.conf_thre, self.nms_thre, ratio)
            box = expand_bboxes_with_filter(bboxes, width, height)
 if box:
 buffer_img.set("bboxes", box)
 has_hand.push_back(buffer_img)
 else:
 draw_color_palette(img_data)
 img_buffer = modelbox.Buffer(self.get_bind_device(), img_data)
 img_buffer.copy_meta(buffer_img)
 no_hand.push_back(img_buffer)
 return modelbox.Status.StatusCode.STATUS_SUCCESS
 def close(self):
 # Close the flowunit
 return modelbox.Status()

可以看到,在open中我們進行了一些引數獲取,process進行邏輯處理,輸入輸出可以通過data_context來獲取,值得注意的是輸出時我們返回的是圖,在檢測到手時為圖附加了檢測框資訊,該資訊可以被下一單元獲取。

同樣的,完善其餘通用功能單元,具體可以參考我們提供的程式碼。

應用執行

我們需要準備一個mp4檔案拷貝到data資料夾下,我們提供了測試視訊hand.mp4,然後開啟工程目錄下bin/mock_task.toml檔案,修改其中的任務輸入和任務輸出配置為如下內容:

# 任務輸入,mock模擬目前僅支援一路rtsp或者本地url
# rtsp攝像頭,type = "rtsp", url裡面寫入rtsp地址
# 其它用"url",比如可以是本地檔案地址, 或者httpserver的地址,(攝像頭 url = "0")
[input]
type = "url"
url = "../data/hand.mp4"
# 任務輸出,目前僅支援"webhook", 和本地輸出"local"(輸出到螢幕,url="0", 輸出到rtsp,填寫rtsp地址)
# (local 還可以輸出到本地檔案,這個時候注意,檔案可以是相對路徑,是相對這個mock_task.toml檔案本身)
[output]
type = "local"
url = "../hilens_data_dir/paint.mp4"

配置好後在工程路徑下執行build_project.sh進行工程構建:

rock@rock-3a:~/lxy/examples$ cd workspace/hand_painting/
rock@rock-3a:~/lxy/examples/workspace/hand_painting$ ./build_project.sh 
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/graph/hand_painting.toml to Unix format...
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/graph/modelbox.conf to Unix format...
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/extract_roi/extract_roi.toml to Unix format...
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/painting/painting.toml to Unix format...
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/etc/flowunit/yolox_post/yolox_post.toml to Unix format...
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/model/hand_detection/hand_detection.toml to Unix format...
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/model/pose_detection/pose_detection.toml to Unix format...
dos2unix: converting file /home/rock/lxy/examples/workspace/hand_painting/bin/mock_task.toml to Unix format...
build success: you can run main.sh in ./bin folder
rock@rock-3a:~/lxy/examples/workspace/hand_painting$

構建完成後執行專案:

rock@rock-3a:~/lxy/examples/workspace/hand_painting$ ./bin/main.sh

等待稍許即可以在hilens_data_dir資料夾下看到執行結果:

除了mp4外我們也支援很多其他型別的輸入輸出,ModelBox PC TOOL也提供了推流與拉流功能,選擇輸入實時視訊流,啟動:

執行程式時配置輸出地址為推流地址,即可在本機網頁中檢視到執行結果:

如果需要對應用進行效能評估,只需要在流程圖配置檔案中開啟profile:

[profile]
profile=true # 啟用profile
trace=true # 啟用traceing
dir="/tmp/modelbox/perf" # 設定跟蹤檔案路徑

配置啟動後,啟動執行流程圖,profile會每隔60s記錄一次統計資訊,trace會在任務執行過程中和結束時,輸出統計資訊。

執行流程圖後,會生成效能相關的json檔案,通過將json檔案載入到瀏覽器中即可檢視timeline資訊。

  1. 開啟chrome瀏覽器。
  2. 瀏覽器中輸入chrome://tracing/。
  3. 點選介面中的Load按鈕,載入trace的json檔案。
  4. 載入成功後,將看到類似下面的timeline檢視:

打包部署

打包

除錯完成後,同樣可以通過create.py指令碼將應用打包釋出:

./create.py -t rpm -n hand_painting

控制檯中輸出:

sdk version is modelbox-rk-aarch64-1.0.8.8
call mb-pkg-tool pack [folder] > [rpm file] to building rpm, waiting...
success: create hand_painting.rpm in /home/rock/lxy/examples/workspace/hand_painting

等待稍許,可以看到專案工程下已經生成了rpm資料夾和打包好的應用:

部署

將打包好的應用上傳至華為雲賬號下的obs桶中:

在專業版裝置管理中找到開發板,點選建立部署:

技能包選擇剛剛上傳的應用:

如果需要,可以配置一些啟動引數,否則預設完成即可:

這樣我們就已經完成了一個AI應用,從模型訓練到轉換到開發到部署的全部流程。關於ModelBox核心概念、功能單元和流程圖開發的更多介紹,可檢視ModelBox手冊。

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