Android语义分割后处理太慢

Posted

技术标签:

【中文标题】Android语义分割后处理太慢【英文标题】:Android semantic segmentation post-processing is too slow 【发布时间】:2020-10-03 11:03:16 【问题描述】:

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

我成功地将其转换为 tflite 格式,并且它在启用 GPU 且 10 个线程的 OnePlus 7 上的推理时间在 105-140 毫秒之间。但是在这里我遇到了一个问题:这两个 android 示例或任何您可以找到的语义分割的一般执行时间约为 1050-1300 毫秒(小于 1FPS)。该管道中较慢的部分是图像后处理(~900-1150ms)。您可以在 Deeplab#segment 方法中看到该部分。因为除了 bg 之外我只有 1 个班级 - 我没有 this third loop,但其他所有内容都没有受到影响,而且仍然很慢。与其他常见的移动尺寸(如 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。

【问题讨论】:

我认为你应该在 android 中尝试 OpenCv。看看this 项目,其中 OpenCv 在一个用于分割肺病的安卓应用程序中使用。 OpenCv 有一个用于斑点检测的类,它可以在斑点(掩码)的边缘绘制矩形。如果您需要任何帮助,请标记我 感谢@Farmaker 的帮助! 【参考方案1】:

新更新。感谢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 作为主干。

【讨论】:

以上是关于Android语义分割后处理太慢的主要内容,如果未能解决你的问题,请参考以下文章

语义分割标注labelme图片处理过程

语义分割(研究现状技术基础)

如何为多类语义分割预处理 RGB 分割掩码?

基于深度学习的图像语义分割方法综述

《基于深度学习的图像语义分割方法综述》阅读理解

RangeNet++ 解读快速准确的激光雷达语义分割