Tutorial_Cpp.md 4.16 KB
Newer Older
liucong's avatar
liucong committed
1
2
3
4
5
6
7
8
9
10
# 分类器



本示例通过ResNet50模型说明如何使用MIGraphX C++ API进行分类模型的推理,包括如何预处理、推理并获取推理结果。



## 模型简介

liucong's avatar
liucong committed
11
本示例使用了经典的ResNet50模型,onnx文件在Resource/Models/文件夹下,模型结构可以通过netron (https://netron.app/) 查看,该模型的输入shape为[1,3,224,224] ,数据排布为NCHW。
liucong's avatar
liucong committed
12
13
14
15
16
17
18



## 预处理

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

liucong's avatar
liucong committed
19
20
21
- 图像格式转换,BGR转换为RGB

- 调整图像大小,并在中心窗口位置裁剪出224x224大小的图像
liucong's avatar
liucong committed
22

liucong's avatar
liucong committed
23
24
25
- normalize操作,对图像减均值再除方差

- 转换数据排布为NCHW
liucong's avatar
liucong committed
26

liucong's avatar
liucong committed
27
  
liucong's avatar
liucong committed
28

liucong's avatar
liucong committed
29
本示例代码主要采用了OpenCV实现了预处理操作:
liucong's avatar
liucong committed
30
31
32
33
34
35
36

```c++
ErrorCode Classifier::Classify(const std::vector<cv::Mat> &srcImages,std::vector<std::vector<ResultOfPrediction>> &predictions)
{
	...
	
    // 预处理
liucong's avatar
liucong committed
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
    std::vector<cv::Mat> image;
    for(int i =0;i<srcImages.size();++i)
    {
        //BGR转换为RGB
        cv::Mat imgRGB;
        cv::cvtColor(srcImages[i], imgRGB, cv::COLOR_BGR2RGB);

        // 调整大小,保持长宽比
        cv::Mat shrink;
        float ratio = (float)256 / min(imgRGB.cols, imgRGB.rows);
        if(imgRGB.rows > imgRGB.cols)
        {
            cv::resize(imgRGB, shrink, cv::Size(256, int(ratio * imgRGB.rows)), 0, 0);
        }
        else
        {
            cv::resize(imgRGB, shrink, cv::Size(int(ratio * imgRGB.cols), 256), 0, 0);
        }

        // 裁剪中心窗口
        int start_x = shrink.cols/2 - 224/2;
        int start_y = shrink.rows/2 - 224/2;
        cv::Rect rect(start_x, start_y, 224, 224);
        cv::Mat images = shrink(rect);
        image.push_back(images);
    }

    // normalize并转换为NCHW
liucong's avatar
liucong committed
65
    cv::Mat inputBlob;
liucong's avatar
liucong committed
66
67
68
69
70
    Image2BlobParams image2BlobParams;
    image2BlobParams.scalefactor=cv::Scalar(1/58.395, 1/57.12, 1/57.375);
    image2BlobParams.mean=cv::Scalar(123.675, 116.28, 103.53);
    image2BlobParams.swapRB=false;
    blobFromImagesWithParams(image,inputBlob,image2BlobParams);
liucong's avatar
liucong committed
71
72
73
74
75
                    
     ...
}
```

liucong's avatar
liucong committed
76
blobFromImagesWithParams()函数支持多个输入图像,首先对输入图像各通道减对应的均值,然后乘以各通道对应的缩放系数,最后转换为NCHW,最终将转换好的数据保存到inputBlob中,然后就可以输入到模型中执行推理了。
liucong's avatar
liucong committed
77
78
79
80
81
82
83
84
85
86
87
88
89
90



## 推理

完成预处理后,就可以执行推理了:

```c++
ErrorCode Classifier::Classify(const std::vector<cv::Mat> &srcImages,std::vector<std::vector<ResultOfPrediction>> &predictions)
{
	...
	
	// 预处理
	
liucong's avatar
liucong committed
91
	// 创建输入数据
liucong's avatar
liucong committed
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
    std::unordered_map<std::string, migraphx::argument> inputData;
    inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};

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

    // 获取输出节点的属性
    migraphx::argument result  = results[0]; // 获取第一个输出节点的数据
   	
   	...
   	
}
```

- inputData表示MIGraphX的输入数据,inputData是一个映射关系,每个输入节点名都会对应一个输入数据,如果有多个输入,则需要为每个输入节点名创建数据,inputName表示输入节点名,migraphx::argument{inputShape, (float*)inputBlob.data}表示该节点名对应的数据,这里是通过前面预处理的数据inputBlob来创建的,第一个参数表示数据的shape,第二个参数表示数据指针。
- net.eval(inputData)返回模型的推理结果,由于这里只有一个输出节点,所以std::vector中只有一个数据,results[0]表示第一个输出节点,这里对应resnetv24_dense0_fwd节点,获取输出数据。

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

liucong's avatar
liucong committed
111
112
113
114
115
116
117
118
119
```
...
// 推理
std::vector<std::string> outputNames = {"resnetv24_dense0_fwd"}
std::vector<migraphx::argument> results = net.eval(inputData, outputNames);
...
```

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