4

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

3 回答 3

2
  1. 我相信255/0您的代码中存在复制/粘贴错误,而不是真实代码。
  2. 我想知道纯 Java 解决方案的时间成本是多少,尤其是当您将其与推理的时间成本进行权衡时。对我来说,使用 Google 的稍大一点的位图mobilenet_v1_1.0_224,天真的浮点缓冲区准备时间不到推理时间的 5%。
  3. 我可以量化 tflite 模型(使用相同的tflite_convert实用程序 .tflite.h5.--inference_input_type=QUANTIZED_UINT8--post_training_quantize
    • 生成的模型大约是 float32 模型的 25%,这本身就是一项成就。
    • 生成的模型运行速度大约快两倍(至少在某些设备上)。
    • 并且,生成的模型消耗unit8输入。这意味着不是imgData.putFloat(((val>> 16) & 0xFF) / 255.f)我们写imgData.put((val>> 16) & 0xFF),等等。

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

putFLoat(byteval / 256f)

其中bytevalint 在 [0:255] 范围内。

于 2019-04-30T22:02:03.443 回答
0

如此处所述,使用此处的以下代码将位图转换为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
}
于 2021-10-31T19:05:44.797 回答
0

对于浮点数,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)。

于 2021-11-02T15:45:31.503 回答