【问题标题】:Converting Bitmap to ByteBuffer (float) in Tensorflow-lite Android在 Tensorflow-lite Android 中将位图转换为 ByteBuffer(浮点数)
【发布时间】:2019-09-10 14:59:06
【问题描述】:

在用于图像分类的 tensorflow-lite android 演示代码中,首先将图像转换为 ByteBuffer 格式以获得更好的性能。这种从位图转换为浮点格式以及随后转换为字节缓冲区的操作似乎是一项昂贵的操作(循环,位运算符,浮动内存复制等)。我们试图用opencv实现相同的逻辑以获得一些速度优势。以下代码可以正常工作;但是由于这个转换中的一些逻辑错误,模型的输出(这个数据被馈送到的)似乎是不正确的。模型的输入应该是RGB,数据类型为float[1,197,197,3]。

我们如何使用opencv(或任何其他方式)加快位图到字节缓冲区转换的过程?

标准位图到字节缓冲区的转换:-

/** Writes Image data into a {@code ByteBuffer}. */
  private void convertBitmapToByteBuffer(Bitmap bitmap) {
    if (imgData == null) {
      return;
    }
    imgData.rewind();


    bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());



    long startTime = SystemClock.uptimeMillis();

    // Convert the image to floating point.
    int pixel = 0;

    for (int i = 0; i < getImageSizeX(); ++i) {
      for (int j = 0; j < getImageSizeY(); ++j) {
        final int val = intValues[pixel++];

        imgData.putFloat(((val>> 16) & 0xFF) / 255.f);
        imgData.putFloat(((val>> 8) & 0xFF) / 255.f);
        imgData.putFloat((val & 0xFF) / 255.f);
      }
    }

    long endTime = SystemClock.uptimeMillis();
    Log.d(TAG, "Timecost to put values into ByteBuffer: " + Long.toString(endTime - startTime));
  }

OpenCV 位图到 ByteBuffer :-

    /** Writes Image data into a {@code ByteBuffer}. */
      private void convertBitmapToByteBuffer(Bitmap bitmap) {
        if (imgData == null) {
          return;
        }
        imgData.rewind();


        bitmap.getPixels(intValues, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight());

        long startTime = SystemClock.uptimeMillis();


        Mat bufmat = new Mat(197,197,CV_8UC3);
        Mat newmat = new Mat(197,197,CV_32FC3);


        Utils.bitmapToMat(bitmap,bufmat);
        Imgproc.cvtColor(bufmat,bufmat,Imgproc.COLOR_RGBA2RGB);

        List<Mat> sp_im = new ArrayList<Mat>(3);


        Core.split(bufmat,sp_im);

        sp_im.get(0).convertTo(sp_im.get(0),CV_32F,1.0/255/0);
        sp_im.get(1).convertTo(sp_im.get(1),CV_32F,1.0/255.0);
        sp_im.get(2).convertTo(sp_im.get(2),CV_32F,1.0/255.0);

        Core.merge(sp_im,newmat);



        //bufmat.convertTo(newmat,CV_32FC3,1.0/255.0);
        float buf[] = new float[197*197*3];


        newmat.get(0,0,buf);

        //imgData.wrap(buf).order(ByteOrder.nativeOrder()).getFloat();
        imgData.order(ByteOrder.nativeOrder()).asFloatBuffer().put(buf);


        long endTime = SystemClock.uptimeMillis();
        Log.d(TAG, "Timecost to put values into ByteBuffer: " + Long.toString(endTime - startTime));
      }

【问题讨论】:

标签: java android opencv tensorflow-lite mappedbytebuffer


【解决方案1】:
  1. 我认为您代码中的 255/0 是复制/粘贴错误,而不是真实代码。
  2. 我想知道纯 Java 解决方案的时间成本是多少,尤其是当您权衡推理的时间成本时。对我来说,对于 Google 的 mobilenet_v1_1.0_224,位图稍大一些,简单的浮点缓冲区准备时间不到推理时间的 5%。
  3. 我可以量化 tflite 模型(使用从 .h5 生成 .tflite 文件的相同 tflite_convert 实用程序。实际上可能有三个量化操作,但我只使用了两个:--inference_input_type=QUANTIZED_UINT8--post_training_quantize
    • 生成的模型大小约为 float32 模型的 25%,这本身就是一项成就。
    • 生成的模型运行速度大约快两倍(至少在某些设备上)。
    • 而且,生成的模型使用 unit8 输入。这意味着我们用imgData.put((val&gt;&gt; 16) &amp; 0xFF)代替imgData.putFloat(((val&gt;&gt; 16) &amp; 0xFF) / 255.f),以此类推。

顺便说一句,我不认为你的公式是正确的。为了在涉及 float32 缓冲区时获得最佳精度,我们使用

putFLoat(byteval / 256f)

其中byteval 是 [0:255] 范围内的 int。

【讨论】:

  • 谢谢,这是一个愚蠢的错误。我将 255/0 更改为 255.0。现在它可以完美运行,速度是原来的两倍。实际上我过于雄心勃勃,无法优化这部分 TF 代码。我是试图删除昂贵的嵌套循环。我们在此转换过程中并行运行其他代码,因此所花费的时间增加了 4 倍。我们尝试了这两种量化方法;但它不能正常工作。此外,在这种情况下,我们会失去准确性(负担不起)。我们需要尝试量化感知训练。我们正在使用 gpu 委托和 tflite 将只在 gpu 中运行浮点模型。
  • 在简单的 Java 实现中,您不需要嵌套循环 for i / for j。切换到具有预先计算限制的单循环可能会给您带来显着的提升。
  • 它更快;但不如当前的opencv方法快。看起来opencv有霓虹灯优化的低级向量化操作......
  • 很高兴知道。我希望我们可以使用量化模型,它对我们来说比 GPU 更快,也更小。顺便说一句,从表面上看,量化仅输入不应该降低准确性(这意味着,将字节转换为浮点数不能提高准确性),并且生成的模型可能仍然适用于 GpuDelegate。
  • 取决于场景或用例;对于我们的语义分割案例,输出中确实显示了小的准确度损失。准确度损失取决于量化级别。GPU 仅支持浮点 32(浮点 16 仍处于试验阶段)。权重仅量化减小尺寸;但是 tflite gpu(dequantize) 不支持某些运算符。如果您使用更大的图像/模型,您可能会看到量化 cpu 和 gpu 之间的差异。GPU 会更快。顺便说一下,您所说的仅输入量化是什么意思?它是量化的吗? UINT8 输入?这不会在 gpu 中运行。我们尝试了您前面提到的两种方法;但它没有工作
【解决方案2】:

here 所述,使用here 中的以下代码将位图转换为ByteBuffer(float32)

private fun convertBitmapToByteBuffer(bitmap: Bitmap): ByteBuffer? {
    val byteBuffer =
        ByteBuffer.allocateDirect(4 * BATCH_SIZE * inputSize * inputSize * PIXEL_SIZE)
    byteBuffer.order(ByteOrder.nativeOrder())
    val intValues = IntArray(inputSize * inputSize)
    bitmap.getPixels(intValues, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)
    var pixel = 0
    for (i in 0 until inputSize) {
        for (j in 0 until inputSize) {
            val `val` = intValues[pixel++]
            byteBuffer.putFloat(((`val` shr 16 and 0xFF) - IMAGE_MEAN) / IMAGE_STD)
            byteBuffer.putFloat(((`val` shr 8 and 0xFF) - IMAGE_MEAN) / IMAGE_STD)
            byteBuffer.putFloat(((`val` and 0xFF) - IMAGE_MEAN) / IMAGE_STD)
        }
    }
    return byteBuffer
}

【讨论】:

    【解决方案3】:

    对于浮点数,mean = 1 和 std = 255.0,函数为:

    fun bitmapToBytebufferWithOpenCV(bitmap: Bitmap): ByteBuffer {
                val startTime = SystemClock.uptimeMillis()
                val imgData = ByteBuffer.allocateDirect(1 * 257 * 257 * 3 * 4)
                imgData.order(ByteOrder.nativeOrder())
    
                val bufmat = Mat()
                val newmat = Mat()
                Utils.bitmapToMat(bitmap, bufmat)
                Imgproc.cvtColor(bufmat, bufmat, Imgproc.COLOR_RGBA2RGB)
                val splitImage: List<Mat> = ArrayList(3)
    
                Core.split(bufmat, splitImage)
                splitImage[0].convertTo(splitImage[0], CV_32F, 1.0 / 255.0)
                splitImage[1].convertTo(splitImage[1], CV_32F, 1.0 / 255.0)
                splitImage[2].convertTo(splitImage[2], CV_32F, 1.0 / 255.0)
                Core.merge(splitImage, newmat)
    
                val buf = FloatArray(257 * 257 * 3)
                newmat.get(0, 0, buf)
    
                for (i in buf.indices) {
                    imgData.putFloat(buf[i])
                }
                imgData.rewind()
                val endTime = SystemClock.uptimeMillis()
                Log.v("Bitwise", (endTime - startTime).toString())
                return imgData
            }
    

    不幸的是,这比 Sunit 上面写的 for 循环和按位运算 (8ms) 稍慢 (10ms)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-25
      • 2016-12-03
      • 1970-01-01
      • 2014-11-10
      • 2023-03-18
      • 2012-11-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多