香橙派AIpro NPU初试YOLOv8检测
去年年底下单的AIpro, 当时还以为是20T的NPU, 感觉赚大了, 非常爽快的下单等着到货, 然后就是漫长的等待, 过年前几天到了, 过年一直在走亲访友也没时间试玩一下, 今天休息一天, 正好试一试这个已经晚了这么久的"好饭".
本文所有内容依赖于昇腾论坛和官网, 非常感谢大佬们提供的导航帖子:
https://www.hiascend.com/forum/thread-0285140173361311056-1-1.html
前置工作介绍
我这次初试YOLOv8检测模型的部署, 开发设备为一台笔记本(无NPU), 操作系统为ubuntu23.04, 部署设备就是香橙派AIpro, 按照 https://www.hiascend.com/forum/thread-0260140249549075069-1-1.html 刷机即可, 下面主要讲一下如何给笔记本配置模型转换的环境吧.
不得不吐槽一下华为的教程文档真的很sb*, 给一堆流程图介绍, 也没有下载链接可以跳转, 这里贴一张图:

反正我看了这个完全不知道下面我要干什么, 哪怕是操作命令, 下载命令都不知道去哪找去.
进入正题:
将训练好的pytorch模型部署到atlas310b4上的流程大概也许是(我本次走的流程):
pytorch -> onnx -> om
所以我们主要关注的应该是onnx -> om的流程, 这个流程可以在非华为NPU设备上完成, 下面我就介绍一下我是如何走通的这一流程.
先贴一个被我找了很久翻到的下载网站: https://www.hiascend.com/zh/developer/download/community/result?module=cann

我的设备是x86_64架构, 可以通过lscpu查看:
然后我们要下载x86平台开发套件软件包, 这个工具包含了转换模型的可执行程序atc, #后的内容是命令显示的日志:
根据报错日志我们发现需要增加--install命令行参数, 重新执行:
根据日志我们需要把 source /home/ubuntu/Ascend/ascend-toolkit/set_env.sh这句配置环境的命令加入到.bashrc中让系统模型配置好运行环境.
然后执行一下atc命令看一眼:
我们可以看到环境中缺动态库libascend_hal.so, 我们去/home/ubuntu/Ascend/ascend-toolkit/latest中找一找:
我们将这个库的目录路径放到set_env.sh中:
现在你可以用atc工具转换你的模型了
YOLOv8检测模型裁剪后处理导出ONNX
如果你已经训练了自己的YOLOv8模型了, 记得新开一个conda环境用来执行下面的操作, 避免训练和导出环境不一致, 在新的环境中安装我裁剪模型并修改导出逻辑的triplemu/model-only分支的代码:
写个脚本用于导出ONNX模型:
执行后导出的日志:
我把除了head里卷积的部分都裁掉了, 剩下的额外写后处理代码实现吧.
ONNX模型转OM模型
用第一步得到的atc工具即可, 转换的命令我贴在下面了:
讲一下我理解的参数含义吧:
--mode 设置为0, 含义为离线转换om模型
--model 设置为你需要转换的onnx模型
--framework 0:Caffe; 1:MindSpore; 3:Tensorflow; 5:Onnx, 我用的是onnx所以选5
--input_format 输入的数据格式, onnx的话默认就是NCHW格式
--input_shape 输入的数据形状, 格式是 "输入名:N,C,H,W"
--output 输出om模型的前缀名, 会自动补一个".om"后缀
--output_type 输出的数据类型, 可以选 "FP32" "FP16" 等, 根据你的需求来即可
--host_env_os 执行模型端的环境, 刷机的话应该都是linux
--host_env_cpu 执行模型端的cpu, 是aarch64架构的
--soc_version 这个是需要自己添加, AIpro的npu就是 "Ascend310B4"
如果遇到缺python包的报错可以尝试自己安装一下python包:
执行成功的话会有一个success的日志:
我们得到了yolov8-det.om模型, 马上就可以上板子跑了
OM模型上板运行
我们将一张测试图片, om模型放到板子上, 然后根据你的模型自己修改一下推理代码, 我下面贴一下最简单的推理代码, 仅供参考:
import cv2
import time
import random
import numpy as np
from numpy import ndarray
from typing import List, Tuple
from ais_bench.infer.interface import InferSession
CLASS_COLORS = [[random.randint(0, 255) for _ in range(3)] for _ in range(80)]
CLASS_NAMES = ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus',
'train', 'truck', 'boat', 'traffic light', 'fire hydrant',
'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog',
'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe',
'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee',
'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat',
'baseball glove', 'skateboard', 'surfboard', 'tennis racket',
'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl',
'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot',
'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch',
'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop',
'mouse', 'remote', 'keyboard', 'cell phone', 'microwave',
'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock',
'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush')
def softmax(x: ndarray, axis: int = -1) -> ndarray:
e_x = np.exp(x - np.max(x, axis=axis, keepdims=True))
y = e_x / e_x.sum(axis=axis, keepdims=True)
return y
def sigmoid(x: ndarray) -> ndarray:
return 1. / (1. + np.exp(-x))
def postprocess(
feats: List[ndarray],
conf_thres: float = 0.25,
reg_max: int = 16) -> Tuple:
dfl = np.arange(0, reg_max, dtype=np.float32)
scores_pro = []
boxes_pro = []
labels_pro = []
for i in range(3):
stride = 8 << i
score_feat = feats[i * 2][0]
boxes_feat = feats[i * 2 + 1][0]
score_feat = sigmoid(score_feat)
labels = score_feat.argmax(-1)
scores = score_feat.max(-1)
indices = np.where(scores > conf_thres)
hIdx, wIdx = indices
num_proposal = hIdx.size
if not num_proposal:
continue
scores = scores[hIdx, wIdx]
boxes = boxes_feat[hIdx, wIdx].reshape(-1, 4, reg_max)
boxes = softmax(boxes, -1) @ dfl
labels = labels[hIdx, wIdx]
for k in range(num_proposal):
h, w = hIdx[k], wIdx[k]
score = scores[k]
clsid = labels[k]
x0, y0, x1, y1 = boxes[k]
x0 = (w + 0.5 - x0) * stride
y0 = (h + 0.5 - y0) * stride
x1 = (w + 0.5 + x1) * stride
y1 = (h + 0.5 + y1) * stride
scores_pro.append(float(score))
boxes_pro.append(np.array([x0, y0, x1 - x0, y1 - y0], dtype=np.float32))
labels_pro.append(clsid)
boxes_pro = np.array(boxes_pro, dtype=np.float32)
scores_pro = np.array(scores_pro, dtype=np.float32)
labels_pro = np.array(labels_pro, dtype=np.int32)
return boxes_pro, scores_pro, labels_pro
def non_max_suppression(
boxes: ndarray,
scores: ndarray,
labels: ndarray,
conf_thres: float = 0.25,
iou_thres: float = 0.65,
) -> Tuple[ndarray, ndarray, ndarray]:
# indices = cv2.dnn.NMSBoxesBatched(boxes, scores, labels, conf_thres,
# iou_thres)
indices = cv2.dnn.NMSBoxes(boxes, scores, conf_thres, iou_thres)
return boxes[indices], scores[indices], labels[indices]
def main():
model_path = 'yolov8-det.om'
img_path = 'bus.jpg'
model = InferSession(0, model_path)
ori_img = cv2.imread(img_path)
img = np.ascontiguousarray(ori_img[:, :, ::-1].transpose(2, 0, 1)[np.newaxis], dtype=np.float32) / 255.
# warmup 30 times
for i in range(30):
tmp = np.random.randn(1, 3, 640, 640).astype(np.float32)
model.infer([tmp])
# calculate infer FPS
start = time.perf_counter()
outputs = model.infer([img])
end = time.perf_counter()
print(f'Inference FPS: {1 / (end - start)}')
boxes, scores, labels = non_max_suppression(*postprocess(outputs))
for box, score, label in zip(boxes, scores, labels):
x1, y1, w, h = box.round().astype(np.int32).tolist()
x2, y2 = x1 + w, y1 + h
cv2.rectangle(ori_img, (x1, y1), (x2, y2), CLASS_COLORS[label], 2, cv2.LINE_AA)
cv2.putText(ori_img, CLASS_NAMES[label], (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255),
2, cv2.LINE_AA)
cv2.imwrite(ori_img, 'result.jpg')
if __name__ == '__main__':
main() 上面的代码保存到板子上, 如果你的模型是自定义的模型那么就需要修改类别名之类的, 为了简化推理逻辑, 我提前将图片resize和补边了, 如果你应用的话请注意修改对应的框复原逻辑.
下面是检测结果, 看上去还可以, 打印的日志推理部分是32FPS, 这个8T的NPU还是很强的.

总结
想要在AIpro上部署自己的模型可以通过atc工具对onnx进行转换, 然后利用板子上的ais_bench库加载模型, 并利用opencv/numpy等实现前后处理等逻辑.
后面可以展望一下用ascendcl 中的c接口实现一下简单的推理demo, 还有就是模型量化等等
参考: 昇腾社区-官网丨昇腾万里 让智能无所不及
大概扫了一眼, 好像跟cuda的接口很像.
香橙派AIpro NPU初试YOLOv8检测
去年年底下单的AIpro, 当时还以为是20T的NPU, 感觉赚大了, 非常爽快的下单等着到货, 然后就是漫长的等待, 过年前几天到了, 过年一直在走亲访友也没时间试玩一下, 今天休息一天, 正好试一试这个已经晚了这么久的"好饭".
本文所有内容依赖于昇腾论坛和官网, 非常感谢大佬们提供的导航帖子:
https://www.hiascend.com/forum/thread-0285140173361311056-1-1.html
前置工作介绍
我这次初试
YOLOv8检测模型的部署, 开发设备为一台笔记本(无NPU), 操作系统为ubuntu23.04, 部署设备就是香橙派AIpro, 按照 https://www.hiascend.com/forum/thread-0260140249549075069-1-1.html 刷机即可, 下面主要讲一下如何给笔记本配置模型转换的环境吧.不得不吐槽一下华为的教程文档真的很sb*, 给一堆流程图介绍, 也没有下载链接可以跳转, 这里贴一张图:
反正我看了这个完全不知道下面我要干什么, 哪怕是操作命令, 下载命令都不知道去哪找去.
进入正题:
将训练好的pytorch模型部署到atlas310b4上的流程大概也许是(我本次走的流程):
pytorch -> onnx -> om
所以我们主要关注的应该是onnx -> om的流程, 这个流程可以在非华为NPU设备上完成, 下面我就介绍一下我是如何走通的这一流程.
先贴一个被我找了很久翻到的下载网站: https://www.hiascend.com/zh/developer/download/community/result?module=cann
我的设备是
x86_64架构, 可以通过lscpu查看:然后我们要下载
x86平台开发套件软件包, 这个工具包含了转换模型的可执行程序atc, #后的内容是命令显示的日志:根据报错日志我们发现需要增加
--install命令行参数, 重新执行:根据日志我们需要把
source /home/ubuntu/Ascend/ascend-toolkit/set_env.sh这句配置环境的命令加入到.bashrc中让系统模型配置好运行环境.然后执行一下
atc命令看一眼:我们可以看到环境中缺动态库
libascend_hal.so, 我们去/home/ubuntu/Ascend/ascend-toolkit/latest中找一找:我们将这个库的目录路径放到
set_env.sh中:sudo su # 先进入root账户, 否则没权限 echo "export LD_LIBRARY_PATH=\${ASCEND_TOOLKIT_HOME}/x86_64-linux/devlib:\$LD_LIBRARY_PATH" >> /home/ubuntu/Ascend/ascend-toolkit/set_env.sh exit source ~/.bashrc atc # 这回应该可以看到atc工具的日志了现在你可以用
atc工具转换你的模型了YOLOv8检测模型裁剪后处理导出ONNX
如果你已经训练了自己的YOLOv8模型了, 记得新开一个conda环境用来执行下面的操作, 避免训练和导出环境不一致, 在新的环境中安装我裁剪模型并修改导出逻辑的
triplemu/model-only分支的代码:写个脚本用于导出ONNX模型:
from ultralytics import YOLO model = YOLO('yolov8s.pt', 'detect') model.export(format='onnx', opset=13, simplify=True)执行后导出的日志:
我把除了head里卷积的部分都裁掉了, 剩下的额外写后处理代码实现吧.
ONNX模型转OM模型
用第一步得到的
atc工具即可, 转换的命令我贴在下面了:讲一下我理解的参数含义吧:
--mode设置为0, 含义为离线转换om模型--model设置为你需要转换的onnx模型--framework0:Caffe; 1:MindSpore; 3:Tensorflow; 5:Onnx, 我用的是onnx所以选5--input_format输入的数据格式, onnx的话默认就是NCHW格式--input_shape输入的数据形状, 格式是 "输入名:N,C,H,W"--output输出om模型的前缀名, 会自动补一个".om"后缀--output_type输出的数据类型, 可以选 "FP32" "FP16" 等, 根据你的需求来即可--host_env_os执行模型端的环境, 刷机的话应该都是linux--host_env_cpu执行模型端的cpu, 是aarch64架构的--soc_version这个是需要自己添加, AIpro的npu就是 "Ascend310B4"如果遇到缺python包的报错可以尝试自己安装一下python包:
执行成功的话会有一个success的日志:
我们得到了
yolov8-det.om模型, 马上就可以上板子跑了OM模型上板运行
我们将一张测试图片, om模型放到板子上, 然后根据你的模型自己修改一下推理代码, 我下面贴一下最简单的推理代码, 仅供参考:
import cv2 import time import random import numpy as np from numpy import ndarray from typing import List, Tuple from ais_bench.infer.interface import InferSession CLASS_COLORS = [[random.randint(0, 255) for _ in range(3)] for _ in range(80)] CLASS_NAMES = ('person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush') def softmax(x: ndarray, axis: int = -1) -> ndarray: e_x = np.exp(x - np.max(x, axis=axis, keepdims=True)) y = e_x / e_x.sum(axis=axis, keepdims=True) return y def sigmoid(x: ndarray) -> ndarray: return 1. / (1. + np.exp(-x)) def postprocess( feats: List[ndarray], conf_thres: float = 0.25, reg_max: int = 16) -> Tuple: dfl = np.arange(0, reg_max, dtype=np.float32) scores_pro = [] boxes_pro = [] labels_pro = [] for i in range(3): stride = 8 << i score_feat = feats[i * 2][0] boxes_feat = feats[i * 2 + 1][0] score_feat = sigmoid(score_feat) labels = score_feat.argmax(-1) scores = score_feat.max(-1) indices = np.where(scores > conf_thres) hIdx, wIdx = indices num_proposal = hIdx.size if not num_proposal: continue scores = scores[hIdx, wIdx] boxes = boxes_feat[hIdx, wIdx].reshape(-1, 4, reg_max) boxes = softmax(boxes, -1) @ dfl labels = labels[hIdx, wIdx] for k in range(num_proposal): h, w = hIdx[k], wIdx[k] score = scores[k] clsid = labels[k] x0, y0, x1, y1 = boxes[k] x0 = (w + 0.5 - x0) * stride y0 = (h + 0.5 - y0) * stride x1 = (w + 0.5 + x1) * stride y1 = (h + 0.5 + y1) * stride scores_pro.append(float(score)) boxes_pro.append(np.array([x0, y0, x1 - x0, y1 - y0], dtype=np.float32)) labels_pro.append(clsid) boxes_pro = np.array(boxes_pro, dtype=np.float32) scores_pro = np.array(scores_pro, dtype=np.float32) labels_pro = np.array(labels_pro, dtype=np.int32) return boxes_pro, scores_pro, labels_pro def non_max_suppression( boxes: ndarray, scores: ndarray, labels: ndarray, conf_thres: float = 0.25, iou_thres: float = 0.65, ) -> Tuple[ndarray, ndarray, ndarray]: # indices = cv2.dnn.NMSBoxesBatched(boxes, scores, labels, conf_thres, # iou_thres) indices = cv2.dnn.NMSBoxes(boxes, scores, conf_thres, iou_thres) return boxes[indices], scores[indices], labels[indices] def main(): model_path = 'yolov8-det.om' img_path = 'bus.jpg' model = InferSession(0, model_path) ori_img = cv2.imread(img_path) img = np.ascontiguousarray(ori_img[:, :, ::-1].transpose(2, 0, 1)[np.newaxis], dtype=np.float32) / 255. # warmup 30 times for i in range(30): tmp = np.random.randn(1, 3, 640, 640).astype(np.float32) model.infer([tmp]) # calculate infer FPS start = time.perf_counter() outputs = model.infer([img]) end = time.perf_counter() print(f'Inference FPS: {1 / (end - start)}') boxes, scores, labels = non_max_suppression(*postprocess(outputs)) for box, score, label in zip(boxes, scores, labels): x1, y1, w, h = box.round().astype(np.int32).tolist() x2, y2 = x1 + w, y1 + h cv2.rectangle(ori_img, (x1, y1), (x2, y2), CLASS_COLORS[label], 2, cv2.LINE_AA) cv2.putText(ori_img, CLASS_NAMES[label], (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2, cv2.LINE_AA) cv2.imwrite(ori_img, 'result.jpg') if __name__ == '__main__': main()上面的代码保存到板子上, 如果你的模型是自定义的模型那么就需要修改类别名之类的, 为了简化推理逻辑, 我提前将图片resize和补边了, 如果你应用的话请注意修改对应的框复原逻辑.
下面是检测结果, 看上去还可以, 打印的日志推理部分是32FPS, 这个8T的NPU还是很强的.
总结
想要在AIpro上部署自己的模型可以通过atc工具对onnx进行转换, 然后利用板子上的
ais_bench库加载模型, 并利用opencv/numpy等实现前后处理等逻辑.后面可以展望一下用
ascendcl中的c接口实现一下简单的推理demo, 还有就是模型量化等等参考: 昇腾社区-官网丨昇腾万里 让智能无所不及
大概扫了一眼, 好像跟cuda的接口很像.