Android端的图像分割功能在人脸抠图、背景虚化、医学影像识别等场景中应用广泛,但分割模型从训练环境迁移到手机端时,往往会出现效果不对、结果全黑或者掩码错位等问题。本文将以tflite分割模型为例,完整讲解Android平台上Segmentation测试的流程,涵盖模型加载、数据预处理、推理执行和结果可视化几个关键环节。

一、图像分割的基本原理与模型输出格式
图像分割的任务是为输入图像的每一个像素分配一个类别标签。与目标检测只输出边界框不同,分割模型的输出是一个与输入分辨率相近的概率矩阵。以一个典型的人像分割模型为例,输入张量形状为1x256x256x3,输出张量形状为1x256x256x2,最后一个维度表示每个像素属于背景和前景两个类别的概率。
理解输出格式是做分割测试的前提。有的模型输出softmax之后的概率值,每一帧像素的两个通道概率之和为1;有的模型直接输出logit值,需要手动做归一化;还有的模型输出单通道掩码,数值在0到1之间表示前景概率。拿到模型后,第一步应该通过Netron等工具查看输入输出的shape和数据类型,确认是float32还是uint8,这直接决定了后续预处理的方式。
如果模型是量化版本,输入输出都是uint8类型,测试时需要额外记录quantization参数,即scale和zero_point,在送入模型前把float像素值转换为量化后的整数,推理完成后再反量化回来。忽略这一步是初学者最常犯的错误,会导致输出结果完全乱掉。
二、在Android工程中集成并加载分割模型
集成tflite模型首先需要在build.gradle中添加依赖,并把模型文件放到assets目录下。依赖配置如下:
dependencies {
implementation 'org.tensorflow:tensorflow-lite:2.14.0'
implementation 'org.tensorflow:tensorflow-lite-support:0.4.4'
}加载模型时使用Interpreter类,建议通过MappedByteBuffer方式加载,这样可以减少一次内存拷贝,对大模型尤其友好。示例代码如下:
private Interpreter loadModel(Context context) throws IOException {
AssetFileDescriptor fd = context.getAssets().openFd("segmentation_model.tflite");
FileInputStream fis = new FileInputStream(fd.getFileDescriptor());
FileChannel channel = fis.getChannel();
MappedByteBuffer buffer = channel.map(
FileChannel.MapMode.READ_ONLY, fd.getStartOffset(), fd.getDeclaredLength());
return new Interpreter(buffer, new Interpreter.Options().setNumThreads(4));
}模型加载完成后,可以通过interpreter.getInputTensor(0)获取输入张量的shape,在代码里打印出来与预期尺寸对比。如果发现shape与文档不一致,说明拿到的模型版本不对,应该先解决这个问题再继续测试,否则后面的调试全部是无用功。
三、输入预处理与推理执行
预处理环节要做三件事:缩放到模型要求的尺寸、调整通道顺序、归一化数值。Android的Bitmap默认是ARGB排列,而tflite模型通常期望RGB排列。下面的代码演示了完整的预处理过程:
private float[] preprocess(Bitmap bitmap, int inputSize) {
Bitmap scaled = Bitmap.createScaledBitmap(bitmap, inputSize, inputSize, true);
int[] pixels = new int[inputSize * inputSize];
scaled.getPixels(pixels, 0, inputSize, 0, 0, inputSize, inputSize);
float[] input = new float[inputSize * inputSize * 3];
for (int i = 0; i < pixels.length; i++) {
int p = pixels[i];
// 归一化到0-1,按RGB顺序填充
input[i * 3] = ((p >> 16) & 0xFF) / 255.0f;
input[i * 3 + 1] = ((p >> 8) & 0xFF) / 255.0f;
input[i * 3 + 2] = (p & 0xFF) / 255.0f;
}
return input;
}注意归一化系数要与训练时保持一致。有的训练流程用除以255的方式,有的用减均值除标准差的方式(ImageNet的均值是123.675、116.28、103.53),如果不一致,分割边缘会出现明显的锯齿或整片误判。推理执行时按模型shape构建输出数组:
float[][][][] output = new float[1][256][256][2];
interpreter.run(input, output);
// 取每个像素前景概率,生成掩码
float[] mask = new float[256 * 256];
for (int y = 0; y < 256; y++) {
for (int x = 0; x & 256; x++) {
mask[y * 256 + x] = output[0][y][x][1];
}
}四、分割结果可视化与常见问题排查
拿到概率掩码后,可以设定一个阈值(一般取0.5)进行二值化,再把掩码缩放回原图尺寸,用半透明颜色叠加显示。可视化是测试中最直观的验证手段,建议同时显示原图、掩码图和叠加图三张图片,方便定位问题出在模型本身还是前后处理环节。
private Bitmap overlayMask(Bitmap src, float[] mask, int size, float threshold) {
Bitmap scaled = Bitmap.createScaledBitmap(src, size, size, true);
Bitmap result = scaled.copy(Bitmap.Config.ARGB_8888, true);
for (int i = 0; i < mask.length; i++) {
if (mask[i] > threshold) {
int x = i % size, y = i / size;
result.setPixel(x, y, 0x80FF0000); // 半透明红色标注前景
}
}
return result;
}测试中遇到的结果异常通常有几类。掩码全黑说明前景概率始终偏低,优先检查归一化方式和通道顺序;掩码整体偏移或者比例不对,多半是缩放时没有保持宽高比,或者输出解析时行列索引用反了;掩码噪点很多,可以适当提高阈值或者对掩码做一次高斯模糊再二值化。另外要注意Bitmap的宽高与模型输入尺寸不同时,直接用createScaledBitmap拉伸会改变目标形状,如果测试图片中的人脸比例差异较大,建议先做letterbox填充再缩放,保持几何关系不失真。
性能方面,建议在真机上统计单帧推理耗时,用SystemClock.elapsedRealtime记录前后时间差。256x256的分割模型在中端机型上一般能跑到30毫秒以内,如果明显偏慢,可以检查是否开启了GPU委托,通过setUseGPU(true)将推理放到GPU上执行,通常能获得数倍的加速效果。完成以上验证后,一套完整的Android分割测试流程就算搭建完毕了。
AndroidSegmentation图像分割修改时间:2026-09-07 12:06:41