网站开发用台式机电脑配置,山东省品牌专业群建设网站,网站开发的工作内容,北京网页游戏制作查看#xff1a;OpenCV系列文章目录#xff08;持续更新中......#xff09;
上一篇#xff1a;OpenCV4.9.0在Android 开发简介
下一篇#xff1a;在 MacOS 中安装 本指南旨在帮助您在基于 Android 相机预览的 CV 应用程序中使用 OpenCL ™。教程是为 Android Studio 20… 查看OpenCV系列文章目录持续更新中......
上一篇OpenCV4.9.0在Android 开发简介
下一篇在 MacOS 中安装 本指南旨在帮助您在基于 Android 相机预览的 CV 应用程序中使用 OpenCL ™。教程是为 Android Studio 2022.2.1 编写的。它已使用 Ubuntu 22.04 进行了测试。
本教程假定您已安装并配置了以下内容
Android Studio (2022.2.1.)JDK 17Android SDKAndroid NDK (25.2.9519653)从 github 或发布版下载 OpenCV 源代码并按照 wiki 上的指令构建。
它还假定您熟悉 Android Java 和 JNI 编程基础知识。如果您需要上述任何方面的帮助可以参考我们的 Android 开发简介指南。
本教程还假设您有一个启用了 OpenCL 的 Android 操作设备。
相关源代码位于 opencv/samples/android/tutorial-4-opencl 目录下的 OpenCV 示例中。
如何使用 OpenCL 构建自定义 OpenCV Android SDK
组装和配置 Android OpenCL SDK。示例的 JNI 部分依赖于标准的 Khornos OpenCL 标头以及 OpenCL 和 libOpenCL.so 的C包装器。标准的 OpenCL 标头可以从 OpenCV 存储库中的第三方目录或您的 Linux 分发包中复制。C 包装器可在 Github 上的官方 Khronos 存储库中找到。按以下方式将头文件复制到教学目录 cd your_path/ mkdir ANDROID_OPENCL_SDK mkdir ANDROID_OPENCL_SDK/include cd ANDROID_OPENCL_SDK/include
cp -r path_to_opencv/opencv/3rdparty/include/opencl/1.2/CL . cd CL
wget https://github.com/KhronosGroup/OpenCL-CLHPP/raw/main/include/CL/opencl.hpp
wget https://github.com/KhronosGroup/OpenCL-CLHPP/raw/main/include/CL/cl2.hpp libOpenCL.so 可以随 BSP 一起提供也可以从任何具有相关架构的 OpenCL-cabaple Android 设备下载 cd your_path/ANDROID_OPENCL_SDK mkdir lib cd lib
adb pull /system/vendor/lib64/libOpenCL.so libOpenCL.so 的系统版本可能有很多特定于平台的依赖关系。-Wl,--allow-shlib-undefined 标志允许忽略在构建过程中未使用的第三方符号。以下 CMake 行允许将 JNI 部件链接到标准 OpenCL但不能将 loadLibrary 包含在应用程序包中。系统 OpenCL API 用于运行时。 target_link_libraries(${target} -lOpenCL) 使用 OpenCL 构建自定义 OpenCV Android SDK。默认情况下OpenCL 支持 T-API 在 Android 操作系统的 OpenCV 构建中处于禁用状态。但可以在启用 OpenCL/T-API 的情况下在本地重建适用于 Android 的 OpenCVCMake 的 use 选项。您还需要为 CMake 指定 Android OpenCL SDK use 选项的路径。如果您正在使用 OpenCV 构建 OpenCV请按照 wiki 上的说明进行操作。在 中设置这些 CMake 参数例如-DWITH_OPENCLON-DANDROID_OPENCL_SDKpath_to_your_Android_OpenCL_SDKbuild_sdk.py.config.pyndk-18-api-level-21.config.py
ABI(3, arm64-v8a, None, 21, cmake_varsdict(WITH_OPENCL: ON, ANDROID_OPENCL_SDK: path_to_your_Android_OpenCL_SDK))
如果您使用 cmake/ninja 构建 OpenCV请使用以下 bash 脚本设置您的NDK_VERSION和路径而不是路径示例
cd path_to_opencv mkdir build cd build
export NDK_VERSION25.2.9519653
export ANDROID_SDK/home/user/Android/Sdk/
export ANDROID_OPENCL_SDK/path_to_ANDROID_OPENCL_SDK/
export ANDROID_HOME$ANDROID_SDK
export ANDROID_NDK_HOME$ANDROID_SDK/ndk/$NDK_VERSION/
cmake -GNinja -DCMAKE_TOOLCHAIN_FILE$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_STLc_shared -DANDROID_NATIVE_API_LEVEL24
-DANDROID_SDK$ANDROID_SDK -DANDROID_NDK$ANDROID_NDK_HOME -DBUILD_JAVAON -DANDROID_HOME$ANDROID_SDK -DBUILD_ANDROID_EXAMPLESON
-DINSTALL_ANDROID_EXAMPLESON -DANDROID_ABIarm64-v8a -DWITH_OPENCLON -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如下所示Activity
public class Tutorial4Activity extends Activity {
private 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 new MyGLSurfaceView(this);
setContentView(mView);
}
Override
protected void onPause() {
mView.onPause();
super.onPause();
}
Override
protected void onResume() {
super.onResume();
mView.onResume();
}
}
和最小的类View分别是
public class MyGLSurfaceView extends CameraGLSurfaceView implements CameraGLSurfaceView.CameraTextureListener {
static final String LOGTAG MyGLSurfaceView;
protected int procMode NativePart.PROCESSING_MODE_NO_PROCESSING;
static final String[] procModeName new String[] {No Processing, CPU, OpenCL Direct, OpenCL via OpenCV};
protected int frameCounter;
protected long lastNanoTime;
TextView mFpsText null;
public MyGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
}
Override
public boolean onTouchEvent(MotionEvent e) {
if(e.getAction() MotionEvent.ACTION_DOWN)
((Activity)getContext()).openOptionsMenu();
return true;
}
Override
public void surfaceCreated(SurfaceHolder holder) {
super.surfaceCreated(holder);
//NativePart.initCL();
}
Override
public void surfaceDestroyed(SurfaceHolder holder) {
//NativePart.closeCL();
super.surfaceDestroyed(holder);
}
public void setProcessingMode(int newMode) {
if(newMode0 newModeprocModeName.length)
procMode newMode;
else
Log.e(LOGTAG, Ignoring invalid processing mode: newMode);
((Activity) getContext()).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getContext(), Selected mode: procModeName[procMode], Toast.LENGTH_LONG).show();
}
});
}
Override
public void onCameraViewStarted(int width, int height) {
((Activity) getContext()).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getContext(), onCameraViewStarted, Toast.LENGTH_SHORT).show();
}
});
if (NativePart.builtWithOpenCL())
NativePart.initCL();
frameCounter 0;
lastNanoTime System.nanoTime();
}
Override
public void onCameraViewStopped() {
((Activity) getContext()).runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(getContext(), onCameraViewStopped, Toast.LENGTH_SHORT).show();
}
});
}
Override
public boolean onCameraTexture(int texIn, int texOut, int width, int height) {
// FPS
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() {
public void run() {
mFpsText.setText(FPS: fps);
}
};
new Handler(Looper.getMainLooper()).post(fpsUpdater);
} else {
Log.d(LOGTAG, mFpsText null);
mFpsText (TextView)((Activity) getContext()).findViewById(R.id.fps_text_view);
}
frameCounter 0;
lastNanoTime System.nanoTime();
}
if(procMode NativePart.PROCESSING_MODE_NO_PROCESSING)
return false;
NativePart.processFrame(texIn, texOut, width, height, procMode);
return true;
}
}
注意
我们使用两个渲染器类一个用于旧版 Camera API另一个用于现代 Camera2。
一个最小的类Renderer可以在 Java 中实现OpenGL ES 2.0 在 Java 中可用但由于我们将使用 OpenCL 修改预览纹理因此让我们将 OpenGL 的东西移动到 JNI。下面是 JNI 内容的简单 Java 包装器
public class NativePart {
static
{
System.loadLibrary(opencv_java4);
System.loadLibrary(JNIpart);
}
public static final int PROCESSING_MODE_NO_PROCESSING 0;
public static final int PROCESSING_MODE_CPU 1;
public static final int PROCESSING_MODE_OCL_DIRECT 2;
public static final int PROCESSING_MODE_OCL_OCV 3;
public static native boolean builtWithOpenCL();
public static native int initCL();
public static native void closeCL();
public static native void processFrame(int tex1, int tex2, int w, int h, int mode);
}
由于 Camera 和Camera2 API 在相机设置和控制方面存在很大差异因此让我们为两个相应的渲染器创建一个基类
public abstract class MyGLRendererBase implements GLSurfaceView.Renderer, SurfaceTexture.OnFrameAvailableListener {
protected final String LOGTAG MyGLRendererBase;
protected SurfaceTexture mSTex;
protected MyGLSurfaceView mView;
protected boolean mGLInit false;
protected boolean mTexUpdate false;
MyGLRendererBase(MyGLSurfaceView view) {
mView view;
}
protected abstract void openCamera();
protected abstract void closeCamera();
protected abstract void setCameraPreviewSize(int width, int height);
public void onResume() {
Log.i(LOGTAG, onResume);
}
public void onPause() {
Log.i(LOGTAG, onPause);
mGLInit false;
mTexUpdate false;
closeCamera();
if(mSTex ! null) {
mSTex.release();
mSTex null;
NativeGLRenderer.closeGL();
}
}
Override
public synchronized void onFrameAvailable(SurfaceTexture surfaceTexture) {
//Log.i(LOGTAG, onFrameAvailable);
mTexUpdate true;
mView.requestRender();
}
Override
public void onDrawFrame(GL10 gl) {
//Log.i(LOGTAG, onDrawFrame);
if (!mGLInit)
return;
synchronized (this) {
if (mTexUpdate) {
mSTex.updateTexImage();
mTexUpdate false;
}
}
NativeGLRenderer.drawFrame();
}
Override
public void onSurfaceChanged(GL10 gl, int surfaceWidth, int surfaceHeight) {
Log.i(LOGTAG, onSurfaceChanged(surfaceWidthxsurfaceHeight));
NativeGLRenderer.changeSize(surfaceWidth, surfaceHeight);
setCameraPreviewSize(surfaceWidth, surfaceHeight);
}
Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
Log.i(LOGTAG, onSurfaceCreated);
String strGLVersion GLES20.glGetString(GLES20.GL_VERSION);
if (strGLVersion ! null)
Log.i(LOGTAG, OpenGL ES version: strGLVersion);
int hTex NativeGLRenderer.initGL();
mSTex new SurfaceTexture(hTex);
mSTex.setOnFrameAvailableListener(this);
openCamera();
mGLInit true;
}
}
如您所见 Camera 和 Camera2 APIs的继承者应实现以下抽象方法
protected abstract void openCamera();
protected abstract void closeCamera();
protected abstract void setCameraPreviewSize(int width, int height);
让我们把它们实现的细节留给本教程之外请参考源代码查看它们。
预览帧修改
OpenGL ES 2.0 初始化的细节也相当简单明了这里要引用的嘈杂但这里重要的一点是作为相机预览目标的 OpeGL 纹理应该是类型不是在内部它以 YUV 格式保存图片数据。这使得无法通过 CL-GL 互操作 共享它并通过 C/C 代码访问其像素数据。为了克服这个限制我们必须使用 FrameBuffer 对象又名 FBO执行从这个纹理到另一个常规纹理的 OpenGL 渲染
OpenGL ES 2.0 初始化的细节也相当简单明了这里要引用的嘈杂但这里重要的一点是作为相机预览目标的 OpeGL 纹理应该是类型GL_TEXTURE_EXTERNAL_OES不是GL_TEXTURE_2D在内部它以 YUV 格式保存图片数据。这使得无法通过 CL-GL cl_khr_gl_sharing互操作 共享它并通过 C/C 代码访问其像素数据。为了克服这个限制我们必须使用 FrameBuffer 对象又名 FBO执行从这个纹理GL_TEXTURE_2D到另一个常规纹理的 OpenGL 渲染。
C/C code
之后我们可以从 C/C 读取 glReadPixels()复制像素数据并通过修改后将它们写回纹理 glTexSubImage2D()。
直接 OpenCL 调用
此外该纹理可以在不复制的情况下与 OpenCL 共享但我们必须以特殊方式创建 OpenCL context如下
int initCL()
{
dumpCLinfo();
LOGE(initCL: start initCL);
EGLDisplay mEglDisplay eglGetCurrentDisplay();
if (mEglDisplay EGL_NO_DISPLAY)
LOGE(initCL: eglGetCurrentDisplay() returned EGL_NO_DISPLAY, error %x, eglGetError());
EGLContext mEglContext eglGetCurrentContext();
if (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 };
try
{
haveOpenCL false;
cl::Platform p cl::Platform::getDefault();
std::string ext p.getInfoCL_PLATFORM_EXTENSIONS();
if(ext.find(cl_khr_gl_sharing) std::string::npos)
LOGE(Warning: CL-GL sharing isnt supported by PLATFORM);
props[5] (cl_context_properties) p();
theContext cl::Context(CL_DEVICE_TYPE_GPU, props);
std::vectorcl::Device devs theContext.getInfoCL_CONTEXT_DEVICES();
LOGD(Context returned %d devices, taking the 1st one, devs.size());
ext devs[0].getInfoCL_DEVICE_EXTENSIONS();
if(ext.find(cl_khr_gl_sharing) std::string::npos)
LOGE(Warning: CL-GL sharing isnt supported by DEVICE);
theQueue cl::CommandQueue(theContext, devs[0]);
cl::Program::Sources src(1, std::make_pair(oclProgI2I, sizeof(oclProgI2I)));
theProgI2I cl::Program(theContext, src);
theProgI2I.build(devs);
cv::ocl::attachContext(p.getInfoCL_PLATFORM_NAME(), p(), theContext(), devs[0]());
if( cv::ocl::useOpenCL() )
LOGD(OpenCVOpenCL works OK!);
else
LOGE(Cant init OpenCV with OpenCL TAPI);
haveOpenCL true;
}
catch(const cl::Error e){
LOGE(cl::Error: %s (%d), e.what(), e.err());
return 1;
}
catch(const std::exception e)
{
LOGE(std::exception: %s, e.what());
return 2;
}
catch(...)
{
LOGE( OpenCL info: unknown error while initializing OpenCL stuff );
return 3;
}
LOGD(initCL completed);
if (haveOpenCL)
return 0;
else
return 4;
}
然后纹理可以被对象包装 cl::ImageGL并通过 OpenCL 调用进行处理
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;
images.push_back(imgIn);
images.push_back(imgOut);
int64_t t getTimeMs();
theQueue.enqueueAcquireGLObjects(images);
theQueue.finish();
LOGD(enqueueAcquireGLObjects() costs %d ms, getTimeInterval(t));
t getTimeMs();
cl::Kernel Laplacian(theProgI2I, Laplacian); //TODO: may be done once
Laplacian.setArg(0, imgIn);
Laplacian.setArg(1, imgOut);
theQueue.finish();
LOGD(Kernel() costs %d ms, getTimeInterval(t));
t getTimeMs();
theQueue.enqueueNDRangeKernel(Laplacian, cl::NullRange, cl::NDRange(w, h), cl::NullRange);
theQueue.finish();
LOGD(enqueueNDRangeKernel() costs %d ms, getTimeInterval(t));
t getTimeMs();
theQueue.enqueueReleaseGLObjects(images);
theQueue.finish();
LOGD(enqueueReleaseGLObjects() costs %d ms, getTimeInterval(t));
OpenCV T-API
但是与其自己编写 OpenCL 代码不如使用隐式调用 OpenCL 的 OpenCV T-API。您只需要将创建的 OpenCL 上下文传递给 OpenCV通过cv::ocl::attachContext() 并以某种 cv::UMat.方式将 OpenGL 纹理包装起来。不幸的是OpenCL 缓冲区在内部保留它不能包装在 OpenGL 纹理或 OpenCL 图像上 - 因此我们必须在此处复制图像数据
int64_t t getTimeMs();
cl::ImageGL imgIn (theContext, CL_MEM_READ_ONLY, GL_TEXTURE_2D, 0, texIn);
std::vector cl::Memory images(1, imgIn);
theQueue.enqueueAcquireGLObjects(images);
theQueue.finish();
cv::UMat uIn, uOut, uTmp;
cv::ocl::convertFromImage(imgIn(), uIn);
LOGD(loading texture data to OpenCV UMat costs %d ms, getTimeInterval(t));
theQueue.enqueueReleaseGLObjects(images);
t getTimeMs();
//cv::blur(uIn, uOut, cv::Size(5, 5));
cv::Laplacian(uIn, uTmp, CV_8U);
cv:multiply(uTmp, 10, uOut);
cv::ocl::finish();
LOGD(OpenCV processing costs %d ms, getTimeInterval(t));
t getTimeMs();
cl::ImageGL imgOut(theContext, CL_MEM_WRITE_ONLY, GL_TEXTURE_2D, 0, texOut);
images.clear();
images.push_back(imgOut);
theQueue.enqueueAcquireGLObjects(images);
cl_mem clBuffer (cl_mem)uOut.handle(cv::ACCESS_READ);
cl_command_queue q (cl_command_queue)cv::ocl::Queue::getDefault().ptr();
size_t offset 0;
size_t origin[3] { 0, 0, 0 };
size_t region[3] { (size_t)w, (size_t)h, 1 };
CV_Assert(clEnqueueCopyBufferToImage (q, clBuffer, imgOut(), offset, origin, region, 0, NULL, NULL) CL_SUCCESS);
theQueue.enqueueReleaseGLObjects(images);
cv::ocl::finish();
LOGD(uploading results to texture costs %d ms, getTimeInterval(t));
注意
当通过 OpenCL 图像包装器将修改后的图像放回原始 OpenGL 纹理时我们必须再制作一个图像数据副本。
性能说明
为了比较在具有720p相机分辨率的Sony Xperia Z3上通过C / C代码调用cv::Laplacian与cv::Mat直接OpenCL调用使用OpenCL图像进行输入和输出和OpenCV T-API调用cv::Laplacian与cv::UMat完成的相同预览帧修改Laplacian的FPS
C/C 版本显示 3-4 fps直接 OpenCL 调用显示 25-27 fpsOpenCV T-API 显示 11-13 fps由于额外的来回复制cl_imagecl_buffer 参考文献
1、《Use OpenCL in Android camera preview based CV application》 Andrey Pavlenko, Alexander Panov