当前位置:   article > 正文

Yolov5笔记--RKNN推理部署源码的粗略理解_rknn yolov5

rknn yolov5

1--基础知识

①Yolov5的输出

格式一般为a × b × c × 85的形式,其中a*b*c表示框的数目,85则涵盖框的位置信息(x,y,w,h)、置信度Pc和80个类别的预测概率c1,...,c80。下图展示了不同版本Yolov5的输出信息:

②阈值过滤锚框

简要介绍两种过滤锚框的方法:

A:利用置信度(box_confidence,即Pc)和预测概率(box_class_probs,即c1,...,c80)计算锚框的得分(box_scores),如果最高的得分高于过滤阈值(threshold),则保留该锚框的信息,反之过滤该锚框。

B:利用非线性激活函数(如Sigmoid)函数等处理置信度(box_confidence,即Pc),将最高的结果与过滤阈值比较,高则保留,低则过滤。(RKNN提供的方式)

③非极大值抑制(NMS)

通过计算交并比(intersection over Union,IOU)进一步过滤锚框。

IOU介绍:IOU即两个矩形框的交集面积与并集面积的比值。

简略介绍Yolov5利用NMS过滤锚框的一种方法:计算IOU的值,若value≤NMS_THRESH,则保留锚框,反之去除。

2--RKNN部署Yolov5源码

项目地址:rknn-toolkit2

  1. import os
  2. import urllib
  3. import traceback
  4. import time
  5. import sys
  6. import numpy as np
  7. import cv2
  8. from rknn.api import RKNN
  9. ONNX_MODEL = 'yolov5s.onnx'
  10. RKNN_MODEL = 'yolov5s.rknn'
  11. IMG_PATH = './bus.jpg'
  12. DATASET = './dataset.txt'
  13. QUANTIZE_ON = True
  14. BOX_THESH = 0.5
  15. NMS_THRESH = 0.6
  16. IMG_SIZE = 640
  17. CLASSES = ("person", "bicycle", "car", "motorbike ", "aeroplane ", "bus ", "train", "truck ", "boat", "traffic light",
  18. "fire hydrant", "stop sign ", "parking meter", "bench", "bird", "cat", "dog ", "horse ", "sheep", "cow", "elephant",
  19. "bear", "zebra ", "giraffe", "backpack", "umbrella", "handbag", "tie", "suitcase", "frisbee", "skis", "snowboard", "sports ball", "kite",
  20. "baseball bat", "baseball glove", "skateboard", "surfboard", "tennis racket", "bottle", "wine glass", "cup", "fork", "knife ",
  21. "spoon", "bowl", "banana", "apple", "sandwich", "orange", "broccoli", "carrot", "hot dog", "pizza ", "donut", "cake", "chair", "sofa",
  22. "pottedplant", "bed", "diningtable", "toilet ", "tvmonitor", "laptop ", "mouse ", "remote ", "keyboard ", "cell phone", "microwave ",
  23. "oven ", "toaster", "sink", "refrigerator ", "book", "clock", "vase", "scissors ", "teddy bear ", "hair drier", "toothbrush ")
  24. def sigmoid(x):
  25. return 1 / (1 + np.exp(-x))
  26. def xywh2xyxy(x):
  27. # Convert [x, y, w, h] to [x1, y1, x2, y2]
  28. y = np.copy(x)
  29. y[:, 0] = x[:, 0] - x[:, 2] / 2 # top left x
  30. y[:, 1] = x[:, 1] - x[:, 3] / 2 # top left y
  31. y[:, 2] = x[:, 0] + x[:, 2] / 2 # bottom right x
  32. y[:, 3] = x[:, 1] + x[:, 3] / 2 # bottom right y
  33. return y
  34. def process(input, mask, anchors):
  35. anchors = [anchors[i] for i in mask]
  36. grid_h, grid_w = map(int, input.shape[0:2])
  37. box_confidence = sigmoid(input[..., 4])
  38. box_confidence = np.expand_dims(box_confidence, axis=-1)
  39. box_class_probs = sigmoid(input[..., 5:])
  40. box_xy = sigmoid(input[..., :2])*2 - 0.5
  41. col = np.tile(np.arange(0, grid_w), grid_w).reshape(-1, grid_w)
  42. row = np.tile(np.arange(0, grid_h).reshape(-1, 1), grid_h)
  43. col = col.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
  44. row = row.reshape(grid_h, grid_w, 1, 1).repeat(3, axis=-2)
  45. grid = np.concatenate((col, row), axis=-1)
  46. box_xy += grid
  47. box_xy *= int(IMG_SIZE/grid_h)
  48. box_wh = pow(sigmoid(input[..., 2:4])*2, 2)
  49. box_wh = box_wh * anchors
  50. box = np.concatenate((box_xy, box_wh), axis=-1)
  51. return box, box_confidence, box_class_probs
  52. def filter_boxes(boxes, box_confidences, box_class_probs):
  53. """Filter boxes with box threshold. It's a bit different with origin yolov5 post process!
  54. # Arguments
  55. boxes: ndarray, boxes of objects.
  56. box_confidences: ndarray, confidences of objects.
  57. box_class_probs: ndarray, class_probs of objects.
  58. # Returns
  59. boxes: ndarray, filtered boxes.
  60. classes: ndarray, classes for boxes.
  61. scores: ndarray, scores for boxes.
  62. """
  63. box_classes = np.argmax(box_class_probs, axis=-1)
  64. box_class_scores = np.max(box_class_probs, axis=-1)
  65. pos = np.where(box_confidences[..., 0] >= BOX_THESH)
  66. boxes = boxes[pos]
  67. classes = box_classes[pos]
  68. scores = box_class_scores[pos]
  69. return boxes, classes, scores
  70. def nms_boxes(boxes, scores):
  71. """Suppress non-maximal boxes.
  72. # Arguments
  73. boxes: ndarray, boxes of objects.
  74. scores: ndarray, scores of objects.
  75. # Returns
  76. keep: ndarray, index of effective boxes.
  77. """
  78. x = boxes[:, 0]
  79. y = boxes[:, 1]
  80. w = boxes[:, 2] - boxes[:, 0]
  81. h = boxes[:, 3] - boxes[:, 1]
  82. areas = w * h
  83. order = scores.argsort()[::-1]
  84. keep = []
  85. while order.size > 0:
  86. i = order[0]
  87. keep.append(i)
  88. xx1 = np.maximum(x[i], x[order[1:]])
  89. yy1 = np.maximum(y[i], y[order[1:]])
  90. xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
  91. yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
  92. w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
  93. h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
  94. inter = w1 * h1
  95. ovr = inter / (areas[i] + areas[order[1:]] - inter)
  96. inds = np.where(ovr <= NMS_THRESH)[0]
  97. order = order[inds + 1]
  98. keep = np.array(keep)
  99. return keep
  100. def yolov5_post_process(input_data):
  101. masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
  102. anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
  103. [59, 119], [116, 90], [156, 198], [373, 326]]
  104. boxes, classes, scores = [], [], []
  105. for input, mask in zip(input_data, masks):
  106. b, c, s = process(input, mask, anchors)
  107. b, c, s = filter_boxes(b, c, s)
  108. boxes.append(b)
  109. classes.append(c)
  110. scores.append(s)
  111. boxes = np.concatenate(boxes)
  112. boxes = xywh2xyxy(boxes)
  113. classes = np.concatenate(classes)
  114. scores = np.concatenate(scores)
  115. nboxes, nclasses, nscores = [], [], []
  116. for c in set(classes):
  117. inds = np.where(classes == c)
  118. b = boxes[inds]
  119. c = classes[inds]
  120. s = scores[inds]
  121. keep = nms_boxes(b, s)
  122. nboxes.append(b[keep])
  123. nclasses.append(c[keep])
  124. nscores.append(s[keep])
  125. if not nclasses and not nscores:
  126. return None, None, None
  127. boxes = np.concatenate(nboxes)
  128. classes = np.concatenate(nclasses)
  129. scores = np.concatenate(nscores)
  130. return boxes, classes, scores
  131. def draw(image, boxes, scores, classes):
  132. """Draw the boxes on the image.
  133. # Argument:
  134. image: original image.
  135. boxes: ndarray, boxes of objects.
  136. classes: ndarray, classes of objects.
  137. scores: ndarray, scores of objects.
  138. all_classes: all classes name.
  139. """
  140. for box, score, cl in zip(boxes, scores, classes):
  141. top, left, right, bottom = box
  142. print('class: {}, score: {}'.format(CLASSES[cl], score))
  143. print('box coordinate left,top,right,down: [{}, {}, {}, {}]'.format(top, left, right, bottom))
  144. top = int(top)
  145. left = int(left)
  146. right = int(right)
  147. bottom = int(bottom)
  148. cv2.rectangle(image, (top, left), (right, bottom), (255, 0, 0), 2)
  149. cv2.putText(image, '{0} {1:.2f}'.format(CLASSES[cl], score),
  150. (top, left - 6),
  151. cv2.FONT_HERSHEY_SIMPLEX,
  152. 0.6, (0, 0, 255), 2)
  153. def letterbox(im, new_shape=(640, 640), color=(0, 0, 0)):
  154. # Resize and pad image while meeting stride-multiple constraints
  155. shape = im.shape[:2] # current shape [height, width]
  156. if isinstance(new_shape, int):
  157. new_shape = (new_shape, new_shape)
  158. # Scale ratio (new / old)
  159. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
  160. # Compute padding
  161. ratio = r, r # width, height ratios
  162. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  163. dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
  164. dw /= 2 # divide padding into 2 sides
  165. dh /= 2
  166. if shape[::-1] != new_unpad: # resize
  167. im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
  168. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
  169. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
  170. im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
  171. return im, ratio, (dw, dh)
  172. if __name__ == '__main__':
  173. # Create RKNN object
  174. rknn = RKNN(verbose=True)
  175. # pre-process config
  176. print('--> Config model')
  177. rknn.config(mean_values=[[0, 0, 0]], std_values=[[255, 255, 255]])
  178. print('done')
  179. # Load ONNX model
  180. print('--> Loading model')
  181. ret = rknn.load_onnx(model=ONNX_MODEL, outputs=['378', '439', '500'])
  182. if ret != 0:
  183. print('Load model failed!')
  184. exit(ret)
  185. print('done')
  186. # Build model
  187. print('--> Building model')
  188. ret = rknn.build(do_quantization=QUANTIZE_ON, dataset=DATASET)
  189. if ret != 0:
  190. print('Build model failed!')
  191. exit(ret)
  192. print('done')
  193. # Export RKNN model
  194. print('--> Export rknn model')
  195. ret = rknn.export_rknn(RKNN_MODEL)
  196. if ret != 0:
  197. print('Export rknn model failed!')
  198. exit(ret)
  199. print('done')
  200. # Init runtime environment
  201. print('--> Init runtime environment')
  202. ret = rknn.init_runtime()
  203. # ret = rknn.init_runtime('rk3566')
  204. if ret != 0:
  205. print('Init runtime environment failed!')
  206. exit(ret)
  207. print('done')
  208. # Set inputs
  209. img = cv2.imread(IMG_PATH)
  210. # img, ratio, (dw, dh) = letterbox(img, new_shape=(IMG_SIZE, IMG_SIZE))
  211. img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  212. img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
  213. # Inference
  214. print('--> Running model')
  215. outputs = rknn.inference(inputs=[img])
  216. np.save('./onnx_yolov5_0.npy', outputs[0])
  217. np.save('./onnx_yolov5_1.npy', outputs[1])
  218. np.save('./onnx_yolov5_2.npy', outputs[2])
  219. print('done')
  220. # post process
  221. input0_data = outputs[0]
  222. input1_data = outputs[1]
  223. input2_data = outputs[2]
  224. input0_data = input0_data.reshape([3, -1]+list(input0_data.shape[-2:]))
  225. input1_data = input1_data.reshape([3, -1]+list(input1_data.shape[-2:]))
  226. input2_data = input2_data.reshape([3, -1]+list(input2_data.shape[-2:]))
  227. input_data = list()
  228. input_data.append(np.transpose(input0_data, (2, 3, 0, 1)))
  229. input_data.append(np.transpose(input1_data, (2, 3, 0, 1)))
  230. input_data.append(np.transpose(input2_data, (2, 3, 0, 1)))
  231. boxes, classes, scores = yolov5_post_process(input_data)
  232. img_1 = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
  233. if boxes is not None:
  234. draw(img_1, boxes, scores, classes)
  235. # show output
  236. # cv2.imshow("post process result", img_1)
  237. # cv2.waitKey(0)
  238. # cv2.destroyAllWindows()
  239. rknn.release()

3--代码粗略解读

①模型及数据:

  1. ONNX_MODEL = 'yolov5s.onnx'
  2. RKNN_MODEL = 'yolov5s.rknn'
  3. IMG_PATH = './bus.jpg'
  4. DATASET = './dataset.txt'

在rknn的开源项目中,提供了一个用于测试的yolov5s.onnx文件。

 ②模型导入

  1. # Load ONNX model
  2. print('--> Loading model')
  3. ret = rknn.load_onnx(model=ONNX_MODEL, outputs=['378', '439', '500'])
  4. if ret != 0:
  5. print('Load model failed!')
  6. exit(ret)
  7. print('done')

这段代码截取导入了onnx模型的三个输出,其编号为'378'、‘439’、‘500’,即上图中红框的三个输出。

③后处理模块

  1. def filter_boxes(boxes, box_confidences, box_class_probs):
  2. """Filter boxes with box threshold. It's a bit different with origin yolov5 post process!
  3. # Arguments
  4. boxes: ndarray, boxes of objects.
  5. box_confidences: ndarray, confidences of objects.
  6. box_class_probs: ndarray, class_probs of objects.
  7. # Returns
  8. boxes: ndarray, filtered boxes.
  9. classes: ndarray, classes for boxes.
  10. scores: ndarray, scores for boxes.
  11. """
  12. box_classes = np.argmax(box_class_probs, axis=-1)
  13. box_class_scores = np.max(box_class_probs, axis=-1)
  14. pos = np.where(box_confidences[..., 0] >= BOX_THESH)
  15. boxes = boxes[pos]
  16. classes = box_classes[pos]
  17. scores = box_class_scores[pos]
  18. return boxes, classes, scores
  19. def nms_boxes(boxes, scores):
  20. """Suppress non-maximal boxes.
  21. # Arguments
  22. boxes: ndarray, boxes of objects.
  23. scores: ndarray, scores of objects.
  24. # Returns
  25. keep: ndarray, index of effective boxes.
  26. """
  27. x = boxes[:, 0]
  28. y = boxes[:, 1]
  29. w = boxes[:, 2] - boxes[:, 0]
  30. h = boxes[:, 3] - boxes[:, 1]
  31. areas = w * h
  32. order = scores.argsort()[::-1]
  33. keep = []
  34. while order.size > 0:
  35. i = order[0]
  36. keep.append(i)
  37. xx1 = np.maximum(x[i], x[order[1:]])
  38. yy1 = np.maximum(y[i], y[order[1:]])
  39. xx2 = np.minimum(x[i] + w[i], x[order[1:]] + w[order[1:]])
  40. yy2 = np.minimum(y[i] + h[i], y[order[1:]] + h[order[1:]])
  41. w1 = np.maximum(0.0, xx2 - xx1 + 0.00001)
  42. h1 = np.maximum(0.0, yy2 - yy1 + 0.00001)
  43. inter = w1 * h1
  44. ovr = inter / (areas[i] + areas[order[1:]] - inter)
  45. inds = np.where(ovr <= NMS_THRESH)[0]
  46. order = order[inds + 1]
  47. keep = np.array(keep)
  48. return keep
  49. def yolov5_post_process(input_data):
  50. masks = [[0, 1, 2], [3, 4, 5], [6, 7, 8]]
  51. anchors = [[10, 13], [16, 30], [33, 23], [30, 61], [62, 45],
  52. [59, 119], [116, 90], [156, 198], [373, 326]]
  53. boxes, classes, scores = [], [], []
  54. for input, mask in zip(input_data, masks):
  55. b, c, s = process(input, mask, anchors)
  56. b, c, s = filter_boxes(b, c, s)
  57. boxes.append(b)
  58. classes.append(c)
  59. scores.append(s)
  60. boxes = np.concatenate(boxes)
  61. boxes = xywh2xyxy(boxes)
  62. classes = np.concatenate(classes)
  63. scores = np.concatenate(scores)
  64. nboxes, nclasses, nscores = [], [], []
  65. for c in set(classes):
  66. inds = np.where(classes == c)
  67. b = boxes[inds]
  68. c = classes[inds]
  69. s = scores[inds]
  70. keep = nms_boxes(b, s)
  71. nboxes.append(b[keep])
  72. nclasses.append(c[keep])
  73. nscores.append(s[keep])
  74. if not nclasses and not nscores:
  75. return None, None, None
  76. boxes = np.concatenate(nboxes)
  77. classes = np.concatenate(nclasses)
  78. scores = np.concatenate(nscores)
  79. return boxes, classes, scores

主要是阈值过滤(filter_boxes)和NMS(nms_boxes)两部分。

4--利用自导出yolov5.onnx模型

①使用yolov5提供的export.py函数导出yolov5.onnx模型

python export.py --weights yolov5s.pt --img-size 640 --include onnx --train

②再使用onnxsim简化导出的yolov5.onnx模型

onnxsim安装和使用:onnx-simplifier

③要完全使用rknn提供的部署转换代码,需要根据简化后的onnx模型,选取合适层的输出,以替代以下代码中的‘378’,‘439’和‘500’,如下图onnx例子中的'326',‘346’,‘366’.

ret = rknn.load_onnx(model=ONNX_MODEL, outputs=['378', '439', '500'])

上图中onnx模型的下载地址: code:q2dl

未完待续!

5--运行rknn的demo时出现的问题

①错误:E build: ImportError: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.29' not found

解决方法:

  1. #解决方法
  2. wget http://ftp.gnu.org/gnu/glibc/glibc-2.29.tar.gz (建议手动下载)
  3. tar -zxvf glibc-2.29.tar.gz
  4. cd glibc-2.29
  5. sudo apt-get install bison
  6. sudo apt-get install
  7. sudo apt-get install gcc build-essential
  8. unset LD_LIBRARY_PATH
  9. mkdir build
  10. cd build
  11. ../configure --prefix=/usr/local/glibc-2.29
  12. make -j8
  13. sudo make install
  14. cd /lib/x86_64-linux-gnu
  15. sudo ln -sf /usr/local/glibc-2.29/lib/libm-2.29.so libm.so.6 # 建立软连接

未完待续!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/579084
推荐阅读
相关标签
  

闽ICP备14008679号