使用MindSpore Lite实现图像分类(ArkTS)
引言
随着人工智能技术的飞速发展,图像分类作为计算机视觉领域的一个重要分支,已经在众多领域得到了广泛应用。从医疗影像分析到自动驾驶,从电子商务的商品识别到安防监控中的人脸识别,图像分类技术正深刻地影响着我们的生活和工作方式。为了满足日益增长的需求,高效的图像分类解决方案显得尤为重要。MindSpore Lite作为华为推出的轻量级AI推理框架,以其高效、灵活的特点,成为开发者在端侧设备上实现图像分类任务的理想选择。本文将详细介绍如何使用MindSpore Lite在ArkTS环境中实现图像分类应用,涵盖从模型选择、图像预处理、推理代码编写到结果输出的完整流程,并通过示例代码帮助开发者快速上手。
MindSpore Lite简介
MindSpore Lite是华为开源的轻量级AI推理框架,专为移动和嵌入式设备设计。它具有以下特点:
- 高效性:通过优化的内核算法和汇编级优化,MindSpore Lite能够在资源受限的设备上实现高性能的推理。
- 轻量化:支持模型量化、剪枝等压缩技术,显著减小模型体积,加快推理速度。
- 跨平台支持:兼容多种操作系统和硬件平台,包括ARM、x86等。
- 易于集成:提供丰富的API接口,方便开发者快速集成到自己的应用中。
ArkTS简介
ArkTS是华为推出的一种新型编程语言,旨在简化HarmonyOS应用的开发。它结合了TypeScript的语法和强大的类型系统,提供了更高的开发效率和更好的代码可维护性。ArkTS特别适合用于开发用户界面和交互逻辑,与MindSpore Lite结合使用,可以实现高效、美观的图像分类应用。
开发环境准备
在开始之前,确保您的开发环境已经安装了以下工具和库:
- DevEco Studio:版本 >= 4.1,并更新SDK到API 11或以上。
- MindSpore Lite:下载并安装最新版本的MindSpore Lite SDK。
- ArkTS开发工具:确保您的ArkTS开发环境已经配置好。
开发流程概述
使用MindSpore Lite在ArkTS环境中实现图像分类的主要步骤如下:
- 选择图像分类模型:选择一个适合的预训练模型,如MobileNetV2。
- 图像预处理:对输入图像进行裁剪、缩放、归一化等处理,使其符合模型的输入要求。
- 编写推理代码:使用MindSpore Lite的API加载模型并进行推理。
- 结果输出:将推理结果进行解析,并在界面上展示分类结果。
详细开发步骤
1. 选择图像分类模型
在本示例中,我们将使用MobileNetV2作为图像分类模型。MobileNetV2是一个轻量级的卷积神经网络,适用于移动和嵌入式设备。
将预训练的MobileNetV2模型文件(mobilenetv2.ms)放置在项目的entry/src/main/resources/rawfile
目录下。
2. 图像预处理
图像预处理是将输入图像转换为模型所需的格式和范围的过程。主要包括以下步骤:
- 图像裁剪和缩放:将图像调整为模型所需的输入尺寸(如224x224)。
- 归一化:将图像像素值归一化到[0, 1]或[-1, 1]范围。
3. 编写推理代码
推理代码主要包括以下几个步骤:
- 加载模型:从文件中读取模型数据并加载到内存中。
- 创建上下文:设置推理所需的线程数、设备类型等参数。
- 设置输入数据:将预处理后的图像数据传递给模型。
- 执行推理:调用模型的推理接口进行计算。
- 获取输出结果:从模型输出中提取分类结果。
4. 结果输出
推理完成后,需要对输出结果进行处理,提取出分类概率最高的类别。
示例代码
以下是一个完整的示例代码,展示了如何在ArkTS中使用MindSpore Lite实现图像分类:
工程默认设备定义的能力集可能不包含MindSporeLite。需在DevEco Studio工程的entry/src/main目录下,手动创建syscap.json文件,内容如下:
{
"devices": {
"general": [
// 需跟module.json5中deviceTypes保持一致。
"default"
]
},
"development": {
"addedSysCaps": [
"SystemCapability.AI.MindSporeLite"
]
}
}
import { photoAccessHelper } from '@kit.MediaLibraryKit';
import { BusinessError } from '@ohos.base';
import image from '@ohos.multimedia.image';
import { resourceManager } from '@kit.LocalizationKit'
import { fileIo } from '@kit.CoreFileKit';
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { mindSporeLite } from '@kit.MindSporeLiteKit';
const PERMISSIONS: Permissions[] = ['ohos.permission.READ_IMAGEVIDEO'];
@Entry
@Component
struct Index {
@State modelPredict: string = 'MindSporeLite ArkTS Demo';
@State modelName: string = 'mobilenetv2.ms';
@State modelInputHeight: number = 224;
@State modelInputWidth: number = 224;
@State uris: Array<string> = [];
@State max: number = 0;
@State maxIndex: number = 0;
@State maxArray: Array<number> = [];
@State maxIndexArray: Array<number> = [];
@State labelsNameMmap: string[] = [
'Herd', 'Safari', 'Bangle', 'Cushion', 'Countertop',
'Prom', 'Branch', 'Sports', 'Sky', 'Community',
'Wheel', 'Cola', 'Tuxedo', 'Flowerpot', 'Team',
'Computer', 'Unicycle', 'Brig', 'Aerospace engineering', 'Scuba diving',
'Goggles', 'Fruit', 'Badminton', 'Horse', 'Sunglasses',
'Fun', 'Prairie', 'Poster', 'Flag', 'Speedboat',
'Eyelash', 'Veil', 'Mobile phone', 'Wheelbarrow', 'Saucer',
'Leather', 'Drawer', 'Paper', 'Pier', 'Waterfowl',
'Tights', 'Rickshaw', 'Vegetable', 'Handrail', 'Ice',
'Metal', 'Flower', 'Wing', 'Silverware', 'Event',
'Skyline', 'Money', 'Comics', 'Handbag', 'Porcelain',
'Rodeo', 'Curtain', 'Tile', 'Human mouth', 'Army',
'Menu', 'Boat', 'Snowboarding', 'Cairn terrier', 'Net',
'Pasteles', 'Cup', 'Rugby', 'Pho', 'Cap',
'Human hair', 'Surfing', 'Loveseat', 'Museum', 'Shipwreck',
'Trunk (Tree)', 'Plush', 'Monochrome', 'Volcano', 'Rock',
'Pillow', 'Presentation', 'Nebula', 'Subwoofer', 'Lake',
'Sledding', 'Bangs', 'Tablecloth', 'Necklace', 'Swimwear',
'Standing', 'Jeans', 'Carnival', 'Softball', 'Centrepiece',
'Skateboarder', 'Cake', 'Dragon', 'Aurora', 'Skiing',
'Bathroom', 'Dog', 'Needlework', 'Umbrella', 'Church',
'Fire', 'Piano', 'Denim', 'Bridle', 'Cabinetry',
'Lipstick', 'Ring', 'Television', 'Roller', 'Seal',
'Concert', 'Product', 'News', 'Fast food', 'Horn (Animal)',
'Tattoo', 'Bird', 'Bridegroom', 'Love', 'Helmet',
'Dinosaur', 'Icing', 'Miniature', 'Tire', 'Toy',
'Icicle', 'Jacket', 'Coffee', 'Mosque', 'Rowing',
'Wetsuit', 'Camping', 'Underwater', 'Christmas', 'Gelato',
'Whiteboard', 'Field', 'Ragdoll', 'Construction', 'Lampshade',
'Palace', 'Meal', 'Factory', 'Cage', 'Clipper (Boat)',
'Gymnastics', 'Turtle', 'Human foot', 'Marriage', 'Web page',
'Human beard', 'Fog', 'Wool', 'Cappuccino', 'Lighthouse',
'Lego', 'Sparkler', 'Sari', 'Model', 'Temple',
'Beanie', 'Building', 'Waterfall', 'Penguin', 'Cave',
'Stadium', 'Smile', 'Human hand', 'Park', 'Desk',
'Shetland sheepdog', 'Bar', 'Eating', 'Neon', 'Dalmatian',
'Crocodile', 'Wakeboarding', 'Longboard', 'Road', 'Race',
'Kitchen', 'Odometer', 'Cliff', 'Fiction', 'School',
'Interaction', 'Bullfighting', 'Boxer', 'Gown', 'Aquarium',
'Superhero', 'Pie', 'Asphalt', 'Surfboard', 'Cheeseburger',
'Screenshot', 'Supper', 'Laugh', 'Lunch', 'Party ',
'Glacier', 'Bench', 'Grandparent', 'Sink', 'Pomacentridae',
'Blazer', 'Brick', 'Space', 'Backpacking', 'Stuffed toy',
'Sushi', 'Glitter', 'Bonfire', 'Castle', 'Marathon',
'Pizza', 'Beach', 'Human ear', 'Racing', 'Sitting',
'Iceberg', 'Shelf', 'Vehicle', 'Pop music', 'Playground',
'Clown', 'Car', 'Rein', 'Fur', 'Musician',
'Casino', 'Baby', 'Alcohol', 'Strap', 'Reef',
'Balloon', 'Outerwear', 'Cathedral', 'Competition', 'Joker',
'Blackboard', 'Bunk bed', 'Bear', 'Moon', 'Archery',
'Polo', 'River', 'Fishing', 'Ferris wheel', 'Mortarboard',
'Bracelet', 'Flesh', 'Statue', 'Farm', 'Desert',
'Chain', 'Aircraft', 'Textile', 'Hot dog', 'Knitting',
'Singer', 'Juice', 'Circus', 'Chair', 'Musical instrument',
'Room', 'Crochet', 'Sailboat', 'Newspaper', 'Santa claus',
'Swamp', 'Skyscraper', 'Skin', 'Rocket', 'Aviation',
'Airliner', 'Garden', 'Ruins', 'Storm', 'Glasses',
'Balance', 'Nail (Body part)', 'Rainbow', 'Soil ', 'Vacation ',
'Moustache', 'Doily', 'Food', 'Bride ', 'Cattle',
'Pocket', 'Infrastructure', 'Train', 'Gerbil', 'Fireworks',
'Pet', 'Dam', 'Crew', 'Couch', 'Bathing',
'Quilting', 'Motorcycle', 'Butterfly', 'Sled', 'Watercolor paint',
'Rafting', 'Monument', 'Lightning', 'Sunset', 'Bumper',
'Shoe', 'Waterskiing', 'Sneakers', 'Tower', 'Insect',
'Pool', 'Placemat', 'Airplane', 'Plant', 'Jungle',
'Armrest', 'Duck', 'Dress', 'Tableware', 'Petal',
'Bus', 'Hanukkah', 'Forest', 'Hat', 'Barn',
'Tubing', 'Snorkeling', 'Cool', 'Cookware and bakeware', 'Cycling',
'Swing (Seat)', 'Muscle', 'Cat', 'Skateboard', 'Star',
'Toe', 'Junk', 'Bicycle', 'Bedroom', 'Person',
'Sand', 'Canyon', 'Tie', 'Twig', 'Sphynx',
'Supervillain', 'Nightclub', 'Ranch', 'Pattern', 'Shorts',
'Himalayan', 'Wall', 'Leggings', 'Windsurfing', 'Deejay',
'Dance', 'Van', 'Bento', 'Sleep', 'Wine',
'Picnic', 'Leisure', 'Dune', 'Crowd', 'Kayak',
'Ballroom', 'Selfie', 'Graduation', 'Frigate', 'Mountain',
'Dude', 'Windshield', 'Skiff', 'Class', 'Scarf',
'Bull', 'Soccer', 'Bag', 'Basset hound', 'Tractor',
'Swimming', 'Running', 'Track', 'Helicopter', 'Pitch',
'Clock', 'Song', 'Jersey', 'Stairs', 'Flap',
'Jewellery', 'Bridge', 'Cuisine', 'Bread', 'Caving',
'Shell', 'Wreath', 'Roof', 'Cookie', 'Canoe'];
aboutToAppear() {
abilityAccessCtrl.createAtManager().requestPermissionsFromUser(getContext(this), PERMISSIONS).then((data) => {
if (data.authResults[0] == 0) {
hilog.info(0xFF00, 'MindSporeLiteArkTSDemo', '%{public}s', 'request permission success');
} else {
hilog.info(0xFF00, 'MindSporeLiteArkTSDemo', '%{public}s', 'user rejected');
}
}).catch((err: BusinessError) => {
hilog.error(0xFF00, 'MindSporeLiteArkTSDemo', '%{public}s',
`request permission failed, error message: ${(err as BusinessError).message}`);
});
}
build() {
Row() {
Column() {
Text(this.modelPredict)
.focusable(true)
.fontSize(30)
.fontWeight(FontWeight.Bold)
//图片显示
Image(this.uris[0]).width(320).height(540).margin(15)
//显示分类最大值
if (this.max) {
Text(this.labelsNameMmap[this.maxIndexArray[0]] +
': ' +
(this.maxArray[0] / 100).toString() +
'%\n' +
this.labelsNameMmap[this.maxIndexArray[1]] +
': ' +
(this.maxArray[1] / 100).toString() +
'%\n' +
this.labelsNameMmap[this.maxIndexArray[2]] +
': ' +
(this.maxArray[2] / 100).toString() +
'%\n' +
this.labelsNameMmap[this.maxIndexArray[3]] +
': ' +
(this.maxArray[3] / 100).toString() +
'%').focusable(true).fontSize(20)
}
Button() {
Text('photo')
.fontSize(30)
.fontWeight(FontWeight.Bold)
}
.type(ButtonType.Capsule)
.margin({
top: 20
})
.backgroundColor('#0D9FFB')
.width('40%')
.height('5%')
.onClick(() => {
let resMgr: resourceManager.ResourceManager = getContext().getApplicationContext().resourceManager;
resMgr.getRawFileContent(this.modelName).then(modelBuffer => {
//获取相册图片
//1.创建图片-视频类型文件选择选项实例
let photoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
//2.选择媒体文件类型和选择媒体文件的最大数目
photoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE; // 过滤选择媒体文件类型为IMAGE
photoSelectOptions.maxSelectNumber = 1; // 选择媒体文件的最大数目
//3.创建图库选择器实例,调用select()接口拉起图库界面进行文件选择。文件选择成功后,返回 photoSelectResult 结果集。
let photoPicker = new photoAccessHelper.PhotoViewPicker();
photoPicker.select(photoSelectOptions, async (
err: BusinessError, photoSelectResult: photoAccessHelper.PhotoSelectResult) => {
if (err) {
console.error('MS_LITE_ERR: PhotoViewPicker.select failed with err: ' + JSON.stringify(err));
return;
}
console.info('MS_LITE_LOG: PhotoViewPicker.select successfully, ' +
'photoSelectResult uri: ' + JSON.stringify(photoSelectResult));
this.uris = photoSelectResult.photoUris;
console.info('MS_LITE_LOG: uri: ' + this.uris);
// 预处理图片数据
try {
// 1.使用fileIo.openSync接口,通过uri打开这个文件得到fd
let file = fileIo.openSync(this.uris[0], fileIo.OpenMode.READ_ONLY);
console.info('MS_LITE_LOG: file fd: ' + file.fd);
// 2.通过fd使用fileIo.readSync接口读取这个文件内的数据
let inputBuffer = new ArrayBuffer(4096000);
let readLen = fileIo.readSync(file.fd, inputBuffer);
console.info('MS_LITE_LOG: readSync data to file succeed and inputBuffer size is:' + readLen);
// 3.通过 PixelMap 预处理
let imageSource = image.createImageSource(file.fd);
let pixelMap = await imageSource.createPixelMap();
let info = await pixelMap.getImageInfo();
console.info('MS_LITE_LOG: info.width = ' + info.size.width);
console.info('MS_LITE_LOG: info.height = ' + info.size.height);
// 4.获取图片buffer数据readBuffer,并进行处理
pixelMap.scale(256.0 / info.size.width, 256.0 / info.size.height).then(() => {
pixelMap.crop(
{ x: 16, y: 16, size: { height: this.modelInputHeight, width: this.modelInputWidth } }
).then(async () => {
let info = await pixelMap.getImageInfo();
console.info('MS_LITE_LOG: crop info.width = ' + info.size.width);
console.info('MS_LITE_LOG: crop info.height = ' + info.size.height);
//需要创建的像素buffer大小
let readBuffer = new ArrayBuffer(this.modelInputHeight * this.modelInputWidth * 4);
await pixelMap.readPixelsToBuffer(readBuffer);
console.info('MS_LITE_LOG: Succeeded in reading image pixel data, buffer: ' +
readBuffer.byteLength);
//处理readBuffer
const imageArr = new Uint8Array(
readBuffer.slice(0, this.modelInputHeight * this.modelInputWidth * 4));
console.info('MS_LITE_LOG: imageArr length: ' + imageArr.length);
let means = [0.485, 0.456, 0.406];
let stds = [0.229, 0.224, 0.225];
let float32View = new Float32Array(this.modelInputHeight * this.modelInputWidth * 3);
let index = 0;
for (let i = 0; i < imageArr.length; i++) {
if ((i + 1) % 4 == 0) {
float32View[index] = (imageArr[i - 3] / 255.0 - means[0]) / stds[0]; // B
float32View[index+1] = (imageArr[i - 2] / 255.0 - means[1]) / stds[1]; // G
float32View[index+2] = (imageArr[i - 1] / 255.0 - means[2]) / stds[2]; // R
index += 3;
}
}
console.info('MS_LITE_LOG: float32View length: ' + float32View.length);
let printStr = 'float32View data:';
for (let i = 0; i < 20; i++) {
printStr += ' ' + float32View[i];
}
console.info('MS_LITE_LOG: float32View data: ' + printStr);
let inputs: ArrayBuffer[] = [float32View.buffer];
// predict
modelPredict(modelBuffer.buffer.slice(0), inputs).then(outputs => {
console.info('=========MS_LITE_LOG: MS_LITE predict success=====');
// 结果打印
for (let i = 0; i < outputs.length; i++) {
let out = new Float32Array(outputs[i].getData());
let printStr = outputs[i].name + ':';
for (let j = 0; j < out.length; j++) {
printStr += out[j].toString() + ',';
}
console.info('MS_LITE_LOG: ' + printStr);
// 取分类最大值top5
this.max = 0;
this.maxIndex = 0;
this.maxArray = [];
this.maxIndexArray = [];
let newArray = out.filter(value => value !== this.max)
for (let n = 0; n < 5; n++) {
this.max = out[0];
this.maxIndex = 0;
// 取最大值
for (let m = 0; m < newArray.length; m++) {
if (newArray[m] > this.max) {
this.max = newArray[m];
this.maxIndex = m;
}
}
this.maxArray.push(Math.round(this.max * 10000))
this.maxIndexArray.push(this.maxIndex)
// filter函数 数组过滤函数
newArray = newArray.filter(value => value !== this.max)
}
console.info('MS_LITE_LOG: max:' + this.maxArray);
console.info('MS_LITE_LOG: maxIndex:' + this.maxIndexArray);
}
console.info('=========MS_LITE_LOG END=========');
})
})
})
// 5.关闭文件
fileIo.closeSync(file)
} catch (err) {
console.error('MS_LITE_LOG: uri: open file fd failed.' + err)
}
})
})
})
}
.width('100%')
}
.height('100%')
}
}
export default async function modelPredict(
modelBuffer: ArrayBuffer, inputsBuffer: ArrayBuffer[]): Promise<mindSporeLite.MSTensor[]> {
//1.创建上下文
let context: mindSporeLite.Context = {};
context.target = ['cpu'];
context.cpu = {}
context.cpu.threadNum = 2;
context.cpu.threadAffinityMode = 1;
context.cpu.precisionMode = 'enforce_fp32';
//2.加载模型
let msLiteModel: mindSporeLite.Model = await mindSporeLite.loadModelFromBuffer(modelBuffer, context);
//3.设置输入数据
let modelInputs: mindSporeLite.MSTensor[] = msLiteModel.getInputs();
// 本模型不支持其他shape resize
for (let i = 0; i < inputsBuffer.length; i++) {
let inputBuffer = inputsBuffer[i];
if (inputBuffer != null) {
modelInputs[i].setData(inputBuffer as ArrayBuffer);
}
}
//4.执行推理
console.info('=========MS_LITE_LOG: MS_LITE predict start=====');
let modelOutputs: mindSporeLite.MSTensor[] = await msLiteModel.predict(modelInputs);
return modelOutputs;
}
结论
通过本文的介绍,我们了解了如何使用MindSpore Lite在ArkTS环境中实现图像分类应用。从模型选择、图像预处理、推理代码编写到结果输出,每个步骤都进行了详细的讲解,并提供了相应的示例代码。希望本文能够帮助开发者快速上手,实现高效的图像分类功能。
----
以上