PAYATHON 2026

如何统一 Python 与 Android 的 RGB 像素和 MobileNetV3 向量?

数据小陈

结论

比较 embedding 前,先统一两端使用的图像字节、透明像素处理方式和预处理流程。Bitmap.getPixels() 能提取 Android 解码后的 RGB 值,但不保证这些值与 Pillow 的 convert("RGB") 在透明度语义、色彩转换和缩放结果上相同。含透明像素的 PNG 尤其可能出现差异。

Python 样例先执行 Image.open(path).convert("RGB"),Kotlin 样例则通过 BitmapFactory.decodeStream 解码,再调用 getPixels()。两端列出的左上角 20×20 像素确实不同,但仅凭这些输出,无法判断差异是否完全来自透明度。现有的原始像素对比代码也没有显示图片尺寸、alpha 通道信息,以及两端的输入文件是否逐字节相同。建议先核对这些条件。

先核对同一份原始图像

比较解码结果前,先确认 Python 文件和 Android assets 中的图片是同一份文件,并打印两端解码后的宽度和高度。相同的像素坐标不一定对应同一张图;文件版本或尺寸不同,也会导致大片区域不一致。

原图解码和缩放到模型输入尺寸后的结果要分开比较。不要把原图左上角 20×20 像素的输出与 resize 后的张量混在一起判断。

明确透明像素的处理方式

Pillow 的 convert("RGB") 会移除 alpha 通道。Android 解码透明 PNG 后得到的像素值,则会受到 alpha 和位图表示方式影响。完全透明像素中隐藏的 RGB 信息,可能无法从 Android 位图中恢复。因此,图片含透明区域时,两端直接读取 RGB,未必会得到相同值。

先选定统一的处理规则,例如把透明区域铺在白色背景上,再让 Python 和 Android 按同一规则合成后送入模型。Python 侧可以这样处理:

from PIL import Image

img = Image.open(path).convert("RGBA")
background = Image.new("RGBA", img.size, (255, 255, 255, 255))
img = Image.alpha_composite(background, img).convert("RGB")

如果业务要求使用黑色背景,把 (255, 255, 255, 255) 改为 (0, 0, 0, 255)。Android 侧也要用相同颜色合成,并确保送入模型的是不透明图像。两端的 alpha 处理方式应保持一致。

让缩放和模型输入保持一致

即使原图 RGB 值一致,不同的缩放算法或色彩空间转换也可能导致 resize 后的数值不同。在验证之前,不应假定 Python 的 img.resize((self.width, self.height)) 与 Kotlin 的 ResizeOp(..., BILINEAR) 会生成逐像素相同的结果。应明确两端的缩放实现和参数,并直接比较缩放后、归一化前的 224×224×3 数组。

两端的归一化公式对应如下:

Python:   (x / 127.5) - 1.0
Android:  (x - 127.5) / 127.5

对于相同的 x,这两个表达式在数学上等价。如果输入像素已经对齐,但张量仍有差异,再检查数据类型、通道顺序和张量布局,确认两端都是 RGB、NHWC,并且使用的数据类型符合模型签名。

推荐的处理方式

要让跨端匹配更稳定,可以固定一套可复用的预处理约定:

  1. 确认输入文件相同,并明确透明区域的合成背景。
  2. 明确色彩空间和通道顺序。
  3. 固定缩放尺寸和算法。
  4. 对缩放后的像素使用相同的归一化公式。
  5. 用少量固定图片,依次比较解码像素、缩放结果、模型输入张量和最终 embedding。

如果必须保证像素级一致,可以先在受控环境中将输入规范化为不透明、固定尺寸的 RGB 图片,再让两端使用这份规范化结果。若还要求 embedding 数值严格一致,也需要固定模型文件、输入张量、推理实现和数值精度。仅让 RGB 像素接近,无法保证不同设备上的推理结果逐位相同。

备注:内容仅供参考。