1# 使用MindSpore Lite实现图像分类(ArkTS)
2
3## 场景说明
4
5开发者可以使用[@ohos.ai.mindSporeLite](../../reference/apis-mindspore-lite-kit/js-apis-mindSporeLite.md),在UI代码中集成MindSpore Lite能力,快速部署AI算法,进行AI模型推理,实现图像分类的应用。
6
7图像分类可实现对图像中物体的识别,在医学影像分析、自动驾驶、电子商务、人脸识别等有广泛的应用。
8
9## 基本概念
10
11在进行开发前,请先了解以下概念。
12
13**张量**:它与数组和矩阵非常相似,是MindSpore Lite网络运算中的基本数据结构。
14
15**Float16推理模式**:  Float16又称半精度,它使用16比特表示一个数。Float16推理模式表示推理的时候用半精度进行推理。
16
17## 接口说明
18
19这里给出MindSpore Lite推理的通用开发流程中涉及的一些接口,具体请见下列表格。更多接口及详细内容,请见[@ohos.ai.mindSporeLite (推理能力)](../../reference/apis-mindspore-lite-kit/js-apis-mindSporeLite.md)。
20
21| 接口名                                                       | 描述             |
22| ------------------------------------------------------------ | ---------------- |
23| loadModelFromFile(model: string, context?: Context): Promise<Model> | 从路径加载模型。 |
24| getInputs(): MSTensor[]                                      | 获取模型的输入。 |
25| predict(inputs: MSTensor[]): Promise<MSTensor[]>       | 推理模型。       |
26| getData(): ArrayBuffer                                       | 获取张量的数据。 |
27| setData(inputArray: ArrayBuffer): void                       | 设置张量的数据。 |
28
29## 开发流程
30
311. 选择图像分类模型。
322. 在端侧使用MindSpore Lite推理模型,实现对选择的图片进行分类。
33
34## 环境准备
35
36安装DevEco Studio,要求版本 >= 4.1,并更新SDK到API 11或以上。
37
38## 开发步骤
39
40本文以对相册的一张图片进行推理为例,提供使用MindSpore Lite实现图像分类的开发指导。
41
42### 选择模型
43
44本示例程序中使用的图像分类模型文件为[mobilenetv2.ms](https://download.mindspore.cn/model_zoo/official/lite/mobilenetv2_openimage_lite/1.5/mobilenetv2.ms),放置在entry/src/main/resources/rawfile工程目录下。
45
46如果开发者有其他图像分类的预训练模型,请参考[MindSpore Lite 模型转换](mindspore-lite-converter-guidelines.md)介绍,将原始模型转换成.ms格式。
47
48### 编写代码
49
50#### 图像输入和预处理
51
521. 此处以获取相册图片为例,调用[@ohos.file.picker](../../reference/apis-core-file-kit/js-apis-file-picker.md) 实现相册图片文件的选择。
53
54   ```ts
55   import { photoAccessHelper } from '@kit.MediaLibraryKit';
56   import { BusinessError } from '@kit.BasicServicesKit';
57
58   let uris: Array<string> = [];
59
60   // 创建图片文件选择实例
61   let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
62
63   // 设置选择媒体文件类型为IMAGE,设置选择媒体文件的最大数目
64   photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
65   photoSelectOptions.maxSelectNumber = 1;
66
67   // 创建图库选择器实例,调用select()接口拉起图库界面进行文件选择。文件选择成功后,返回photoSelectResult结果集。
68   let photoPicker = new photoAccessHelper.PhotoViewPicker();
69   photoPicker.select(photoSelectOptions, async (
70     err: BusinessError, photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
71     if (err) {
72       console.error('MS_LITE_ERR: PhotoViewPicker.select failed with err: ' + JSON.stringify(err));
73       return;
74     }
75     console.info('MS_LITE_LOG: PhotoViewPicker.select successfully, ' +
76       'photoSelectResult uri: ' + JSON.stringify(photoSelectResult));
77     uris = photoSelectResult.photoUris;
78     console.info('MS_LITE_LOG: uri: ' + uris);
79   })
80   ```
81
822. 根据模型的输入尺寸,调用[@ohos.multimedia.image](../../reference/apis-image-kit/js-apis-image.md) (实现图片处理)、[@ohos.file.fs](../../reference/apis-core-file-kit/js-apis-file-fs.md) (实现基础文件操作) API对选择图片进行裁剪、获取图片buffer数据,并进行标准化处理。
83
84   ```ts
85   import { image } from '@kit.ImageKit';
86   import { fileIo } from '@kit.CoreFileKit';
87
88   let modelInputHeight: number = 224;
89   let modelInputWidth: number = 224;
90
91   // 使用fileIo.openSync接口,通过uri打开这个文件得到fd
92   let file = fileIo.openSync(this.uris[0], fileIo.OpenMode.READ_ONLY);
93   console.info('MS_LITE_LOG: file fd: ' + file.fd);
94
95   // 通过fd使用fileIo.readSync接口读取这个文件内的数据
96   let inputBuffer = new ArrayBuffer(4096000);
97   let readLen = fileIo.readSync(file.fd, inputBuffer);
98   console.info('MS_LITE_LOG: readSync data to file succeed and inputBuffer size is:' + readLen);
99
100   // 通过PixelMap预处理
101   let imageSource = image.createImageSource(file.fd);
102   imageSource.createPixelMap().then((pixelMap) => {
103     pixelMap.getImageInfo().then((info) => {
104       console.info('MS_LITE_LOG: info.width = ' + info.size.width);
105       console.info('MS_LITE_LOG: info.height = ' + info.size.height);
106       // 根据模型输入的尺寸,将图片裁剪为对应的size,获取图片buffer数据readBuffer
107       pixelMap.scale(256.0 / info.size.width, 256.0 / info.size.height).then(() => {
108         pixelMap.crop(
109           { x: 16, y: 16, size: { height: modelInputHeight, width: modelInputWidth } }
110         ).then(async () => {
111           let info = await pixelMap.getImageInfo();
112           console.info('MS_LITE_LOG: crop info.width = ' + info.size.width);
113           console.info('MS_LITE_LOG: crop info.height = ' + info.size.height);
114           // 需要创建的像素buffer大小
115           let readBuffer = new ArrayBuffer(modelInputHeight * modelInputWidth * 4);
116           await pixelMap.readPixelsToBuffer(readBuffer);
117           console.info('MS_LITE_LOG: Succeeded in reading image pixel data, buffer: ' +
118           readBuffer.byteLength);
119           // 处理readBuffer,转换成float32格式,并进行标准化处理
120           const imageArr = new Uint8Array(
121             readBuffer.slice(0, modelInputHeight * modelInputWidth * 4));
122           console.info('MS_LITE_LOG: imageArr length: ' + imageArr.length);
123           let means = [0.485, 0.456, 0.406];
124           let stds = [0.229, 0.224, 0.225];
125           let float32View = new Float32Array(modelInputHeight * modelInputWidth * 3);
126           let index = 0;
127           for (let i = 0; i < imageArr.length; i++) {
128             if ((i + 1) % 4 == 0) {
129               float32View[index] = (imageArr[i - 3] / 255.0 - means[0]) / stds[0]; // B
130               float32View[index+1] = (imageArr[i - 2] / 255.0 - means[1]) / stds[1]; // G
131               float32View[index+2] = (imageArr[i - 1] / 255.0 - means[2]) / stds[2]; // R
132               index += 3;
133             }
134           }
135           console.info('MS_LITE_LOG: float32View length: ' + float32View.length);
136           let printStr = 'float32View data:';
137           for (let i = 0; i < 20; i++) {
138             printStr += ' ' + float32View[i];
139           }
140           console.info('MS_LITE_LOG: float32View data: ' + printStr);
141         })
142       })
143     });
144   });
145   ```
146
147#### 编写推理代码
148
1491. 工程默认设备定义的能力集可能不包含MindSporeLite。需在DevEco Studio工程的entry/src/main目录下,手动创建syscap.json文件,内容如下:
150
151   ```json
152   {
153     "devices": {
154       "general": [
155         // 需跟module.json5中deviceTypes保持一致。
156         "default"
157       ]
158     },
159     "development": {
160       "addedSysCaps": [
161         "SystemCapability.AI.MindSporeLite"
162       ]
163     }
164   }
165   ```
166
1672. 调用[@ohos.ai.mindSporeLite](../../reference/apis-mindspore-lite-kit/js-apis-mindSporeLite.md)实现端侧推理。具体开发过程及细节如下:
168
169   1. 创建上下文,设置线程数、设备类型等参数。
170   2. 加载模型。本文从内存加载模型。
171   3. 加载数据。模型执行之前需要先获取输入,再向输入的张量中填充数据。
172   4. 执行推理。使用predict接口进行模型推理。
173
174   ```ts
175   // model.ets
176   import { mindSporeLite } from '@kit.MindSporeLiteKit'
177
178   export default async function modelPredict(
179     modelBuffer: ArrayBuffer, inputsBuffer: ArrayBuffer[]): Promise<mindSporeLite.MSTensor[]> {
180
181     // 1.创建上下文,设置线程数、设备类型等参数。
182     let context: mindSporeLite.Context = {};
183     context.target = ['cpu'];
184     context.cpu = {}
185     context.cpu.threadNum = 2;
186     context.cpu.threadAffinityMode = 1;
187     context.cpu.precisionMode = 'enforce_fp32';
188
189     // 2.从内存加载模型。
190     let msLiteModel: mindSporeLite.Model = await mindSporeLite.loadModelFromBuffer(modelBuffer, context);
191
192     // 3.设置输入数据。
193     let modelInputs: mindSporeLite.MSTensor[] = msLiteModel.getInputs();
194     for (let i = 0; i < inputsBuffer.length; i++) {
195       let inputBuffer = inputsBuffer[i];
196       if (inputBuffer != null) {
197         modelInputs[i].setData(inputBuffer as ArrayBuffer);
198       }
199     }
200
201     // 4.执行推理。
202     console.info('=========MS_LITE_LOG: MS_LITE predict start=====');
203     let modelOutputs: mindSporeLite.MSTensor[] = await msLiteModel.predict(modelInputs);
204     return modelOutputs;
205   }
206   ```
207
208#### 进行推理并输出结果
209
210加载模型文件,调用推理函数,对相册选择的图片进行推理,并对推理结果进行处理。
211
212```ts
213import modelPredict from './model';
214import { resourceManager } from '@kit.LocalizationKit'
215
216let modelName: string = 'mobilenetv2.ms';
217let max: number = 0;
218let maxIndex: number = 0;
219let maxArray: Array<number> = [];
220let maxIndexArray: Array<number> = [];
221
222// 完成图像输入和预处理后的buffer数据保存在float32View,具体可见上文图像输入和预处理中float32View的定义和处理。
223let inputs: ArrayBuffer[] = [float32View.buffer];
224let resMgr: resourceManager.ResourceManager = getContext().getApplicationContext().resourceManager;
225resMgr.getRawFileContent(modelName).then(modelBuffer => {
226  // predict
227  modelPredict(modelBuffer.buffer.slice(0), inputs).then(outputs => {
228    console.info('=========MS_LITE_LOG: MS_LITE predict success=====');
229    // 结果打印
230    for (let i = 0; i < outputs.length; i++) {
231      let out = new Float32Array(outputs[i].getData());
232      let printStr = outputs[i].name + ':';
233      for (let j = 0; j < out.length; j++) {
234        printStr += out[j].toString() + ',';
235      }
236      console.info('MS_LITE_LOG: ' + printStr);
237      // 取分类占比的最大值
238      this.max = 0;
239      this.maxIndex = 0;
240      this.maxArray = [];
241      this.maxIndexArray = [];
242      let newArray = out.filter(value => value !== max)
243      for (let n = 0; n < 5; n++) {
244        max = out[0];
245        maxIndex = 0;
246        for (let m = 0; m < newArray.length; m++) {
247          if (newArray[m] > max) {
248            max = newArray[m];
249            maxIndex = m;
250          }
251        }
252        maxArray.push(Math.round(max * 10000))
253        maxIndexArray.push(maxIndex)
254        // filter函数,数组过滤函数
255        newArray = newArray.filter(value => value !== max)
256      }
257      console.info('MS_LITE_LOG: max:' + maxArray);
258      console.info('MS_LITE_LOG: maxIndex:' + maxIndexArray);
259    }
260    console.info('=========MS_LITE_LOG END=========');
261  })
262})
263```
264
265### 调测验证
266
2671. 在DevEco Studio中连接设备,点击Run entry,编译Hap,有如下显示:
268
269   ```shell
270   Launching com.samples.mindsporelitearktsdemo
271   $ hdc shell aa force-stop com.samples.mindsporelitearktsdemo
272   $ hdc shell mkdir data/local/tmp/xxx
273   $ hdc file send C:\Users\xxx\MindSporeLiteArkTSDemo\entry\build\default\outputs\default\entry-default-signed.hap "data/local/tmp/xxx"
274   $ hdc shell bm install -p data/local/tmp/xxx
275   $ hdc shell rm -rf data/local/tmp/xxx
276   $ hdc shell aa start -a EntryAbility -b com.samples.mindsporelitearktsdemo
277   ```
278
2792. 在设备屏幕点击photo按钮,选择图片,点击确定。设备屏幕显示所选图片的分类结果,在日志打印结果中,过滤关键字”MS_LITE“,可得到如下结果:
280
281   ```verilog
282   08-06 03:24:33.743   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: PhotoViewPicker.select successfully, photoSelectResult uri: {"photoUris":["file://media/Photo/13/IMG_1501955351_012/plant.jpg"]}
283   08-06 03:24:33.795   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: readSync data to file succeed and inputBuffer size is:32824
284   08-06 03:24:34.147   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: crop info.width = 224
285   08-06 03:24:34.147   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: crop info.height = 224
286   08-06 03:24:34.160   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: Succeeded in reading image pixel data, buffer: 200704
287   08-06 03:24:34.970   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     =========MS_LITE_LOG: MS_LITE predict start=====
288   08-06 03:24:35.432   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     =========MS_LITE_LOG: MS_LITE predict success=====
289   08-06 03:24:35.447   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: Default/head-MobileNetV2Head/Sigmoid-op466:0.0000034338463592575863,0.000014028532859811094,9.119685273617506e-7,0.000049100715841632336,9.502661555416125e-7,3.945370394831116e-7,0.04346757382154465,0.00003971960904891603...
290   08-06 03:24:35.499   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: max:9497,7756,1970,435,46
291   08-06 03:24:35.499   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     MS_LITE_LOG: maxIndex:323,46,13,6,349
292   08-06 03:24:35.499   22547-22547  A03d00/JSAPP                   com.sampl...liteark+  I     =========MS_LITE_LOG END=========
293   ```
294
295### 效果示意
296
297在设备上,点击photo按钮,选择相册中的一张图片,点击确定。在图片下方显示此图片占比前4的分类信息。
298
299<img src="figures/step1.png" width="20%"/>     <img src="figures/step2.png" width="20%"/>     <img src="figures/step3.png" width="20%"/>     <img src="figures/step4.png" width="20%"/>
300
301## 相关实例
302
303针对使用MindSpore Lite进行图像分类应用的开发,有以下相关实例可供参考:
304
305- [基于ArkTS接口的MindSpore Lite应用开发(ArkTS)(API11)](https://gitee.com/openharmony/applications_app_samples/tree/master/code/DocsSample/ApplicationModels/MindSporeLiteArkTSDemo)
306
307<!--RP1--><!--RP1End-->