这里主要是OpenCV dnn部署yolov7, 在部署yolov7原版是没有问题的。这里直接训练出来yolov7模型,然后根据作者提供的export.py导出.onnx模型文件。
一,导出onnx文件
导出时后面不要添加 --grid ,否则opencv读取没问题,推理报错。nms我们要后期自己做处理。
命令如下
python export.py
当然了export.py里面的参数要设置好,下面是我设置的参数:
parser.add_argument('--weights', type=str, default='/home/fpga/Desktop/test/yolo/rgb/best.pt', help='weights path')
parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') # height, width
parser.add_argument('--batch-size', type=int, default=1, help='batch size')
parser.add_argument('--dynamic', action='store_true', help='dynamic ONNX axes')
parser.add_argument('--dynamic-batch', action='store_true', help='dynamic batch onnx for tensorrt and onnx-runtime')
parser.add_argument('--grid', action='store_true', help='export Detect() layer grid')
parser.add_argument('--end2end', action='store_true', help='export end2end onnx')
parser.add_argument('--max-wh', type=int, default=None, help='None for tensorrt nms, int value for onnx-runtime nms')
parser.add_argument('--topk-all', type=int, default=100, help='topk objects for every images')
parser.add_argument('--iou-thres', type=float, default=0.45, help='iou threshold for NMS')
parser.add_argument('--conf-thres', type=float, default=0.1, help='conf threshold for NMS')
parser.add_argument('--device', default='cpu', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--simplify', action='store_true', help='simplify onnx model')
parser.add_argument('--include-nms', action='store_true', help='export end2end onnx')
parser.add_argument('--fp16', action='store_true', help='CoreML FP16 half-precision export')
parser.add_argument('--int8', action='store_true', help='CoreML INT8 quantization')
二. 模型部署:
如果采用的是yolov7-e6就需要对训练代码进行修改,主要是ReOrg算子OpenCV不支持,所以需要对其进行修改。ReOrg算子是silce操作,所以存在一定的问题。
修改后就可以按照原版进行转换模型导出。
具体修改如下:
```
class ReOrg(nn.Module):
def __init__(self):
super(ReOrg, self).__init__()
def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2)
#origin code
# return torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)
self.concat=Contract(gain=2)
return self.concat(x)
``` 这里Contract(gain=2)函数是在models/common.py里的,这里和yolov5 focus是类似的。部署修改方法都是一样的。
2.OpenCV版本不一致导致的问题:
OpenCV 4.6要添加如下代码:
std::sort(netOutputImg.begin(), netOutputImg.end(), [](Mat &A, Mat &B) {return A.size > B.size; });//opencv 4.6
OpenCV部署如下部分代码:std::vector<int> classIds;//结果id数组
std::vector<float> confidences;//结果每个id对应置信度数组
std::vector<cv::Rect> boxes;//每个id矩形框
float ratio_h = (float)netInputImg.rows / netHeight;
float ratio_w = (float)netInputImg.cols / netWidth;
int net_width = className.size() + 5; //输出的网络宽度是类别数+5
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;//下一行
}
}
}
}
三,Python版本
Python版本和c++版本类似无非就是c++代码转化位Python代码,export导出onnx是相同的。
更多代码链接:
|