当前位置:   article > 正文

目标检测算法再升级!YOLOv8保姆级教程一键体验_yolo export --model yolov8n.pt --format onnx --ops

yolo export --model yolov8n.pt --format onnx --opset 11 --simplify true

YOLO作为一种基于图像全局信息进行预测的目标检测系统,始终保持着极高的迭代更新率,从YOLOv5到YOLOv8,本次升级主要包括结构算法、命令行界面、Python API等。具体到YOLOv8,它可以在大型数据集上进行训练,并且能够在各种硬件平台上运行;YOLOv8还有一个关键特性是它的可扩展性,由于其被设计成一个框架,支持所有以前YOLO的版本,使得在不同版本之间切换和比较它们的性能变得容易。

本次内容《目标检测算法再升级!YOLOv8保姆级教程一键体验》,地平线开发者社区优秀开发者林松将会一步步引导大家在地平线旭日®X3派(下文简称旭日X3派)成功部署YOLOv8目标检测模型,并附上精度速度初探!相关问题欢迎大家注册加入地平线开发者社区交流讨论,配置文件及代码详见地平线开发者社区。

环境配置

本文所使用的脚本和代码目录结构和说明如下:

  1. ├── project # X3 工作目录
  2. │ ├── calib_f32 # 量化校准数据集
  3. │ ├── coco128 # 量化校准和待检测图片
  4. │ ├── config.yaml # onnx 转 bin 模型配置
  5. │ ├── modules.py -> ../ultralytics/ultralytics/nn/modules.py # 软链接 YOLOv8 后处理文件
  6. │ ├── onnxruntime-infer.py # pc 端读取 onnx 并检测
  7. │ ├── requirements.txt # python 依赖包
  8. │ ├── step1_export_onnx.py # YOLOv8 ONNX 导出
  9. │ ├── step2_make_calib.py # 制作量化校准数据集
  10. │ ├── step3_convert_bin.sh # onnx 转 bin 脚本
  11. │ ├── step4_inference.py # X3 推理代码
  12. │ ├── yolo-comparison-plots.png # YOLO 模型对比图
  13. │ ├── yolov8n.onnx # 转换好的 onnx
  14. │ ├── yolov8n.pt # YOLOv8 pytorch 权重
  15. │ └── yolov8n_horizon.bin # 转换好的 bin 模型
  16. ├── ultralytics # YOLOv8 仓库
  17. │ ├── CITATION.cff
  18. │ ├── CONTRIBUTING.md
  19. │ ├── LICENSE
  20. │ ├── MANIFEST.in
  21. │ ├── README.md
  22. │ ├── README.zh-CN.md
  23. │ ├── docker
  24. │ ├── docs
  25. │ ├── examples
  26. │ ├── mkdocs.yml
  27. │ ├── requirements.txt
  28. │ ├── setup.cfg
  29. │ ├── setup.py
  30. │ ├── tests
  31. │ └── ultralytics

YOLOv8 PyTorch环境配置

请在开发机中导出ONNX模型,安装PyTorch ONNX等依赖,再安装YOLOv8:

  1. cd project
  2. python3 -m pip install -r requirements.txt
  3. cd ../ultralytics
  4. python3 setup.py install
  5. cd ../project

模型导出

修改YOLOv8后处理代码

将YOLOv8中ultralytics/ultralytics/nn/modules.py软链接到 project/modules.py,方便定位到修改的代码位置,其中中有两个trick:

  1. # *************************************************************************************************************** #
  2. # *************************************************************************************************************** #
  3. # 原仓库的版本带后处理 注释掉!!!!
  4. # def forward(self, x):
  5. # shape = x[0].shape # BCHW
  6. # for i in range(self.nl):
  7. # x[i] = torch.cat((self.cv2[i](x[i]), self.cv3[i](x[i])), 1)
  8. # if self.training:
  9. # return x
  10. # elif self.dynamic or self.shape != shape:
  11. # self.anchors, self.strides = (x.transpose(0, 1) for x in make_anchors(x, self.stride, 0.5))
  12. # self.shape = shape
  13. #
  14. # box, cls = torch.cat([xi.view(shape[0], self.no, -1) for xi in x], 2).split((self.reg_max * 4, self.nc), 1)
  15. # dbox = dist2bbox(self.dfl(box), self.anchors.unsqueeze(0), xywh=True, dim=1) * self.strides
  16. # y = torch.cat((dbox, cls.sigmoid()), 1)
  17. # return y if self.export else (y, x)
  18. # *************************************************************************************************************** #
  19. # *************************************************************************************************************** #
  20. # X3 部署使用的版本!!!!
  21. def forward(self, x):
  22. res = []
  23. for i in range(self.nl):
  24. bboxes = self.cv2[i](x[i]).permute(0, 2, 3, 1)
  25. scores = self.cv3[i](x[i]).permute(0, 2, 3, 1)
  26. res.append(bboxes)
  27. res.append(scores)
  28. # 返回 tuple 不会导出报错
  29. return tuple(res)
  30. # *************************************************************************************************************** #
  31. # *************************************************************************************************************** #
  • 导出Transpose(permute)节点
  1. bboxes = self.cv2[i](x[i]).permute(0, 2, 3, 1)
  2. scores = self.cv3[i](x[i]).permute(0, 2, 3, 1)

由于旭日X3派支持的模型格式为NHWC,但是PyTorch训练的模型是NCHW,因此我们导出的ONNX模型在转换bin时会在网络头和尾插入Transpose结点,而这个 Transpose节点的顺序是[0, 3, 1, 2],可以发现与我们插入的[0, 2, 3, 1]节点正好可以抵消,相当与少了个Transpose节点,这样是可以提升模型推理速度,避免不必要的计算的。

  • 将输出处理成 tuple

这步主要是为了让YOLOv8能够顺利导出不报错,如果使用list则会报tulpe的错误。

使用YOLOv8导出的ONNX

执行 step1_export_onnx.py,可以下载官方的权重并导出 ONNX。

  1. # 导入 YOLOv8
  2. from ultralytics import YOLO
  3. # 载入预训练权重
  4. model = YOLO("yolov8n.pt")
  5. # 指定 opset=11 并且使用 onnx-sim 简化 ONNX
  6. success = model.export(format="onnx", opset=11, simplify=True)
python3 step1_export_onnx.py

注意:旭日X3派支持ONNX opset = 10/11,其他版本会无法通过模型工具链编译。

使用ONNXRuntime推理导出ONNX

为了避免导出的ONNX出错,最好使用ONNXRuntime来验证一下模型的正确性。

  1. def letterbox(im, new_shape=(640, 640), color=114):
  2. # Resize and pad image while meeting stride-multiple constraints
  3. shape = im.shape[:2] # current shape [height, width]
  4. if isinstance(new_shape, int):
  5. new_shape = (new_shape, new_shape)
  6. # Scale ratio (new / old)
  7. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
  8. # Compute padding
  9. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  10. dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
  11. dw /= 2 # divide padding into 2 sides
  12. dh /= 2
  13. if shape[::-1] != new_unpad: # resize
  14. im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
  15. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
  16. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
  17. im = cv2.copyMakeBorder(im, top, bottom, left, right,
  18. cv2.BORDER_CONSTANT,
  19. value=(color, color, color)) # add border
  20. return im, 1 / r, (dw, dh)
  21. def ratioresize(im, new_shape=(640, 640), color=114):
  22. shape = im.shape[:2] # current shape [height, width]
  23. if isinstance(new_shape, int):
  24. new_shape = (new_shape, new_shape)
  25. new_h, new_w = new_shape
  26. padded_img = np.ones((new_h, new_w, 3), dtype=np.uint8) * color
  27. # Scale ratio (new / old)
  28. r = min(new_h / shape[0], new_w / shape[1])
  29. # Compute padding
  30. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
  31. if shape[::-1] != new_unpad:
  32. im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
  33. padded_img[: new_unpad[1], : new_unpad[0]] = im
  34. padded_img = np.ascontiguousarray(padded_img)
  35. return padded_img, 1 / r, (0, 0)

本文使用的两种图像缩放方法,letterbox是YOLOv8中训练时启用的方法,由于需要四周padding并且后处理要根据padding的数值还原,较为麻烦。使用 ratioresize方法,在保持图像的长宽比例的同时,使用右下角padding避免了后处理计算偏移量。

  1. if __name__ == '__main__':
  2. images_path = Path('./coco128')
  3. model_path = Path('./yolov8n.onnx')
  4. score_thres = 0.4
  5. iou_thres = 0.65
  6. num_classes = 80
  7. try:
  8. session = onnxruntime.InferenceSession(str(model_path), providers=['CPUExecutionProvider'])
  9. model_h, model_w = session.get_inputs()[0].shape[2:]
  10. except Exception as e:
  11. print(f'Load model error.\n{e}')
  12. exit()
  13. else:
  14. try:
  15. # 预热10次推理
  16. for _ in range(10):
  17. session.run(None, {'images': np.random.randn(1, 3, model_h, model_w).astype(np.float32)})
  18. except Exception as e:
  19. print(f'Warm up model error.\n{e}')
  20. cv2.namedWindow("results", cv2.WINDOW_AUTOSIZE)
  21. for img_path in images_path.iterdir():
  22. image = cv2.imread(str(img_path))
  23. t0 = time.perf_counter()
  24. ## yolov8 training letterbox
  25. # resized, ratio, (dw, dh) = letterbox(image, (model_h, model_w))
  26. resized, ratio, (dw, dh) = ratioresize(image, (model_h, model_w))
  27. buffer = blob(resized)
  28. t1 = time.perf_counter()
  29. outputs = session.run(None, {'images': buffer})
  30. outputs = [o[0] for o in outputs]
  31. t2 = time.perf_counter()
  32. results = postprocess(
  33. outputs, score_thres, iou_thres,
  34. image.shape[0], image.shape[1], dh, dw, ratio, ratio,
  35. 16, num_classes)
  36. results = nms(*results)
  37. t3 = time.perf_counter()
  38. for (x0, y0, x1, y1, score, label) in results:
  39. x0, y0, x1, y1 = map(int, [x0, y0, x1, y1])
  40. cls_id = int(label)
  41. cls = CLASSES[cls_id]
  42. color = COLORS[cls]
  43. cv2.rectangle(image, [x0, y0], [x1, y1], color, 1)
  44. cv2.putText(image,
  45. f'{cls}:{score:.3f}', (x0, y0 - 2),
  46. cv2.FONT_HERSHEY_SIMPLEX,
  47. 0.325, [0, 0, 225],
  48. thickness=1)
  49. t4 = time.perf_counter()
  50. cv2.imshow('results', image)

上述是推理主函数,为了保证模型打印的耗时稳定,前期启动了10次推理预热,建议端侧部署时一定记得预热一下。可以看到结果正确,后处理逻辑也是对的。

生成量化校准数据集

执行step2_make_calib.py,可以读取coco128目录下随机50张图片,制作校准数据集。

  1. img = cv2.imread(str(i))
  2. img = letterbox(img)[0]
  3. img = blob(img[:, :, ::-1]) # bgr -> rgb
  4. print(img.shape)
  5. img.astype(np.float32).tofile(str(save / (i.stem + '.rgbchw')))

制作校准数据集主要是读图-> resize -> uint8转float -> numpy.tofile。在calib_f32目录下会生成50个rgbchw结尾的文件:

python3 step2_make_calib.py

使用地平线提供的Docker编译bin模型

将docker_openexplorer_centos_7_xj3_v2.4.2.tar.gz下载到本地开发机,并使用以下命令开启docker:

  1. cd ../
  2. wget -c ftp://vrftp.horizon.ai/Open_Explorer_gcc_9.3.0/2.4.2/docker_openexplorer_centos_7_xj3_v2.4.2.tar.gz
  3. docker load -i docker_openexplorer_centos_7_xj3_v2.4.2.tar.gz
  4. docker run -it --name horizonX3 -v ${PWD}/project:/open_explorer/project openexplorer/ai_toolchain_centos_7_xj3:v2.4.2
  5. docker exec -it horizonX3 /bin/bash

进入容器后,执行:

  1. cd project
  2. bash step3_convert_bin.sh

编译成功后会打印如下日志:

  1. /model.22/cv3.2/cv3.2.2/Conv BPU id(0) HzSQuantizedConv 0.998216 67.505043
  2. 2023-01-31 21:17:24,261 INFO [Tue Jan 31 21:17:24 2023] End to Horizon NN Model Convert.
  3. 2023-01-31 21:17:24,315 INFO start convert to *.bin file....
  4. 2023-01-31 21:17:24,345 INFO ONNX model output num : 6
  5. 2023-01-31 21:17:24,346 INFO ############# model deps info #############
  6. 2023-01-31 21:17:24,346 INFO hb_mapper version : 1.9.9
  7. 2023-01-31 21:17:24,346 INFO hbdk version : 3.37.2
  8. 2023-01-31 21:17:24,346 INFO hbdk runtime version: 3.14.14
  9. 2023-01-31 21:17:24,346 INFO horizon_nn version : 0.14.0
  10. 2023-01-31 21:17:24,346 INFO ############# model_parameters info #############
  11. 2023-01-31 21:17:24,346 INFO onnx_model : /open_explorer/workspace/yolov8/yolov8n.onnx
  12. 2023-01-31 21:17:24,346 INFO BPU march : bernoulli2
  13. 2023-01-31 21:17:24,346 INFO layer_out_dump : False
  14. 2023-01-31 21:17:24,346 INFO log_level : DEBUG
  15. 2023-01-31 21:17:24,346 INFO working dir : /open_explorer/workspace/yolov8/model_output
  16. 2023-01-31 21:17:24,346 INFO output_model_file_prefix: yolov8n_horizon
  17. 2023-01-31 21:17:24,347 INFO ############# input_parameters info #############
  18. 2023-01-31 21:17:24,347 INFO ------------------------------------------
  19. 2023-01-31 21:17:24,347 INFO ---------input info : images ---------
  20. 2023-01-31 21:17:24,347 INFO input_name : images
  21. 2023-01-31 21:17:24,347 INFO input_type_rt : nv12
  22. 2023-01-31 21:17:24,347 INFO input_space&range : regular
  23. 2023-01-31 21:17:24,347 INFO input_layout_rt : NHWC
  24. 2023-01-31 21:17:24,347 INFO input_type_train : rgb
  25. 2023-01-31 21:17:24,347 INFO input_layout_train : NCHW
  26. 2023-01-31 21:17:24,347 INFO norm_type : data_scale
  27. 2023-01-31 21:17:24,347 INFO input_shape : 1x3x640x640
  28. 2023-01-31 21:17:24,347 INFO input_batch : 1
  29. 2023-01-31 21:17:24,347 INFO scale_value : 0.003921568627451,
  30. 2023-01-31 21:17:24,347 INFO cal_data_dir : /open_explorer/calib_f32
  31. 2023-01-31 21:17:24,347 INFO ---------input info : images end -------
  32. 2023-01-31 21:17:24,347 INFO ------------------------------------------
  33. 2023-01-31 21:17:24,347 INFO ############# calibration_parameters info #############
  34. 2023-01-31 21:17:24,348 INFO preprocess_on : False
  35. 2023-01-31 21:17:24,348 INFO calibration_type: : max
  36. 2023-01-31 21:17:24,348 INFO cal_data_type : float32
  37. 2023-01-31 21:17:24,348 INFO max_percentile : 0.99999
  38. 2023-01-31 21:17:24,348 INFO per_channel : True
  39. 2023-01-31 21:17:24,348 INFO ############# compiler_parameters info #############
  40. 2023-01-31 21:17:24,348 INFO hbdk_pass_through_params: --core-num 2 --fast --O3
  41. 2023-01-31 21:17:24,348 INFO input-source : {'images': 'pyramid', '_default_value': 'ddr'}
  42. 2023-01-31 21:17:24,354 INFO Convert to runtime bin file sucessfully!
  43. 2023-01-31 21:17:24,354 INFO End Model Convert
  44. /model.22/cv3.2/cv3.2.2/Conv BPU id(0) HzSQuantizedConv 0.998216 67.505043

上文的0.998216表示量化后的模型最后一层输出的余弦相似度,越接近1代表模型精度保持的越高(PS.model_output/yolov8n_horizon.bin是转换完的bin模型)。

推理测试

上板测试

将project文件夹打包上传到旭日X3派中,可以使用ssh或者U盘复制到旭日X3派工作目录中。假设保存到入/home/sunrise/project,推理前处理需要将输入转换到 nv12:

  1. def bgr2nv12_opencv(image):
  2. height, width = image.shape[:2]
  3. area = height * width
  4. yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420).reshape((area * 3 // 2,))
  5. y = yuv420p[:area]
  6. uv_planar = yuv420p[area:].reshape((2, area // 4))
  7. uv_packed = uv_planar.transpose((1, 0)).reshape((area // 2,))
  8. nv12 = np.zeros_like(yuv420p)
  9. nv12[:area] = y
  10. nv12[area:] = uv_packed
  11. return nv12

使用终端执行:

  1. cd /home/sunrise/project
  2. sudo python3 -m pip install opencv-python # 安装 X3 推理依赖
  3. mv model_output/yolov8n_horizon.bin ./
  4. sudo python3 step4_inference.py

会看到图片检测并绘制的结果,还会打印推理的耗时情况:

得出结果:
前处理:30.4ms
推理:168.5ms
后处理:66ms
画图:0.8ms
全程耗时:265.9ms

本文转自地平线开发者社区
原作者:tripleMu
原链接:https://developer.horizon.ai:8005/forumDetail/118363850511908357
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小舞很执着/article/detail/864090
推荐阅读
相关标签
  

闽ICP备14008679号