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