Bob's Blog

Web开发、测试框架、自动化平台、APP开发、机器学习等

返回上页首页

pytorch转为onnx并调用时遇到的问题汇总



目前用yolov5已经生成了可用的pytorch的模型文件,后缀名为pt,加载和预测用的代码较多,打算试一下转换为onnx的类型用opencv加载,减少代码量也减少第三方包的安装。

发现没有想象中简便,有时间的时候逐步尝试并记录一下遇到的问题。

首先在用yolov5本身的export.py进行转换,然后用opencv加载onnx

import cv2

net = cv2.dnn.readNetFromONNX("./yolov5.onnx")
net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)

加载时遇到类似getLayerInstance或findCommonShape的错误

# [ERROR:0@26.902] global /Users/xperience/actions-runner/_work/opencv-python/opencv-python/opencv/modules/dnn/src/onnx/onnx_importer.cpp (1021) handleNode DNN/ONNX: ERROR during processing node with 3 inputs and 1 outputs: [Range]:(onnx_node!Range_296) from domain='ai.onnx'
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# cv2.error: OpenCV(4.6.0) /Users/xperience/actions-runner/_work/opencv-python/opencv-python/opencv/modules/dnn/src/onnx/onnx_importer.cpp:1040: error: (-2:Unspecified error) in function 'handleNode'
# > Node [Range@ai.onnx]:(onnx_node!Range_296) parse error: OpenCV(4.6.0) /Users/xperience/actions-runner/_work/opencv-python/opencv-python/opencv/modules/dnn/src/layer_internals.hpp:110: error: (-2:Unspecified error) Can't create layer "onnx_node!Range_296" of type "Range" in function 'getLayerInstance'
# [ERROR:0@0.089] global onnx_importer.cpp:1034 handleNode DNN/ONNX: ERROR during processing node with 2 inputs and 1 outputs: [Mul]:(onnx_node!Mul_401) from domain='ai.onnx'
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# cv2.error: OpenCV(4.9.0) /Users/xperience/GHA-OpenCV-Python2/_work/opencv-python/opencv-python/opencv/modules/dnn/src/onnx/onnx_importer.cpp:1053: error: (-2:Unspecified error) in function 'handleNode'
# > Node [Mul@ai.onnx]:(onnx_node!Mul_401) parse error: OpenCV(4.9.0) /Users/xperience/GHA-OpenCV-Python2/_work/opencv-python/opencv-python/opencv/modules/dnn/src/layers/nary_eltwise_layers.cpp:144: error: (-215:Assertion failed) shape[i] == 1 || outShape[i] == 1 in function 'findCommonShape'

此时重新指定export的参数,比如:

python export.py --weights ./yolov5.pt --img 640 --simplify --optimize --opset 12 --include onnx

然后确认一下opencv-python的版本,我试过4.6.0.66无法加载,但换做4.9.0.80则可以。

不过加载时成功了,在推理时却报了其他错误

[ERROR:0@0.168] global net_impl.cpp:1169 getLayerShapesRecursively OPENCV/DNN: [Slice]:(onnx_node_output_0!1276): getMemoryShapes() throws exception. inputs=1 outputs=1/1 blobs=0
[ERROR:0@0.168] global net_impl.cpp:1172 getLayerShapesRecursively     input[0] = [ 1834 3456 ]
[ERROR:0@0.168] global net_impl.cpp:1176 getLayerShapesRecursively     output[0] = [ 1834 3456 ]
[ERROR:0@0.168] global net_impl.cpp:1182 getLayerShapesRecursively Exception message: OpenCV(4.9.0) /Users/xperience/GHA-OpenCV-Python2/_work/opencv-python/opencv-python/opencv/modules/dnn/src/layers/slice_layer.cpp:259: error: (-215:Assertion failed) sliceRanges_rw[i].size() <= inpShape.size() in function 'getMemoryShapes'

Traceback (most recent call last):
  File "ultralytics_yolov5/load_onnx.py", line 24, in <module>
    preds = net.forward()
cv2.error: OpenCV(4.9.0) /Users/xperience/GHA-OpenCV-Python2/_work/opencv-python/opencv-python/opencv/modules/dnn/src/layers/slice_layer.cpp:259: error: (-215:Assertion failed) sliceRanges_rw[i].size() <= inpShape.size() in function 'getMemoryShapes'

未明确原因,这部分之后来更新


updated 2024.3.1, 目前能用export.py和detect.py来导出onnx和用onnx来识别图片,没报错,但是没有预测的结果。。。虽然用同样的pt文件,也是使用detect.py则有预测结果。。

python export.py --weights ./yolov5.pt --img 640 --simplify --optimize --include onnx  # export
python detect.py --weights ./yolov5.onnx --source ./screenshot.png --dnn  # detect

 


updated 2024.7.16, 用各种关键词搜索啊,比如'yolov5 onnx 摆脱深度学习框架', 还真找到了可用的。这里记录一下,发现虽然转换了onnx后能正常加载,也能推理给出检测结果,但是实际结果和区域与之前直接用pt模型有些许差异,考虑是不是因为图片预处理这里导致的影响,之后有时间看了后再更新。

首先切换到yolov5官方,拉取最新代码,执行export (实际这里的500会转换到512,但因为训练时指定的500,这里也用了500,实际是512,而且在检测时给与图片预处理时往往默认是640,会产生错误,还需要将640的地方改成512)

python export.py --weights ./models/elements.pt --img 500 --simplify --optimize --opset 12 --include onnx

然后改一改别人的脚本,发现如下的代码能用,不需要用到pytorch了,但代码有点乱,后续要整理一下,并看下预测结果差异的问题,是不是跟图片预处理有关。

先是onnx_detect.py:

import cv2
import onnxruntime
import argparse
import numpy as np

from utils import letterbox, scale_coords


class Detector:
    """
    检测类
    """

    def __init__(self, opt):
        super(Detector, self).__init__()
        self.img_size = opt.img_size
        self.threshold = opt.conf_thres
        self.iou_thres = opt.iou_thres
        self.stride = 1
        self.weights = opt.weights
        self.init_model()

    def init_model(self):
        """
        模型初始化这一步比较固定写法
        :return:
        """
        sess = onnxruntime.InferenceSession(self.weights)  # 加载模型权重
        self.input_name = sess.get_inputs()[0].name  # 获得输入节点
        output_names = []
        for i in range(len(sess.get_outputs())):
            print("output node:", sess.get_outputs()[i].name)
            output_names.append(sess.get_outputs()[i].name)  # 所有的输出节点
        print(output_names)
        self.output_name = sess.get_outputs()[0].name  # 获得输出节点的名称
        print(f"input name {self.input_name}-----output_name{self.output_name}")
        input_shape = sess.get_inputs()[0].shape  # 输入节点形状
        print("input_shape:", input_shape)
        self.m = sess

    def preprocess(self, img):
        """
        图片预处理过程
        :param img:
        :return:
        """
        img0 = img.copy()
        img = letterbox(img, new_shape=self.img_size)[0]  # 图片预处理
        img = img[:, :, ::-1].transpose(2, 0, 1)
        img = np.ascontiguousarray(img).astype(np.float32)
        img /= 255.0
        img = np.expand_dims(img, axis=0)
        assert len(img.shape) == 4
        return img0, img

    def detect(self, im):
        """

        :param img:
        :return:
        """
        img0, img = self.preprocess(im)
        pred = self.m.run(None, {self.input_name: img})[0]  # 执行推理
        pred = pred.astype(np.float32)
        pred = np.squeeze(pred, axis=0)
        boxes = []
        classIds = []
        confidences = []
        for detection in pred:
            scores = detection[5:]
            classID = np.argmax(scores)
            confidence = scores[classID] * detection[4]  # 置信度为类别的概率和目标框概率值得乘积

            if confidence > self.threshold:
                box = detection[0:4]
                (centerX, centerY, width, height) = box.astype("int")
                x = int(centerX - (width / 2))
                y = int(centerY - (height / 2))
                boxes.append([x, y, int(width), int(height)])
                classIds.append(classID)
                confidences.append(float(confidence))
        idxs = cv2.dnn.NMSBoxes(boxes, confidences, self.threshold, self.iou_thres)  # 执行nms算法
        pred_boxes = []
        pred_confes = []
        pred_classes = []
        if len(idxs) > 0:
            for i in idxs.flatten():
                confidence = confidences[i]
                if confidence >= self.threshold:
                    pred_boxes.append(boxes[i])
                    pred_confes.append(confidence)
                    pred_classes.append(classIds[i])
        return im, pred_boxes, pred_confes, pred_classes


def main(opt):
    det = Detector(opt)
    image = cv2.imread(opt.img)
    shape = (det.img_size, det.img_size)
    names = ['text_field', 'button', 'checkbox', 'dropdown', 'link', 'radio_button', 'switch']

    img, pred_boxes, pred_confes, pred_classes = det.detect(image)
    if len(pred_boxes) > 0:
        for i, _ in enumerate(pred_boxes):
            box = pred_boxes[i]
            left, top, width, height = box[0], box[1], box[2], box[3]
            box = (left, top, left + width, top + height)
            box = np.squeeze(
                scale_coords(shape, np.expand_dims(box, axis=0).astype("float"), img.shape[:2]).round(), axis=0).astype(
                "int")  # 进行坐标还原
            x0, y0, x1, y1 = box[0], box[1], box[2], box[3]
            # 执行画图函数
            cv2.rectangle(image, (x0, y0), (x1, y1), (0, 0, 255), thickness=2)
            cv2.putText(image, '{0}--{1:.2f}'.format(names[pred_classes[i]], pred_confes[i]), (x0, y0 - 2),
                        cv2.FONT_HERSHEY_SIMPLEX, 1, (235, 123, 76), thickness=2)
    cv2.imshow("detector", image)
    while True:
        user_input = cv2.waitKey(3000)
        if user_input == ord('q'):
            break
    cv2.destroyAllWindows()


#
if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--weights', nargs='+', type=str, default='./models/elements.onnx', help='onnx path(s)')    # 留意这里,改下默认模型地址
    parser.add_argument('--img', type=str, default='./img/screenshot.png', help='source')  # file/folder, 0 for webcam  # 留意这里,改下默认图片地址
    parser.add_argument('--img-size', type=int, default=512, help='inference size (pixels)')  # 留意这里,将默认的图片大小改为512以作对应
    parser.add_argument('--conf-thres', type=float, default=0.33, help='object confidence threshold')    # 置信度和下面的iou都可以按需要更改
    parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS')
    parser.add_argument('--line-thickness', default=1, type=int, help='bounding box thickness (pixels)')
    parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
    parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
    opt = parser.parse_args()
    main(opt)

然后是utils.py:

import numpy as np
import cv2


def letterbox(img, new_shape=(640, 640), auto=False, scaleFill=False, scaleUp=True):
    """
    python的信封图片缩放
    :param img: 原图
    :param new_shape: 缩放后的图片
    :param color: 填充的颜色
    :param auto: 是否为自动
    :param scaleFill: 填充
    :param scaleUp: 向上填充
    :return:
    """
    shape = img.shape[:2]  # current shape[height,width]
    if isinstance(new_shape, int):
        new_shape = (new_shape, new_shape)
    r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    if not scaleUp:
        r = min(r, 1.0)  # 确保不超过1
    ration = r, r  # width,height 缩放比例
    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]
    if auto:
        dw, dh = np.mod(dw, 64), np.mod(dh, 64)
    elif scaleFill:
        dw, dh = 0.0, 0.0
        new_unpad = (new_shape[1], new_shape[0])
        ration = new_shape[1] / shape[1], new_shape[0] / shape[0]
    # 均分处理
    dw /= 2
    dh /= 2
    if shape[::-1] != new_unpad:
        img = cv2.resize(img, 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))
    img = cv2.copyMakeBorder(img, top, bottom, left, right, cv2.BORDER_CONSTANT, value=(114, 114, 114))  # 添加边界
    return img, ration, (dw, dh)


def clip_coords(boxes, img_shape):
    """
    图片的边界处理
    :param boxes: 检测框
    :param img_shape: 图片的尺寸
    :return:
    """
    boxes[:, 0].clip(0, img_shape[1])  # x1
    boxes[:, 1].clip(0, img_shape[0])  # y1
    boxes[:, 2].clip(0, img_shape[1])  # x2
    boxes[:, 3].clip(0, img_shape[0])  # x2


def scale_coords(img1_shape, coords, img0_shape, ratio_pad=None):
    """
    坐标还原
    :param img1_shape: 旧图像的尺寸
    :param coords: 坐标
    :param img0_shape:新图像的尺寸
    :param ratio_pad: 填充率
    :return:
    """
    if ratio_pad is None:  # 从img0_shape中计算
        gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1])  # gain=old/new
        pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2
    else:
        gain = ratio_pad[0][0]
        pad = ratio_pad[1]

    coords[:, [0, 2]] -= pad[0]  # x padding
    coords[:, [1, 3]] -= pad[1]  # y padding
    coords[:, :4] /= gain
    clip_coords(coords, img0_shape)
    return coords

用于加载从pt转到onnx的模型也仅需要如下的包,少多了,requirements.txt如下:

coloredlogs==15.0.1
flatbuffers==24.3.25
humanfriendly==10.0
mpmath==1.3.0
numpy==1.24.4
onnxruntime==1.18.1
opencv-python==4.10.0.84
packaging==24.1
protobuf==5.27.2
sympy==1.13.0

 

对于paddle ocr的模型转换为onnx,可以先安装包

pip install paddle2onnx
pip install onnxruntime

然后开始转

paddle2onnx --model_dir ./paddle/det/en/en_PP-OCRv3_det_infer/ --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file ./paddle/det/det_model.onnx --opset_version 11 --enable_onnx_checker True
paddle2onnx --model_dir ./paddle/rec/en/en_PP-OCRv3_rec_infer/ --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file ./paddle/rec/rec_model.onnx --opset_version 11 --enable_onnx_checker True
paddle2onnx --model_dir ./paddle/cls/ch_ppocr_mobile_v2.0_cls_infer/ --model_filename inference.pdmodel --params_filename inference.pdiparams --save_file ./paddle/cls/cls_model.onnx --opset_version 11 --enable_onnx_checker True

 

下一篇:  记录桌面端应用的自动化的一些坑和解决方式
上一篇:  Blazor Hybrid在mac上的调试

共有0条评论

添加评论

暂无评论