Tutorial_Cpp.md 8.78 KB
Newer Older
yaoht's avatar
yaoht committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# YOLOV9检测器

YOLOV9模型是目前工业界使用较多的算法,官方提供了多个不同版本的预训练模型,本份文档主要介绍了如何基于migraphx构建YOLOV9推理,包括:静态推理、动态shape推理,该示例推理流程对YOLOV9其他版本的模型同样适用。

## 模型简介

YOLOv9引入了可编程梯度信息 (PGI) 和广义高效层聚合网络 (GELAN) 等开创性技术,标志着实时目标检测领域的重大进步。
YOLOv9从可逆函数角度理论上分析了现有的CNN架构,基于这种分析,YOLOv9作者还设计了PGI和辅助可逆分支,并取得了优秀的结果;
YOLOv9用到的PGI解决了深度监督只能用于极深的神经网络架构的问题,因此使得新的轻量级架构才更适合落地;
YOLOv9中设计的GELAN仅使用传统卷积,就能实现比基于最先进技术的深度可分卷积设计更高的参数使用率,同时展现出轻量级、快速和精确的巨大优势;
基于所提出的PGI和GELAN,YOLOv9在MS COCO数据集上的性能在所有方面都大大超过了现有的实时目标检测器。

<img src=./yolov9_model.jpg style="zoom:100%;" align=middle>

## 检测器参数设置

samples工程中的Resource/Configuration.xml文件的DetectorYOLOV9节点表示YOLOV9检测器的参数,相关参数主要依据官方推理示例进行设置。各个参数含义如下:

- ModelPathDynamic:yolov9动态模型存放路径
- ModelPathStatic:yolov9静态模型存放路径
- ClassNameFile:coco数据集类别文件存放路径
- UseFP16:是否使用FP16推理模式
- NumberOfClasses:检测类别数量
- ConfidenceThreshold:置信度阈值,用于判断proposal内的物体是否为正样本
- NMSThreshold:非极大值抑制阈值,用于消除重复框

```yaml
<ModelPathDynamic>"../Resource/Models/yolov9-c_dynamic.onnx"</ModelPathDynamic>
<ModelPathStatic>"../Resource/Models/yolov9-c.onnx"</ModelPathStatic>
<ClassNameFile>"../Resource/Models/coco.names"</ClassNameFile>
<UseFP16>0</UseFP16><!--是否使用FP16-->
<NumberOfClasses>80</NumberOfClasses><!--类别数(不包括背景类),COCO:80,VOC:20-->
<ConfidenceThreshold>0.5</ConfidenceThreshold>
<NMSThreshold>0.5</NMSThreshold>
```

## 模型初始化

模型初始化首先通过parse_onnx()函数加载YOLOV9的onnx模型。

- 静态推理:调用parse_onnx函数对静态模型进行解析

```cpp
ErrorCode DetectorYOLOV9::Initialize(InitializationParameterOfDetector initializationParameterOfDetector, bool dynamic)
{
    ...

    // 加载模型
    net = migraphx::parse_onnx(modelPath);
    LOG_INFO(stdout,"succeed to load model: %s\n",GetFileName(modelPath).c_str());
    ...
    
}
```

- 动态shape推理:需要设置模型输入的最大shape,本示例设为{1,3,1024,1024}

```cpp
ErrorCode DetectorYOLOV9::Initialize(InitializationParameterOfDetector initializationParameterOfDetector, bool dynamic)
{
    ...
    
    migraphx::onnx_options onnx_options;
    onnx_options.map_input_dims["images"]={1,3,1024,1024};// 
    net = migraphx::parse_onnx(modelPath, onnx_options);
    
    ...
}
```

## 预处理

在将数据输入到模型之前,需要对图像做如下预处理操作:

- 转换数据排布为NCHW

- 归一化[0.0, 1.0]

- 输入数据的尺寸变换:静态推理将输入大小固定为relInputShape=[1,3,640,640],动态推理对输入图像尺寸变换为设定的动态尺寸。

```cpp
ErrorCode DetectorYOLOV9::Detect(const cv::Mat &srcImage, std::vector<std::size_t> &relInputShape, std::vector<ResultOfDetection> &resultsOfDetection, bool dynamic)
{ 
    ...
  
    // 数据预处理并转换为NCHW格式
    inputSize = cv::Size(relInputShape[3], relInputShape[2]);
    cv::Mat inputBlob;
    cv::dnn::blobFromImage(srcImage,
                    inputBlob,
                    1 / 255.0,
                    inputSize,
                    cv::Scalar(0, 0, 0),
                    true,
                    false);
                    
   ...
}
```

## 推理

利用migraphx推理得到YOLOV9模型的输出。其中静态推理输入数据inputData的shape大小为模型的固定输入尺寸,动态推理则为实际输入的尺寸。

```cpp
ErrorCode DetectorYOLOV9::Detect(const cv::Mat &srcImage, std::vector<std::size_t> &relInputShape, std::vector<ResultOfDetection> &resultsOfDetection, bool dynamic)
{	
	...
	
	// 创建输入数据
    migraphx::parameter_map inputData;
    if(dynamic)
    {
        inputData[inputName]= migraphx::argument{migraphx::shape(inputShape.type(), relInputShape), (float*)inputBlob.data};
    }
    else
    {
        inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};
    }
    

    // 推理
    std::vector<migraphx::argument> inferenceResults = net.eval(inputData);
    
    ...
    
}
```

YOLOV9的MIGraphX推理结果inferenceResults是一个std::vector< migraphx::argument >类型,YOLOV9的onnx模型包含一个输出,所以result等于inferenceResults[0],result包含三个维度:outputShape.lens()[0]=1表示batch信息,outputShape.lens()[1]=84表示对每个proposal的预测信息。同时可将84拆分为4+80,前4个参数用于判断每一个特征点的回归参数,回归参数调整后可以获得预测框,最后80个参数用于判断每一个特征点所包含的物体种类,outputShape.lens()[2]=8400表示生成proposal数量。

另外,如果想要指定输出节点,可以在eval()方法中通过提供outputNames参数来实现:

```cpp
...
// 推理
std::vector<std::string> outputNames = {"output0"}
std::vector<migraphx::argument> inferenceResults = net.eval(inputData, outputNames);
...
```

如果没有指定outputName参数,则默认输出所有输出节点,此时输出节点的顺序与ONNX中输出节点顺序保持一致,可以通过netron查看ONNX文件的输出节点的顺序。

获取上述信息之后进行proposal筛选,筛选过程根据confidenceThreshold阈值进行筛选,proposal的最大置信度得分maxClassScore由80个类别的分数最高值确定,当maxClassScore大于confidenceThreshold阈值,则进一步获取当前proposal的坐标信息和预测物体类别信息,小于该阈值则不做处理。

```cpp
ErrorCode DetectorYOLOV5::Detect(const cv::Mat &srcImage, std::vector<std::size_t> &relInputShape, std::vector<ResultOfDetection> &resultsOfDetection, bool dynamic)
{

	...
	//获取先验框的个数
    int numProposal = outs[0].size[2];
    int numOut = outs[0].size[1];

    //变换输出的维度
    outs[0] = outs[0].reshape(1, numOut);
    cv::transpose(outs[0], outs[0]);

    float *data = (float *)outs[0].data;

    //生成先验框
    std::vector<float> confidences;
    std::vector<cv::Rect> boxes;
    std::vector<int> classIds;
    float ratioh = (float)srcImage.rows / inputSize.height, ratiow = (float)srcImage.cols / inputSize.width;

    //计算x,y,w,h
    for (int n = 0; n < numProposal; n++)
    {
        float *classes_scores = data+4;
        cv::Mat scores(1, classNames.size(), CV_32FC1, classes_scores);

        cv::Point class_id;
        double maxClassScore;
        cv::minMaxLoc(scores, 0, &maxClassScore, 0, &class_id);
        if (maxClassScore > yolov9Parameter.confidenceThreshold)
        {
            confidences.push_back(maxClassScore);
            classIds.push_back(class_id.x);

            float x = data[0];
            float y = data[1];
            float w = data[2];
            float h = data[3];

            int left = int((x - 0.5 * w) * ratiow);
            int top = int((y - 0.5 * h) * ratioh);

            int width = int(w * ratiow);
            int height = int(h * ratioh);

            boxes.push_back(cv::Rect(left, top, width, height));
        }
        data += numOut;
    }
	
	...
}
```

为了消除重叠锚框,输出最终的YOLOV9目标检测结果,执行非极大值抑制对筛选之后的proposal进行处理,最后保存检测结果到resultsOfDetection中。

```cpp
ErrorCode DetectorYOLOV5::Detect(const cv::Mat &srcImage, std::vector<std::size_t> &relInputShape, std::vector<ResultOfDetection> &resultsOfDetection, bool dynamic)
{

	...
    
    // 执行non maximum suppression消除冗余重叠boxes
    std::vector<int> indices;
    cv::dnn::NMSBoxes(boxes, confidences, yolov5Parameter.confidenceThreshold, yolov5Parameter.nmsThreshold, indices);
    for (size_t i = 0; i < indices.size(); ++i)
    {
        int idx = indices[i];
        int classID=classIds[idx];
        string className=classNames[classID];
        float confidence=confidences[idx];
        cv::Rect box = boxes[idx];
		
        //保存每个最终预测proposal的坐标值、置信度分数、类别ID
        ResultOfDetection result;
        result.boundingBox=box;
        result.confidence=confidence;// confidence
        result.classID=classID; // label
        result.className=className;
        resultsOfDetection.push_back(result);
    }

    ...
}
```