当前位置:   article > 正文

【树莓派-yolov5/yolov8部署篇】_树莓派 yolo

树莓派 yolo

本篇记录总结一下最近踩的坑和解决方法,以及现存问题。

已经完成:

1.在pc端训练好pt模型,并转成了onnx模型

2.树莓派镜像已安装,摄像头及无线模块测试使用良好。

基本情况:

硬件:树莓派4B+CSI摄像头+无线模块(自带)

镜像:树莓派官方

主要依赖包及版本:

(armv7l架构)

测试代码:

测试opencv:

  1. import cv2
  2. img=cv2.imread("image.jpg")
  3. print(img.shape)
  4. cv2.imshow("output",img)
  5. cv2.waitKey(0)

使用opencv测试onnx模型:

  1. import cv2
  2. import numpy as np
  3. import time
  4. def plot_one_box(x, img, color=None, label=None, line_thickness=None):
  5. """
  6. description: Plots one bounding box on image img,
  7. this function comes from YoLov5 project.
  8. param:
  9. x: a box likes [x1,y1,x2,y2]
  10. img: a opencv image object
  11. color: color to draw rectangle, such as (0,255,0)
  12. label: str
  13. line_thickness: int
  14. return:
  15. no return
  16. """
  17. tl = (
  18. line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1
  19. ) # line/font thickness
  20. color = color or [random.randint(0, 255) for _ in range(3)]
  21. c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
  22. cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
  23. if label:
  24. tf = max(tl - 1, 1) # font thickness
  25. t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
  26. c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
  27. cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA) # filled
  28. cv2.putText(
  29. img,
  30. label,
  31. (c1[0], c1[1] - 2),
  32. 0,
  33. tl / 3,
  34. [225, 255, 255],
  35. thickness=tf,
  36. lineType=cv2.LINE_AA,
  37. )
  38. def post_process_opencv(outputs,model_h,model_w,img_h,img_w,thred_nms,thred_cond):
  39. conf = outputs[:,4].tolist()
  40. c_x = outputs[:,0]/model_w*img_w
  41. c_y = outputs[:,1]/model_h*img_h
  42. w = outputs[:,2]/model_w*img_w
  43. h = outputs[:,3]/model_h*img_h
  44. p_cls = outputs[:,5:]
  45. if len(p_cls.shape)==1:
  46. p_cls = np.expand_dims(p_cls,1)
  47. cls_id = np.argmax(p_cls,axis=1)
  48. p_x1 = np.expand_dims(c_x-w/2,-1)
  49. p_y1 = np.expand_dims(c_y-h/2,-1)
  50. p_x2 = np.expand_dims(c_x+w/2,-1)
  51. p_y2 = np.expand_dims(c_y+h/2,-1)
  52. areas = np.concatenate((p_x1,p_y1,p_x2,p_y2),axis=-1)
  53. print(areas.shape)
  54. areas = areas.tolist()
  55. ids = cv2.dnn.NMSBoxes(areas,conf,thred_cond,thred_nms)
  56. return np.array(areas)[ids],np.array(conf)[ids],cls_id[ids]
  57. def infer_image(net,img0,model_h,model_w,thred_nms=0.4,thred_cond=0.5):
  58. img = img0.copy()
  59. img = cv2.resize(img,[model_h,model_w])
  60. blob = cv2.dnn.blobFromImage(img, scalefactor=1/255.0, swapRB=True)
  61. net.setInput(blob)
  62. outs = net.forward()[0]
  63. print(outs[0])
  64. det_boxes,scores,ids = post_process_opencv(outs,model_h,model_w,img0.shape[0],img0.shape[1],thred_nms,thred_cond)
  65. return det_boxes,scores,ids
  66. if __name__=="__main__":
  67. dic_labels= {0:'0',
  68. 1:'1',
  69. 2:'2',
  70. 3:'3',
  71. 4:'4',
  72. 5:'5',
  73. 6:'6',
  74. 7:'7',
  75. 8:'8',
  76. 9:'9'
  77. }
  78. model_h = 640
  79. model_w = 640
  80. file_model = '/home/pi/best.onnx'
  81. net = cv2.dnn.readNet(file_model)
  82. img0 = cv2.imread('/home/pi/bus.jpg')
  83. t1 = time.time()
  84. det_boxes,scores,ids = infer_image(net,img0,model_h,model_w,thred_nms=0.4,thred_cond=0.5)
  85. t2 = time.time()
  86. print("cost time %.2fs"%(t2-t1))
  87. for box,score,id in zip(det_boxes,scores,ids):
  88. label = '%s:%.2f'%(dic_labels[id],score)
  89. plot_one_box(box.astype(np.int16), img0, color=(255,0,0), label=label, line_thickness=None)
  90. cv2.imshow('img',img0)
  91. cv2.waitKey(0)

一、部署onnx模型

使用onnx模型的好处是针对硬件不同进行了优化加速。且onnx模型作为中间模型可适配性高。

onnx是用yolo中的export.py文件生成,用netron可视化查看模型结构。下图为我转出来的模型以及官方转出来的onnx模型结构。后两张是grid和yolov8,

使用opencv直接调用官方转出的onnx模型结果可以直接跑通,但跑出的图片标记混乱,应该是nms参数和labels需要调整,图片没有对应目标。

使用自己的onnx模型跑出来的结果报错bbox类型出错,暂时还未解决,求路过大佬指点,卡这里很久了一直没定位到错误。

二、直接部署pt模型

onnx部署未果,使用yolo打包进来直接安装依赖部署。

网特别慢老安装错误,手动离线安装whl,pip3 list结果贴在上图基本情况处。

run detect.py结果:仍然报错malloc(): smallbin double linked list corrupted  Backend terminated or disconnected.Fatal Python error: Aborted段错误,改错已经人麻了qwq

三、opencv调用onnx解决方法

寻求外界帮助,在pc端检查错误定位原因基本上是此testonnx的代码可能是适用于更新版本的yolo算法跑出来的结果。我去问了贾志刚老师,贾老师也是建议我试试yolov5的6.0版本。(但是我暂时没有尝试

根据5.0版本导出的onnx的输出格式,写了一版使用opencv调用的代码:

在pc端测试良好,在树莓派测试也通过。

  1. import cv2
  2. import argparse
  3. import numpy as np
  4. import time
  5. class yolov5():
  6. def __init__(self, yolo_type, confThreshold=0.5, nmsThreshold=0.5, objThreshold=0.5):
  7. # 需修改:mylabels.names是类别名
  8. with open('mylabels.names', 'rt') as f:
  9. self.classes = f.read().rstrip('\n').split('\n') ###这个是在coco数据集上训练的模型做opencv部署的,如果你在自己的数据集上训练出的模型做opencv部署,那么需要修改self.classes
  10. self.colors = [np.random.randint(0, 255, size=3).tolist() for _ in range(len(self.classes))]
  11. num_classes = len(self.classes)
  12. # 需修改
  13. anchors = [ [10, 13, 16, 30, 33, 23], [30, 61, 62, 45, 59, 119],[116, 90, 156, 198, 373, 326],]
  14. self.nl = len(anchors)
  15. self.na = len(anchors[0]) // 2
  16. self.no = num_classes + 5
  17. self.grid = [np.zeros(1)] * self.nl
  18. # 需修改,注意和anchors对应
  19. self.stride = np.array([8.,16.,32.])
  20. self.anchor_grid = np.asarray(anchors, dtype=np.float32).reshape(self.nl, 1, -1, 1, 1, 2)
  21. self.net = cv2.dnn.readNet(yolo_type + '.onnx')
  22. self.confThreshold = confThreshold
  23. self.nmsThreshold = nmsThreshold
  24. self.objThreshold = objThreshold
  25. def _make_grid(self, nx=20, ny=20):
  26. xv, yv = np.meshgrid(np.arange(ny), np.arange(nx))
  27. return np.stack((xv, yv), 2).reshape((1, 1, ny, nx, 2)).astype(np.float32)
  28. def postprocess(self, frame, outs):
  29. frameHeight = frame.shape[0]
  30. frameWidth = frame.shape[1]
  31. ratioh, ratiow = frameHeight / 640, frameWidth / 640
  32. # Scan through all the bounding boxes output from the network and keep only the
  33. # ones with high confidence scores. Assign the box's class label as the class with the highest score.
  34. classIds = []
  35. confidences = []
  36. boxes = []
  37. for out in outs:
  38. for detection in out:
  39. scores = detection[5:]
  40. classId = np.argmax(scores)
  41. confidence = scores[classId]
  42. if confidence > self.confThreshold and detection[4] > self.objThreshold:
  43. center_x = int(detection[0] * ratiow)
  44. center_y = int(detection[1] * ratioh)
  45. width = int(detection[2] * ratiow)
  46. height = int(detection[3] * ratioh)
  47. left = int(center_x - width / 2)
  48. top = int(center_y - height / 2)
  49. classIds.append(classId)
  50. confidences.append(float(confidence))
  51. boxes.append([left, top, width, height])
  52. # Perform non maximum suppression to eliminate redundant overlapping boxes with
  53. # lower confidences.
  54. indices = cv2.dnn.NMSBoxes(boxes, confidences, self.confThreshold, self.nmsThreshold)
  55. for i in indices:
  56. # i = i[0]
  57. box = boxes[i]
  58. left = box[0]
  59. top = box[1]
  60. width = box[2]
  61. height = box[3]
  62. frame = self.drawPred(frame, classIds[i], confidences[i], left, top, left + width, top + height)
  63. return frame
  64. def drawPred(self, frame, classId, conf, left, top, right, bottom):
  65. # Draw a bounding box.
  66. cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), thickness=4)
  67. label = '%.2f' % conf
  68. label = '%s:%s' % (self.classes[classId], label)
  69. # Display the label at the top of the bounding box
  70. labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
  71. top = max(top, labelSize[1])
  72. # cv.rectangle(frame, (left, top - round(1.5 * labelSize[1])), (left + round(1.5 * labelSize[0]), top + baseLine), (255,255,255), cv.FILLED)
  73. cv2.putText(frame, label, (left, top - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), thickness=2)
  74. return frame
  75. def detect(self, srcimg):
  76. blob = cv2.dnn.blobFromImage(srcimg, 1 / 255.0, (640, 640), [0, 0, 0], swapRB=True, crop=False)
  77. # Sets the input to the network
  78. self.net.setInput(blob)
  79. # Runs the forward pass to get output of the output layers
  80. outs = self.net.forward(self.net.getUnconnectedOutLayersNames())
  81. z = [] # inference output
  82. for i in range(self.nl):
  83. bs, _, nx, ny, _ = outs[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
  84. # outs[i] = outs[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
  85. # outs[i] = outs[i].reshape(bs, self.na, self.no, ny, nx).transpose(0, 1, 3, 4, 2)
  86. if self.grid[i].shape[2:4] != outs[i].shape[2:4]:
  87. self.grid[i] = self._make_grid(nx, ny)
  88. y = 1 / (1 + np.exp(-outs[i])) ### sigmoid
  89. ###其实只需要对x,y,w,h做sigmoid变换的, 不过全做sigmoid变换对结果影响不大,因为sigmoid是单调递增函数,那么就不影响类别置信度的排序关系,因此不影响后面的NMS
  90. ###不过设断点查看类别置信度,都是负数,看来有必要做sigmoid变换把概率值强行拉回到0到1的区间内
  91. y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * int(self.stride[i])
  92. y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
  93. z.append(y.reshape(bs, -1, self.no))
  94. z = np.concatenate(z, axis=1)
  95. return z
  96. if __name__ == "__main__":
  97. parser = argparse.ArgumentParser()
  98. parser.add_argument("--imgpath", type=str, default='data/images/pic1.jpg', help="image path")
  99. parser.add_argument('--net_type', default='runs/train/exp/weights/1213/best', choices=['yolov5s', 'yolov5l', 'yolov5m', 'yolov5x'])
  100. parser.add_argument('--confThreshold', default=0.5, type=float, help='class confidence')
  101. parser.add_argument('--nmsThreshold', default=0.5, type=float, help='nms iou thresh')
  102. parser.add_argument('--objThreshold', default=0.5, type=float, help='object confidence')
  103. args = parser.parse_args()
  104. yolonet = yolov5(args.net_type, confThreshold=args.confThreshold, nmsThreshold=args.nmsThreshold, objThreshold=args.objThreshold)
  105. srcimg = cv2.imread(args.imgpath)
  106. H, W, _ = srcimg.shape
  107. t1 = time.time()
  108. dets = yolonet.detect(srcimg)
  109. t2 = time.time()
  110. srcimg = yolonet.postprocess(srcimg, dets)
  111. print("cost time %.2fs" % (t2 - t1))
  112. winName = 'Deep learning object detection in OpenCV'
  113. cv2.namedWindow(winName, cv2.WINDOW_NORMAL)
  114. cv2.imshow(winName, srcimg)
  115. cv2.waitKey(0)
  116. cv2.destroyAllWindows()

pc端版本:

pthon 3.9

numpy                         1.19.5

onnx                          1.9.0

onnxruntime                   1.16.3
opencv-python                 4.7.0.72

树莓派端版本:

python 3.7

numpy                   1.21.5             
onnxruntime             1.9.1          
opencv-python           4.5.5.62 

跑出来结果正常,就是树莓派跑的很慢,21秒推理一张照片。

四、继续优化方法

有考虑使用其他方法yolov5-lite轻量化yolov5或者使用yolov8.

将以下代码创建、拷贝到yolov8根目录下。

具体代码my_export.py:

from ultralytics import YOLO
# Load a model
model = YOLO('yolov8n.pt')  # load an official model
# Export the model
model.export(format='onnx', imgsz=[480, 640], opset=12) # 导出一定不要修改这里参数

然后命令行:

python my_export.py

即可导出onnx模型。

测试onnx模型:

输出格式【1,14,8400】

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

闽ICP备14008679号