在 Tensorflow-lite 中将 Bitmap 转换为 ByteBuffer (float) Android

Converting Bitmap to ByteBuffer (float) in Tensorflow-lite Android

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

我们如何使用 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 位图到字节缓冲区:-

    /** 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));
      }
  1. 我认为您代码中的 255/0 是一个 copy/paste 错误,不是真正的代码。
  2. 我想知道纯 Java 解决方案的时间成本是多少,尤其是当您将它与推理的时间成本进行权衡时。对我来说,Google 的 mobilenet_v1_1.0_224 的位图稍微大一点,朴素的浮动缓冲区准备不到推理时间的 5%。
  3. 我可以量化 tflite 模型(使用从 [=14= 生成 .tflite 文件的 tflite_convert 实用程序)。实际上可能有三个量化操作,但我只用了两个:--inference_input_type=QUANTIZED_UINT8--post_training_quantize
    • 生成的模型大约是 float32 模型大小的 25%,这本身就是一项成就。
    • 生成的模型运行速度大约快两倍(至少在某些设备上是这样)。
    • 并且,生成的模型消耗 unit8 个输入。这意味着我们写 imgData.put((val>> 16) & 0xFF) 而不是 imgData.putFloat(((val>> 16) & 0xFF) / 255.f),依此类推。

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

putFLoat(byteval / 256f)

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

如前所述 here use the below code from 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
}

对于浮点数,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 循环和按位运算(8 毫秒)稍慢(10 毫秒)。