織夢(mèng)做的網(wǎng)站首頁(yè)被篡改企業(yè)網(wǎng)頁(yè)
?查看:OpenCV系列文章目錄(持續(xù)更新中......)
上一篇:OpenCV4.9.0在Android 開(kāi)發(fā)簡(jiǎn)介
下一篇:在 MacOS 中安裝
本指南旨在幫助您在基于 Android 相機(jī)預(yù)覽的 CV 應(yīng)用程序中使用?OpenCL ?。教程是為?Android Studio?2022.2.1 編寫的。它已使用 Ubuntu 22.04 進(jìn)行了測(cè)試。
本教程假定您已安裝并配置了以下內(nèi)容:
- Android Studio (2022.2.1.+)
- JDK 17
- Android SDK
- Android NDK (25.2.9519653+)
- 從?github?或發(fā)布版下載 OpenCV 源代碼,并按照?wiki 上的指令構(gòu)建。
它還假定您熟悉 Android Java 和 JNI 編程基礎(chǔ)知識(shí)。如果您需要上述任何方面的幫助,可以參考我們的?Android 開(kāi)發(fā)簡(jiǎn)介指南。
本教程還假設(shè)您有一個(gè)啟用了 OpenCL 的 Android 操作設(shè)備。
相關(guān)源代碼位于?opencv/samples/android/tutorial-4-opencl?目錄下的 OpenCV 示例中。
如何使用 OpenCL 構(gòu)建自定義 OpenCV Android SDK
- 組裝和配置 Android OpenCL SDK。示例的 JNI 部分依賴于標(biāo)準(zhǔn)的 Khornos OpenCL 標(biāo)頭,以及 OpenCL 和 libOpenCL.so 的C++包裝器。標(biāo)準(zhǔn)的 OpenCL 標(biāo)頭可以從 OpenCV 存儲(chǔ)庫(kù)中的第三方目錄或您的 Linux 分發(fā)包中復(fù)制。C++ 包裝器可在?Github 上的官方 Khronos 存儲(chǔ)庫(kù)中找到。按以下方式將頭文件復(fù)制到教學(xué)目錄:
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
cd your_path/ANDROID_OPENCL_SDK && mkdir lib && cd lib adb pull /system/vendor/lib64/libOpenCL.so
-Wl,--allow-shlib-undefined
標(biāo)志允許忽略在構(gòu)建過(guò)程中未使用的第三方符號(hào)。以下 CMake 行允許將 JNI 部件鏈接到標(biāo)準(zhǔn) OpenCL,但不能將 loadLibrary 包含在應(yīng)用程序包中。系統(tǒng) OpenCL API 用于運(yùn)行時(shí)。
target_link_libraries(${target} -lOpenCL)
使用 OpenCL 構(gòu)建自定義 OpenCV Android SDK。默認(rèn)情況下,OpenCL 支持 (T-API) 在 Android 操作系統(tǒng)的 OpenCV 構(gòu)建中處于禁用狀態(tài)。但可以在啟用 OpenCL/T-API 的情況下在本地重建適用于 Android 的 OpenCV:CMake 的 use 選項(xiàng)。您還需要為 CMake 指定 Android OpenCL SDK: use 選項(xiàng)的路徑。如果您正在使用 OpenCV 構(gòu)建 OpenCV,請(qǐng)按照?wiki 上的說(shuō)明進(jìn)行操作。在 中設(shè)置這些 CMake 參數(shù),例如:-DWITH_OPENCL=ON
-DANDROID_OPENCL_SDK=path_to_your_Android_OpenCL_SDK
build_sdk.py
.config.py
ndk-18-api-level-21.config.py
ABI("3", "arm64-v8a", None, 21, cmake_vars=dict('WITH_OPENCL': 'ON', 'ANDROID_OPENCL_SDK': 'path_to_your_Android_OpenCL_SDK'))
如果您使用 cmake/ninja 構(gòu)建 OpenCV,請(qǐng)使用以下 bash 腳本(設(shè)置您的NDK_VERSION和路徑,而不是路徑示例):
cd path_to_opencv && mkdir build && cd build
export NDK_VERSION=25.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_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 ..
前言
現(xiàn)在,通過(guò) OpenCL 使用?GPGPU?來(lái)增強(qiáng)應(yīng)用程序性能是一種相當(dāng)現(xiàn)代的趨勢(shì)。一些CV算法(例如圖像過(guò)濾)在GPU上的運(yùn)行速度比在CPU上快得多。最近,它在 Android 操作系統(tǒng)上已成為可能。
對(duì)于 Android 操作的設(shè)備,最流行的 CV 應(yīng)用場(chǎng)景是在預(yù)覽模式下啟動(dòng)相機(jī),將一些 CV 算法應(yīng)用于每個(gè)幀,并顯示由該 CV 算法修改的預(yù)覽幀。
讓我們考慮一下如何在這種情況下使用 OpenCL。具體來(lái)說(shuō),讓我們嘗試兩種方式:直接調(diào)用 OpenCL API 和最近引入的 OpenCV T-API(又名透明 API)——一些 OpenCV 算法的隱式 OpenCL 加速。
應(yīng)用程序結(jié)構(gòu)
啟動(dòng) Android API 級(jí)別 11 (Android 3.0)?相機(jī) API?允許使用 OpenGL 紋理作為預(yù)覽幀的目標(biāo)。Android API 級(jí)別 21 帶來(lái)了一個(gè)新的?Camera2 API,它提供了對(duì)相機(jī)設(shè)置和使用模式的更多控制,它允許預(yù)覽幀的多個(gè)目標(biāo),特別是 OpenGL 紋理。
在 OpenGL 紋理中擁有預(yù)覽幀對(duì)于使用 OpenCL 來(lái)說(shuō)很劃算,因?yàn)橛幸粋€(gè)?OpenGL-OpenCL 互操作性 API (cl_khr_gl_sharing),允許與 OpenCL 函數(shù)共享 OpenGL 紋理數(shù)據(jù)而無(wú)需復(fù)制(當(dāng)然有一些限制)。
讓我們?yōu)槲覀兊膽?yīng)用程序創(chuàng)建一個(gè)基礎(chǔ),該基礎(chǔ)僅將 Android 相機(jī)配置為將預(yù)覽幀發(fā)送到 OpenGL 紋理,并在顯示器上顯示這些幀,而無(wú)需進(jìn)行任何處理。
用于此目的的最小類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(newMode>=0 && newMode<procModeName.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;
}
}
注意
我們使用兩個(gè)渲染器類:一個(gè)用于舊版 Camera?API,另一個(gè)用于現(xiàn)代?Camera2。
一個(gè)最小的類Renderer
可以在 Java 中實(shí)現(xiàn)(OpenGL ES 2.0 在 Java?中可用),但由于我們將使用 OpenCL 修改預(yù)覽紋理,因此讓我們將 OpenGL 的東西移動(dòng)到 JNI。下面是 JNI 內(nèi)容的簡(jiǎn)單 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 在相機(jī)設(shè)置和控制方面存在很大差異,因此讓我們?yōu)閮蓚€(gè)相應(yīng)的渲染器創(chuàng)建一個(gè)基類:
?
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;
}
}
如您所見(jiàn),?Camera
?和?Camera2?
APIs的繼承者應(yīng)實(shí)現(xiàn)以下抽象方法:
protected abstract void openCamera();
protected abstract void closeCamera();
protected abstract void setCameraPreviewSize(int width, int height);
讓我們把它們實(shí)現(xiàn)的細(xì)節(jié)留給本教程之外,請(qǐng)參考源代碼查看它們。
預(yù)覽幀修改
OpenGL ES 2.0 初始化的細(xì)節(jié)也相當(dāng)簡(jiǎn)單明了,這里要引用的嘈雜,但這里重要的一點(diǎn)是,作為相機(jī)預(yù)覽目標(biāo)的 OpeGL 紋理應(yīng)該是類型(不是),在內(nèi)部它以?YUV?格式保存圖片數(shù)據(jù)。這使得無(wú)法通過(guò) CL-GL 互操作 () 共享它并通過(guò) C/C++ 代碼訪問(wèn)其像素?cái)?shù)據(jù)。為了克服這個(gè)限制,我們必須使用?FrameBuffer 對(duì)象(又名 FBO)執(zhí)行從這個(gè)紋理到另一個(gè)常規(guī)紋理的 OpenGL 渲染
OpenGL ES 2.0 初始化的細(xì)節(jié)也相當(dāng)簡(jiǎn)單明了,這里要引用的嘈雜,但這里重要的一點(diǎn)是,作為相機(jī)預(yù)覽目標(biāo)的 OpeGL 紋理應(yīng)該是類型(GL_TEXTURE_EXTERNAL_OES不是GL_TEXTURE_2D),在內(nèi)部它以?YUV?格式保存圖片數(shù)據(jù)。這使得無(wú)法通過(guò) CL-GL cl_khr_gl_sharing互操作 () 共享它并通過(guò) C/C++ 代碼訪問(wèn)其像素?cái)?shù)據(jù)。為了克服這個(gè)限制,我們必須使用?FrameBuffer 對(duì)象(又名 FBO)執(zhí)行從這個(gè)紋理GL_TEXTURE_2D到另一個(gè)常規(guī)紋理的 OpenGL 渲染。
C/C++ code
之后,我們可以從 C/C++ 讀取(?glReadPixels()
復(fù)制)像素?cái)?shù)據(jù),并通過(guò)修改后將它們寫回紋理?glTexSubImage2D()
。
直接 OpenCL 調(diào)用
此外,該紋理可以在不復(fù)制的情況下與 OpenCL 共享,但我們必須以特殊方式創(chuàng)建 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.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]);
cl::Program::Sources src(1, std::make_pair(oclProgI2I, sizeof(oclProgI2I)));
theProgI2I = cl::Program(theContext, src);
theProgI2I.build(devs);
cv::ocl::attachContext(p.getInfo<CL_PLATFORM_NAME>(), p(), theContext(), devs[0]());
if( cv::ocl::useOpenCL() )
LOGD("OpenCV+OpenCL works OK!");
else
LOGE("Can't 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;
}
然后,紋理可以被對(duì)象包裝?cl::ImageGL
并通過(guò) OpenCL 調(diào)用進(jìn)行處理
?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 代碼,不如使用隱式調(diào)用 OpenCL 的?OpenCV T-API。您只需要將創(chuàng)建的 OpenCL 上下文傳遞給 OpenCV(通過(guò)cv::ocl::attachContext() ),并以某種??
.方式將 OpenGL 紋理包裝起來(lái)。不幸的是,OpenCL?緩沖區(qū)在內(nèi)部保留,它不能包裝在 OpenGL?紋理或 OpenCL?圖像上 - 因此我們必須在此處復(fù)制圖像數(shù)據(jù):cv::UMat
?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));
注意
當(dāng)通過(guò) OpenCL 圖像包裝器將修改后的圖像放回原始 OpenGL 紋理時(shí),我們必須再制作一個(gè)圖像數(shù)據(jù)副本。
性能說(shuō)明
為了比較在具有720p相機(jī)分辨率的Sony Xperia Z3上,通過(guò)C / C++代碼(調(diào)用cv::Laplacian與cv::Mat),直接OpenCL調(diào)用(使用OpenCL圖像進(jìn)行輸入和輸出)和OpenCV?T-API(調(diào)用cv::Laplacian
與cv::UMat
)完成的相同預(yù)覽幀修改(Laplacian)的FPS:
- C/C++ 版本顯示?3-4 fps
- 直接 OpenCL 調(diào)用顯示?25-27 fps
- OpenCV T-API?顯示?11-13 fps(由于額外的來(lái)回復(fù)制)
cl_image
cl_buffer
參考文獻(xiàn):
1、《Use OpenCL in Android camera preview based CV application》? Andrey Pavlenko, Alexander?Panov