Merge pull request #24662 from asmorkalov:as/android_native_camera

Added experimental NativeCameraView class for Android 24+.
pull/24592/head
Alexander Smorkalov 11 months ago committed by GitHub
commit 06ff35c9af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      CMakeLists.txt
  2. 8
      modules/java/android_sdk/CMakeLists.txt
  3. 191
      modules/java/generator/android-24/java/org/opencv/android/NativeCameraView.java
  4. 3
      modules/java/generator/gen_java.py
  5. 2
      modules/videoio/include/opencv2/videoio.hpp
  6. 8
      modules/videoio/src/cap_android_camera.cpp

@ -1554,6 +1554,11 @@ if(WITH_GPHOTO2 OR HAVE_GPHOTO2)
status(" gPhoto2:" HAVE_GPHOTO2 THEN "YES" ELSE NO)
endif()
if(ANDROID)
status(" MEDIANDK:" HAVE_ANDROID_MEDIANDK THEN "YES" ELSE NO)
status(" NDK Camera:" HAVE_ANDROID_NATIVE_CAMERA THEN "YES" ELSE NO)
endif()
# Order is similar to CV_PARALLEL_FRAMEWORK in core/src/parallel.cpp
ocv_build_features_string(parallel_status EXCLUSIVE
IF HAVE_TBB THEN "TBB (ver ${TBB_VERSION_MAJOR}.${TBB_VERSION_MINOR} interface ${TBB_INTERFACE_VERSION})"

@ -27,12 +27,19 @@ if(ANDROID_SDK_COMPATIBLE_TARGET)
set(ANDROID_SDK_COMPATIBLE_TARGET "${ANDROID_SDK_COMPATIBLE_TARGET}" CACHE INTERNAL "")
endif()
string(REGEX REPLACE "android-" "" android_sdk_target_num ${ANDROID_SDK_COMPATIBLE_TARGET})
if( (ANDROID_SDK_TARGET AND ANDROID_SDK_TARGET LESS 21) OR (android_sdk_target_num LESS 21) )
message(STATUS "[OpenCV for Android SDK]: A new OpenGL Camera Bridge (CameraGLSurfaceView, CameraGLRendererBase, CameraRenderer, Camera2Renderer) is disabled, because ANDROID_SDK_TARGET (${android_sdk_target_num}) < 21")
else()
ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-21/java" "${java_src_dir}")
endif()
if( (ANDROID_SDK_TARGET AND ANDROID_SDK_TARGET LESS 24) OR (android_sdk_target_num LESS 24) )
message(STATUS "[OpenCV for Android SDK]: An experiemntal Native Camera is disabled, because ANDROID_SDK_TARGET (${android_sdk_target_num}) < 24")
else()
ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-24/java" "${java_src_dir}")
endif()
# copy boilerplate
file(GLOB_RECURSE seed_project_files_rel RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/android_lib/" "${CMAKE_CURRENT_SOURCE_DIR}/android_lib/*")
list(REMOVE_ITEM seed_project_files_rel "${ANDROID_MANIFEST_FILE}")
@ -113,6 +120,7 @@ else() # gradle build
#TODO: INSTALL ONLY
ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android/java" "${java_src_dir}")
ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-21/java" "${java_src_dir}")
ocv_copyfiles_append_dir(JAVA_SRC_COPY "${OPENCV_JAVA_BINDINGS_DIR}/gen/android-24/java" "${java_src_dir}")
# copy boilerplate
set(__base_dir "${CMAKE_CURRENT_SOURCE_DIR}/android_gradle_lib/")

@ -0,0 +1,191 @@
package org.opencv.android;
import org.opencv.core.Mat;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;
import org.opencv.videoio.Videoio;
import org.opencv.videoio.VideoCapture;
import org.opencv.videoio.VideoWriter;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
/**
* This class is an implementation of a bridge between SurfaceView and OpenCV VideoCapture.
* The class is experimental implementation and not recoomended for production usage.
*/
public class NativeCameraView extends CameraBridgeViewBase {
public static final String TAG = "NativeCameraView";
private boolean mStopThread;
private Thread mThread;
protected VideoCapture mCamera;
protected NativeCameraFrame mFrame;
public NativeCameraView(Context context, int cameraId) {
super(context, cameraId);
}
public NativeCameraView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected boolean connectCamera(int width, int height) {
/* 1. We need to instantiate camera
* 2. We need to start thread which will be getting frames
*/
/* First step - initialize camera connection */
if (!initializeCamera(width, height))
return false;
/* now we can start update thread */
mThread = new Thread(new CameraWorker());
mThread.start();
return true;
}
@Override
protected void disconnectCamera() {
/* 1. We need to stop thread which updating the frames
* 2. Stop camera and release it
*/
if (mThread != null) {
try {
mStopThread = true;
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mThread = null;
mStopThread = false;
}
}
/* Now release camera */
releaseCamera();
}
public static class OpenCvSizeAccessor implements ListItemAccessor {
public int getWidth(Object obj) {
Size size = (Size)obj;
return (int)size.width;
}
public int getHeight(Object obj) {
Size size = (Size)obj;
return (int)size.height;
}
}
private boolean initializeCamera(int width, int height) {
synchronized (this) {
if (mCameraIndex == -1) {
Log.d(TAG, "Try to open default camera");
mCamera = new VideoCapture(0, Videoio.CAP_ANDROID);
} else {
Log.d(TAG, "Try to open camera with index " + mCameraIndex);
mCamera = new VideoCapture(mCameraIndex, Videoio.CAP_ANDROID);
}
if (mCamera == null)
return false;
if (mCamera.isOpened() == false)
return false;
mFrame = new NativeCameraFrame(mCamera);
mCamera.set(Videoio.CAP_PROP_FRAME_WIDTH, width);
mCamera.set(Videoio.CAP_PROP_FRAME_HEIGHT, height);
mFrameWidth = (int)mCamera.get(Videoio.CAP_PROP_FRAME_WIDTH);
mFrameHeight = (int)mCamera.get(Videoio.CAP_PROP_FRAME_HEIGHT);
if ((getLayoutParams().width == LayoutParams.MATCH_PARENT) && (getLayoutParams().height == LayoutParams.MATCH_PARENT))
mScale = Math.min(((float)height)/mFrameHeight, ((float)width)/mFrameWidth);
else
mScale = 0;
if (mFpsMeter != null) {
mFpsMeter.setResolution(mFrameWidth, mFrameHeight);
}
AllocateCache();
}
Log.i(TAG, "Selected camera frame size = (" + mFrameWidth + ", " + mFrameHeight + ")");
return true;
}
private void releaseCamera() {
synchronized (this) {
if (mFrame != null) mFrame.release();
if (mCamera != null) mCamera.release();
}
}
private static class NativeCameraFrame implements CvCameraViewFrame {
@Override
public Mat rgba() {
mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('R','G','B','3'));
mCapture.retrieve(mBgr);
Log.d(TAG, "Retrived frame with size " + mBgr.cols() + "x" + mBgr.rows() + " and channels: " + mBgr.channels());
Imgproc.cvtColor(mBgr, mRgba, Imgproc.COLOR_RGB2RGBA);
return mRgba;
}
@Override
public Mat gray() {
mCapture.set(Videoio.CAP_PROP_FOURCC, VideoWriter.fourcc('G','R','E','Y'));
mCapture.retrieve(mGray);
Log.d(TAG, "Retrived frame with size " + mGray.cols() + "x" + mGray.rows() + " and channels: " + mGray.channels());
return mGray;
}
public NativeCameraFrame(VideoCapture capture) {
mCapture = capture;
mGray = new Mat();
mRgba = new Mat();
mBgr = new Mat();
}
public void release() {
if (mGray != null) mGray.release();
if (mRgba != null) mRgba.release();
if (mBgr != null) mBgr.release();
}
private VideoCapture mCapture;
private Mat mRgba;
private Mat mGray;
private Mat mBgr;
};
private class CameraWorker implements Runnable {
public void run() {
do {
if (!mCamera.grab()) {
Log.e(TAG, "Camera frame grab failed");
break;
}
deliverAndDrawFrame(mFrame);
} while (!mStopThread);
}
}
}

@ -1419,7 +1419,8 @@ if __name__ == "__main__":
java_base_path = os.path.join(dstdir, 'java'); mkdir_p(java_base_path)
java_test_base_path = os.path.join(dstdir, 'test'); mkdir_p(java_test_base_path)
for (subdir, target_subdir) in [('src/java', 'java'), ('android/java', None), ('android-21/java', None)]:
for (subdir, target_subdir) in [('src/java', 'java'), ('android/java', None),
('android-21/java', None), ('android-24/java', None)]:
if target_subdir is None:
target_subdir = subdir
java_files_dir = os.path.join(SCRIPT_DIR, subdir)

@ -108,7 +108,7 @@ enum VideoCaptureAPIs {
CAP_PVAPI = 800, //!< PvAPI, Prosilica GigE SDK
CAP_OPENNI = 900, //!< OpenNI (for Kinect)
CAP_OPENNI_ASUS = 910, //!< OpenNI (for Asus Xtion)
CAP_ANDROID = 1000, //!< Android - not used
CAP_ANDROID = 1000, //!< MediaNDK (API Level 21+) and NDK Camera (API level 24+) for Android
CAP_XIAPI = 1100, //!< XIMEA Camera API
CAP_AVFOUNDATION = 1200, //!< AVFoundation framework for iOS (OS X Lion will have the same API)
CAP_GIGANETIX = 1300, //!< Smartek Giganetix GigEVisionSDK

@ -533,6 +533,7 @@ public:
cachedIndex = index;
cameraManager = std::shared_ptr<ACameraManager>(ACameraManager_create(), deleter_ACameraManager);
if (!cameraManager) {
LOGE("Cannot create camera manager!");
return false;
}
ACameraIdList* cameraIds = nullptr;
@ -591,6 +592,7 @@ public:
}
}
}
LOGI("Best resolution match: %dx%d", bestMatchWidth, bestMatchHeight);
ACameraMetadata_const_entry val = { 0, };
camera_status_t status = ACameraMetadata_getConstEntry(cameraMetadata.get(), ACAMERA_SENSOR_INFO_EXPOSURE_TIME_RANGE, &val);
@ -654,7 +656,11 @@ public:
return false;
}
sessionOutput = std::shared_ptr<ACaptureSessionOutput>(output, deleter_ACaptureSessionOutput);
ACaptureSessionOutputContainer_add(outputContainer.get(), sessionOutput.get());
cStatus = ACaptureSessionOutputContainer_add(outputContainer.get(), sessionOutput.get());
if (cStatus != ACAMERA_OK) {
LOGE("CaptureSessionOutput Container add failed with error code: %d", cStatus);
return false;
}
sessionOutputAdded = true;
ACameraOutputTarget* target;

Loading…
Cancel
Save