【问题标题】:Is there a way to crop Image/ImageProxy (before passing to MLKit's analyzer)?有没有办法裁剪 Image/ImageProxy(在传递给 MLKit 的分析器之前)?
【发布时间】:2021-05-02 17:49:03
【问题描述】:

我将 CameraX 的 Analyzer 用例与 MLKit 的 BarcodeScanner 一起使用。我想裁剪从相机接收到的部分图像,然后再将其传递给扫描仪。

我现在正在做的是将ImageProxy(我在分析器中收到)转换为Bitmap,对其进行裁剪,然后将其传递给BarcodeScanner。缺点是它不是一个非常快速和高效的过程。

我还注意到运行此代码时在 Logcat 中收到的警告:

ML Kit 检测到您似乎将相机帧传递给 检测器作为位图对象。这是低效的。请用 用于 camera2 API 的 YUV_420_888 格式或用于(旧版)相机的 NV21 格式 API 并直接将字节数组传递给 ML Kit。

最好不要进行ImageProxy 转换,但是如何裁剪我要分析的矩形?

我已经尝试过设置cropRect 类的cropRect 字段,但它似乎不会影响最终结果。

【问题讨论】:

  • 虽然是一个单独的问题,但我也在努力修改与此 imageProxy 相关的内容。您在这方面取得了进展吗?
  • @topher217 不幸的是,我没有。目前我坚持转换为位图,然后对其应用裁剪、旋转等
  • 我正在取得一些进展。不确定您的特定实现,但如果您使用的是default sample code,您是否尝试过使用ImageProxy.setCropRect() 方法?例如,您可以将 imageProxy.setCropRect(myCroppingRect) 放在第 406 行和第 407 行之间 here
  • @topher217 我没有使用此示例,但是是的,我尝试了setCropRect 方法,但似乎没有任何效果。根据该方法的描述,它设置了“有效像素区域”,但我无法找到这究竟是什么意思,因为设置后没有任何改变,条形码检测器继续检测指定范围之外的条形码裁剪矩形。
  • 你在哪里找到这样的描述?您是否正在创建自己的自定义 ImageProxy 类?从我通过 ctrl+单击示例代码中的继承树所看到的情况来看,Android Studio 将我指向一个 ImageProxy 接口,该接口具有一个空的 setCropRect 方法,而 javadoc 的描述性要少得多。我在ImageProxy 上可以找到的唯一文档也是没有此类描述的接口。你在哪里找到所说的imageProxy.image.cropRect 类,你是如何访问/创建你的 imageProxy 实例的?

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


【解决方案1】:

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

  1. 首先为用例设置视口,如下所示:https://developer.android.com/training/camerax/configuration

  2. 获取裁剪的位图进行分析

    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 再转换为位图。这应该是一种更快的方式。

  1. 将视口设置为以前的。

  2. 将 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/

如果有人可以帮助改进代码,我会很高兴。

【讨论】:

  • 我不确定它是如何工作的,因为 YUV 图像有 3 个平面,而且你只得到 Y 和 V 平面,而不是 U,即这里缺少 mediaImage.planes[1].buffer,所以看起来您生成的图像会缺少一些颜色
  • @AdamBurley 提到这个答案不起作用。这种转换不能只用两架飞机完成,我到处都是奇怪的颜色。种植都不起作用。这个另一个答案看起来更有希望stackoverflow.com/a/52740776/2658287
  • 在上面的评论之后,我确实进一步研究了它。发布的解决方案确实有效,但仅适用于交错 U 和 V 平面的某些设备。在此类设备上,U 缓冲区将由字节 U1、V1、U2、V2 等组成,而 V 缓冲区将是 V1、U1、V2、U2 等。但情况并非总是如此,有些设备不会交错缓冲区,在这种情况下,发布的代码将不起作用。解决它的正确方法是考虑平面的 pixelStride 属性,它告诉您该平面的每个字节之间的像素数(即交错的 2 个字节,或非交错的 1 个字节)
  • @adam-burley 如果我单独添加 U 缓冲区 - 它会解决问题吗?
  • 好的,我会检查答案stackoverflow.com/a/52740776/2658287,然后修正我的答案。
【解决方案2】:

您将使用 ImageProxy.SetCroprect 获取矩形,然后使用 CropRect 设置它。 例如,如果你有 imageProxy,你会做:ImageProxy.setCropRect(Rect) 然后你会做 ImageProxy.CropRect。

【讨论】:

  • 这绝对不是真的。 SetCroped 方法只设置图像元数据,而不是实际图像。
猜你喜欢
  • 2021-07-15
  • 2011-08-04
  • 2014-11-02
  • 2013-05-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-28
  • 2011-12-10
相关资源
最近更新 更多