首页 > 解决方案 > Android语义分割后处理太慢

问题描述

如果有人能就上周我一直在工作但没有成功的任务提供建议,我将不胜感激。我有语义分割模型(MobileNetV3 + Lightweight ASPP)。简短信息:输入 - 1024x1024,输出 - 相同大小和 2 个类(bg 和车辆),所以我的输出形状是(1、1048576、2)。我不是移动开发人员或 java 世界的人,所以我使用了一些完整的图像分割示例来测试它:来自 google 的示例:https ://github.com/tensorflow/examples/tree/master/lite/ examples/image_segmentation 和另一个开源的:https ://github.com/pillarpond/image-segmenter-android

我成功地将它转换为 tflite 格式,它在启用 GPU 的 OnePlus 7 上的推理时间在 105-140 毫秒之间,对于这种大小。但是在这里我遇到了一个问题:这两个 android 示例或任何您可以找到的语义分割的一般执行时间约为 1050-1300 毫秒(小于 1FPS)。该管道中较慢的部分是图像后处理(~900-1150ms)。您可以在Deeplab#segment方法中看到该部分。因为除了 bg 我只有一堂课 - 我没有第三个循环,但其他一切都没有受到影响,而且仍然很慢。与其他常见的移动尺寸(如 128/226/512)相比,输出尺寸并不小,但仍然如此。我认为在现代智能手机上处理 1024x1024 矩阵并在画布上绘制矩形不应该花费太多时间。我尝试了不同的解决方案,例如将矩阵操作拆分为线程,或者之前创建所有这些对象(如 RectF 和 Recognition),然后在嵌套循环中用新数据填充它们的属性,但我没有成功。在桌面端,我用 numpy 和 opencv 轻松处理它,我什至不了解如何在 Android 中做同样的事情,它是否有效。这是我在 python 中使用的代码:

CLASS_COLORS = [(0, 0, 0), (255, 255, 255)] # black for bg and white for mask


def get_image_array(image_input, width, height):
    img = cv2.imread(image_input, 1)
    img = cv2.resize(img, (width, height))
    img = img.astype(np.float32)
    img[:, :, 0] -= 128.0
    img[:, :, 1] -= 128.0
    img[:, :, 2] -= 128.0
    img = img[:, :, ::-1]
    return img

def get_segmentation_array(seg_arr, n_classes):
    output_height = seg_arr.shape[0]
    output_width = seg_arr.shape[1]
    seg_img = np.zeros((output_height, output_width, 3))
    for c in range(n_classes):
        seg_arr_c = seg_arr[:, :] == c
        seg_img[:, :, 0] += ((seg_arr_c)*(CLASS_COLORS[c][0])).astype('uint8')
        seg_img[:, :, 1] += ((seg_arr_c)*(CLASS_COLORS[c][1])).astype('uint8')
        seg_img[:, :, 2] += ((seg_arr_c)*(CLASS_COLORS[c][2])).astype('uint8')

    return seg_img


interpreter = tf.lite.Interpreter(model_path=f"my_model.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()


img_arr = get_image_array("input.png", 1024, 1024)
interpreter.set_tensor(input_details[0]['index'], np.array([x]))
interpreter.invoke()

output = interpreter.get_tensor(output_details[0]['index'])
output = output.reshape((1024,  1024, 2)).argmax(axis=2)
seg_img = get_segmentation_array(output, 2)
cv2.imwrite("output.png", seg_img)

也许有什么比当前的后处理解决方案更强大的东西。我真的很感激这方面的任何帮助。我确信有任何东西可以改善后处理并将其时间减少到 ~100 毫秒,所以我一般会有 ~5FPS。

标签: javaandroidtensorflow-liteopencv4androidsemantic-segmentation

解决方案


新更新。感谢Farmaker,我使用了在他的仓库中从上面的评论中找到的一段代码,现在管道看起来像:

    int channels = 3;
    int n_classes = 2;
    int float_byte_size = 4;
    int width = model.inputWidth;
    int height = model.inputHeight;

    int[] intValues = new int[width * height];
    ByteBuffer inputBuffer = ByteBuffer.allocateDirect(width * height * channels * float_byte_size).order(ByteOrder.nativeOrder());
    ByteBuffer outputBuffer = ByteBuffer.allocateDirect(width * height * n_classes * float_byte_size).order(ByteOrder.nativeOrder());

    Bitmap input = textureView.getBitmap(width, height);
    input.getPixels(intValues, 0, width, 0, 0, height, height);

    inputBuffer.rewind();
    outputBuffer.rewind();

    for (final int value: intValues) {
        inputBuffer.putFloat(((value >> 16 & 0xff) - 128.0) / 1.0f);
        inputBuffer.putFloat(((value >> 8 & 0xff) - 128.0) / 1.0f);
        inputBuffer.putFloat(((value & 0xff) - 128.0) / 1.0f);
    }

    tfLite.run(inputBuffer, outputBuffer);

    final Bitmap output = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    outputBuffer.flip();
    int[] pixels = new int[width * height];
    for (int i = 0; i < width * height; i++) {
        float max = outputBuffer.getFloat();
        float val = outputBuffer.getFloat();
        int id = val > max ? 1 : 0;
        pixels[i] = id == 0 ? 0x00000000 : 0x990000ff;
    }
    output.setPixels(pixels, 0, width, 0, 0, width, height);
    resultView.setImageBitmap(resizeBitmap(output, resultView.getWidth(), resultView.getHeight()));


    public static Bitmap resizeBitmap(Bitmap bm, int newWidth, int newHeight) {
        int width = bm.getWidth();
        int height = bm.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // CREATE A MATRIX FOR THE MANIPULATION
        Matrix matrix = new Matrix();
        // RESIZE THE BIT MAP
        matrix.postScale(scaleWidth, scaleHeight);

        // "RECREATE" THE NEW BITMAP
        Bitmap resizedBitmap = Bitmap.createBitmap(
                bm, 0, 0, width, height, matrix, false);
        bm.recycle();
        return resizedBitmap;
    }

现在后处理时间约为 70-130 毫秒,第 95 次约为 90 毫秒,除了图像预处理时间约为 60 毫秒,推理时间约为 140 毫秒,其他启用 GPU 和 10 个线程的东西大约 30-40 毫秒给了我一般执行时间约为 330 毫秒,即 3FPS!这是针对 1024x1024 的大型模型。在这一点上,我非常满意并想为我的模型尝试不同的配置,包括 MobilenetV3 small 作为主干。


推荐阅读