Bootstrap

OrangePi AIpro评测 - AI服务篇

0. 环境


●OrangePi AIpro
●windows电脑
●路由器

之前我已经对OrangePi AIpro进行了些嵌入式基本操作的评测。接下来进行AI部分。来看看华为昇腾的特别之处。


1.普通CPU和AI CPU


这里请提前用调试串口或者ssh到板子上,记得用户名和密码,分别是HwHiAiUser,Mind@123。串口波特率是115200.


1.1 npu-smi info


npu-smi info

(base) HwHiAiUser@orangepiaipro:~$ npu-smi info
+--------------------------------------------------------------------------------------------------------+
| npu-smi 23.0.0                                   Version: 23.0.0                                       |
+-------------------------------+-----------------+------------------------------------------------------+
| NPU     Name                  | Health          | Power(W)     Temp(C)           Hugepages-Usage(page) |
| Chip    Device                | Bus-Id          | AICore(%)    Memory-Usage(MB)                        |
+===============================+=================+======================================================+
| 0       310B4                 | Alarm           | 0.0          45                15    / 15            |
| 0       0                     | NA              | 0            1564 / 7545                             |
+===============================+=================+======================================================+
(base) HwHiAiUser@orangepiaipro:~$


1.2 npu-smi info -t cpu-num-cfg


说实话,我也不太会看。只知道是8G,7545表示8GB内存。然后默认4核中,3个核是普通CPU,1个是AI CPU,平时AI CPU占用率是0%的。


这个操作可以查看到目前CPU情况。

(base) HwHiAiUser@orangepiaipro:~$ npu-smi info -t cpu-num-cfg -i 0 -c 0
        Current AI CPU number          : 1
        Current control CPU number     : 3
        Current data CPU number        : 0
(base) HwHiAiUser@orangepiaipro:~$


这个是查看利用率。

(base) HwHiAiUser@orangepiaipro:~$ npu-smi info -t usages -i 0 -c 0
        Memory Capacity(MB)            : 7545
        Memory Usage Rate(%)           : 21
        Hugepages Total(page)          : 15
        Hugepages Usage Rate(%)        : 100
        Aicore Usage Rate(%)           : 0
        Aicpu Usage Rate(%)            : 0
        Ctrlcpu Usage Rate(%)          : 2
        Memory Bandwidth Usage Rate(%) : 1
(base) HwHiAiUser@orangepiaipro:~$


然后参考说明书,这个指令还可以设置AI CPU为普通CPU,不过我暂时不操作了。

2.AI Demo


看用户手册,这里的demo是需要先运行Jupyter Lab的。这距离实际部署又差了一段距离。不过对于新手还是比较友好的,多了图形界面。


2.1 运行 Jupyter Lab


运行这个 Jupyter需要命令行执行:

cd ~/samples/notebooks
./start_notebook.sh

看这里的链接,是无法通过局域网访问的。
我想要通过局域网访问:

jupyter lab --ip 192.168.123.179  --port 5678 --allow-root --no-browser


注意这里192.168.123.179是OrangePi AIpro的ip,和windows电脑处于同一个路由器下。

电脑可以访问了:

2.2 运行yolov5 demo


我也不是很熟悉Jupyter,最开始是在百度Ai Studio类似的工具中看到的,按播放可以运行一个个代码块。
打开 yolov5下的main.ipynb。这个格式的文件都需要用Jupyter打开才可以的,不能直接用python运行的。不便于部署,但是便于入门,也可以带有很多注释。带有注释就弥补了很多python库缺少文档的缺点。c/c++的开源库,往往编程基础扎实,也能输出文档。python的库没那么多规矩。

打开 main.ipynb后,点击>>重新运行。

随后等待一段时间,就会有视频输出了。这个demo未修改下,是处理video的。

Jupyter不错,可以显示处理结果。图片和视频都可以。

根据用户手册,这个demo还可以处理图像。修改第4个代码块中的infer_mode,修改为image,再次执行。

会输出结果:

到这里就运行了第一个demo。本demo其实还可以调用摄像头进行识别。不过我暂时未接入摄像头。
其余的demo用同样的方法就可以执行。目前有这几个demo,只要适当修改,就可以直接应用于特定场景当中。

demo还是做得挺完善的,有图像视频处理、文本、图像HDR、语音分类等。其实实际部署到板卡的AI模型,难点在于基本都是在英伟达的GPU中训练,然后再经过模型转换后放到板子上运行的。我以前在电脑中训练过微小模型(LeNet),用的是Tensorflow+Keras,部署到英伟达的GPU、Jetson都很方便,但是要移植到别的AI板卡,就卡在自己不会算子这里。

3.还原为脚本


我还是偏爱脚本,后续可以直接命令行运行。
切换目录:

cd ~/samples/notebooks/01-yolov5


创建文件:

vim main.py


添加内容:

# 导入代码依赖
import cv2
import numpy as np
import ipywidgets as widgets
from IPython.display import display
import torch
from skvideo.io import vreader, FFmpegWriter
import IPython.display
from ais_bench.infer.interface import InferSession

from det_utils import letterbox, scale_coords, nms

def preprocess_image(image, cfg, bgr2rgb=True):
    """图片预处理"""
    img, scale_ratio, pad_size = letterbox(image, new_shape=cfg['input_shape'])
    if bgr2rgb:
        img = img[:, :, ::-1]
    img = img.transpose(2, 0, 1)  # HWC2CHW
    img = np.ascontiguousarray(img, dtype=np.float32)
    return img, scale_ratio, pad_size


def draw_bbox(bbox, img0, color, wt, names):
    """在图片上画预测框"""
    det_result_str = ''
    for idx, class_id in enumerate(bbox[:, 5]):
        if float(bbox[idx][4] < float(0.05)):
            continue
        img0 = cv2.rectangle(img0, (int(bbox[idx][0]), int(bbox[idx][1])), (int(bbox[idx][2]), int(bbox[idx][3])),
                             color, wt)
        img0 = cv2.putText(img0, str(idx) + ' ' + names[int(class_id)], (int(bbox[idx][0]), int(bbox[idx][1] + 16)),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
        img0 = cv2.putText(img0, '{:.4f}'.format(bbox[idx][4]), (int(bbox[idx][0]), int(bbox[idx][1] + 32)),
                           cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
        det_result_str += '{} {} {} {} {} {}\n'.format(
            names[bbox[idx][5]], str(bbox[idx][4]), bbox[idx][0], bbox[idx][1], bbox[idx][2], bbox[idx][3])
    return img0


def get_labels_from_txt(path):
    """从txt文件获取图片标签"""
    labels_dict = dict()
    with open(path) as f:
        for cat_id, label in enumerate(f.readlines()):
            labels_dict[cat_id] = label.strip()
    return labels_dict


def draw_prediction(pred, image, labels):
    """在图片上画出预测框并进行可视化展示"""
    imgbox = widgets.Image(format='jpg', height=720, width=1280)
    img_dw = draw_bbox(pred, image, (0, 255, 0), 2, labels)
    imgbox.value = cv2.imencode('.jpg', img_dw)[1].tobytes()
    display(imgbox)


def infer_image(img_path, model, class_names, cfg):
    """图片推理"""
    # 图片载入
    image = cv2.imread(img_path)
    # 数据预处理
    img, scale_ratio, pad_size = preprocess_image(image, cfg)
    # 模型推理
    output = model.infer([img])[0]

    output = torch.tensor(output)
    # 非极大值抑制后处理
    boxout = nms(output, conf_thres=cfg["conf_thres"], iou_thres=cfg["iou_thres"])
    pred_all = boxout[0].numpy()
    # 预测坐标转换
    scale_coords(cfg['input_shape'], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size))
    # 图片预测结果可视化
    draw_prediction(pred_all, image, class_names)


def infer_frame_with_vis(image, model, labels_dict, cfg, bgr2rgb=True):
    # 数据预处理
    img, scale_ratio, pad_size = preprocess_image(image, cfg, bgr2rgb)
    # 模型推理
    output = model.infer([img])[0]

    output = torch.tensor(output)
    # 非极大值抑制后处理
    boxout = nms(output, conf_thres=cfg["conf_thres"], iou_thres=cfg["iou_thres"])
    pred_all = boxout[0].numpy()
    # 预测坐标转换
    scale_coords(cfg['input_shape'], pred_all[:, :4], image.shape, ratio_pad=(scale_ratio, pad_size))
    # 图片预测结果可视化
    img_vis = draw_bbox(pred_all, image, (0, 255, 0), 2, labels_dict)
    return img_vis


def img2bytes(image):
    """将图片转换为字节码"""
    return bytes(cv2.imencode('.jpg', image)[1])


def infer_video(video_path, model, labels_dict, cfg):
    """视频推理"""
    image_widget = widgets.Image(format='jpeg', width=800, height=600)
    display(image_widget)

    # 读入视频
    cap = cv2.VideoCapture(video_path)
    while True:
        ret, img_frame = cap.read()
        if not ret:
            break
        # 对视频帧进行推理
        image_pred = infer_frame_with_vis(img_frame, model, labels_dict, cfg, bgr2rgb=True)
        image_widget.value = img2bytes(image_pred)


def infer_camera(model, labels_dict, cfg):
    """外设摄像头实时推理"""
    def find_camera_index():
        max_index_to_check = 10  # Maximum index to check for camera

        for index in range(max_index_to_check):
            cap = cv2.VideoCapture(index)
            if cap.read()[0]:
                cap.release()
                return index

        # If no camera is found
        raise ValueError("No camera found.")

    # 获取摄像头
    camera_index = find_camera_index()
    cap = cv2.VideoCapture(camera_index)
    # 初始化可视化对象
    image_widget = widgets.Image(format='jpeg', width=1280, height=720)
    display(image_widget)
    while True:
        # 对摄像头每一帧进行推理和可视化
        _, img_frame = cap.read()
        image_pred = infer_frame_with_vis(img_frame, model, labels_dict, cfg)
        image_widget.value = img2bytes(image_pred)
		
cfg = {
    'conf_thres': 0.4,  # 模型置信度阈值,阈值越低,得到的预测框越多
    'iou_thres': 0.5,  # IOU阈值,高于这个阈值的重叠预测框会被过滤掉
    'input_shape': [640, 640],  # 模型输入尺寸
}

model_path = 'yolo.om'
label_path = './coco_names.txt'
# 初始化推理模型
model = InferSession(0, model_path)
labels_dict = get_labels_from_txt(label_path)


infer_mode = 'image'

if infer_mode == 'image':
    img_path = 'world_cup.jpg'
    infer_image(img_path, model, labels_dict, cfg)
elif infer_mode == 'camera':
    infer_camera(model, labels_dict, cfg)
elif infer_mode == 'video':
    video_path = 'racing.mp4'
    infer_video(video_path, model, labels_dict, cfg)
	
	

运行测试:

python3 main.py

这里用脚本调用AI处理器,比上面的Jupyter更利于实际部署。图形界面给学习上手较好,linux服务器还不一定有图形界面。当服务器是后台服务器,没有前端技术,就会只有命令行执行程序。下文还给设备添加HTTP接口,实现命令行被局域网访问。但是如果要执行特定代码,还需要继续开发。

4.如何通过HTTP被调用


假如本板卡不是个大脑,只是个辅助性的AI。那么本板卡需要开发个接口API,给外部调用。虽然SSH也是可以远程执行脚本,不过还是HTTP接口的API比较友好。HTTP形式的,也利于跨平台跨设备进行互相通信。

接下来演示基于python+flask实现的http接口的hello world。


4.1 创建文件 http_api.py

from flask import Flask

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False  # 禁止中文转义

@app.route('/HelloWorld')
def hello_world():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host="0.0.0.0")

4.2 安装依赖

python3 -m pip install flask

4.3 运行

python3 http_api.py

4.4 接口测试


浏览器打开 192.168.123.179:5000/HelloWorld

用postman测试http接口:

这里用的是Get方式。后续还可以开发HTTP POST接口用以数据交互。比如可以post图像文件到板卡,然后板子用python调用AI处理器进行图像处理。不过本文暂时没有做到那么多。

小结


本人对华为昇腾的AI架构、AI算子等并不熟悉的。只是个门外汉运行下demo,并没有深入到AI部分的开发。本文通过npu-smi这个指令,查看到昇腾的CPU的特别之处,就是这个处理器支持分配CPU核为普通CPU或者AI CPU,这是一大特色。然后参考了用户手册,运行了第一个AI demo。感觉板卡算力还算可以,处理视频也没消耗很多时间就出结果了。记得以前用opencv处理视频的每一帧,CPU模式下1分钟的图片至少也要处理十几分钟。这显得昇腾的AI处理能力在图片、视频方面发挥得还是不错的。最后本文给出了在板子上开发HTTP接口的hello world示例。AI板卡还是需要个方便调用的接口,便于将小AI整进系统中。

参考

[1]OrangePi_AI_Pro_昇腾_用户手册_v0.3.1.pdf
[2]香橙派AIpro学习资源一站式导航,https://www.hiascend.com/forum/thread-0285140173361311056-1-1.html
[3]香橙派AIpro官网资料下载,http://www.orangepi.cn/html/hardWare/computerAndMicrocontrollers/service-and-support/Orange-Pi-AIpro.html

;