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, 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 文件中


然后调用

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!”文本视图




    

        我们已经有了相机设备特性给出的输出大小,所以我们只需将 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是一个图像处理应用程序,可以让您即时翻译日语汉字,并查看其含义和读数的读数。​​​​​​​

实现效果:

Kotlin 集成 Camera2 API 在安卓上,非常棒_第1张图片

Android: https://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

文章来源

你可能感兴趣的:(安卓,android,kotlin,开发语言)