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 及以上

搭建步骤

  1. 安装 DevEco Studio:去华为开发者官网下载安装
  2. 配置开发环境:确保网络环境正常
  3. 设备调试:使用真机进行调试

项目结构

├── 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>

这些头文件的作用:

  • cstringcstdlib:C++ 标准库
  • egl.heglext.h:EGL 接口,OpenGL ES 的窗口系统
  • gl2.hgl2ext.h:OpenGL ES 接口
  • xeg_gles_extension.h:XEngine 的 OpenGL ES 扩展接口
  • xeg_gles_neural_upscale.h:AI 超分的核心接口
  • native_buffer.hexternal_window.h:HarmonyOS 的原生缓冲区接口,AI 超分需要通过它来管理图像数据

和普通 GPU 超分相比,AI 超分多了 native_buffer.hexternal_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_buffernative_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:创建一个 EGLImage
  • eglDestroyImageKHR:销毁一个 EGLImage
  • glEGLImageTargetTexture2DOES:把 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,然后创建一个 EGLImageKHREGLImageKHR 是 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 超分的输入资源创建与关联流程如下:

配置 NativeBuffer 参数

分配 OH_NativeBuffer

创建 NativeWindowBuffer

获取 EGL 扩展函数指针

创建 EGLImageKHR

创建 OpenGL ES 纹理

设置纹理参数 环绕/过滤模式

将 EGLImage 关联到纹理

纹理与 NativeBuffer 共享内存

第五步:在输入纹理上渲染

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);
}

不需要超分功能时,按顺序销毁资源:

  1. 删除 framebuffer
  2. 删除纹理
  3. 销毁 EGLImage
  4. 销毁 NativeWindowBuffer
  5. 释放原生缓冲区

注意销毁顺序:先销毁 EGLImage,再销毁 NativeWindowBuffer,最后释放原生缓冲区。这是因为它们之间有引用关系,反过来销毁可能会导致错误。

资源销毁的顺序关系如下:

删除 Framebuffer

删除纹理

销毁 EGLImage

销毁 NativeWindowBuffer

释放 OH_NativeBuffer

AI 超分和普通超分的区别

你可能会问:既然都是超分,为什么要分 AI 版和普通版?主要区别在于:

方面 AI 超分 普通超分
算法 使用神经网络模型 使用传统图像处理算法
效果 细节更自然,伪影更少 效果一般,可能有锯齿
性能开销 较大,需要更多计算 较小,计算量少
输入要求 需要 OH_NativeBuffer 直接使用纹理
适用场景 对画质要求高的场景 对性能要求高的场景

简单来说:AI 超分效果更好,但开销更大;普通超分效果一般,但更快。选择哪个取决于你的需求。

AI 超分的整体工作流程如下:

查询设备是否支持AI超分

创建 NativeBuffer 和 EGLImage

关联到 OpenGL ES 纹理

在输入纹理上渲染场景

设置AI超分参数

绑定输出 Framebuffer

调用 HMS_XEG_RenderNeuralUpscale

输出高分辨率图像

按顺序销毁资源

适用场景

AI 超分特别适合以下场景:

  • 游戏渲染:先用低分辨率渲染(性能好),再 AI 超分到高分辨率(画质好)
  • 视频播放:把低分辨率视频超分到高分辨率显示
  • 图片查看:放大图片时保持清晰度,特别是人脸、文字等细节

注意事项

  1. 设备支持:不是所有设备都支持 AI 超分,一定要先查询扩展
  2. 锐化参数sharpness 要根据实际效果来调整。太大会有伪影,太小效果不明显
  3. 分辨率比例:超分的倍率不宜过大,一般 1.5 到 2 倍比较合适
  4. 资源管理:AI 超分涉及多个资源(纹理、EGLImage、NativeBuffer),要注意正确销毁
  5. 对齐要求:原生缓冲区需要 512 字节对齐,这是 AI 超分的特殊要求

总结

OpenGL ES 版本的 AI 超分和 Vulkan 版本功能相同,核心流程:

  1. 查询设备是否支持 AI 超分
  2. 创建原生缓冲区和 EGLImage,关联到 OpenGL ES 纹理
  3. 在输入纹理上渲染场景
  4. 设置 AI 超分参数(锐化度、裁剪窗口、输入缓冲区)
  5. 调用 HMS_XEG_RenderNeuralUpscale 执行超分
  6. 销毁资源

如果你的应用已经在用 OpenGL ES,用这个版本会更方便。如果用的是 Vulkan,就用 Vulkan 版本。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐