# RetinaFace人脸检测器

RetinaFace是目前被广泛使用的一种人脸检测器模型，本示例主要说明了如何在MIGraphX中使用RetinaFace人脸检测器。

## 模型简介

RetinaFace是一个经典的人脸检测模型(https://arxiv.org/abs/1905.00641)，采用了SSD架构。

<img src="./Image/RetinaFace_01.png" style="zoom:60%;" align=center>

本示例采用了如下的开源实现：https://github.com/biubug6/Pytorch_Retinaface，作者提供了restnet50 和mobilenet0.25两个预训练模型，本示例使用了mobilenet0.25预训练模型，将mobilenet0.25预训练模型下载下来后，保存到Pytorch_Retinaface工程的weights目录。

## 模型转换

通过下面的步骤可以将mobilenet0.25预训练转换成onnx文件：

- 修改data/config.py：将cfg_mnet中的'pretrain': True,修改为'pretrain': False,

- 执行如下命令就可以将weights目录下的mobilenet0.25_Final.pth模型转换为onnx文件了

   ```
   # 进入Pytorch_Retinaface工程根目录
   cd <path_to_Pytorch_Retinaface>
   
   # 转换模型
   python convert_to_onnx.py
   ```

   注意：如果需要修改模型的输入大小，可以修改args.long_side参数，默认为640x640。

   模型转换成功后，会在当前目录生成FaceDetector.onnx文件，利用该模型就可以使用MIGraphX进行推理了，本示例在samples工程中的Python/RetinaFace目录中提供了已经修改好的代码，在该目录下执行python convert_to_onnx.py可以直接生成onnx文件，注意运行前需要安装依赖，你可以在非DCU环境下执行模型转换，如果需要在DCU环境下安装依赖，依赖安装方法参考下文中运行示例一节中的说明。

## 推理

Pytorch_Retinaface工程提供了原始Pytorch版本的推理测试代码detect.py，我们只需要将其中使用Pytorch推理的部分转换为MIGraphX推理就可以了，samples工程中的Python/RetinaFace/detect.py文件为已经转换好的推理代码，下面我们看一下是如何转换的：

- 将加载模型部分修改为migraphx的方式加载

   ```
   # 加载模型
   model = migraphx.parse_onnx("./FaceDetector.onnx")
   ```

- 模型加载成功后，需要通过model.compile进行编译，可以通过device_id设置使用哪一块设备

   ```
   model.compile(t=migraphx.get_target("gpu"),device_id=0)
   ```

- 编译成功后，就可以输入图像进行推理了，由于本示例使用的onnx模型的输入大小是640x640，所以对于输入图像需要先resize到640x640

   ```
   # resize到onnx模型输入大小
   image_path = "./curve/test.jpg"
   img_raw = cv2.imread(image_path, cv2.IMREAD_COLOR)
   img_raw = cv2.resize(img_raw, (640,640))
   ```

- 预处理部分跟作者的代码保持一致即可，这部分不需要修改

- 下面是最关键的一步，将pytorch推理net(img)转换为MIGraphX推理migraphx_run(model,args.cpu,img)，其中migraphx_run实现如下：

   ```
   def migraphx_run(model,cpu,data_tensor):
       # 将输入的tensor数据转换为numpy
       if cpu:
           data_numpy=data_tensor.cpu().numpy()
           device = torch.device("cpu")
       else:
           data_numpy=data_tensor.detach().cpu().numpy()
           device = torch.device("cuda")
       
       img_data = np.zeros(data_numpy.shape).astype("float32")
       for i in range(data_numpy.shape[0]):
           img_data[i, :, :, :] = data_numpy[i, :, :, :]
   
       # 执行推理
       result = model.run({model.get_parameter_names()[0]: img_data})
   
       # 将结果转换为tensor
       result0=torch.from_numpy(np.array(result[0], copy=False)).to(device)
       result1=torch.from_numpy(np.array(result[1], copy=False)).to(device)
       result2=torch.from_numpy(np.array(result[2], copy=False)).to(device)
   
       return (result0,result1,result2)
   ```

   首先需要将tensor数据转换为numpy，转换好的数据保存在img_data中，然后通过{model.get_parameter_names()[0]: img_data}创建MIGraphX的输入数据，并使用model.run执行推理，result为推理返回的结果，然后通过torch.from_numpy的方式转换为tensor类型并返回，为了保持与Pytorch推理结果的格式一致，转换的时候需要注意输出结果的顺序，MIGraphX的输出结果顺序与onnx中保持一致，可以通过netron (https://netron.app/) 查看：

   <img src="./Image/RetinaFace_02.png" style="zoom:80%;" align=center>

   所以第一个输出结果对应pytorch结果中的loc,第二个对应conf, 第三个对应landms，所以返回的结果是(result0,result1,result2)。

- 推理执行成功后，需要执行后处理才能得到最终的检测结果，由于我们模型推理输出的格式与原始的Pytorch模型输出是一致的，所以后处理可以直接使用原来的，不需要修改。
