Android OpenCV 摄像头实时预览

Android摄像头预览

Android OpenCV开发过程中,我们有3种可选方式去实现Android摄像头预览功能:

  • 使用Android系统Camera API
  • 使用CameraX(JetPack组件)
  • 使用OpenCV SDK辅助类(JavaCameraView、JavaCamera2View等)

当然,有人可能会有疑问?后面两种方式不也是使用Android系统的Camera APi嘛。你说的很有道理,只是CameraX为我们处理了很多麻烦的问题,如设备管理,生命周期管理等逻辑,让开发者更专注于生产业务需求,降低系统API使用难度。而今天我们要重点介绍的Android OpenCV SDK,也是基于Android Camera API的封装,以便我们在使用OpenCV的场景下快速使用相机功能。同样是封装,CameraX旨在帮助开发者快速构建相机类应用,而Android OpenCV SDK的封装则更适合OpenCV的图像处理业务场景,去繁就简。不同的场景下采用不同的封装解决各自的业务问题,无可厚非。

Android OpenCV 辅助类

从下图可以看出,针对Android平台的辅助类,基于全部与Camera相关。其实也正常,OpenCV图像处理在Android平台上的使用场景是很难与Camera无关的。

Android OpenCV辅助类

利用Android OpenCV SDK实现相机预览

1. 申明权限

<uses-permission android:name="android.permission.CAMERA" />

2. 布局添加JavaCameraView或者JavaCamera2View至布局

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:opencv="http://schemas.android.com/apk/res-auto">
    <data></data>
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <org.opencv.android.JavaCameraView
            android:id="@+id/preview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            opencv:camera_id="any"
            opencv:show_fps="true" />
    </FrameLayout>
</layout>

3. Activity继承CameraActivity,实现CameraBridgeViewBase.CvCameraViewListener或者CameraBridgeViewBase.CvCameraViewListener2接口,两个接口的差异主要集中在onCameraFrame回调。

override fun onCameraViewStarted(width: Int, height: Int) {
}

override fun onCameraViewStopped() {
}

override fun onCameraFrame(inputFrame: CvCameraViewFrame): Mat? {
    return inputFrame.rgba()
}

CvCameraViewListener:

public Mat onCameraFrame(Mat inputFrame);

CvCameraViewListener2:

public Mat onCameraFrame(CvCameraViewFrame inputFrame);
public interface CvCameraViewFrame {

    /**
     * This method returns RGBA Mat with frame
     */
    public Mat rgba();

    /**
     * This method returns single channel gray scale Mat with frame
     */
    public Mat gray();
};

可以看到后者多出一个返回灰度图像的方法。

5. 获取JavaCameraView或者JavaCamera2View实例,并通过setCvCameraViewListener设置CvCameraViewListener或者CvCameraViewListener2

override fun onCreate(savedInstanceState: Bundle?) {
    Log.i(App.TAG, "called onCreate")
    super.onCreate(savedInstanceState)
    window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
    setContentView(mBinding.root)
    mOpenCvCameraView = mBinding.preview
    mOpenCvCameraView.setCvCameraViewListener(this)
}

6. 复写方法getCameraViewList

注意:大部分初学者使用Android OpenCV SDK尝试预览却黑屏多因未复写此方法

override fun getCameraViewList(): List<CameraBridgeViewBase?> {
    return listOf(mOpenCvCameraView)
}

这个方法的作用,查阅CameraActivity源码一目了然,关键在于onCameraPermissionGranted方法中的cameraBridgeViewBase.setCameraPermissionGranted()方法。(注意:关注下setCameraPermissionGranted方法的注释可有效避免预览黑屏

protected void onCameraPermissionGranted() {
    List<? extends CameraBridgeViewBase> cameraViews = getCameraViewList();
    if (cameraViews == null) {
        return;
    }
    for (CameraBridgeViewBase cameraBridgeViewBase: cameraViews) {
        if (cameraBridgeViewBase != null) {
            cameraBridgeViewBase.setCameraPermissionGranted();
        }
    }
}
/**
 * This method is provided for clients, so they can signal camera permission has been granted.
 * The actual onCameraViewStarted callback will be delivered only after setCameraPermissionGranted
 * and enableView have been called and surface is available
 */
public void setCameraPermissionGranted() {
    synchronized(mSyncObject) {
        mCameraPermissionGranted = true;
        checkCurrentState();
    }
}

7. 初始化OpenCV Libs并定义初始化结果回调

private val mLoaderCallback: BaseLoaderCallback = object : BaseLoaderCallback(this) {
    override fun onManagerConnected(status: Int) {
        when (status) {
            SUCCESS -> {
                Log.i(App.TAG, "OpenCV loaded successfully")
                mOpenCvCameraView.enableView()
            }
            else -> {
                super.onManagerConnected(status)
            }
        }
    }
}

override fun onResume() {
    super.onResume()
    if (!OpenCVLoader.initDebug()) {
        Log.d(
            App.TAG,
            "Internal OpenCV library not found. Using OpenCV Manager for initialization"
        )
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_0_0, this, mLoaderCallback)
    } else {
        Log.d(App.TAG, "OpenCV library found inside package. Using it!")
        mLoaderCallback.onManagerConnected(LoaderCallbackInterface.SUCCESS)
    }
}

8. 设置横屏

<activity
    android:name=".ui.CameraPreviewActivity"
    android:exported="false"
    android:screenOrientation="landscape" />

使用效果

在这里插入图片描述

常见问题

1. 黑屏问题

继承CameraActivity情况下是否复写getCameraViewList

2. 竖屏情况下预览图像颠倒90度

屏幕方向:Android系统中,屏幕的左上角是坐标系统的原点。原点向右是X轴正方向,向下是Y轴正方向。

相机传感器方向:手机相机图像传感器被固定到手机上有一个默认的取景方向。一般大家拍照都是横屏拍照,且相机的快门在上方,所以,相机模组与音量上下键所在一侧为相机传感器X轴正方向。如下图:

相机传感器方向

相机预览方向:由于手机屏幕可以360度旋转,为了保证用户在任何角度都能看到正确的预览画面,Android系统底层根据当前手机屏幕的方向对图像传感器采集的数据进行了旋转处理,然后才送显。相机API中可以通过setDisplayOrientation()设置相机预览方向。默认情况下,这个值为0,与图像传感器一致。因此横屏下,由于屏幕方向和预览方向一致,预览图像正常;而竖屏下,屏幕方向与预览方向垂直,所以会出现颠倒90度的现象。

解决方式一:强制使用landscape

android:screenOrientation="landscape"

解决方式二:操作Canvas

针对Canvas做矩阵操作,由于是针对画布做缩放等操作。具体代码链接:

https://gist.github.com/heaversm/63e8036af6a124aecf3b26898bd2a0ad

解决方法三:onCameraFrame回调中处理Mat

推荐使用【解决方式一】

源码

https://github.com/onlyloveyd/LearningAndroidOpenCV

关注我

关注左侧公众号【OpenCV or Android】
回复【计算机视觉】【Android】【Flutter】【OpenCV】白嫖学习资料。

onlyloveyd CSDN认证博客专家 Android Kotlin OpenCV
个人公众号【OpenCV or Android】,热爱Android、Kotlin、Flutter和OpenCV。毕业于华中科技大学计算机专业,曾就职于华为武汉研究所。目前在三线小城市生活,专注Android、OpenCV、Kotlin、Flutter等有趣的技术。
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页