android - 有没有办法裁剪 Image/ImageProxy(在传递给 MLKit 的分析器之前)?

标签 android android-image android-camera2 android-camerax google-mlkit

我正在使用 CameraX的分析器用例与 MLKit 的 BarcodeScanner .我想裁剪从相机接收到的图像的一部分,然后再将其传递给扫描仪。
我现在正在做的是转换 ImageProxy (我在分析器中收到)到 Bitmap ,裁剪它,然后将其传递给 BarcodeScanner .缺点是它不是一个非常快速和有效的过程。
我还注意到运行此代码时在 Logcat 中收到的警告:

ML Kit has detected that you seem to pass camera frames to the detector as a Bitmap object. This is inefficient. Please use YUV_420_888 format for camera2 API or NV21 format for (legacy) camera API and directly pass down the byte array to ML Kit.


最好不做ImageProxy转换,但是如何裁剪要分析的矩形?
我已经尝试过设置 cropRect Image 的字段(imageProxy.image.cropRect) 类,但它似乎不会影响最终结果。

最佳答案

是的,确实,如果您使用 ViewPort 并将视口(viewport)设置为您的 UseCases(imageCapture 或 imageAnalysis as here https://developer.android.com/training/camerax/configuration ),您只能获得有关裁剪矩形的信息,特别是如果您使用 ImageAnalysis(因为如果您使用 imageCapture,对于磁盘上的图像在保存之前被裁剪,它不适用于 ImageAnalysis 并且如果您使用 imageCapture 而不保存在磁盘上),这里解决了我如何解决这个问题:

  • 首先为用例设置视口(viewport),如下所示:https://developer.android.com/training/camerax/configuration
  • 获取裁剪的位图进行分析
    override fun analyze(imageProxy: ImageProxy) {
         val mediaImage = imageProxy.image
         if (mediaImage != null && mediaImage.format == ImageFormat.YUV_420_888) {
             croppedBitmap(mediaImage, imageProxy.cropRect).let { bitmap ->
                 requestDetectInImage(InputImage.fromBitmap(bitmap, rotation))
                     .addOnCompleteListener { imageProxy.close() }
             }
         } else {
             imageProxy.close()
         }
     }
    
     private fun croppedBitmap(mediaImage: Image, cropRect: Rect): Bitmap {
         val yBuffer = mediaImage.planes[0].buffer // Y
         val vuBuffer = mediaImage.planes[2].buffer // VU
    
         val ySize = yBuffer.remaining()
         val vuSize = vuBuffer.remaining()
    
         val nv21 = ByteArray(ySize + vuSize)
    
         yBuffer.get(nv21, 0, ySize)
         vuBuffer.get(nv21, ySize, vuSize)
    
         val yuvImage = YuvImage(nv21, ImageFormat.NV21, mediaImage.width, mediaImage.height, null)
         val outputStream = ByteArrayOutputStream()
         yuvImage.compressToJpeg(cropRect, 100, outputStream)
         val imageBytes = outputStream.toByteArray()
    
         return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
     }
    

  • 转换速度可能会有所下降,但在我的设备上我没有注意到差异。我在 compressToJpeg 方法中设置了 100 质量,但是 mb 如果设置的质量较低,它可以提高速度,它需要测试。
    更新:21 年 5 月 2 日:
    我找到了另一种方法,无需转换为 jpeg 再转换为位图。这应该是一种更快的方法。
  • 将视口(viewport)设置为以前的。
  • 将 YUV_420_888 转换为 NV21,然后进行裁剪和分析。
     override fun analyze(imageProxy: ImageProxy) {
         val mediaImage = imageProxy.image
         if (mediaImage != null && mediaImage.format == ImageFormat.YUV_420_888) {
             croppedNV21(mediaImage, imageProxy.cropRect).let { byteArray ->
                 requestDetectInImage(
                     InputImage.fromByteArray(
                         byteArray,
                         imageProxy.cropRect.width(),
                         imageProxy.cropRect.height(),
                         rotation,
                         IMAGE_FORMAT_NV21,
                     )
                 )
                     .addOnCompleteListener { imageProxy.close() }
             }
         } else {
             imageProxy.close()
         }
     }
    
     private fun croppedNV21(mediaImage: Image, cropRect: Rect): ByteArray {
         val yBuffer = mediaImage.planes[0].buffer // Y
         val vuBuffer = mediaImage.planes[2].buffer // VU
    
         val ySize = yBuffer.remaining()
         val vuSize = vuBuffer.remaining()
    
         val nv21 = ByteArray(ySize + vuSize)
    
         yBuffer.get(nv21, 0, ySize)
         vuBuffer.get(nv21, ySize, vuSize)
    
         return cropByteArray(nv21, mediaImage.width, cropRect)
     }
    
     private fun cropByteArray(array: ByteArray, imageWidth: Int, cropRect: Rect): ByteArray {
         val croppedArray = ByteArray(cropRect.width() * cropRect.height())
         var i = 0
         array.forEachIndexed { index, byte ->
             val x = index % imageWidth
             val y = index / imageWidth
    
             if (cropRect.left <= x && x < cropRect.right && cropRect.top <= y && y < cropRect.bottom) {
                 croppedArray[i] = byte
                 i++
             }
         }
    
         return croppedArray
     }
    

  • 我从这里获得的第一个收获乐趣: Android: How to crop images using CameraX?
    我还发现了另一种裁剪乐趣,似乎更复杂:
    private fun cropByteArray(src: ByteArray, width: Int, height: Int, cropRect: Rect, ): ByteArray {
        val x = cropRect.left * 2 / 2
        val y = cropRect.top * 2 / 2
        val w = cropRect.width() * 2 / 2
        val h = cropRect.height() * 2 / 2
        val yUnit = w * h
        val uv = yUnit / 2
        val nData = ByteArray(yUnit + uv)
        val uvIndexDst = w * h - y / 2 * w
        val uvIndexSrc = width * height + x
        var srcPos0 = y * width
        var destPos0 = 0
        var uvSrcPos0 = uvIndexSrc
        var uvDestPos0 = uvIndexDst
        for (i in y until y + h) {
            System.arraycopy(src, srcPos0 + x, nData, destPos0, w) //y memory block copy
            srcPos0 += width
            destPos0 += w
            if (i and 1 == 0) {
                System.arraycopy(src, uvSrcPos0, nData, uvDestPos0, w) //uv memory block copy
                uvSrcPos0 += width
                uvDestPos0 += w
            }
        }
        return nData
    }
    
    我从这里获得的第二次收获乐趣:
    https://www.programmersought.com/article/75461140907/
    如果有人可以帮助改进代码,我会很高兴。

    关于android - 有没有办法裁剪 Image/ImageProxy(在传递给 MLKit 的分析器之前)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63390243/

    相关文章:

    android - PNG 混合模式

    Android Camera2前置摄像头

    android - OpenSL,同时播放不同的声音 fragment 与 android ndk?

    android - 禁用自动更改方向

    android - 不工作先点击查看

    机器人过渡

    image - NativeScript 高效处理图像

    java - 将捕获的图像从 Camera2 Api 传递到另一个 Activity

    Android Studio : Attempt to invoke virtual method 'void android.view.View.setOnClickListener' on a null object reference

    android - 在应用程序中打开电子邮件附件?