使用C++ OpenCV加载YOLOV7模型报错:error:(-215:Assertion failed)

文章讨论了在使用OpenCV的DNN模块加载ONNX格式的YOLOV7模型时遇到的识别错误,特别是与模型的后处理操作有关的问题。通过设置export为True并使用onnxsim简化模型可以解决部分问题。同时,文章提到了在C++中实现YOLOV7的后处理代码来补充OpenCV加载模型后的缺失步骤,并且指出了OpenCV在处理ONNX模型时对动态输入的不支持。
摘要由CSDN通过智能技术生成

问题分析:      

        使用OpenCV中的DNN模块可以加载我们转化好的ONNX模型,但是由于模型的一些操作可能导致DNN模块中的ONNX加载模块识别不了,从而导致报错,这里会报 start (int)shape.size() && <=  end (int)shape.size()的错误,在YOLOV7中如果不使用作者的export.py的情况下,直接对我们的模型转换的话会存在一个后处理操作,如下代码所示。

    def forward(self, x):
        # x = x.copy()  # for profiling
        z = []  # inference output
        self.training |= self.export
        for i in range(self.nl):
            x[i] = self.m[i](x[i])  # conv
            bs, _, ny, nx = x[i].shape  # x(bs,255,20,20) to x(bs,3,20,20,85)
            x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()

            if not self.training:  # inference
                if self.grid[i].shape[2:4] != x[i].shape[2:4]:
                    self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
                y = x[i].sigmoid()
                if not torch.onnx.is_in_onnx_export():
                    y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i]  # xy
                    y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]  # wh  给他变身
                else:
                    xy, wh, conf = y.split((2, 2, self.nc + 1), 4)  # y.tensor_split((2, 4, 5), 4)  # torch 1.8.0
                    xy = xy * (2. * self.stride[i]) + (self.stride[i] * (self.grid[i] - 0.5))  # new xy
                    wh = wh ** 2 * (4 * self.anchor_grid[i].data)  # new wh
                    y = torch.cat((xy, wh, conf), 4)
                z.append(y.view(bs, -1, self.no))

        可以看到如果我们不把Export置为True的话,那么将会对YOLOV7中的三个特征图求每个特征图点对应到原图的信息,如果使用pytorch进行推理确实方便很多,并且不需要在C++上写这些处理,但是我不设置Export的值,将导出的ONNX模型放到OpenCV C++上做推理就会报标题错误。

        使用的OpenCV版本是4.5.5,ONNX是1.13,这里我猜测可能是这些后处理操作所影响的,于是参考官方的export.py将export设置为True,转换后在OpenCV上推理,但是给我报了一个input和output的错误,找了大量资料,最终发现如果使用onnxsim进行简化错误将会消失。

import onnxsim
onnx_model = onnx.load_model(r"./yolov7_4.onnx")
print('\nStarting to simplify ONNX...')
onnx_model, check = onnxsim.simplify(onnx_model)
assert check, 'assert check failed'

# print(onnx.helper.printable_graph(onnx_model.graph))  # print a human readable model
model = onnx_model
model.ir_version = 8
onnx.save(onnx_model,r"./yolov7_4.onnx")

报如上错误可以使用这段代码进行简化ONNX。其中包括了改变onnx输出风格的代码。

C++编写后处理代码

        将模型加载完成后,我们的模型进行推理得到的结果并没有做如上处理,所以需要用C++把如上的处理补上,代码如下。参考了作者:https://github.com/UNeedCryDear/yolov7-opencv-dnn-cpp

for (int stride = 0; stride < strideSize; stride++) {    //stride
		float* pdata = (float*)netOutputImg[stride].data;
		int grid_x = (int)(netWidth / netStride[stride]);
		int grid_y = (int)(netHeight / netStride[stride]);
		for (int anchor = 0; anchor < 3; anchor++) {	//anchors
			const float anchor_w = netAnchors[stride][anchor * 2];
			const float anchor_h = netAnchors[stride][anchor * 2 + 1];
			for (int i = 0; i < grid_y; i++) {
				for (int j = 0; j < grid_x; j++) {
					float box_score = sigmoid_x(pdata[4]); ;//获取每一行的box框中含有某个物体的概率
					if (box_score >= boxThreshold) {
						cv::Mat scores(1, className.size(), CV_32FC1, pdata + 5);
						Point classIdPoint;
						double max_class_socre;
						minMaxLoc(scores, 0, &max_class_socre, 0, &classIdPoint);
						max_class_socre = sigmoid_x(max_class_socre);
						if (max_class_socre >= classThreshold) {
							float x = (sigmoid_x(pdata[0]) * 2.f - 0.5f + j) * netStride[stride];  //x
							float y = (sigmoid_x(pdata[1]) * 2.f - 0.5f + i) * netStride[stride];   //y
							float w = powf(sigmoid_x(pdata[2]) * 2.f, 2.f) * anchor_w;   //w
							float h = powf(sigmoid_x(pdata[3]) * 2.f, 2.f) * anchor_h;  //h
							int left = (int)(x - 0.5 * w) * ratio_w + 0.5;
							int top = (int)(y - 0.5 * h) * ratio_h + 0.5;
							classIds.push_back(classIdPoint.x);
							confidences.push_back(max_class_socre * box_score);
							boxes.push_back(Rect(left, top, int(w * ratio_w), int(h * ratio_h)));
						}
					}
					pdata += net_width;//下一行
				}

作者采用了一种串行处理方法,由于网络未进行后处理输出的结果默认为([1,3,20,20,85],[1,3,40,40,85],[1,3,80,80,85]),形成数据指针,每次指向每个特征图像素殿点的信息,85个进行迭代,那么就可以将之前没做的处理做完。在进行推理就可以了,NMS采用OpenCV自带的进行推导。

总结

        在报各种OpenCV的错误时,如果差不到资料,就使用nerton模块查看一下自己的ONNX模型导出是否出现问题。如果发现没问题,在Python中的CV2的DNN模块测一下模型。如果可以运行但是C++的OpenCV加载报错,大概率是网络出现一些识别不了的操作了。还有一件事,OpenCV做ONNX推理的时候是不支持动态输入的,一定不要动态导出。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值