HarmonyOS AI 超分:用 OpenGL ES 让图像更聪明地变清晰
HarmonyOS AI 超分:用 OpenGL ES 让图像更聪明地变清晰
什么是 AI 超分
上一篇我们介绍了用 Vulkan 实现 AI 超分,这篇来看看用 OpenGL ES 怎么做。功能是一样的:把低分辨率图像通过 AI 算法"重建"成高分辨率图像,而且 AI 超分比传统算法效果更好。
简单回顾一下:传统超分就像一个普通画家凭经验画大画,AI 超分则像一个经过大量训练的专业画家,他见过无数张照片,知道什么样的细节应该出现在什么位置。所以 AI 超分出来的图像,细节更自然,伪影更少。
环境搭建
硬件要求
- 设备类型:请参考 XEngine 开发指南的硬件要求
软件要求
- DevEco Studio 版本:DevEco Studio 6.0.0 Release 及以上
- HarmonyOS SDK 版本:HarmonyOS 6.0.0 Release SDK 及以上
搭建步骤
- 安装 DevEco Studio:去华为开发者官网下载安装
- 配置开发环境:确保网络环境正常
- 设备调试:使用真机进行调试
项目结构
├── entry/src/main // 代码区
│ ├── cpp
│ │ ├── types
│ │ │ ├── libnativerender
│ │ │ └── index.d.ts // native层接口注册文件
│ │ │── napi_init.cpp // native api层接口的具体实现函数
│ │ │── CMakeLists.txt // native层编译配置
│ │ │── 3rdParty // 三方件
│ │ │── common // 通用接口
│ │ │── model // 模型
│ │ │── file // 文件管理
│ │ │── libs // 三方动态库
│ │ │── manager // native&arkts交互
│ │ │── render // 渲染
│ │ │── shader // 渲染shader
│ ├── ets
│ │ ├── entryability
│ │ │ └── EntryAbility.ts // 程序入口类
│ │ ├── pages
│ │ │ └── index.ets // 主界面展示类
│ ├── resources // 资源文件目录
│ │ ├── base
│ │ │ ├── media
│ │ │ └── icon.png // 图片资源
│ │ ├── rawfile/model/Sponza
│ │ │ └── sponza.obj // 模型资源
第一步:引入头文件
#include <cstring>
#include <cstdlib>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
#include <GLES2/gl2ext.h>
#include <xengine/xeg_gles_extension.h>
#include <xengine/xeg_gles_neural_upscale.h>
#include <native_buffer/native_buffer.h>
#include <native_window/external_window.h>
这些头文件的作用:
cstring、cstdlib:C++ 标准库egl.h、eglext.h:EGL 接口,OpenGL ES 的窗口系统gl2.h、gl2ext.h:OpenGL ES 接口xeg_gles_extension.h:XEngine 的 OpenGL ES 扩展接口xeg_gles_neural_upscale.h:AI 超分的核心接口native_buffer.h、external_window.h:HarmonyOS 的原生缓冲区接口,AI 超分需要通过它来管理图像数据
和普通 GPU 超分相比,AI 超分多了 native_buffer.h 和 external_window.h 这两个头文件,因为 AI 超分需要使用 HarmonyOS 的原生缓冲区来传递图像数据。
第二步:配置 CMakeLists.txt
find_library(
native-buffer-lib
native_buffer
)
find_library(
native-window-lib
native_window
)
find_library(
xengine-lib
xengine
)
find_library(
EGL-lib
EGL
)
find_library(
GLES-lib
GLESv3
)
target_link_libraries(nativerender PUBLIC
${EGL-lib} ${GLES-lib} ${xengine-lib} ${native-window-lib} ${native-buffer-lib})
和普通 GPU 超分相比,这里多了两个库:native_buffer 和 native_window。这两个库是 HarmonyOS 提供的原生缓冲区管理库,AI 超分需要用它们来创建和管理图像缓冲区。
第三步:查询设备是否支持
const char* extensions = (const char*)HMS_XEG_GetString(XEG_EXTENSIONS);
if (!strstr(extensions, XEG_NEURAL_UPSCALE_EXTENSION_NAME)) {
exit(1); // return error
}
和普通 GPU 超分一样,先检查设备是否支持 AI 超分。注意这里的扩展名是 XEG_NEURAL_UPSCALE_EXTENSION_NAME,不是 XEG_SPATIAL_UPSCALE_EXTENSION_NAME。
第四步:创建输入纹理
这一步和普通 GPU 超分有很大不同。AI 超分需要使用 HarmonyOS 的原生缓冲区来管理图像数据,所以要先创建一个 OH_NativeBuffer,然后把它关联到 OpenGL ES 纹理上。
设置渲染分辨率
uint32_t renderWidth = 800;
uint32_t renderHeight = 600;
uint32_t displayWidth = 1200;
uint32_t displayHeight = 900;
定义原始渲染分辨率(800x600)和超分后的目标分辨率(1200x900)。这里是 1.5 倍超分。
获取 EGL 扩展函数
PFNEGLCREATEIMAGEKHRPROC fp_eglCreateImageKHR = reinterpret_cast<PFNEGLCREATEIMAGEKHRPROC>(eglGetProcAddress("eglCreateImageKHR"));
PFNEGLDESTROYIMAGEKHRPROC fp_eglDestroyImageKHR = reinterpret_cast<PFNEGLDESTROYIMAGEKHRPROC>(eglGetProcAddress("eglDestroyImageKHR"));
PFNGLEGLIMAGETARGETTEXTURE2DOESPROC fp_glEGLImageTargetTexture2DOES = reinterpret_cast<PFNGLEGLIMAGETARGETTEXTURE2DOESPROC>(eglGetProcAddress("glEGLImageTargetTexture2DOES"));
获取三个 EGL 扩展函数的指针。这些函数用来创建和管理 EGLImage,是连接 HarmonyOS 原生缓冲区和 OpenGL ES 纹理的桥梁。
eglCreateImageKHR:创建一个 EGLImageeglDestroyImageKHR:销毁一个 EGLImageglEGLImageTargetTexture2DOES:把 EGLImage 关联到 OpenGL ES 纹理
创建原生缓冲区
OH_NativeBuffer_Config config = {};
config.width = renderWidth;
config.height = renderHeight;
config.usage = NATIVEBUFFER_USAGE_CPU_READ | NATIVEBUFFER_USAGE_CPU_READ_OFTEN | NATIVEBUFFER_USAGE_HW_TEXTURE | NATIVEBUFFER_USAGE_HW_RENDER| NATIVEBUFFER_USAGE_ALIGNMENT_512;
config.format = NATIVEBUFFER_PIXEL_FMT_RGBA_8888;
OH_NativeBuffer* bufferHandle = OH_NativeBuffer_Alloc(&config);
创建一个 OH_NativeBuffer。这个缓冲区是 HarmonyOS 提供的,可以在 CPU 和 GPU 之间共享数据。
config.usage 设置了缓冲区的用途:
NATIVEBUFFER_USAGE_CPU_READ:CPU 可以读取NATIVEBUFFER_USAGE_HW_TEXTURE:可以作为纹理使用NATIVEBUFFER_USAGE_HW_RENDER:可以作为渲染目标NATIVEBUFFER_USAGE_ALIGNMENT_512:512 字节对齐,AI 超分要求的
创建 EGLImage 并关联到纹理
OHNativeWindowBuffer *nativeWindowBuffer = OH_NativeWindow_CreateNativeWindowBufferFromNativeBuffer(bufferHandle);
EGLImageKHR eglImage = fp_eglCreateImageKHR(eglGetCurrentDisplay(), EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_OHOS, static_cast<EGLClientBuffer>(nativeWindowBuffer), nullptr);
把 OH_NativeBuffer 包装成 OHNativeWindowBuffer,然后创建一个 EGLImageKHR。EGLImageKHR 是 EGL 的图像对象,可以在不同的 EGL 客户端之间共享图像数据。
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
fp_glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, eglImage);
创建一个 OpenGL ES 纹理,设置纹理参数,然后用 glEGLImageTargetTexture2DOES 把 EGLImage 关联到这个纹理上。这样,这个纹理就和 OH_NativeBuffer 共享同一块内存了。
AI 超分的输入资源创建与关联流程如下:
第五步:在输入纹理上渲染
GLuint fboID = 0;
glGenFramebuffers(1, &fboID);
glBindFramebuffer(GL_FRAMEBUFFER, fboID);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureID, 0);
创建一个 framebuffer,把输入纹理作为颜色附件绑定上去。这样渲染的结果就会写入这个纹理。
glViewport(0, 0, renderWidth, renderHeight);
设置视口大小为原始渲染分辨率(800x600)。之后就可以在这个 framebuffer 上渲染场景了。
第六步:设置 AI 超分参数
float sharpness = 0.3f;
HMS_XEG_NeuralUpscaleParameter(XEG_NEURAL_UPSCALE_SHARPNESS, &sharpness);
设置锐化参数。sharpness 的值范围是 [0, 1],0.3 是一个平衡值。值越大,图像越锐利,但可能会有伪影。
GLuint inputScissor[4] = {0, 0, renderWidth, renderHeight};
HMS_XEG_NeuralUpscaleParameter(XEG_NEURAL_UPSCALE_SCISSOR, inputScissor);
设置输入纹理的裁剪窗口。inputScissor 是一个包含 4 个元素的数组:起始坐标 (x, y) 和宽高。这里设置为整个纹理。
HMS_XEG_NeuralUpscaleParameter(XEG_NEURAL_UPSCALE_INPUT_HANDLE, bufferHandle);
设置 AI 超分的输入缓冲区句柄。这是 AI 超分和普通 GPU 超分的一个重要区别:AI 超分需要显式地告诉引擎输入数据在哪里。
第七步:执行 AI 超分
glBindFramebuffer(GL_FRAMEBUFFER, 0);
glViewport(0, 0, displayWidth, displayHeight);
HMS_XEG_RenderNeuralUpscale(textureID);
绑定默认 framebuffer(就是屏幕),设置视口为超分后的目标分辨率(1200x900),然后调用 HMS_XEG_RenderNeuralUpscale 执行 AI 超分。
textureID 是输入纹理,AI 超分会读取这个纹理的内容,然后把超分后的结果写入当前绑定的 framebuffer。
第八步:销毁资源
glDeleteFramebuffers(1, &fboID);
glDeleteTextures(1, &textureID);
if (eglImage != nullptr) {
fp_eglDestroyImageKHR(eglGetCurrentDisplay(), eglImage);
}
if (nativeWindowBuffer != nullptr) {
OH_NativeWindow_DestroyNativeWindowBuffer(nativeWindowBuffer);
}
if (bufferHandle != nullptr) {
OH_NativeBuffer_Unreference(bufferHandle);
}
不需要超分功能时,按顺序销毁资源:
- 删除 framebuffer
- 删除纹理
- 销毁 EGLImage
- 销毁 NativeWindowBuffer
- 释放原生缓冲区
注意销毁顺序:先销毁 EGLImage,再销毁 NativeWindowBuffer,最后释放原生缓冲区。这是因为它们之间有引用关系,反过来销毁可能会导致错误。
资源销毁的顺序关系如下:
AI 超分和普通超分的区别
你可能会问:既然都是超分,为什么要分 AI 版和普通版?主要区别在于:
| 方面 | AI 超分 | 普通超分 |
|---|---|---|
| 算法 | 使用神经网络模型 | 使用传统图像处理算法 |
| 效果 | 细节更自然,伪影更少 | 效果一般,可能有锯齿 |
| 性能开销 | 较大,需要更多计算 | 较小,计算量少 |
| 输入要求 | 需要 OH_NativeBuffer | 直接使用纹理 |
| 适用场景 | 对画质要求高的场景 | 对性能要求高的场景 |
简单来说:AI 超分效果更好,但开销更大;普通超分效果一般,但更快。选择哪个取决于你的需求。
AI 超分的整体工作流程如下:
适用场景
AI 超分特别适合以下场景:
- 游戏渲染:先用低分辨率渲染(性能好),再 AI 超分到高分辨率(画质好)
- 视频播放:把低分辨率视频超分到高分辨率显示
- 图片查看:放大图片时保持清晰度,特别是人脸、文字等细节
注意事项
- 设备支持:不是所有设备都支持 AI 超分,一定要先查询扩展
- 锐化参数:
sharpness要根据实际效果来调整。太大会有伪影,太小效果不明显 - 分辨率比例:超分的倍率不宜过大,一般 1.5 到 2 倍比较合适
- 资源管理:AI 超分涉及多个资源(纹理、EGLImage、NativeBuffer),要注意正确销毁
- 对齐要求:原生缓冲区需要 512 字节对齐,这是 AI 超分的特殊要求
总结
OpenGL ES 版本的 AI 超分和 Vulkan 版本功能相同,核心流程:
- 查询设备是否支持 AI 超分
- 创建原生缓冲区和 EGLImage,关联到 OpenGL ES 纹理
- 在输入纹理上渲染场景
- 设置 AI 超分参数(锐化度、裁剪窗口、输入缓冲区)
- 调用
HMS_XEG_RenderNeuralUpscale执行超分 - 销毁资源
如果你的应用已经在用 OpenGL ES,用这个版本会更方便。如果用的是 Vulkan,就用 Vulkan 版本。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)