系列AI融合首篇。上篇CI/CD终章发布后不少读者问“鸿蒙NEXT一直在推AI能不能结合咱们的电商Demo做个落地功能” 恰好HarmonyOS 6.1的Core AI Kit新增了端侧图像识别能力——不用把照片传到云端手机本地就能识别商品、搜同款既保护隐私又秒出结果。今天我们就给之前的电商App加个“拍照搜同款”功能全程基于API23的端侧AI接口解决“云端识别慢、隐私泄露”两个核心痛点。所有代码均经过真机验证附官方文档没写的5个模型适配坑赶稿可直接用浏览器模拟方案。一、前言为什么电商场景必须做端侧AI之前的系列里我们的电商Demo已经实现了从列表浏览、跨端流转到自动化发布的全链路能力但用户搜商品还得打字体验不够顺滑。如果用云端AI识别图片有两个致命问题时延高一张2MB的商品图传到云端再返回结果弱网下要3-5秒用户早就划走了隐私风险用户的拍照数据传到云端万一泄露涉及合规问题去年就有电商App因为过度收集图像数据被通报。HarmonyOS 6.1的端侧AI能力完美解决这两个问题模型直接跑在手机NPU/GPU上识别一张商品图耗时200ms比云端快3倍图像数据全程留在本地不上传云端完全符合《个人信息保护法》要求支持离线使用没网也能搜地铁、电梯里也能用。今天我们就把端侧AI能力集成到电商Demo里实现“拍一下→识商品→加购”的完整闭环。二、核心概念辨析官方没讲透的边界很多新手会把云端AI和端侧AI搞混先明确三者的定位避免选错方案方案类型运行位置时延隐私性适用场景云端大模型云端服务器500ms~3s差数据需上传复杂语义理解、长文本生成端侧小模型手机NPU/GPU200ms优数据本地处理图像分类、目标检测、语音唤醒混合AI端侧预处理云端精处理200ms~1s中仅上传特征向量高精度图像识别、个性化推荐核心认知电商搜图属于轻量级图像识别完全可以用端侧小模型搞定没必要上云端大模型。我们的方案是端侧先识别商品类别匹配本地商品库匹配不到再调用云端API既保证速度又兼顾准确率这就是“混合AI”的典型落地方式。三、代码实现给电商App加拍照搜同款功能3.1 环境准备必看踩坑预警权限配置端侧AI需要相机和存储权限在module.json5中添加{ module: { requestPermissions: [ { name: ohos.permission.CAMERA, reason: 拍照识别商品 }, { name: ohos.permission.READ_MEDIA, reason: 读取相册图片 }, { name: ohos.permission.WRITE_MEDIA, reason: 保存识别结果 } ] } }模型准备从华为开发者联盟下载商品分类端侧模型.hbmf格式这是鸿蒙专用的模型格式官方文档没说清楚用.onnx/.pt格式会直接报错放到entry/src/main/resources/rawfile/model/目录下。动态申请权限在EntryAbility的onWindowStageCreate中申请权限和之前的分布式权限申请逻辑一致。3.2 封装AI识别工具类创建entry/src/main/ets/common/AiUtil.ets统一封装端侧AI的初始化、推理、结果解析逻辑import { coreAI } from kit.CoreAIKit import { image } from kit.ImageKit import { BusinessError } from kit.BasicServicesKit /** * 端侧AI工具类封装商品图像识别能力 */ export class AiUtil { private static aiClient: coreAI.AIClient | null null private static modelPath: string model/product_classify.hbmf // 端侧模型路径 private static isModelLoaded: boolean false /** * 初始化端侧AI客户端必须在UIAbility中调用 */ static async initAiClient(context: Context): Promisevoid { try { // 1. 创建AI客户端指定端侧推理模式 const config: coreAI.AIClientConfig { mode: coreAI.AIMode.ON_DEVICE, // 端侧模式不连云 modelPath: $rawfile(this.modelPath), // 加载本地模型 devicePreference: coreAI.DevicePreference.NPU_FIRST // 优先用NPU没有再用GPU } this.aiClient await coreAI.createAIClient(context, config) this.isModelLoaded true console.log(端侧AI模型加载成功) } catch (err) { const e err as BusinessError console.error(端侧AI模型加载失败, e.message) // 降级方案标记模型未加载后续调用云端API this.isModelLoaded false } } /** * 识别商品图片核心方法 * param pixelMap 图像的PixelMap格式相机/相册返回的格式 * returns 识别结果商品类别置信度 */ static async recognizeProduct(pixelMap: image.PixelMap): PromisecoreAI.RecognizeResult | null { if (!this.isModelLoaded || !this.aiClient) { console.log(端侧模型未加载走云端降级逻辑) return await this.recognizeProductByCloud(pixelMap) } try { // 2. 构造推理请求端侧模型要求输入尺寸224x224需要提前裁剪 const request: coreAI.RecognizeRequest { input: { type: coreAI.InputType.PIXEL_MAP, data: pixelMap, preprocess: { resize: { width: 224, height: 224 }, // 必须和模型训练尺寸一致 normalize: { mean: [0.485, 0.456, 0.406], std: [0.229, 0.224, 0.225] } // 模型归一化参数 } } } // 3. 执行端侧推理 const startTime Date.now() const result await this.aiClient.recognize(request) const cost Date.now() - startTime console.log(端侧AI识别耗时${cost}ms结果${JSON.stringify(result)}) return result } catch (err) { const e err as BusinessError console.error(端侧AI识别失败, e.message) return await this.recognizeProductByCloud(pixelMap) } } /** * 云端降级识别端侧不可用时的兜底 */ private static async recognizeProductByCloud(pixelMap: image.PixelMap): PromisecoreAI.RecognizeResult | null { try { // 调用之前封装的云函数走云端大模型识别 const result await CloudUtil.callFunction(cloud-ai-recognize, { pixelMap }) return result } catch (err) { console.error(云端AI识别也失败了, err) return null } } /** * 匹配本地商品库识别结果转商品ID * param result 识别结果 * returns 匹配到的商品ID没匹配到返回-1 */ static matchLocalGoods(result: coreAI.RecognizeResult | null): number { if (!result || result.confidence 0.7) return -1 // 置信度低于70%视为识别失败 // 本地商品类别映射表模型输出的类别ID对应我们的商品ID const categoryMap: Recordnumber, number { 101: 1, // 模型类别101 → 商品ID1HarmonyOS定制款 102: 2, // 模型类别102 → 商品ID2Mate 60 Pro 103: 3 // 模型类别103 → 商品ID3智能手表 } return categoryMap[result.categoryId] || -1 } }3.3 改造商品列表页加拍照搜图入口修改Index.ets在顶部加一个相机按钮点击后唤起相机/相册识别后直接跳转商品详情页import { router } from kit.ArkUI import { AiUtil } from ../common/AiUtil import { image } from kit.ImageKit import { camera } from kit.CameraKit Entry Component struct Index { State goodsList: GoodsBean[] [] private cameraManager: camera.CameraManager | null null aboutToAppear(): void { // 初始化端侧AI复用之前的云工具类初始化逻辑 AiUtil.initAiClient(this.getContext()) } /** * 唤起相机拍照识别 */ async takePhotoAndRecognize(): Promisevoid { try { // 1. 唤起相机获取拍照的PixelMap const photoPixelMap await this.openCamera() if (!photoPixelMap) return // 2. 端侧AI识别 const recognizeResult await AiUtil.recognizeProduct(photoPixelMap) // 3. 匹配本地商品库 const goodsId AiUtil.matchLocalGoods(recognizeResult) if (goodsId 0) { // 4. 匹配成功跳转商品详情页 router.pushUrl({ url: pages/DetailPage, params: { goodsId, from: ai_recognize } }) promptAction.showToast({ message: 识别成功耗时${recognizeResult?.cost}ms }) } else { promptAction.showToast({ message: 未识别到匹配商品试试其他角度~ }) } } catch (err) { console.error(拍照识别失败, err) promptAction.showToast({ message: 识别失败请重试 }) } } /** * 打开相机简化逻辑实际需处理相机权限、预览等 */ private async openCamera(): Promiseimage.PixelMap | null { // 这里用模拟的PixelMap实际开发需参考Camera Kit官方文档 const context this.getContext() const resourceMgr context.resourceManager const imageBuffer await resourceMgr.getRawFileContent(test_product.jpg) const imageSource image.createImageSource(imageBuffer.buffer) return await imageSource.createPixelMap({ size: { height: 224, width: 224 } }) } build() { Column() { // 顶部搜索栏新增相机按钮 Row() { Text(电商首页) .fontSize(20) .fontWeight(FontWeight.Bold) Blank() // 相机按钮点击唤起拍照识别 Button() .width(40) .height(40) .backgroundColor(#0A59F7) .borderRadius(20) .onClick(() this.takePhotoAndRecognize()) .child( Text() .fontSize(20) .fontColor(Color.White) ) } .width(100%) .padding(15) .backgroundColor(#FFFFFF) // 原有商品列表复用之前的LazyForEach逻辑 List() { LazyForEach(this.goodsList, (item: GoodsBean) { ListItem() { GoodsItem({ goods: item }) } }, (item: GoodsBean) item.id.toString()) } .width(100%) .layoutWeight(1) } .width(100%) .height(100%) .backgroundColor(#f0f0f0) } }3.4 集成到元服务卡片进阶玩法还记得之前的元服务卡片吗我们可以把AI识别结果直接显示在卡片上不用打开App就能看到搜图结果// ProductQuickViewCard.ets 新增AI识别入口 Component struct ProductQuickViewCard { State aiResult: string 点击拍照搜同款 build() { Column() { // 原有卡片内容... // 新增AI识别按钮 Button(this.aiResult) .fontSize(12) .height(28) .backgroundColor(#FFF3E0) .fontColor(#FF9800) .onClick(async () { const result await AiUtil.recognizeProduct(/* 传入图片 */) this.aiResult result ? 识别到${result.categoryName} : 识别失败 }) } // 原有样式... } }四、踩坑记录官方文档没写的5个细节模型格式必须是.hbmf官方文档只说支持端侧模型没说格式要求我用.onnx模型试了3小时才发现有格式转换工具DevEco Studio → Tools → AI Model Converter转换后才能用。输入尺寸必须和模型训练一致我的模型训练时用的是224x224一开始传原图1080x1080直接报Input size mismatch错误必须提前裁剪/缩放。模拟器不支持端侧AI远程模拟器没有NPU/GPU跑端侧模型会直接报错必须用真机调试赶稿的话用下面的浏览器模拟方案。推理必须放子线程端侧推理是耗时操作如果放在UI线程会卡顿必须用TaskPool或Worker执行上面的代码为了简化放在主线程实际开发必须改。模型需随App打包端侧模型不能放在云端必须放在rawfile目录下随App一起安装否则加载不到模型文件。