1

我正在尝试使用captureBurst()方法在 Android 上使用 Camera2 API 捕获图像。我在按钮中开始突发捕获OnClickListener

button?.setOnClickListener(View.OnClickListener {
            imageRequestBuilder?.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE)
            imageRequest = imageRequestBuilder?.build()
            val requests = Collections.nCopies(10, imageRequest)
            cameraCaptureSession?.captureBurst(requests, captureCallback, backgroundHandler)
        })

在我的OnImageAvailableListener听众中,我正在读取捕获的图像,保存并关闭。在这一点上,我想在我的屏幕上显示一些东西,我想在连拍序列中的每个捕获的图像之间显示一些东西。所以我写了这段代码:

override fun onImageAvailable(reader: ImageReader?) {
    val image = reader?.acquireNextImage()
    if(image!=null) {
        synchronized(uiUpdate) {
            runOnUiThread(uiUpdate)
            (uiUpdate as java.lang.Object).wait()
        }

        val buffer = image?.planes?.get(0)?.buffer
        val bytes = ByteArray(buffer?.capacity()!!)
        buffer.get(bytes)
        SaveImageTask().execute(bytes)
    }

    image?.close()
}

private val uiUpdate = object : Runnable {
    override fun run() {
        synchronized(this) {
            textView?.text = "" + ++imageNum
            (this as java.lang.Object).notify()
        }
    }
}

为了测试它并确保这确实发生了,我正在使用我的前置摄像头,并在我的设备上方放置一面镜子,以便摄像头实际捕获应用程序的显示。不幸的是,显示和捕获不同步。我已经尝试将 10 个相同ImageRequestscaptureBurst方法放在方法中,但是当我查看获得的图像时,前三个是相同的(显示仍然显示初始数字 0),但其余的都很好(显示正在同步变化)。为什么不能全部同步,即为什么前几张图像几乎同时拍摄(在连拍开始时),而其他的都很好?

我创建ImageReader了最多 1 个图像,以便一次不捕获多个图像:

imageReader = ImageReader.newInstance(/*some width*/, /*some height*/, /*some format*/, 1)

并且使用模式RequestBuilder创建TEMPLATE_PREVIEW以最大化捕获屏幕。

我知道capture每次在OnImageAvailableListener侦听器中捕获新图像时我都可以调用方法,这是有效的(经过测试!)。但我需要让我的应用程序以尽可能快的速度捕获图像。

有什么帮助吗?

已编辑:这是@alex-cohn 建议的我的日志:

D/onImageAvailable: display #0 Timestamp #0: 111788230978655
D/onImageAvailable: display #1 Timestamp #1: 111788264308655
D/onImageAvailable: display #2 Timestamp #2: 111788297633655
D/onImageAvailable: display #3 Timestamp #3: 111788730892655
D/onImageAvailable: display #4 Timestamp #4: 111788930856655
D/onImageAvailable: display #5 Timestamp #5: 111789030840655
D/onImageAvailable: display #6 Timestamp #6: 111789097494655
D/onImageAvailable: display #7 Timestamp #7: 111789264133655
D/onImageAvailable: display #8 Timestamp #8: 111789364112655
D/onImageAvailable: display #9 Timestamp #9: 111789464097655

编辑2:

我已经设法为每个显示的数字获得一张图像,但是每次捕获一张图像后我都会丢弃两张图像,即:

private val onImageAvailableListener = ImageReader.OnImageAvailableListener {reader ->
        val image = reader?.acquireLatestImage()

        if(image!=null) {
            capturedImagesCount++

            if(capturedImagesCount % frameDrop == 1) {
                Log.i("IMAGE_TIME", "Image available at: "+ SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()).format(Date()))
                synchronized(uiUpdate) {
                    runOnUiThread(uiUpdate)
                    (uiUpdate as java.lang.Object).wait()
                }

                val buffer = image.planes?.get(0)?.buffer
                val bytes = ByteArray(buffer?.capacity()!!)
                buffer.rewind()
                buffer.get(bytes)
                SaveImageTask().execute(bytes)
            }
        }

        image?.close()
    }

在这里,frameDrop设置为 3。此外,maxImages在创建 imageReader 时也设置为 3。我已经在另一台当时正在拍摄 2 张​​图像的设备上对其进行了测试,所以在该设备的情况下,我必须设置frameDropmaxImages2 . 这样做的缺点是它仍然有点慢,毕竟它是以连拍模式拍摄的每三(第二)张图像,这不是您使用captureBurst方法时想要的东西。

我仍然不太明白为什么会这样工作,以及为什么相机会以成对或三胞胎的形式拍摄图像。

4

1 回答 1

1

ImageReader.maxImages设置为 1 无济于事,因为它只会影响您的应用从相机接收图像的方式(一次只能接收一张),但这并不能让您完全控制相机的内部行为。

当突发会话开始时,相机(在您的应用程序进程之外)会尽可能多地加载(在您的情况下为 3 张图像),同时一次将图像(跨进程边界)传递给您的应用程序。这种跨进程工作需要时间,只有在那之后,您才有机会等待相机回调线程进行 UI 更新。

您很幸运在您的开发设备上发现了这种行为。在其他设备上,相机内部可能会有所不同,它可能只缓冲一张图像(如您所料),甚至超过 3 张。这个数字也可能取决于图像分辨率。

如果您迫切需要将图像与 UI 紧密同步,则需要一种删除前几张图像的方法。多少?您可能需要检查每个设备和图像大小。

您可能会发现ImageReader.acquireLatestImage()(将maxImages设置为3)对您更有效。

为了更好地控制这个过程,你可以有类似的东西

private var imageConsumedCount = 0
private var imageDisplayedCount = 0
override fun onImageAvailable(reader: ImageReader?) {
    val image = reader?.acquireNextImage()
    if (image!=null) {
        Log.d("onImageAvailable", "display #${imageDisplayedCount} Timestamp #${imageConsumedCount}: ${image?.timestamp}")
        imageConsumedCount++
        val next_image = reader?.acquireNextImage()
        while (next_image != null) {
            imageConsumedCount++
            image.close()
            image = next_image
            next_image = reader?.acquireNextImage()
        }
        synchronized(uiUpdate) {
            runOnUiThread(uiUpdate)
            (uiUpdate as java.lang.Object).wait()
        }

        imageDisplayedCount++

        val buffer = image?.planes?.get(0)?.buffer
        val bytes = ByteArray(buffer?.capacity()!!)
        buffer.rewind()
        buffer.get(bytes)
        SaveImageTask().execute(bytes)
    }

    image?.close()
}

作为事后的想法,我不喜欢相机回调线程中的这个wait(),我认为它不是真的需要。但最有可能的是,这并不重要,因为实际的采集发生在进程外,暂停的ImageReader不会影响突发。


更新 查看您从onImageAvailable()回调收集的日志:

您是否将maxImages设置为3?我预计显示的计数器和消耗的计数器会变得不同......</p>

图像时间戳之间的延迟不是 unifrom,四舍五入到毫秒:

33 33 433 200 100 67 167 100 100

这表明前三帧没有等待 UI 更新。您可以仅根据此标准删除前两个帧。

至于onCaptureStarted()文档是一个模糊的机器人(可能是故意的)。

当相机设备已开始捕获请求的输出图像时、图像曝光开始时或相机设备已开始处理重新处理请求的输入图像时,将调用此方法。

这是还是说该方法可能在一个捕获会话中被调用 3 次?我不知道。

但是,明确记录的是图像的时间戳与onCaptureStarted(). 这提供了另一个标准来过滤掉过早收集的图像。

实际上,我建议每次在 OnImageAvailableListener 侦听器中捕获新图像时,从调用捕获方法中记录相同的时间戳。这很有可能让您的应用程序以更可预测的方式捕获图像,但同时。

如果您查看日志消息的时间戳(由 logcat 报告,以毫秒为单位;它们可能与图像时间戳不一致),这可以帮助您更好地理解时间线。

于 2019-04-22T18:58:32.343 回答