微信扫码
添加专属顾问
京东金融团队突破性实现鸿蒙端AI超分模型部署,解决用户图片清晰度痛点,技术干货全公开。 核心内容: 1. 鸿蒙端部署AI超分模型的技术挑战与解决方案 2. 从模型转换到端侧部署的完整实践流程 3. 业务场景应用效果与用户体验提升
这可能是全网第一篇完整讲解鸿蒙端使用CANN部署AI模型的文章, 满满干货。
社区作为用户交流、信息传递的核心载体,图片内容(如理财产品截图、投资经验分享配图、用户互动评论图片等)的展示质量直接影响用户的信息获取效率与平台信任感。从京东金融App社区的业务需求来看,当前用户上传图片普遍存在多样性失真问题:部分用户通过老旧设备拍摄的图片分辨率较低,部分用户为节省流量选择低画质压缩上传,还有部分截图类内容因原始来源清晰度不足导致信息模糊(如理财产品收益率数字、合同条款细节等),这些问题不仅降低了内容可读性,还可能因信息传递不清晰引发用户误解。
京东金融App团队已完成Real-ESRGAN-General-x4v3超分辨率模型在安卓端的部署,能够针对性提升评论区、内容详情页、个人主页等核心场景的图片清晰度,从视觉体验层面优化用户留存与互动意愿。
ESRGAN-General-x4v3模型在安卓端的部署,采用的是ONNX框架,该方案已有大量公开资料可参考,且取得显著业务成效。但鸿蒙端部署面临核心技术瓶颈:鸿蒙系统不支持ONNX框架,部署端侧AI仅能使用华为自研的CANN(Compute Architecture for Neural Networks)架构,且当前行业内缺乏基于CANN部署端侧AI的公开资料与成熟方案,全程需技术团队自主探索。接下来我会以ESRGAN-General-x4v3为例, 分享从模型转换(NPU亲和性改造)到端侧离线模型部署的全部过程。
CANN Kit当前仅支持Caffe、TensorFlow、ONNX和MindSpore模型转换为离线模型,其他格式的模型需要开发者自行转换为CANN Kit支持的模型格式。模型转换为OM离线模型,移动端AI程序直接读取离线模型进行推理。
从鸿蒙开发者官网下载 DDK-tools-5.1.1.1 , 解压使用Tools下的OMG工具,将ONNX、TensorFlow模型转换为OM模型。(OMG工具位于Tools下载的tools/tools_omg下,仅可运行在64位Linux平台上。)
2.1.2 下载ESRGAN-General-x4v3模型文件
从https://aihub.qualcomm.com/compute/models/real_esrgan_general_x4v3 下载模型的onnx文件.
注意: 下载链接中的a8a8的量化模型使用了高通的算子(亲测无法转换), CANN工具无法进行转换, 因此请下载float的量化模型。
下载后有两个文件:
•model.onnx文件 (模型结构): 包含计算图、opset版本、节点配置等,文件较小。
•model.data文件 (权重数据): 包含神经网络参数、权重等,文件较大。
现在我们需要把这种分离文件格式的模型合并成一个文件,后续的操作都使用这个。
合并文件:
请使用JoyCode写个合并脚本即可, 提示词: 请写一个脚本, 把onnx模型文件的.onnx和.data文件合并。
1. ONNX opset 版本转换
当前使用CANN进行模型转换, 支持ONNX opset版本7~18(最高支持到V1.13.1), 首先需要查看原始的onnx模型的opset版本是否在支持范围, 这里我们使用Netron(点击下载)可视化工具进行查看。
目前该模型使用的opset版本是20, 因此我们需要把该模型的opset版本转成18, 才可以用CANN转换成鸿蒙上可部署的模型。请使用JoyCode写个opset转换脚本即可, 提示词: 请写一个脚本, 把onnx模型文件的opset版本从20转换成18。
2. OM离线模型
命令行中的参数说明请参见OMG参数,转换命令:
./tools/tools_omg/omg --model new_model_opset18.onnx --framework 5 --output ./model
转换完成后, 生成model.om的模型文件, 该模型文件就是鸿蒙上可以正常使用的模型文件
部署AI模式时, 我们需要确认模型的输入张量和输出张量信息, 请使用JoyCode编写一个脚本, 确定输入输出张量信息, 提示词: 写一个脚本查看onnx模型的输入输出张量信息。
2.2.1 输入张量
BCHW格式, 是深度学习中常见的张量维度排列格式, 在图像处理场景中:
•B (Batch): 批次大小 - 一次处理多少个样本。
•C (Channel): 通道数 - 图像的颜色通道数。
•H (Height): 高度 - 图像的像素高度。
•W (Width): 宽度 - 图像的像素宽度。
由此可以得出结论, 该模型1个批次处理1张宽高为128*128的RGB图片(因为C是3,因此不包含R通道)。
该模型1个批次输出1张宽高为512*512的RGB图片。
超分模型中的BCHW和BHWC是两种不同的张量存储格式,主要区别在于通道维度的位置:
•BCHW格式(Batch-Channel-Height-Width)
◦维度顺序:[批次, 通道, 高度, 宽度]
◦内存布局:通道维度在空间维度之前
◦常用框架:PyTorch、TensorRT等
示例: 形状为 (1, 3, 256, 256) 的RGB图像
内存中的存储顺序: R通道的所有像素 -> G通道的所有像素 -> B通道的所有像素
tensor_bchw = torch.randn(1, 3, 256, 256)访问第一个像素的RGB值需要跨越不同的内存区域pixel_0_0_r = tensor_bchw[0, 0, 0, 0] # R通道pixel_0_0_g = tensor_bchw[0, 1, 0, 0] # G通道 pixel_0_0_b = tensor_bchw[0, 2, 0, 0] # B通道
•BHWC格式(Batch-Height-Width-Channel)
◦维度顺序:[批次, 高度, 宽度, 通道]
◦内存布局:通道维度在最后,像素的所有通道连续存储
◦常用框架:TensorFlow、OpenCV等
示例:形状为 (1, 256, 256, 3) 的RGB图像
内存中的存储顺序:像素(0,0)的RGB -> 像素(0,1)的RGB -> ... -> 像素(0,255)的RGB -> 像素(1,0)的RGB...
tensor_bhwc = tf.random.normal([1, 256, 256, 3])# 访问第一个像素的RGB值在连续的内存位置pixel_0_0_rgb = tensor_bhwc[0, 0, 0, :] # [R, G, B]
3. 鸿蒙端部署核心步骤
1.创建DevEco Studio项目,选择“Native C++”模板,点击“Next”。
2.按需填写“Project name”、“Save location”和“Module name”,选择“Compile SDK”为“5.1.0(18)”及以上版本,点击“Finish”。
3.2 配置项目NAPI
CANN部署只提供了C++接口, 因此需要使用NAPI, 编译HAP时,NAPI层的so需要编译依赖NDK中的libneural_network_core.so和libhiai_foundation.so。
头文件引用
按需引用NNCore和CANN Kit的头文件。
#include"neural_network_runtime/neural_network_core.h"#include"CANNKit/hiai_options.h"
编写CMakeLists.txt
CMakeLists.txt示例代码如下。
cmake_minimum_required(VERSION 3.5.0)project(myNpmLib)set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})include_directories(${NATIVERENDER_ROOT_PATH} ${NATIVERENDER_ROOT_PATH}/include)include_directories(${HMOS_SDK_NATIVE}/sysroot/usr/lib)FIND_LIBRARY(cann-lib hiai_foundation)add_library(imagesr SHARED HIAIModelManager.cpp ImageSuperResolution.cpp)target_link_libraries(imagesr PUBLIC libace_napi.z.so libhilog_ndk.z.so librawfile.z.so ${cann-lib} libneural_network_core.so )3.3 集成模型
模型的加载、编译和推理主要是在native层实现,应用层主要作为数据传递和展示作用。模型推理之前需要对输入数据进行预处理以匹配模型的输入,同样对于模型的输出也需要做处理获取自己期望的结果
为了让App运行时能够读取到模型文件和处理推理结果,需要先把离线模型和模型对应的结果标签文件预置到工程的“entry/src/main/resources/rawfile”目录中。
在App应用创建时加载模型:
1.native层读取模型的buffer。
const char* modelPath = "imagesr.om";RawFile *rawFile = OH_ResourceManager_OpenRawFile(resourceMgr, modelPath);long modelSize = OH_ResourceManager_GetRawFileSize(rawFile);std::unique_ptr<uint8_t[]> modelData = std::make_unique<uint8_t[]>(modelSize);int res = OH_ResourceManager_ReadRawFile(rawFile, modelData.get(), modelSize);
2.使用模型的buffer, 调用OH_NNCompilation_ConstructWithOfflineModelBuffer创建模型的编译实例
HiAI_Compatibility compibility = HMS_HiAICompatibility_CheckFromBuffer(modelData, modelSize);OH_NNCompilation *compilation = OH_NNCompilation_ConstructWithOfflineModelBuffer(modelData, modelSize);
3.(可选)根据需要调用HMS_HiAIOptions_SetOmOptions接口,打开维测功能(如Profiling)。
const char *out_path = "/data/storage/el2/base/haps/entry/files";HiAI_OmType omType = HIAI_OM_TYPE_PROFILING;OH_NN_ReturnCode ret = HMS_HiAIOptions_SetOmOptions(compilation, omType, out_path);
4.设置模型的deviceID。
size_t deviceID = 0;const size_t *allDevicesID = nullptr;uint32_t deviceCount = 0;OH_NN_ReturnCode ret = OH_NNDevice_GetAllDevicesID(&allDevicesID, &deviceCount);for (uint32_t i = 0; i < deviceCount; i++) { const char *name = nullptr; ret = OH_NNDevice_GetName(allDevicesID[i], &name); if (ret != OH_NN_SUCCESS || name == nullptr) { OH_LOG_ERROR(LOG_APP, "OH_NNDevice_GetName failed"); return deviceID; } if (std::string(name) == "HIAI_F") { deviceID = allDevicesID[i]; break; }}ret = OH_NNCompilation_SetDevice(compilation, deviceID);5.调用OH_NNCompilation_Build,执行模型编译。
ret = SetModelBuildOptions(compilation);ret = OH_NNCompilation_Build(compilation);
6.调用OH_NNExecutor_Construct,创建模型执行器。
executor_ = OH_NNExecutor_Construct(compilation);
7.调用OH_NNCompilation_Destroy,释放模型编译实例。
1.处理模型的输入,模型的输入为1*3*128*128格式(BCHW) Float类型的数据, 需要把RGB 数据转成BCHW格式并进行归一化。
从图片中读取的RGB数据为BHWC,需要转换成模型可以识别的BCHW/** * 把bhwc转成bchw */uint8_t *rgbData = static_cast<uint8_t*>(data);uint8_t *floatData_tmp = new uint8_t[length];for (int c = 0; c < 3; ++c) { for (int h = 0; h < 128; ++h) { for (int w = 0; w < 128; ++w) { // HWC 索引: h * width * channels + w * channels +c int hwc_index = h * 128 * 3 + w * 3 + c; // CHW 索引: C * height * width + h* width + W int chw_index = c * 128 * 128 + h * 128 + w; floatData_tmp[chw_index] = rgbData[hwc_index]; } }}//归一化float *floatData = new float[length];for (size_t i = 0; i < length; ++i) { floatData[i] = static_cast<float>(floatData_tmp[i])/ 255.0f;}2.创建模型的输入和输出Tensor,并把应用层传递的数据填充到输入的Tensor中
// 准备输入张量size_t inputCount = 0;OH_NN_ReturnCode ret = OH_NNExecutor_GetInputCount(executor_, &inputCount);for (size_t i = 0; i < inputCount; ++i) { NN_TensorDesc *tensorDesc = OH_NNExecutor_CreateInputTensorDesc(executor_, i); NN_Tensor *tensor = OH_NNTensor_Create(deviceID_, tensorDesc); if (tensor != nullptr) { inputTensors_.push_back(tensor); } OH_NNTensorDesc_Destroy(&tensorDesc);}ret = SetInputTensorData(inputTensors_, inputData);// 准备输出张量size_t outputCount = 0;ret = OH_NNExecutor_GetOutputCount(executor_, &outputCount);for (size_t i = 0; i < outputCount; i++) { NN_TensorDesc *tensorDesc = OH_NNExecutor_CreateOutputTensorDesc(executor_, i); NN_Tensor *tensor = OH_NNTensor_Create(deviceID_, tensorDesc); if (tensor != nullptr) { outputTensors_.push_back(tensor); } OH_NNTensorDesc_Destroy(&tensorDesc);}if (outputTensors_.size() != outputCount) { DestroyTensors(inputTensors_); DestroyTensors(outputTensors_); OH_LOG_ERROR(LOG_APP, "output size mismatch."); return OH_NN_FAILED;}调用OH_NNExecutor_RunSync,完成模型的同步推理。
OH_NN_ReturnCode ret = OH_NNExecutor_RunSync(executor_, inputTensors_.data(), inputTensors_.size(), outputTensors_.data(), outputTensors_.size());
说明
•如果不更换模型,则首次编译加载完成后可多次推理,即一次编译加载,多次推理。
•所有关于模型的操作, 均无法多线程执行。
1.调用OH_NNTensor_GetDataBuffer,获取输出的Tensor,在输出Tensor中会得到模型的输出数据。
// 获取第一个输出张量NN_Tensor* tensor = outputTensors_[0];// 获取张量数据缓冲区void *tensorData = OH_NNTensor_GetDataBuffer(tensor);// 获取张量大小size_t size = 0;OH_NN_ReturnCode ret = OH_NNTensor_GetSize(tensor, &size);float *tensorDataOutput = (float*)malloc(size);// 将tensorData的数据一次性复制到tensorDataOutput中meMCPy(tensorDataOutput, tensorData, size);
2.对Tensor输出数据进行相应的处理
把模型输出的BCHW转成BHWC, 并进行反归一化处理
//把模型输出的BCHW转成BHWCfloat *outputResult = static_cast<float *>(tensorData);float *output_tmp = new float[size/sizeof(float)];for (int h = 0; h < 512; ++h) { for (int w = 0; w < 512; ++w) { for (int c = 0; c < 3; ++c) { output_tmp[h * 512 * 3 + w* 3 + c] = outputResult[c * 512 * 512 + h * 512 + w]; } }}std::vector<float> output(size / sizeof(float), 0.0);for (size_t i = 0; i < size / sizeof(float); ++i) { output[i] = output_tmp[i];}delete [] output_tmp; // 计算总的数据大小size_t totalSize = output.size();// 分配结果数据内存std::unique_ptr<uint8_t[]> result_data = std::make_unique<uint8_t[]>(totalSize);// 将float数据转换为uint8_t (反归一化)size_t index = 0;for (float value : result) { // 将float值转换为uint8_t (0-255范围) float scaledValue = value * 255.0f; scaledValue = std::max(0.0f, std::min(255.0f, scaledValue)); result_data[index++] = static_cast<uint8_t>(scaledValue);}result_data 就是最终的超分数据,可以正常显示4. 总结与技术展望
京东金融App在鸿蒙端部署Real-ESRGAN-General-x4v3超分辨率模型的完整实践过程,成功解决了ONNX模型到OM离线模型转换、BCHW与BHWC张量格式处理、以及基于CANN Kit和NAPI的完整部署链路等关键技术难题。
展望端智能的未来发展,随着芯片算力的指数级增长、模型压缩技术的突破性进展以及边缘计算架构的日趋成熟,端侧设备将从单纯的数据采集终端演进为具备强大推理能力的智能计算节点,通过实现多模态AI融合、实时个性化学习、隐私保护计算和跨设备协同等核心能力,将大语言模型、计算机视觉、语音识别等AI技术深度集成到移动设备中,构建起无需联网即可提供智能服务的自主计算生态,推动人机交互从被动响应向主动感知、预测和服务的范式转变,最终开启真正意义上的普惠人工智能时代。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业
2026-07-07
我的AI墨水屏BOM长这样:三个让供应商抓狂的PM决策
2026-07-07
从 AI 硬件热潮看产品经理如何判断真实需求
2026-07-07
做了7年硬件PM,我发现AI产品立项最容易死在这一步
2026-07-05
想用 Kindle Vibe,结果绕了一大圈
2026-07-04
AI录音硬件四种形态:眼、耳、手、机,谁会成为下一代入口
2026-06-30
我把 Codex 装进了 Pi 5 Max:一块 Android 开发板,开始自己参与 AI 相框开发
2026-06-24
讯飞办公本用一键式龙虾部署,终结“只记不查”的资产浪费
2026-06-22
我把吃灰的 Kindle 用起来了:给 AI 桌宠加一块电子墨水屏
2026-04-11
2026-04-27
2026-04-21
2026-04-20
2026-04-12
2026-05-09
2026-05-13
2026-05-07
2026-05-18
2026-05-15
欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。
在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。
一、 定义
本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。
会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。
知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。
二、 账号注册与登录
登录方式:本网站支持以下登录方式,您可根据实际情况选择:
微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。
手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。
账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。
实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。
未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。
三、 服务内容与规范
知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。
服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。
禁止行为:您在使用服务时不得实施以下行为:
利用技术手段批量爬取、下载、转存知识库内容;
将知识库内容用于商业目的或未经授权地向第三方传播;
干扰本网站正常运行或侵犯其他用户合法权益;
发布违法违规信息或从事违反公序良俗的活动。
四、 知识产权声明
权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。
有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。
侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。
五、 个人信息保护
我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。
您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。
您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。
六、 免责声明
内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。
不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。
第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。
七、 违约责任
如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。
如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。
八、 法律适用与争议解决
本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。
因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。
九、 其他
本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。
本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。
我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。