# YOLOV7检测器

本示例提供了YOLOV7模型的MIGraphX Python推理教程，主要介绍了模型推理过程，根据文档说明执行YOLOV7 Python示例可以得到目标检测结果。

## 模型简介

YOLOV7是2022年最新出现的一种YOLO系列目标检测模型，在论文 [YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object detectors](https://arxiv.org/abs/2207.02696)中提出。

<img src="./YOLOV7_01.png" alt="YOLOV7_02" style="zoom:67%;" />

## 预处理

待检测图像输入模型进行检测之前需要进行预处理，主要包括调整输入的尺寸，归一化等操作。

1. 转换数据排布为NCHW
2. 调整输入数据的尺寸为（1，3，640，640）

```python
def prepare_input(self, image):
    self.img_height, self.img_width = image.shape[:2]
    input_img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
    # 调整图像的尺寸为YOLOV7的输入尺寸
    input_img = cv2.resize(input_img, (self.inputWidth, self.inputHeight))
    # 调整输入数据的维度
    input_img = input_img.transpose(2, 0, 1)
    # 拓展维度，增加batch维度
    input_img = np.expand_dims(input_img, 0)
    input_img = np.ascontiguousarray(input_img)
    input_img = input_img.astype(np.float32)
    # 归一化[0.0, 1.0]
    input_img = input_img / 255

    return input_img
```

其中模型输入的inputWidth、inputHeight通过migraphx对输入模型进行解析获取，代码位置见YOLOV7类初始化位置。

```
class YOLOv7:
    def __init__(self, path, obj_thres=0.5, conf_thres=0.7, iou_thres=0.5):
        self.objectThreshold = obj_thres
        self.confThreshold = conf_thres
        self.nmsThreshold = iou_thres
        
        # 获取模型检测的类别信息
        self.classNames = list(map(lambda x: x.strip(), open('../Resource/Models/coco.names', 'r').readlines()))

        # 解析推理模型
        self.model = migraphx.parse_onnx(path)
      
        # 获取模型输入/输出节点信息
        inputs = self.model.get_inputs()
        outputs = self.model.get_outputs()

        # 获取模型的输入name
        self.inputName = self.model.get_parameter_names()[0]
            
        # 获取模型的输入尺寸
        inputShape = inputShape=inputs[self.inputName].lens()
        self.inputHeight = int(inputShape[2])
        self.inputWidth = int(inputShape[3])
```

## 推理

输入图像预处理完成之后开始进行推理，首先需要利用migraphx进行编译，然后对输入数据进行前向计算得到模型的输出result，在detect函数中调用定义的process_output函数对result进行后处理，得到图片中含有物体的anchor坐标信息、类别置信度、类别ID。

```
def detect(self, image):
    # 输入图片预处理
    input_img = self.prepare_input(image)

    # 模型编译
    self.model.compile(t=migraphx.get_target("gpu"), device_id=0)  # device_id: 设置GPU设备，默认为0号设备
    print("Success to compile")
    # 执行推理
    print("Start to inference")
    start = time.time()
    result = self.model.run({self.inputName: input_img})
    print('net forward time: {:.4f}'.format(time.time() - start))
    # 模型输出结果后处理
    boxes, scores, class_ids = self.process_output(result)

    return boxes, scores, class_ids
```

其中对migraphx推理输出result进行后处理，首先需要对生成的anchor根据是否有物体阈值objectThreshold、置信度阈值confThreshold进行筛选，相关过程定义在process_output函数中。获取筛选后的anchor的坐标信息之后，需要将坐标映射到原图中的位置，相关过程定义在rescale_boxes函数中。

```
def process_output(self, output):
    predictions = np.squeeze(output[0])

    # 筛选包含物体的anchor
    obj_conf = predictions[:, 4]
    predictions = predictions[obj_conf > self.objectThreshold]
    obj_conf = obj_conf[obj_conf > self.objectThreshold]

    # 筛选大于置信度阈值的anchor
    predictions[:, 5:] *= obj_conf[:, np.newaxis]
    scores = np.max(predictions[:, 5:], axis=1)
    valid_scores = scores > self.confThreshold
    predictions = predictions[valid_scores]
    scores = scores[valid_scores]

    # 获取最高置信度分数对应的类别ID
    class_ids = np.argmax(predictions[:, 5:], axis=1)

    # 获取每个物体对应的anchor
    boxes = self.extract_boxes(predictions)

    # 执行非极大值抑制消除冗余anchor
    indices = cv2.dnn.NMSBoxes(boxes.tolist(), scores.tolist(), self.confThreshold, self.nmsThreshold).flatten()

    return boxes[indices], scores[indices], class_ids[indices]
        
def rescale_boxes(self, boxes):
    # 对anchor尺寸进行变换
    input_shape = np.array([self.inputWidth, self.inputHeight, self.inputWidth, self.inputHeight])
    boxes = np.divide(boxes, input_shape, dtype=np.float32)
    boxes *= np.array([self.img_width, self.img_height, self.img_width, self.img_height])
    return boxes
```

根据获取的detect函数输出的boxes、scores、class_ids信息在原图进行结果可视化，包括绘制图像中检测到的物体位置、类别和置信度分数，得到最终的YOLOV7目标检测结果输出。

```
def draw_detections(self, image, boxes, scores, class_ids):
    for box, score, class_id in zip(boxes, scores, class_ids):
        cx, cy, w, h = box.astype(int)

        # 绘制检测物体框
        cv2.rectangle(image, (cx, cy), (cx + w, cy + h), (0, 255, 255), thickness=2)
        label = self.classNames[class_id]
        label = f'{label} {score:.2f}'
        labelSize, baseLine = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1)
        cv2.putText(image, label, (cx, cy - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 255), thickness=2)
    return image
```

