下载APP 随时随地学编程
返回 首页

OpenCV官方文档

基于Android相机预览的CV应用程序中使用OpenCL

本指南旨在帮助您在基于Android相机预览的CV应用程序中使用OpenCL™。它是为基于Eclipse的ADT工具(现在已不再使用Google)编写的,但可以轻松地使用Android Studio进行复制。

本教程假设您已经安装并配置了以下内容:

  • JDK
  • Android SDK和NDK
  • 具有ADT和CDT插件的Eclipse IDE

它还假定您熟悉Android Java和JNI编程基础知识。如果您需要上述任何方面的帮助,您可以参考我们的Android开发入门指南。

本教程还假定您具有启用OpenCL的Android操作设备。

相关的源代码位于OpenCV / samples / android / tutorial-4-opencl目录下的OpenCV示例中。

前言

通过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)Camera API允许使用OpenGL纹理作为预览框架的目标。Android API第21级带来了一个新的Camera2 API,可以更好地控制相机设置和使用模式,特别是允许预览框架和OpenGL纹理的多个目标。

在OpenGL纹理中使用预览框架是一个非常好的使用OpenCL,因为有一个OpenGL-OpenCL互操作性API(cl_khr_gl_sharing),允许与OpenCL功能共享OpenGL纹理数据而不复制(当然有一些限制)。

我们为我们的应用程序创建一个基础,只需配置Android相机将预览帧发送到OpenGL纹理,并在显示屏上显示这些帧,而不进行任何处理。

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 GLSurfaceView {
    MyGLRendererBase mRenderer;
    public MyGLSurfaceView(Context context) {
        super(context);
        if(android.os.Build.VERSION.SDK_INT >= 21)
            mRenderer = new Camera2Renderer(this);
        else
            mRenderer = new CameraRenderer(this);
        setEGLContextClientVersion(2);
        setRenderer(mRenderer);
        setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        super.surfaceCreated(holder);
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        super.surfaceDestroyed(holder);
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        super.surfaceChanged(holder, format, w, h);
    }
    @Override
    public void onResume() {
        super.onResume();
        mRenderer.onResume();
    }
    @Override
    public void onPause() {
        mRenderer.onPause();
        super.onPause();
    }
}

注意:我们使用两个渲染器类:一个用于旧版Camera API,另一个用于现代Camera2。

一个最小的Renderer类可以在Java中实现(OpenGL ES 2.0 在Java中可用),但是由于我们将使用OpenCL修改预览纹理,所以我们将OpenGL的东西移动到JNI。这是一个简单的Java包装器我们的JNI的东西:

public class NativeGLRenderer {
    static
    {
        System.loadLibrary("opencv_java3"); // comment this when using OpenCV Manager
        System.loadLibrary("JNIrender");
    }
    public static native int initGL();
    public static native void closeGL();
    public static native void drawFrame();
    public static native void changeSize(int width, int height);
}

由于CameraCamera2API在相机设置和控制方面有很大不同,我们为两个相应的渲染器创建一个基类:

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("+surfaceWidth+"x"+surfaceHeight+")");
        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和Camera2API应实现以下抽象方法:

protected  abstract  void openCamera();
protected  abstract  void closeCamera();
protected  abstract  void setCameraPreviewSize(int width,int height);

让我们将其实现的细节放在本教程之外,请参考源代码查看。

预览框架修改

OpenGL ES 2.0初始化的细节在这里也很引人瞩目,但重要的一点是,作为相机预览目标的OpeGL纹理应该是类型GL_TEXTURE_EXTERNAL_OES(不是GL_TEXTURE_2D),在内部它保存YUV格式的图像数据。这使得无法通过CL-GL interop(cl_khr_gl_sharing)分享它,并通过C / C ++代码访问其像素数据。为了克服这个限制,我们必须GL_TEXTURE_2D使用FrameBuffer Object(也就是FBO)来执行从这个纹理到另一个常规的OpenGL渲染。

C / C ++代码

之后,我们可以通过C / C ++ 读取(复制)像素数据,glReadPixels()并通过修改后将它们写回纹理glTexSubImage2D()。

直接OpenCL调用

此外,GL_TEXTURE_2D纹理可以与OpenCL共享而不复制,但是我们必须用特殊的方式创建OpenCL上下文:

void 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
    {
        cl::Platform p = cl::Platform::getDefault();
        std::string ext = p.getInfo<CL_PLATFORM_EXTENSIONS>();
        if(ext.find("cl_khr_gl_sharing") == std::string::npos)
            LOGE("Warning: CL-GL sharing isn't supported by PLATFORM");
        props[5] = (cl_context_properties) p();
        theContext = cl::Context(CL_DEVICE_TYPE_GPU, props);
        std::vector<cl::Device> devs = theContext.getInfo<CL_CONTEXT_DEVICES>();
        LOGD("Context returned %d devices, taking the 1st one", devs.size());
        ext = devs[0].getInfo<CL_DEVICE_EXTENSIONS>();
        if(ext.find("cl_khr_gl_sharing") == std::string::npos)
            LOGE("Warning: CL-GL sharing isn't supported by DEVICE");
        theQueue = cl::CommandQueue(theContext, devs[0]);
        // ...
    }
    catch(cl::Error& e)
    {
        LOGE("cl::Error: %s (%d)", e.what(), e.err());
    }
    catch(std::exception& e)
    {
        LOGE("std::exception: %s", e.what());
    }
    catch(...)
    {
        LOGE( "OpenCL info: unknown error while initializing OpenCL stuff" );
    }
    LOGD("initCL completed");
}
注意
要构建此JNI代码,您需要从Khronos网站获取OpenCL 1.2标题,并从您运行应用程序的设备下载libOpenCL.so

然后纹理可以被一个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);
theQueue.enqueueAcquireGLObjects(&images);
theQueue.finish();
cl::Kernel Laplacian = ...
Laplacian.setArg(0, imgIn);
Laplacian.setArg(1, imgOut);
theQueue.finish();
theQueue.enqueueNDRangeKernel(Laplacian, cl::NullRange, cl::NDRange(w, h), cl::NullRange);
theQueue.finish();
theQueue.enqueueReleaseGLObjects(&images);
theQueue.finish();

OpenCV T-API

但是,您可能希望使用OpenCV T-API隐式调用OpenCL 代码,而不是编写OpenCL代码。所有你需要的是将创建的OpenCL上下文传递给OpenCV(via cv::ocl::attachContext()),并以某种方式包装OpenGL纹理cv::UMat。不幸的是在内部UMat保留OpenCL 缓冲区,这不能被OpenGL 纹理或OpenCL 映像包裹- 所以我们必须在这里复制图像数据:

cl::ImageGL imgIn (theContext, CL_MEM_READ_ONLY,  GL_TEXTURE_2D, 0, tex);
std::vector < cl::Memory > images(1, imgIn);
theQueue.enqueueAcquireGLObjects(&images);
theQueue.finish();
cv::UMat uIn, uOut, uTmp;
cv::ocl::convertFromImage(imgIn(), uIn);
theQueue.enqueueReleaseGLObjects(&images);
cv::Laplacian(uIn, uTmp, CV_8U);
cv:multiply(uTmp, 10, uOut);
cv::ocl::finish();
cl::ImageGL imgOut(theContext, CL_MEM_WRITE_ONLY, GL_TEXTURE_2D, 0, tex);
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] = { w, h, 1 };
CV_Assert(clEnqueueCopyBufferToImage (q, clBuffer, imgOut(), offset, origin, region, 0, NULL, NULL) == CL_SUCCESS);
theQueue.enqueueReleaseGLObjects(&images);
cv::ocl::finish();
  • 注意
    当通过OpenCL图像包装器将修改后的图像放置到原始的OpenGL纹理时,我们必须再创建一个图像数据。
  • 注意
    默认情况下,Android OS的OpenCV版本中禁用了OpenCL支持(T-API)(因此在3.0版本的官方软件包中不存在),但是可以在启用OpenCL / T-API的Android上重建本地OpenCV:use -DWITH_OPENCL=YESoption为CMake。
    
cd opencv-build-android
path/to/cmake.exe -GNinja -DCMAKE_MAKE_PROGRAM="path/to/ninja.exe" -DCMAKE_TOOLCHAIN_FILE=path/to/opencv/platforms/android/android.toolchain.cmake -DANDROID_ABI="armeabi-v7a with NEON" -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON path/to/opencv
path/to/ninja.exe install/strip
要使用您自己修改的内容,libopencv_java3.so您必须保留在APK中,不要使用OpenCV Manager并手动加载System.loadLibrary("opencv_java3")

Performance notes

为了比较的性能,我们测量了同一预览帧修改的FPS(拉普拉斯)由C / C ++代码(调用完成cv::Laplaciancv::Mat呼叫(使用的OpenCL),通过直接的OpenCL 图像用于输入和输出),以及由OpenCV的T-API(呼叫要cv::Laplaciancv::UMat)对索尼的Xperia Z3具备720p摄像头分辨率:

  • C / C ++版本显示3-4 fps
  • 直接OpenCL呼叫显示25-27 fps
  • OpenCV的T-API显示11-13 FPS(由于额外的复制从cl_image到cl_buffer和背面)
目录

OpenCV教程

OpenCV入门

OpenCV 在Linux中安装
使用OpenCV与gcc和CMake
使用OpenCV与Eclipse(插件CDT)
OpenCV 在Windows中安装
如何使用OpenCV在“Microsoft Visual Studio”中构建应用程序
Image Watch:在Visual Studio调试器中查看内存中的图像
OpenCV Java开发教程
在Windows中使用OpenCV Java与Eclipse
介绍OpenCV开发与Clojure
OpenCV Android开发教程
OpenCV4 Android SDK
Android开发项目中使用OpenCV库
基于Android相机预览的CV应用程序中使用OpenCL
OpenCV 在iOS中安装教程
基于ARM的Linux系统的交叉编译
OpenCV 图像加载和显示
OpenCV 图像加载,修改和保存
使用OpenCV与biicode dependency manager
为OpenCV编写文档
OpenCV过渡指南

OpenCV iOS

OpenCV iOS Hello
OpenCV iOS-图像处理
OpenCV iOS-视频处理

OpenCV核心功能(核心模块)

Mat-基本图像容器
如何使用OpenCV扫描图像,查找表格和时间测量
OpenCV矩阵上的掩码操作
OpenCV图像操作
使用OpenCV添加(混合)两个图像
OpenCV改变图像的对比度和亮度
OpenCV基本绘图
随机生成器和OpenCV文本
OpenCV离散傅里叶变换
OpenCV文件输入和输出使用XML和YAML文件
与OpenCV 1的互操作性
OpenCV中的英特尔®IPP异步C / C ++库
如何使用OpenCV parallel_for_来并行化代码

OpenCV图像处理(imgproc模块)

OpenCV平滑图像
OpenCV侵蚀和扩张
OpenCV更多形态转化
OpenCV Hit-or-Miss
OpenCV通过使用形态学操作来提取水平和垂直线
OpenCV图像金字塔
OpenCV基本阈值操作
OpenCV使用inRange的阈值操作
OpenCV制作自己的线性滤镜
OpenCV添加边框到您的图像
Sobel衍生物
Laplace Operator
Canny边缘检测器
Hough Line变换
Hough Circle 变换
OpenCV重新映射(Remapping)
OpenCV仿射变换
Opencv直方图同等化(Equalization)
OpenCV直方图计算
OpenCV直方图比较
OpenCV后投影
OpenCV模板匹配
OpenCV在图像中查找轮廓
Convex Hull
OpenCV为轮廓创建边界框和圆
Image Moments
OpenCV点多边形测试
距离变换的图像分割和Watershed算法

OpenCV高级GUI和媒体(highgui模块)

在我们的应用程序中添加一个Trackbar

OpenCV图像输入和输出(imgcodecs模块)

用GDAL读取地理空间栅格文件

OpenCV视频输入和输出(videoio模块)

用OpenCV视频输入和相似度测量
使用OpenCV创建视频
使用Kinect和其他OpenNI兼容的深度传感器
使用Creative Senz3D和其他英特尔感知计算SDK兼容的深度传感器

OpenCV相机校准和3D重建(calib3d模块)

相机校准与方形棋盘
使用OpenCV相机校准
OpenCV纹理对象的实时姿态估计
互动摄像机校准应用

2D特征框架(feature2d模块)

Harris corner检测器
Shi-Tomasi 检测仪
如何创建一个corner检测器
检测子像素中的corners位置
2D特征框架特征检测
2D特征框架功能说明
功能匹配FLANN
Features2D+Homography查找已知对象
OpenCV检测平面物体
AKAZE本地功能匹配
AKAZE和ORB平面跟踪

OpenCV视频分析

OpenCV如何使用背景减法方法

对象检测(objdetect模块)

层叠分类器
层叠分类器训练

深层神经网络(dnn模块)

加载Caffe框架模型
如何启用Halide后端以提高效率
如何为Halide后端安排网络

机器学习(ml模块)

SVM简介
非线性可分离数据的SVM
主成分分析(PCA)简介

计算摄影(照片模块)

高动态范围成像

图像拼接(拼接模块)

高级拼接API(Stitcher类)

GPU加速计算机视觉(cuda模块)

GPU上的相似性检查(PNSR和SSIM)
使用cv :: cuda :: GpuMat推力(thrust)

OpenCV iOS

OpenCV iOS Hello
OpenCV iOS图像处理
OpenCV iOS视频处理

OpenCV Viz

启动Viz
Pose of a widget
OpenCV Viz转换
OpenCV Viz创建小部件
创建3D直方图

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }