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 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】白嫖学习资料。