# 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);
    }

    ...
}
```
