香橙派5使用NPU加速yolov5的实时视频推理

Posted 孙启尧

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了香橙派5使用NPU加速yolov5的实时视频推理相关的知识,希望对你有一定的参考价值。

三、将best.onnx转为RKNN格式

        这一步就需要我们进入到Ubuntu20.04系统中了,我的Ubuntu系统中已经下载好了anaconda,使用anaconda的好处就是可以方便的安装一些库,而且还可以利用conda来配置虚拟环境,做到环境与环境之间相互独立。

        对于我来说,使用了以下命令创建了一个名为rknn_ceshi的虚拟环境

conda create -n rknn_ceshi python=3.8

        之后,点击GitHub - rockchip-linux/rknn-toolkit2把整个项目给下载下来,解压后,我们进入刚刚创立虚拟环境下安装rknn-toolkit2。进入doc目录后,输入命令

pip install -r requirements_cp38-1.4.0.txt -i https://mirror.baidu.com/pypi/simple

        这里一定要带上百度的镜像源,要不然会报错,这个坑折磨了我整整3小时。

        安装完成后,出现下面的界面就说明已经安装好了

        接下来,我们进入packages文件夹,输入一下命令

pip install rknn_toolkit2-1.4.0_22dcfef4-cp38-cp38-linux_x86_64.whl

        出现以下界面,但是这里一定要注意,有一个超级大坑,超级大坑,超级大坑,后续我也是询问了大佬才解决的这个问题,这个地方安装的是rknn-toolkit2-1.4.0-22dcfef4!!!

        出现上面那个界面后,我们在终端输入python,再输入以下命令,如果没有报错,则证明我们的环境已经搭载好了

from rknn.api import RKNN

        接下来,我们要做的就是修改test.py里面的一些内容

        我第一次做的时候,报了错误,当时提示的是没有这个2-1.4.0-22dcfef4版本,我就跑到conda的环境包下,把所有的2-1.4.0-22dcfef4版本改成了2-1.4.0,才解决这个问题。

        可是当我这一次在执行这个文件的时候,就没报这个错误,直接就跑起来了。

        之后在我们的文件夹下出现了best.rknn这样就可以到香橙派5上部署了!!!

四、香橙派5部署rknn实现NPU加速YOLOV5视频推理

        这里给大家强调一下,我使用的是RKNN的python版本来实现NPU加速的,这里我们需要到Github上下载RKNN官方教程,下载完成后进入该文件夹,输入指令

cd /examples/onnx/yolov5

        进入文件夹后,创建一个名为demo.py的文件,将以下代码复制即可,我已经实现了实时视频为了保护隐私,这里我依旧采用官方的yolov5s.rknn模型,而没有用我自己的

import os
import urllib
import traceback
import time
import datetime as dt
import sys
import numpy as np
import cv2
from rknnlite.api import RKNNLite


RKNN_MODEL = 'yolov5s.rknn'
DATASET = './dataset.txt'

QUANTIZE_ON = True

OBJ_THRESH = 0.25
NMS_THRESH = 0.45
IMG_SIZE = 640

CLASSES = ("person", "bicycle", "car", "motorbike ", "aeroplane ", "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", "sofa",
           "pottedplant", "bed", "diningtable", "toilet ", "tvmonitor", "laptop	", "mouse	", "remote ", "keyboard ", "cell phone", "microwave ",
           "oven ", "toaster", "sink", "refrigerator ", "book", "clock", "vase", "scissors ", "teddy bear ", "hair drier", "toothbrush ")


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def xywh2xyxy(x):
    # Convert [x, y, w, h] to [x1, y1, x2, y2]
    y = np.copy(x)
    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
    return y


def process(input, mask, anchors):

    anchors = [anchors[i] for i in mask]
    grid_h, grid_w = map(int, input.shape[0:2])

    box_confidence = sigmoid(input[..., 4])
    box_confidence = np.expand_dims(box_confidence, axis=-1)

    box_class_probs = sigmoid(input[..., 5:])

    box_xy = sigmoid(input[..., :2])*2 - 0.5

    col = np.tile(np.arange(0, grid_w), grid_w).reshape(-1, grid_w)
    row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_h)
    col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
    row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
    grid = np.concatenate((col, row), axis=-1)
    box_xy += grid
    box_xy *= int(IMG_SIZE/grid_h)

    box_wh = pow(sigmoid(input[..., 2:4])*2, 2)
    box_wh = box_wh * anchors

    box = np.concatenate((box_xy, box_wh), axis=-1)

    return box, box_confidence, box_class_probs


def filter_boxes(boxes, box_confidences, box_class_probs):
    """Filter boxes with box threshold. It's a bit different with origin yolov5 post process!
    # Arguments
        boxes: ndarray, boxes of objects.
        box_confidences: ndarray, confidences of objects.
        box_class_probs: ndarray, class_probs of objects.
    # Returns
        boxes: ndarray, filtered boxes.
        classes: ndarray, classes for boxes.
        scores: ndarray, scores for boxes.
    """
    boxes = boxes.reshape(-1, 4)
    box_confidences = box_confidences.reshape(-1)
    box_class_probs = box_class_probs.reshape(-1, box_class_probs.shape[-1])

    _box_pos = np.where(box_confidences >= OBJ_THRESH)
    boxes = boxes[_box_pos]
    box_confidences = box_confidences[_box_pos]
    box_class_probs = box_class_probs[_box_pos]

    class_max_score = np.max(box_class_probs, axis=-1)
    classes = np.argmax(box_class_probs, axis=-1)
    _class_pos = np.where(class_max_score >= OBJ_THRESH)

    boxes = boxes[_class_pos]
    classes = classes[_class_pos]
    scores = (class_max_score* box_confidences)[_class_pos]

    return boxes, classes, scores


def nms_boxes(boxes, scores):
    """Suppress non-maximal boxes.
    # Arguments
        boxes: ndarray, boxes of objects.
        scores: ndarray, scores of objects.
    # Returns
        keep: ndarray, index of effective boxes.
    """
    x = boxes[:, 0]
    y = boxes[:, 1]
    w = boxes[:, 2] - boxes[:, 0]
    h = boxes[:, 3] - boxes[:, 1]

    areas = w * h
    order = scores.argsort()[::-1]

    keep = []
    while order.size > 0:
        i = order[0]
        keep.append(i)

        xx1 = np.maximum(x[i], x[order[1:]])
        yy1 = np.maximum(y[i], y[order[1:]])
        xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
        yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])

        w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
        h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
        inter = w1 * h1

        ovr = inter / (areas[i] + areas[order[1:]] - inter)
        inds = np.where(ovr <= NMS_THRESH)[0]
        order = order[inds + 1]
    keep = np.array(keep)
    return keep


def yolov5_post_process(input_data):
    masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
    anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
               [59, 119], [116, 90], [156, 198], [373, 326]]

    boxes, classes, scores = [], [], []
    for input, mask in zip(input_data, masks):
        b, c, s = process(input, mask, anchors)
        b, c, s = filter_boxes(b, c, s)
        boxes.append(b)
        classes.append(c)
        scores.append(s)

    boxes = np.concatenate(boxes)
    boxes = xywh2xyxy(boxes)
    classes = np.concatenate(classes)
    scores = np.concatenate(scores)

    nboxes, nclasses, nscores = [], [], []
    for c in set(classes):
        inds = np.where(classes == c)
        b = boxes[inds]
        c = classes[inds]
        s = scores[inds]

        keep = nms_boxes(b, s)

        nboxes.append(b[keep])
        nclasses.append(c[keep])
        nscores.append(s[keep])

    if not nclasses and not nscores:
        return None, None, None

    boxes = np.concatenate(nboxes)
    classes = np.concatenate(nclasses)
    scores = np.concatenate(nscores)

    return boxes, classes, scores


def draw(image, boxes, scores, classes, fps):
    """Draw the boxes on the image.
    # Argument:
        image: original image.
        boxes: ndarray, boxes of objects.
        classes: ndarray, classes of objects.
        scores: ndarray, scores of objects.
        fps: int.
        all_classes: all classes name.
    """
    for box, score, cl in zip(boxes, scores, classes):
        top, left, right, bottom = box
        print('class: , score: '.format(CLASSES[cl], score))
        print('box coordinate left,top,right,down: [, , , ]'.format(top, left, right, bottom))
        top = int(top)
        left = int(left)
        right = int(right)
        bottom = int(bottom)

        cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
        cv2.putText(image, '0 1:.2f'.format(CLASSES[cl], score),
                    (top, left - 6),
                    cv2.FONT_HERSHEY_SIMPLEX,
                    0.6, (0, 0, 255), 2)

def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
    # Resize and pad image while meeting stride-multiple constraints
    shape = im.shape[:2]  # current shape [height, width]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)

    # Scale ratio (new / old)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])

    # Compute padding
    ratio = r, r  # width, height ratios
    new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1]  # wh padding

    dw /= 2  # divide padding into 2 sides
    dh /= 2

    if shape[::-1] != new_unpad:  # resize
        im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color)  # add border
    return im, ratio, (dw, dh)

# ==================================
# 如下为改动部分,主要就是去掉了官方 demo 中的模型转换代码,直接加载 rknn 模型,并将 RKNN 类换成了 rknn_toolkit2_lite 中的 RKNNLite 类
# ==================================

rknn = RKNNLite()

# load RKNN model
print('--> Load RKNN model')
ret = rknn.load_rknn(RKNN_MODEL)

# Init runtime environment
print('--> Init runtime environment')
# use NPU core 0 1 2
ret = rknn.init_runtime(core_mask=RKNNLite.NPU_CORE_0_1_2)
if ret != 0:
    print('Init runtime environment failed!')
    exit(ret)
print('done')

# Create a VideoCapture object and read from input file
# If the input is the camera, pass 0 instead of the video file name
cap = cv2.VideoCapture(0)
 
# Check if camera opened successfully
if (cap.isOpened()== False): 
  print("Error opening video stream or file")
 
# Read until video is completed
while(cap.isOpened()):
    start = dt.datetime.utcnow()
    # Capture frame-by-frame
    ret, img = cap.read()
    if not ret:
        break
    
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))

    # Inference
    print('--> Running model')
    outputs = rknn.inference(inputs=[img])
    print('done')

    # post process
    input0_data = outputs[0]
    input1_data = outputs[1]
    input2_data = outputs[2]

    input0_data = input0_data.reshape([3, -1]+list(input0_data.shape[-2:]))
    input1_data = input1_data.reshape([3, -1]+list(input1_data.shape[-2:]))
    input2_data = input2_data.reshape([3, -1]+list(input2_data.shape[-2:]))

    input_data = list()
    input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
    input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
    input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))

    boxes, classes, scores = yolov5_post_process(input_data)
    duration = dt.datetime.utcnow() - start
    fps = round(10000000 / duration.microseconds)

    # draw process result and fps
    img_1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
    cv2.putText(img_1, f'fps: fps',
            (20, 20),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.6, (0, 125, 125), 2)
    if boxes is not None:
        draw(img_1, boxes, scores, classes, fps)

    # show output
    cv2.imshow("post process result", img_1)

    # Press Q on keyboard to  exit
    if cv2.waitKey(25) & 0xFF == ord('q'):
        break
 
# When everything done, release the video capture object
cap.release()
 
# Closes all the frames
cv2.destroyAllWindows()

        这里我开启了CPU定频,可是并没有提高多少NPU的使用率,干脆我也不再把代码放上来了,等到以后有时间我再研究一下。

        之后在终端中,运行命令

python demo.py

        效果我放到了B站,感兴趣的小伙伴可以点进去看一下B站视频

rk3588使用npu进行模型转换和推理,加速AI应用落地

文章目录

🍉零、引言

本文完成于2022-07-02 20:21:55。博主在瑞芯微RK3588的开发板上跑了deepsort跟踪算法,从IP相机中的server拉取rtsp视频流,但是fps只有1.2,和放PPT一样卡顿,无法投入实际应用。本来想使用tensorrt进行加速推理,但是前提需要cuda,rk的板子上都是Arm的手机gpu,没有Nvidia的cuda,所以这条路行不通。那么转过来,使用开发板自带的NPU进行加速推理,岂不是更加可行,而且它本身就是深度学习嵌入式板子,不用NPU真的可惜。

🏅问题来了:怎么使用NPU?在开发板上还是在自己的PC上?要安装什么环境?怎么安装?这些问题都需要依次考虑清楚。因为我在此之前也没有接触过NPU,所以为此做了很多功课,看了很多教程,总计有10h以上。然后今天自己成功实践了下转换rknn模型,并使用npu推理。为了让后面的同学少走弯路,特此花1个h记录下这个使用过程,因为官方教程真的很不详细,很多地方都需要自己踩坑然后填好,那么开始正题!

🍍一、主要功能

RKNN-Toolkit2 是为用户提供在 PC、 Rockchip NPU 平台上进行模型转换、推理和性能评估的开发套件,用户通过该工具提供的 Python 接口可以便捷地完成以下功能:

  1. 🏆模型转换:支持 Caffe、TensorFlow、TensorFlow Lite、ONNX、DarkNet、PyTorch 等模型转为 RKNN 模型,并支持 RKNN 模型导入导出,RKNN 模型能够在 Rockchip NPU 平台上加载使用。
  2. 🎽量 化 功 能 : 支 持 将 浮 点 模 型 量 化 为 定 点 模 型 , 目 前 支 持 的 量 化 方 法 为 非 对 称 量 化 , 并 支 持 混 合 量 化 功 能 。asymmetric_quantized-16 目前版本暂不支持。
  3. 🎯模型推理:能够在 PC 上模拟 Rockchip NPU 运行 RKNN 模型并获取推理结果;或将 RKNN模型分发到指定的 NPU 设备上进行推理并获取推理结果。
  4. 🏋性能和内存评估:将 RKNN 模型分发到指定 NPU 设备上运行,以评估模型在实际设备上运行时的性能和内存占用情况。
  5. 🎼量化精度分析:该功能将给出模型量化前后每一层推理结果与浮点模型推理结果的余弦距离,以便于分析量化误差是如何出现的,为提高量化模型的精度提供思路。

RK3588 内置 NPU 模块, 处理性能最高可达6TOPS。使用该NPU需要下载RKNN SDK,RKNN SDK 为带有 NPU 的RK3588S/RK3588 芯片平台提供编程接口,能够帮助用户部署使用 RKNN-Toolkit2 导出的 RKNN 模型,加速 AI应用 的落地。

🍎二、系统依赖

本开发套件支持运行于 Ubuntu操作系统 (目前版本 Windows、MacOS、Debian 等暂不支持)。
Ubuntu系统需要满足以下运行环境要求:

操作系统版本Ubuntu18.04(x64)及以上
Python 版本3.6 / 3.8
Python 库依赖详见 doc/requirements*.txt

🍌三、安装RKNN-Toolkit

下面我以 Ubuntu 18.04、Python3.6 为例,说明如何快速上手使用 RKNN-Toolkit2。我的开发板为ITX-3588J,但是开发板型号并不影响。

1、安装Python3.6和pip3

sudo apt-get install python3 python3-dev python3-pip

2、 安装相关依赖

sudo apt-get install  zlib1g zlib1g-dev 

注意这个包中间是数字1,不是字母l,直接复制命令回车最安全。

sudo apt-get install libxslt1-dev  libglib2.0-0 libsm6 libgl1-mesa-glx libprotobuf-dev gcc

我的Ubuntu系统已经装了很多SLAM的环境,真的很担心依赖冲突,不过经过检验,发现并没有任何冲突。

3、获取RKNN-Toolkit2安装包

从Firefly官网获取RKNN_SDK_V1.3.0工具, 里面会有RKNN-Toolkit2 安装包,我是保存到百度网盘,然后在Windows下载好之后,使用U盘拷贝到Ubuntu系统的。当然也可以直接通过官网下载,但是比较慢。如果你是从网盘下载的,里面的docker文件不需要下载,用不到,大家切记!解压后,你的文件夹内容应该和我一样(rknpu2_1.3.0是我从外面解压进来了,本教程还用不到)。

4、安装Python环境

之前安装的是Ubuntu依赖,这里需要安装numpy、python_opencv等依赖包。这里为了防止Python环境污染,我使用了virtualenv来管理环境,非常方便且安全,安装和使用方法可以查看这篇教程:如何安装和使用virtualenv——python环境的管理大师。这篇教程中创建的环境也就是我这里要使用的环境。

先激活环境,然后pip install -r doc/requirements_cp36-1.3.0.txt,如果出现报错:


上面在安装bfloat16时失败,因为它依赖于numpy,但是它并没有被安装呢,所以我们先手动安装下numpy。

pip install numpy==1.16.6

然后重新执行命令:pip install -r doc/requirements_cp36-1.3.0.txt,上面已经下载好的依赖不会重复下载,所有依赖下载完成后进行统一安装。

5、安装RKNN-Toolkit2

sudo pip3 install packages/rknn_toolkit2-1.3.0_11912b58-cp36-cp36m-linux_x86_64.whl

可以看到在py36环境中,所有依赖都已满足。

6、检验是否安装成功

python3
from rknn.api import RKNN

如果导入 RKNN 模块没有失败,说明安装成功。同时按住 Ctrl+D 退出 Python3。

🍇四、在PC上仿真运行示例

RKNN-Toolkit2 自带了一个模拟器,可以用来仿真模型在 npu 上运行时的行为。就相当于Android Studio自带模拟器一样。那么真机就相当于开发板。

这里我以 yolov5 为例。examples中的yolov5是一个 onnx 模型,用于目标检测,它是在模拟器上运行的。

1、进入目录

cd examples/onnx/yolov5

2、运行程序

python3 test.py

可以看到程序中执行的主要操作有:创建 RKNN 对象;模型配置;加载 onnx 模型;构建 RKNN 模型;导出 RKNN 模型;加载图片并推理,得到推理结果;释放 RKNN 对象


examples 目录中的其他示例的执行方式与 yolov5 相同,这些模型主要用于图像分类、目标检测。

3、模型和推理结果

简单修改下test.py文件,显示并保存结果图片,不得不说,npu推理的检测框置信度高的离谱,不愧是嵌入式神经网络,比原先yolov5高很多。而且运行速度也比官网的cpu版本快很多。


转 换 后 的 模 型 默 认 保 存 路 径 为/rknn-toolkit2-1.x.x/examples/onnx/yolov5/yolov5s.rknn ,推 理 的 图 片 结 果 保 存 在/rknn-toolkit2-1.x.x/examples/onnx/yolov5/result.jpg

参考文献:Rockchip_Quick_Start_RKNN_Toolkit2_CN-1.3.0.pdf

以上是关于香橙派5使用NPU加速yolov5的实时视频推理的主要内容,如果未能解决你的问题,请参考以下文章

rk3588对npu的再探索,yolov5使用rknn模型推理教程

全流程从头在树莓派4B上部署自己训练的yolov5模型(配合NCS2加速)

rk3588使用npu进行模型转换和推理,加速AI应用落地

目标检测英雄联盟能用YOLOv5实时目标检测了 支持onnx推理

基于b/s架构搭建一个支持多路摄像头的实时处理系统 ---- 使用yolo v5 模型基于GPU 多进程处理视频流

Jetson AGX Xavier实现TensorRT加速YOLOv5进行实时检测