我可以从Android摄像头预览中获取EXIF数据而无需保存文件吗?

发布于 2025-02-04 06:24:09 字数 5415 浏览 2 评论 0原文

我想使用Android摄像头报告图像预览中的采样补丁的照明和颜色信息。 Camerax Preview生成了ImageProxy映像,我可以获取一个平均的LUV数据。我想使用曝光信息和相机白平衡将这些数据转换为绝对的光级别。曝光数据在EXIF信息中,也许还有白平衡信息。

我想要这些信息,但是我们明白了。 EXIF似乎很可能是一条路线,但是欢迎其他任何非EXIF解决方案。

乍一看,看起来Exif总是从文件中读取的。但是,exifinterface 可以从InputStream创建,并且StreamType选项之一是stream_type_exif_data_only。这看起来很有希望 - 看来是一件很有希望的东西,并且只是Exif数据流,而相机预览很容易就可以做到这一点。也许我们可以以某种方式从ImageProxy中获得Exif。

我发现了许多有关如何获取EXIF数据以找出相机方向的旧线程。大约4年前,这些人说EXIF仅从文件中读取。仍然是吗?

回复评论: 由于谨慎行事,我附加了狡猾的代码...

    private class LuvAnalyzer(private val listener:LuvListener) : ImageAnalysis.Analyzer {

        private fun ByteBuffer.toByteArray(): ByteArray {
            rewind()    // Rewind the buffer to zero
            val data = ByteArray(remaining())
            get(data)   // Copy the buffer into a byte array
            return data // Return the byte array
        }

        override fun analyze(image: ImageProxy) {
            // Sum for 1/5 width square of YUV_420_888 image
            val YUV = DoubleArray(3)

            val w = image.width
            val h = image.height
            val sq = kotlin.math.min(h,w) / 5
            val w0 = ((w - sq)/4)*2
            val h0 = ((h - sq)/4)*2

            var ySum = 0
            var uSum = 0
            var vSum = 0

            val y = image.planes[0].buffer.toByteArray()
            val stride = image.planes[0].rowStride

            var offset = h0*stride + w0
            for (row in 1..sq) {
                var o = offset
                for (pix in 1..sq) { ySum += y[o++].toInt() and 0xFF }
                offset += stride
            }
            YUV[0] = ySum.toDouble()/(sq*sq).toDouble()

            val uv = image.planes[1].buffer.toByteArray()
            offset = (h0/2)*stride + w0
            for (row in 1..sq/2) {
                var o = offset
                for (pix in 1..sq/2) {
                    uSum += uv[o++].toInt() and 0xFF
                    vSum += uv[o++].toInt() and 0xFF
                }
                offset += stride
            }
            YUV[1] = uSum.toDouble()/(sq*sq/4).toDouble()
            YUV[2] = vSum.toDouble()/(sq*sq/4).toDouble()

            // val exif = Exif.createFromImageProxy(image)
            listener(YUV)

            image.close()
        }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
                }

            imageCapture = ImageCapture.Builder()
                .build()

            // Image analyser
            val imageAnalyzer = ImageAnalysis.Builder()
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, LuvAnalyzer { LUV ->
                        // Log.d(TAG, "Average LUV: %.1f %.1f %.1f".format(LUV[0], LUV[1], LUV[2]))
                        luvText = "Average LUV: %.1f %.1f %.1f".format(LUV[0], LUV[1], LUV[2])
                    })
                }

            // Select back camera as a default
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture, imageAnalyzer)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }

我正在从图像中平均进行图像。我目前正在尝试从同一ImageProxy获取EXIF数据,因为没有将图像保存到文件中,因为这旨在提供颜色值的流。而且有一个有趣的Exif.CreatefromimageProxy(Image)(现已评论),我在撰写原始音符后发现了它,但我无法做任何事情。

如果将映像保存到.jpg文件,然后再次读回其中,我可能会获取EXIF信息。该相机正在放置一系列预览图像,曝光设置可能一直在改变,因此我必须保存图像流。如果我真的被困,我可能会尝试。但是我觉得有足够的exif钻头和零件可以从相机中获得信息。

  • 更新

Google Camerax-Developers建议使用Camera2 Extender获取曝光信息。我的工作量足以看到数字大致上上下移动。感觉比Exif路线好得多。

我很想将其标记为解决方案,因为它是我的解决方案,但是我将把它打开,因为标题中我最初的问题可能会有答案。

val previewBuilder = Preview.Builder()
            val previewExtender = Camera2Interop.Extender(previewBuilder)

            // Turn AWB off
            previewExtender.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_MODE,
               CaptureRequest.CONTROL_AWB_MODE_DAYLIGHT)

            previewExtender.setSessionCaptureCallback(
                object : CameraCaptureSession.CaptureCallback() {
                    override fun onCaptureCompleted(
                        session: CameraCaptureSession,
                        request: CaptureRequest,
                        result: TotalCaptureResult
                    ) {
                        result.get(CaptureResult.SENSOR_EXPOSURE_TIME)
                        result.get(CaptureResult.SENSOR_SENSITIVITY)
                        result.get(CaptureResult.COLOR_CORRECTION_GAINS)
                        result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM)
                    }
                }
            )

I want to use the Android camera to report lighting and colour information from a sampled patch on the image preview. The camerax preview generates ImageProxy images, and I can get the average LUV data for a patch. I would like to turn this data into absolute light levels using the exposure information and the camera white balance. The exposure data is in the Exif information, and maybe the white balance information too.

I would like this information, however we get it. Exif seems a very likely route, but any other non-Exif solutions are welcome.

At first sight, it looks as if Exif is always read from a file. However, ExifInterface
can be created from an InputStream, and one of the streamType options is STREAM_TYPE_EXIF_DATA_ONLY. This looks promising - it seems something makes and streams just the EXIF data, and a camera preview could easily do just that. Or maybe we can get Exif from the ImageProxy somehow.

I found many old threads on how to get at Exif data to find out the camera orientation. About 4 years ago these people were saying Exif is only read from a file. Is this still so?

Reply to comment:
With due misgiving, I attach my dodgy code...

    private class LuvAnalyzer(private val listener:LuvListener) : ImageAnalysis.Analyzer {

        private fun ByteBuffer.toByteArray(): ByteArray {
            rewind()    // Rewind the buffer to zero
            val data = ByteArray(remaining())
            get(data)   // Copy the buffer into a byte array
            return data // Return the byte array
        }

        override fun analyze(image: ImageProxy) {
            // Sum for 1/5 width square of YUV_420_888 image
            val YUV = DoubleArray(3)

            val w = image.width
            val h = image.height
            val sq = kotlin.math.min(h,w) / 5
            val w0 = ((w - sq)/4)*2
            val h0 = ((h - sq)/4)*2

            var ySum = 0
            var uSum = 0
            var vSum = 0

            val y = image.planes[0].buffer.toByteArray()
            val stride = image.planes[0].rowStride

            var offset = h0*stride + w0
            for (row in 1..sq) {
                var o = offset
                for (pix in 1..sq) { ySum += y[o++].toInt() and 0xFF }
                offset += stride
            }
            YUV[0] = ySum.toDouble()/(sq*sq).toDouble()

            val uv = image.planes[1].buffer.toByteArray()
            offset = (h0/2)*stride + w0
            for (row in 1..sq/2) {
                var o = offset
                for (pix in 1..sq/2) {
                    uSum += uv[o++].toInt() and 0xFF
                    vSum += uv[o++].toInt() and 0xFF
                }
                offset += stride
            }
            YUV[1] = uSum.toDouble()/(sq*sq/4).toDouble()
            YUV[2] = vSum.toDouble()/(sq*sq/4).toDouble()

            // val exif = Exif.createFromImageProxy(image)
            listener(YUV)

            image.close()
        }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            // Used to bind the lifecycle of cameras to the lifecycle owner
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            // Preview
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(binding.viewFinder.surfaceProvider)
                }

            imageCapture = ImageCapture.Builder()
                .build()

            // Image analyser
            val imageAnalyzer = ImageAnalysis.Builder()
                .build()
                .also {
                    it.setAnalyzer(cameraExecutor, LuvAnalyzer { LUV ->
                        // Log.d(TAG, "Average LUV: %.1f %.1f %.1f".format(LUV[0], LUV[1], LUV[2]))
                        luvText = "Average LUV: %.1f %.1f %.1f".format(LUV[0], LUV[1], LUV[2])
                    })
                }

            // Select back camera as a default
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                // Unbind use cases before rebinding
                cameraProvider.unbindAll()

                // Bind use cases to camera
                cameraProvider.bindToLifecycle(
                    this, cameraSelector, preview, imageCapture, imageAnalyzer)

            } catch(exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(this))
    }

I am doing my image averaging from an ImageProxy. I am currently trying to get the Exif data from the same ImageProxy because there not saving images to files, because this is intended to provide a stream of colour values. And there is an intriguing Exif.createFromImageProxy(image) (now commented out) which I discovered after writing the original note, but I can't get it to do anything.

I might get the Exif information if I saved an image to a .jpg file and then read it back in again. The camera is putting out a stream of preview images, and the exposure settings may be changing all the time, so I would have to save a stream of images. If I was really stuck, I might try that. But I feel there are enough Exif bits and pieces to get the information live from the camera.

  • Update

The Google camerax-developers suggest getting the exposure information using the camera2 Extender. I have got it working enough to see the numbers go up and down roughly as they should. This feels a lot better than the Exif route.

I am tempted to mark this as the solution, as it is the solution for me, but I shall leave it open as my original question in the title may have an answer.

val previewBuilder = Preview.Builder()
            val previewExtender = Camera2Interop.Extender(previewBuilder)

            // Turn AWB off
            previewExtender.setCaptureRequestOption(CaptureRequest.CONTROL_AWB_MODE,
               CaptureRequest.CONTROL_AWB_MODE_DAYLIGHT)

            previewExtender.setSessionCaptureCallback(
                object : CameraCaptureSession.CaptureCallback() {
                    override fun onCaptureCompleted(
                        session: CameraCaptureSession,
                        request: CaptureRequest,
                        result: TotalCaptureResult
                    ) {
                        result.get(CaptureResult.SENSOR_EXPOSURE_TIME)
                        result.get(CaptureResult.SENSOR_SENSITIVITY)
                        result.get(CaptureResult.COLOR_CORRECTION_GAINS)
                        result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM)
                    }
                }
            )

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文