在Tensorflow-lite Android中,将位图转换为字节缓冲区 (float)

3
在tensorflow-lite的Android图像分类演示代码中,为了提高性能,首先将图像转换为ByteBuffer格式。从位图到浮点格式的转换以及随后的转换为字节缓冲区似乎是一项昂贵的操作(循环、位运算符、浮点存储器复制等)。我们尝试使用opencv实现相同的逻辑,以获得一些速度优势。以下代码可以正常工作,但由于此转换中存在某些逻辑错误,因此向模型输入数据的输出似乎不正确。模型的输入应该是RGB格式,数据类型为float[1,197,197,3]。
如何使用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();

    // 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));
      }

现在TensorFlow Android演示已经在其最新的“支持库”中包含了一种名为“TensorImage”的数据类型,用于将位图加载到模型中。 - anilsathyan7
你现在可以使用这个类来简化你的工作 https://www.tensorflow.org/lite/inference_with_metadata/lite_support - Farmaker
3个回答

2
  1. 我认为你代码中的255/0是复制粘贴错误,不是真正的代码。
  2. 我想知道纯Java解决方案的时间成本,特别是当你将其与推断的时间成本进行比较时。对于我来说,对于Google的mobilenet_v1_1.0_224稍大一些的位图,天真的浮点缓冲区准备时间少于推断时间的5%。
  3. 我可以使用相同的tflite_convert工具量化tflite模型(从.h5生成.tflite文件)。实际上可能有三个量化操作,但我只使用了两个:--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)

最初的回答:其中byteval是取值范围在[0:255]之间的整数。

它更快,但不如当前的opencv方法快。看起来opencv具有neon优化的低级向量化操作... - anilsathyan7
很高兴知道这个。我希望我们可以使用量化模型,它对我们来说比GPU更快,而且体积也显著更小。顺便说一下,从表面上看,仅量化输入不应该失去准确性(这意味着将字节转换为浮点数不能提高准确性),并且生成的模型仍然可以与GpuDelegate一起使用。 - Alex Cohn
这取决于场景或用例; 对于我们的语义分割情况,小的准确度损失在输出中真的很明显。准确率损失取决于量化级别。GPU仅支持float32(float16仍处于实验阶段)。权重只量化了大小; 但在tflite gpu(去量化)中不支持某些操作符。如果使用更大的图像/模型,则可能会看到量化的CPU和GPU之间的差异。 GPU将更快。顺便问一下,您所说的只输入量化是什么意思? 它是量化的UINT8输入吗? 这在GPU上运行不了。我们尝试了您早期提到的两种方法,但它没有起作用。 - anilsathyan7
在我的实验中,使用--inference_input_type=QUANTIZED_UINT8参数并没有导致GPU代理失败。不幸的是,它也没有运行得更快,可能只是回退到了CPU。 - Alex Cohn
GPU在速度方面甚至比量化模型表现更好 - 在我的实验中,即使在相当普通的设备上,也没有设置线程数。对于量化模型而言,在相同的演示应用程序上,最佳推理时间是在设置3个线程时,这比GPU在浮点运算上更快。 - Alex Cohn
显示剩余4条评论

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
}

0

对于浮点数,均值为1,标准差为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循环和位运算写的稍微慢一些(10毫秒)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接