Kotlin 集成 Camera2 API 在安卓上,非常棒

    今天,我们将要去看一下怎么使用安卓创意集成的 Camera2 API 去得到一个集成相机。具体来说,我们将尽最大努力去用 SurfaceView 展示我们输入源的一张图片,和同时建立 ImageReader 处理来自相同来源的单独的帧。
    
    正如你所知,在Android平台上构建东西时,需要考虑很多未知因素。有这么多不同的设备类型,而且无法预测该设备的服务和硬件会是什么样子。这就是为什么我们需要与API接口,为我们解决这些未知值,我们需要安全地处理代码中的所有事件。Camera2让这一切变得简单。

    因此,当您在某个设备上运行应用程序时,您可能想询问的第一件事是,我可以使用哪种设备?该设备是否同时具有正面和背面摄像头?它支持哪些决议?我们使用CameraManager回答这些问题。

    顺便说一下,我已经开始了一个新的、空的项目,我将从那里开始在MainActivity中构建。使用此API不需要任何外部依赖项。        

需要权限

        实际上,让我们回想一下。首先我们需要请求许可才能使用相机。这里有一个帮助您完成此操作的助手:

/** 请求相机权限助手  */
object CameraPermissionHelper {
    private const val CAMERA_PERMISSION_CODE = 0
    private const val CAMERA_PERMISSION = Manifest.permission.CAMERA

    /** 请检查我们是否拥有此应用程序所需的权限。 */
    fun hasCameraPermission(activity: Activity): Boolean {
        return ContextCompat.checkSelfPermission(activity, CAMERA_PERMISSION) == PackageManager.PERMISSION_GRANTED
    }

    /** 请检查我们是否拥有此应用程序所需的权限,如果没有,请求他们。  */
    fun requestCameraPermission(activity: Activity) {
        ActivityCompat.requestPermissions(
                activity, arrayOf(CAMERA_PERMISSION), CAMERA_PERMISSION_CODE)
    }

    /** 检查是否需要显示此许可的理由。  */
    fun shouldShowRequestPermissionRationale(activity: Activity): Boolean {
        return ActivityCompat.shouldShowRequestPermissionRationale(activity, CAMERA_PERMISSION)
    }

    /** 启动应用程序设置以授予权限  */
    fun launchPermissionSettings(activity: Activity) {
        val intent = Intent()
        intent.action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
        intent.data = Uri.fromParts("package", activity.packageName, null)
        activity.startActivity(intent)
    }
}

实现这个回调

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    if (!CameraPermissionHelper.hasCameraPermission(this)) {
        Toast.makeText(this, "Camera permission is needed to run this application", Toast.LENGTH_LONG)
            .show()
        if (!CameraPermissionHelper.shouldShowRequestPermissionRationale(this)) {
            // 检查“不再询问”时权限被拒绝。
            CameraPermissionHelper.launchPermissionSettings(this)
        }
        finish()
    }

    recreate()
}

确保你加入了这一行在 Manifest 文件中

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

然后调用

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    if (!CameraPermissionHelper.hasCameraPermission(this)) {
        CameraPermissionHelper.requestCameraPermission(this)
        return
    }
}

使用CameraManager访问设备

private fun startCameraSession() {
  val cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager
  if (cameraManager.cameraIdList.isEmpty()) {
    // 没有相机
    return
  }
  val firstCamera = cameraIdList[0]
  cameraManager.openCamera(firstCamera, object: CameraDevice.StateCallback() {
    override fun onDisconnected(p0: CameraDevice) { }
    override fun onError(p0: CameraDevice, p1: Int) { }

    override fun onOpened(cameraDevice: CameraDevice) {
        // 使用相机
        val cameraCharacteristics =    cameraManager.getCameraCharacteristics(cameraDevice.id)

        cameraCharacteristics[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]?.let { streamConfigurationMap ->
            streamConfigurationMap.getOutputSizes(ImageFormat.YUV_420_888)?.let { yuvSizes ->
                val previewSize = yuvSizes.last()

            }

        }
    }
}, Handler { true })
}

        在这里,我们使用 manager 抓取列表中的第一个相机。然后我们调用 CameraManager.openCamera(),传递 id 以访问该相机设备。一旦成功打开,我们将调用CameraManager.getCameraCharacteristics以查询该相机设备的详细信息。如果您想更详细地了解要使用哪台相机,可以在打开相机之前调用 getCameraCharacteristics 来检查它是否具有所需的属性。

        在本例中,我检索相机的 StreamConfigurationMap ,其中包含有关设备支持的输出格式的所有信息。接下来,我获取YUV_420_888格式(如果存在),因为这是我的项目所需要的。您可以根据需要使用其他格式。我对此不太了解。然后,我从该列表中获取最后一个大小,这将是本示例所需的最低分辨率大小。

       下一步,我们需要做一些新人可能不会想到的至关重要的事情。我们需要检查Android设备的方向和摄像头输出的数据的方向是否互换!设备可能是纵向的,但相机仍然根据相机传感器输出横向的图像。Camera2示例项目提供了一种方法来确定是否存在这种情况: 

private fun areDimensionsSwapped(displayRotation: Int, cameraCharacteristics: CameraCharacteristics): Boolean {
    var swappedDimensions = false
    when (displayRotation) {
        Surface.ROTATION_0, Surface.ROTATION_180 -> {
            if (cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 90 || cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 270) {
                swappedDimensions = true
            }
        }
        Surface.ROTATION_90, Surface.ROTATION_270 -> {
            if (cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 0 || cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) == 180) {
                swappedDimensions = true
            }
        }
        else -> {
            // 显示旋转无效
        }
    }
    return swappedDimensions
}

然后返回 startCameraSession:

val previewSize = yuvSizes.last()
// 账户.
val displayRotation = windowManager.defaultDisplay.rotation
val swappedDimensions = areDimensionsSwapped(displayRotation, cameraCharacteristics)
// 如果需要交换长和宽
val rotatedPreviewWidth = if (swappedDimensions) previewSize.height else previewSize.width
val rotatedPreviewHeight = if (swappedDimensions) previewSize.width else previewSize.height

设置 SurfaceView

        此时,我们准备好与 SurfaceView 集成。要做到这一点其实很简单。首先,我们用  SurfaceView 替换自动生成的“Hello World!”文本视图

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <SurfaceView
            android:id="@+id/surfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

        我们已经有了相机设备特性给出的输出大小,所以我们只需将 holder 上的预览固定大小设置为输出大小:

surfaceView.holder.setFixedSize(rotatedPreviewWidth, rotatedPreviewHeight)

现在我们只需要连接输入源,即我们的相机设备和 surface。我们使用CameraCaptureSession.CaptureCallback()执行此操作:

val previewSurface = surfaceView.holder.surface

val captureCallback = object : CameraCaptureSession.StateCallback()
  {
    override fun onConfigureFailed(session: CameraCaptureSession) {}
 
   override fun onConfigured(session: CameraCaptureSession) {
     // 会话已配置
        val previewRequestBuilder =   cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
  .apply {
    addTarget(previewSurface) 
  }
        session.setRepeatingRequest(
          previewRequestBuilder.build(), 
          object: CameraCaptureSession.CaptureCallback() {}, 
          Handler { true }
        )
    }
}

cameraDevice.createCaptureSession(mutableListOf(previewSurface), captureCallback, Handler { true })

        如果你试着运行这个,一切都会好起来的。但是你不会看到任何预览出现…这是因为我们还没有调用startCameraSession()!事实上,它并没有那么简单,因为SurfaceView必须异步准备自己,即使在“活动”是交互式的之后,它也可能没有准备好。
        所以我们需要一个回调:

val surfaceReadyCallback = object: SurfaceHolder.Callback {
    override fun surfaceChanged(p0: SurfaceHolder?, p1: Int, p2: Int, p3: Int) { }
    override fun surfaceDestroyed(p0: SurfaceHolder?) { }

    override fun surfaceCreated(p0: SurfaceHolder?) {
        startCameraSession()
    }
}

        回到onCreate(),访问Kotlin启用的同一合成视图引用:

surfaceView.holder.addCallback(surfaceReadyCallback)

        现在,当你运行时,你应该会看到一个漂亮的小预览…

 使用ImageReader处理图像

        让我们快速完成向应用程序添加另一个输出源的脚手架:ImageReader。Camera2最酷的地方是它可以将任意数量的输入连接到各种输出。因此,我们可以显示预览并处理来自以下来源的图像:

//账户 
surfaceView.holder.setFixedSize(rotatedPreviewWidth, rotatedPreviewHeight)

// 配置图像读取器
val imageReader = ImageReader.newInstance(rotatedPreviewWidth, rotatedPreviewHeight,
    ImageFormat.YUV_420_888, 2)
imageReader.setOnImageAvailableListener({
        // 做一些事
}, Handler { true })

我们配置 ImageReader 和 surface view 用相同的图像格式

ImageReader将其数据渲染到Surface,我们可以直接访问该Surface:

// cont. 
val previewSurface = surfaceView.holder.surface
val recordingSurface = imageReader.surface

        将其添加到我们的回调中。通过这样做,我们确保对于进入预览的每个图像,也会将其发送到 ImageReader:

// cont.
val previewRequestBuilder = camera.createCaptureRequest(TEMPLATE_PREVIEW).apply {
   addTarget(previewSurface)
   addTarget(recordingSurface)
}

并将其添加到我们session中的surfaces列表中:

// cont.
cameraDevice.createCaptureSession(mutableListOf(previewSurface, recordingSurface), captureCallback, Handler { true })

        生成的图像将是一个包含输入图像缓冲区的对象,如果您愿意,可以使用层和所有您需要的东西来分解和重新组合图像的每一位。
        就是这样!我希望这简化了您理解此API的过程。请务必查看主要来源,尤其是样本项目,以获得进一步的澄清。还有一个关于我使用上述代码的应用程序的快速简介:
        KanjiReader是一个图像处理应用程序,可以让您即时翻译日语汉字,并查看其含义和读数的读数。​​​​​​​

实现效果:

Androidhttps://play.google.com/store/apps/details?id=tylerwalker.io.kanjireader

iOS‎Kanji-Reader on the App Store

参考文章1

引用

  1. ​​​​​​​Camera2 Documentation
  2. Camera2 Sample Project

文章来源

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
以下是使用KotlinAndroid中设置Camera2分辨率的代码示例: 首先,您需要在`onCreate()`方法中获取`CameraManager`对象并请求相机权限: ```kotlin private lateinit var cameraManager: CameraManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) cameraManager = getSystemService(Context.CAMERA_SERVICE) as CameraManager if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSIONS) } else { openCamera() } } ``` 接下来,在`openCamera()`方法中打开相机并设置所需的分辨率: ```kotlin private fun openCamera() { val cameraId = cameraManager.cameraIdList[0] // 获取第一个相机ID val characteristics = cameraManager.getCameraCharacteristics(cameraId) val streamConfigurationMap = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) val outputSizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG) // 设置所需的分辨率(这里选择第一个分辨率) val imageReader = ImageReader.newInstance(outputSizes[0].width, outputSizes[0].height, ImageFormat.JPEG, 1) // ... } ``` 在`openCamera()`方法中,我们首先获取相机特性和支持的分辨率。然后,我们选择我们想要的分辨率并使用`ImageReader.newInstance()`方法创建一个`ImageReader`对象。 您可以在此基础上进行修改以满足您的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蒋的学习笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值