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<Model> | 从路径加载模型。 | 31| getInputs(): MSTensor[] | 获取模型的输入。 | 32| predict(inputs: MSTensor[]): Promise<MSTensor[]> | 推理模型。 | 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  382 383  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-->