- 组装和配置 Android OpenCL SDK。示例的 JNI 部分依赖于标准 Khornos OpenCL 标头,以及 OpenCL 和 libOpenCL.so 的C++包装器。标准 OpenCL 标头可以从 OpenCV 存储库中的第三方目录或 Linux 发行版包中复制。C++包装器在Github上的官方Khronos存储库中可用。按以下方式将头文件复制到专用目录:
libOpenCL.so 可以随 BSP 一起提供,也可以从任何具有相关结构的 OpenCL cabaple Android 设备下载。cd your_path/ && mkdir ANDROID_OPENCL_SDK && mkdir ANDROID_OPENCL_SDK/include && cd ANDROID_OPENCL_SDK/includecp -r path_to_opencv/opencv/3rdparty/include/opencl/1.2/CL 。&& cd CLwget https://github.com/KhronosGroup/OpenCL-CLHPP/raw/main/include/CL/opencl.hppwget https://github.com/KhronosGroup/OpenCL-CLHPP/raw/main/include/CL/cl2.hpplibOpenCL.so 的系统版本可能具有许多特定于平台的依赖项。 标志允许忽略在构建过程中未使用的第三方符号。以下 CMake 行允许将 JNI 部分链接到标准 OpenCL,但不能将 loadLibrary 包含在应用程序包中。系统 OpenCL API 在运行时使用。CD your_path/ANDROID_OPENCL_SDK & MKDIR 库 && CD 库adb 拉 /system/vendor/lib64/libOpenCL.so-Wl,--allow-shlib-undefined
target_link_libraries(${target} -lOpenCL)- 使用 OpenCL 构建自定义 OpenCV Android SDK。默认情况下,OpenCL 支持 (T-API) 在 Android 操作系统的 OpenCV 版本中处于禁用状态。但是可以在启用 OpenCL/T-API 的情况下为 Android 本地重建 OpenCV:使用 CMake 选项。您还需要指定 CMake 的 Android OpenCL SDK: use 选项的路径。如果您正在使用 OpenCV 构建,请按照?wiki 上的说明进行操作。在 中设置这些 CMake 参数,例如:
-DWITH_OPENCL=ON
-DANDROID_OPENCL_SDK=path_to_your_Android_OpenCL_SDK
build_sdk.py
.config.py
ndk-18-api-level-21.config.py
如果你使用 cmake/ninja 构建 OpenCV,请使用这个 bash 脚本(设置你的NDK_VERSION和路径,而不是路径示例):ABI(“3”, “arm64-v8a”, None, 21, cmake_vars=dict('WITH_OPENCL': 'ON', 'ANDROID_OPENCL_SDK': 'path_to_your_Android_OpenCL_SDK'))CD path_to_opencv & & mkdir 构建 && cd 构建导出NDK_VERSION=25.2.9519653导出 ANDROID_SDK=/home/user/Android/Sdk/导出 ANDROID_OPENCL_SDK=/path_to_ANDROID_OPENCL_SDK/导出 ANDROID_HOME=$ANDROID_SDK导出ANDROID_NDK_HOME=$ANDROID_SDK/ndk/$NDK_VERSION/cmake -GNinja -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_STL=c++_shared -DANDROID_NATIVE_API_LEVEL=24-DANDROID_SDK=$ANDROID_SDK -DANDROID_NDK=$ANDROID_NDK_HOME -DBUILD_JAVA=ON -DANDROID_HOME=$ANDROID_SDK -DBUILD_ANDROID_EXAMPLES=ON-DINSTALL_ANDROID_EXAMPLES=ON -DANDROID_ABI=arm64-v8a -DWITH_OPENCL=ON -DANDROID_OPENCL_SDK=$ANDROID_OPENCL_SDK ..
通过 OpenCL 使用?GPGPU?来增强应用程序性能现在已成为一种现代趋势。一些 CV 算法(例如图像过滤)在 GPU 上的运行速度比在 CPU 上快得多。最近,它在 Android 操作系统上成为可能。
Android 操作设备最流行的 CV 应用场景是在预览模式下启动相机,将一些 CV 算法应用于每一帧,并显示由该 CV 算法修改的预览帧。
让我们考虑一下在这种情况下如何使用 OpenCL。具体来说,让我们尝试两种方式:直接调用 OpenCL API 和最近引入的 OpenCV T-API(又名透明 API)——一些 OpenCV 算法的隐式 OpenCL 加速。
从 Android API 级别 11 (Android 3.0)?开始,相机 API?允许使用 OpenGL 纹理作为预览帧的目标。Android API 级别 21 带来了一个新的?Camera2 API,它提供了对相机设置和使用模式的更多控制,它允许预览帧和 OpenGL 纹理的多个目标。
在 OpenGL 纹理中拥有预览帧对于使用 OpenCL 来说是一笔不错的选择,因为有一个?OpenGL-OpenCL 互操作性 API (cl_khr_gl_sharing),允许与 OpenCL 函数共享 OpenGL?纹理数据而无需复制(当然有一些限制)。
让我们为我们的应用程序创建一个基础,它只将 Android 相机配置为将预览帧发送到 OpenGL 纹理,并在显示时显示这些帧,而无需任何处理。
用于此目的的最小类如下所示:Activity
?
公共类 Tutorial4Activity 扩展 Activity {
私人MyGLSurfaceView mView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
mView = 新的 MyGLSurfaceView(这个);
setContentView(mView);
}
@Override
受保护的无效 onPause() {
mView.onPause();
super.onPause();
}
@Override
受保护的无效 onResume() {
super.onResume();
mView.onResume();
}
}
和最小类:View
public class MyGLSurfaceView 扩展了 CameraGLSurfaceView 实现 CameraGLSurfaceView.CameraTextureListener {
static final String LOGTAG = “MyGLSurfaceView”;
受保护的 int procMode = NativePart.PROCESSING_MODE_NO_PROCESSING;
static final String[] procModeName = new String[] {“无处理”, “CPU”, “OpenCL Direct”, “OpenCL via OpenCV” };
受保护的 int frameCounter;
保护时间长NanoTime;
TextView mFpsText = 空;
公共MyGLSurfaceView(上下文,属性集属性){
super(上下文,属性);
}
@Override
公共布尔值 onTouchEvent(MotionEvent e) {
if(e.getAction() == MotionEvent.ACTION_DOWN)
((活动)getContext()).openOptionsMenu();
返回 true;
}
@Override
公共空隙 surfaceCreated(SurfaceHolder holder) {
super.surfaceCreated(持有人);
NativePart.initCL();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
NativePart.closeCL();
super.surfaceDestroyed(持有人);
}
公共无效 setProcessingMode(int newMode) {
if(newMode>=0 && newMode<procModeName.length)
procMode = 新模式;
还
Log.e(LOGTAG, “忽略无效的处理模式: ” + newMode);
((活动) getContext()).runOnUiThread(new Runnable() {
公共无效运行() {
Toast.makeText(getContext(), “选择模式: ” + procModeName[procMode], Toast.LENGTH_LONG).show();
}
});
}
@Override
public void onCameraViewStarted(int width, int height) {
((活动) getContext()).runOnUiThread(new Runnable() {
公共无效运行() {
Toast.makeText(getContext(), “onCameraViewStarted”, Toast.LENGTH_SHORT).show();
}
});
如果 (NativePart.builtWithOpenCL())
NativePart.initCL();
frameCounter = 0;
lastNanoTime = System.nanoTime();
}
@Override
公共无效 onCameraViewStopped() {
((活动) getContext()).runOnUiThread(new Runnable() {
公共无效运行() {
Toast.makeText(getContext(), “onCameraViewStopped”, Toast.LENGTH_SHORT).show();
}
});
}
@Override
公共布尔值 onCameraTexture(int texIn, int texOut, int width, int height) {
转数快
frameCounter++;
if(frameCounter >= 30)
{
final int fps = (int) (frameCounter * 1e9 / (System.nanoTime() - lastNanoTime));
Log.i(LOGTAG, “drawFrame() FPS: ”+fps);
if(mFpsText != null) {
Runnable fpsUpdater = new Runnable() {
公共无效运行() {
mFpsText.setText(“FPS: ” + fps);
}
};
新增功能处理程序(Looper.getMainLooper()).post(fpsUpdater);
} 还 {
Log.d(LOGTAG, “mFpsText == null”);
mFpsText = (TextView)((活动) getContext()).findViewById(R.id.fps_text_view);
}
frameCounter = 0;
lastNanoTime = System.nanoTime();
}
if(procMode == NativePart.PROCESSING_MODE_NO_PROCESSING)
返回 false;
NativePart.processFrame(texIn, texOut, width, height, procMode);
返回 true;
}
}
?
注意
我们使用两个渲染器类:一个用于旧版 Camera?API,另一个用于现代?Camera2。
最小的类可以在 Java 中实现(OpenGL ES 2.0 在 Java 中可用),但由于我们将使用 OpenCL 修改预览纹理,因此让我们将 OpenGL 内容移动到 JNI。下面是 JNI 内容的简单 Java 包装器:Renderer
公共类 NativePart {
静态的
{
System.loadLibrary(“opencv_java4”);
System.loadLibrary(“JNIpart”));
}
public static final int PROCESSING_MODE_NO_PROCESSING = 0;
公共静态最终 int PROCESSING_MODE_CPU = 1;
public static final int PROCESSING_MODE_OCL_DIRECT = 2;
public static final int PROCESSING_MODE_OCL_OCV = 3;
公共静态原生布尔值 builtWithOpenCL();
公共静态原生 int initCL();
公共静态原生 void closeCL();
public static native void processFrame(int tex1, int tex2, int w, int h, int mode);
}
由于 和 API 在相机设置和控制方面存在显著差异,因此让我们为两个相应的渲染器创建一个基类:Camera
Camera2
?
公共抽象类 MyGLRendererBase 实现 GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
受保护的最终字符串 LOGTAG = “MyGLRendererBase”;
保护表面纹理 mSTex;
保护MyGLSurfaceView mView;
受保护的布尔值 mGLInit = false;
受保护的布尔值 mTexUpdate = false;
MyGLRendererBase(MyGLSurfaceView 视图) {
mView = 视图;
}
受保护的抽象 void openCamera();
受保护的抽象 void closeCamera();
受保护的抽象 void setCameraPreviewSize(int width, int height);
公共无效 onResume() {
Log.i(LOGTAG, “onResume”);
}
公共无效 onPause() {
Log.i(LOGTAG, “onPause”);
mGLInit = 假;
mTexUpdate = 假;
closeCamera();
if(mSTex != 空) {
mSTex.release();
mSTex = 空;
NativeGLRenderer.closeGL();
}
}
@Override
公共同步 void onFrameAvailable(SurfaceTexture surfaceTexture) {
Log.i(LOGTAG, “onFrameAvailable”);
mTexUpdate = 真;
mView.requestRender();
}
@Override
公共无效 onDrawFrame(GL10 gl) {
Log.i(LOGTAG, “onDrawFrame”);
如果 (!mGLInit)
返回;
已同步(此) {
如果 (mTexUpdate) {
mSTex.updateTexImage();
mTexUpdate = 假;
}
}
NativeGLRenderer.drawFrame();
}
@Override
公共空隙 onSurfaceChanged(GL10 gl, int surfaceWidth, int surfaceHeight) {
Log.i(LOGTAG, “onSurfaceChanged(”+surfaceWidth+“x”+surfaceHeight+")");
NativeGLRenderer.changeSize(surfaceWidth, surfaceHeight);
setCameraPreviewSize(surfaceWidth, surfaceHeight);
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig 配置) {
Log.i(LOGTAG, “onSurfaceCreated”);
字符串 strGLVersion = GLES20.glGetString(GLES20.GL_VERSION);
if (strGLVersion != null)
Log.i(LOGTAG, “OpenGL ES 版本: ” + strGLVersion);
int hTex = NativeGLRenderer.initGL();
mSTex = 新 SurfaceTexture(hTex);
mSTex.setOnFrameAvailableListener(这个);
openCamera();
mGLInit = 真;
}
}
?
如您所见,和 API 的继承者应该实现以下抽象方法:Camera
Camera2
受保护的抽象 void openCamera();受保护的抽象 void closeCamera();受保护的抽象 void setCameraPreviewSize(int width, int height);
让我们在本教程之外留下它们实现的细节,请参考源代码查看它们。
OpenGL ES 2.0 初始化的细节也相当简单和嘈杂,但这里重要的一点是,作为相机预览目标的 OpeGL 纹理应该是类型(不是),内部它以?YUV?格式保存图片数据。这使得无法通过 CL-GL 互操作 () 共享它并通过 C/C++ 代码访问其像素数据。为了克服这个限制,我们必须使用?FrameBuffer 对象(又名 FBO)将 OpenGL 渲染从这个纹理到另一个常规纹理。GL_TEXTURE_EXTERNAL_OES
GL_TEXTURE_2D
cl_khr_gl_sharing
GL_TEXTURE_2D
之后,我们可以通过 从 C/C++ 读取(复制)像素数据,并在修改后通过 将它们写回纹理。glReadPixels()
glTexSubImage2D()
此外,该纹理可以在不复制的情况下与 OpenCL 共享,但我们必须为此以特殊方式创建 OpenCL 上下文:GL_TEXTURE_2D
?
int initCL()
{
dumpCLinfo();
LOGE(“initCL:启动initCL”);
EGLDisplay mEglDisplay = eglGetCurrentDisplay();
如果 (mEglDisplay == EGL_NO_DISPLAY)
LOGE(“initCL: eglGetCurrentDisplay() returned 'EGL_NO_DISPLAY', error = %x”, eglGetError());
EGLContext mEglContext = eglGetCurrentContext();
如果 (mEglContext == EGL_NO_CONTEXT)
LOGE(“initCL: eglGetCurrentContext() returned 'EGL_NO_CONTEXT', error = %x”, eglGetError());
cl_context_properties props[] =
{ CL_GL_CONTEXT_KHR, (cl_context_properties) mEglContext,
CL_EGL_DISPLAY_KHR、(cl_context_properties) mEglDisplay、
CL_CONTEXT_PLATFORM、0、
0 };
尝试
{
haveOpenCL = 假;
cl::P latform p = cl::P latform::getDefault();
std::string ext = p.getInfo<CL_PLATFORM_EXTENSIONS>();
if(ext.find(“cl_khr_gl_sharing”) == std::string::npos)
LOGE(“警告:PLATFORM不支持CL-GL共享”);
道具[5] = (cl_context_properties) p();
theContext = cl::Context(CL_DEVICE_TYPE_GPU, props);
std::vector<cl::D evice> devs = theContext.getInfo<CL_CONTEXT_DEVICES>();
LOGD(“上下文返回了 %d 个设备,取第一个设备”, devs.size());
ext = devs[0].getInfo<CL_DEVICE_EXTENSIONS>();
if(ext.find(“cl_khr_gl_sharing”) == std::string::npos)
LOGE(“警告:DEVICE不支持CL-GL共享”);
theQueue = cl::CommandQueue(theContext, devs[0]);
cl::P rogram::Sources src(1, std::make_pair(oclProgI2I, sizeof(oclProgI2I)));
theProgI2I = cl::P rogram(theContext, src);
progI2I.build(devs);
cv::ocl::attachContext(p.getInfo<CL_PLATFORM_NAME>(), p(), theContext(), devs[0]());
if( cv::ocl::useOpenCL() )
LOGD(“OpenCV+OpenCL 工作正常!”);
还
LOGE(“无法使用 OpenCL TAPI 初始化 OpenCV”);
haveOpenCL = 真;
}
catch(const cl::Error&e)
{
LOGE(“cl::错误:%s (%d)”, e.what(), e.err());
返回 1;
}
catch(const std::exception&e)
{
LOGE(“std::exception: %s”, e.what());
返回 2;
}
捕获(...)
{
LOGE( “OpenCL 信息:初始化 OpenCL 内容时出现未知错误” );
返回 3;
}
LOGD(“initCL 已完成”);
如果(haveOpenCL)
返回 0;
还
返回 4;
}
然后,纹理可以被对象包装,并通过 OpenCL 调用进行处理:cl::ImageGL
cl::ImageGL imgIn (theContext, CL_MEM_READ_ONLY, GL_TEXTURE_2D, 0, texIn);
cl::ImageGL imgOut(theContext, CL_MEM_WRITE_ONLY, GL_TEXTURE_2D, 0, texOut);
std::vector < cl::Memory >图像;
images.push_back(imgIn);
images.push_back(imgOut);
int64_t t = getTimeMs();
theQueue.enqueueAcquireGLObjects(&images);
theQueue.finish();
LOGD(“enqueueAcquireGLObjects() 成本 %d ms”, getTimeInterval(t));
t = getTimeMs();
cl::Kernel Laplacian(theProgI2I, “拉普拉斯”);TODO:可以做一次
拉普拉斯.setArg(0, imgIn);
Laplacian.setArg(1, imgOut);
theQueue.finish();
LOGD(“Kernel() 成本 %d ms”, getTimeInterval(t));
t = getTimeMs();
theQueue.enqueueNDRangeKernel(拉普拉斯, cl::NullRange, cl::NDRange(w, h), cl::NullRange);
theQueue.finish();
LOGD(“enqueueNDRangeKernel() 成本 %d 毫秒”, getTimeInterval(t));
t = getTimeMs();
theQueue.enqueueReleaseGLObjects(&images);
theQueue.finish();
LOGD(“enqueueReleaseGLObjects() 成本 %d ms”, getTimeInterval(t));
?
但是,您可能希望使用隐式调用 OpenCL 的?OpenCV T-API,而不是自己编写 OpenCL 代码。您只需要将创建的 OpenCL 上下文传递给 OpenCV(通过 ),并以某种方式将 OpenGL 纹理包装成 .不幸的是,OpenCL?缓冲区在内部保留,无法包裹在 OpenGL?纹理或 OpenCL?图像上 - 因此我们必须在此处复制图像数据:cv::ocl::attachContext()
cv::UMat
UMat
LOGD(“将结果上传到纹理成本 %d ms”, getTimeInterval(t));
注意
当通过 OpenCL 图像包装器将修改后的图像放回原始 OpenGL 纹理时,我们必须再复制一个图像数据。
为了比较我们在索尼Xperia Z3上以720p相机分辨率测量的相同预览帧修改(拉普拉斯)的性能,这些修改是通过C / C++代码(调用with),直接OpenCL调用(使用OpenCL图像进行输入和输出)和OpenCV?T-API(调用with)完成的:cv::Laplacian
cv::Mat
cv::Laplacian
cv::UMat
cl_image
cl_buffer