分割mask和索引色模式的PNG图像

发布时间:2026/6/26 5:45:53
分割mask和索引色模式的PNG图像 0 分割mask一般而言分割任务的label主要有两种labelme风格的json标注存储了区域多边形的顶点通过按顺序解析points就可以还原出区域多边形。更常见的是图像格式因为大部分情况下即使是json标注计算损失的过程仍然需要在mask张量上进行所以会在预处理阶段就可能转成图像。本文主要介绍对于图像mask索引色模式的PNG图像可能出现的问题。1 索引色PNG图像PNG支持通过调色板palette将灰度值的显示映射为RGB值If the number of distinct pixel values is 256 or less, and the RGB sample depths are not greater than 8, and the alpha channel is absent or exactly 8 bits deep or every pixel is either fully transparent or fully opaque, then the alternative indexed-color representation, achieved through an indexing transformation,may be more efficient for encoding. In the indexed-color representation, each pixel is replaced by an index into a palette. The palette is a list of entries each containing three 8-bit samples (red, green, blue). If an alpha channel is present, there is also a parallel table of 8-bit alpha samples, called the alpha table. - 《Portable Network Graphics (PNG) Specification (Third Edition)》解释一下“more efficient”这个说法。对于真彩色PNG图像它包含RGB共3个色彩通道每个通道位深度为8则每像素需要存储3 bytes的数据。若一张图像尺寸为H x W原始数据量则为3HW bytes。索引色模式下每个像素仅需要保存一个索引则这部分数据量为HW bytes。调色板额外占用3N bytes。在标准提到的极端情况下若N256调色板占用768 bytes总数据量(HW768) bytes。相较于HW768的数量级很小。此处index最大为8 bit即最高256色更高则需要使用RGB或RGBA。对于RGB图像而言这一操作没有任何问题。但当分割mask为灰度图时像素本身就是单通道8 bit数据并且支持更低的位深度此时调色板的数据量完全是负面收益唯一的意义是mask可视化。结论 1除非有可视化需求否则不推荐使用索引色模式代替灰度mask。事实上即使有可视化需求也建议单独处理而非将索引色模式应用到全部mask上。2 不同图像库对索引色图像的读取假设确实需要处理索引色格式的PNG mask不论原因本部分讨论其读取过程。首先创建两个测试用例import numpy as np from PIL import Image data np.array([ [0, 0, 0, 1, 1], [0, 0, 0, 1, 1], [2, 2, 2, 1, 1], [2, 2, 2, 0, 0], [2, 2, 2, 0, 0], ], dtypenp.uint8) grayscale_path grayscale_test.png indexed_path indexed_test.png # --- 灰度模式 --- img_gray Image.fromarray(data, modeL) img_gray.save(grayscale_path) # --- 索引色模式 --- img_idx Image.fromarray(data, modeP) palette [ 255, 0, 0, # red 0, 255, 0, # green 0, 0, 255, # blue ] palette [0] * (768 - len(palette)) img_idx.putpalette(palette) img_idx.save(indexed_path)如图所示2.1 PILimport os import numpy as np from PIL import Image OUT_DIR output_read_results os.makedirs(OUT_DIR, exist_okTrue) grayscale_path grayscale_test.png indexed_path indexed_test.png 读取灰度图像 pil_gray Image.open(grayscale_path) pil_gray_arr np.array(pil_gray) print(f PIL mode: {pil_gray.mode}) print(f numpy shape: {pil_gray_arr.shape}) print(f numpy dtype: {pil_gray_arr.dtype}) print(f 像素值:\n{pil_gray_arr}) 读取索引色模式图像 pil_idx Image.open(indexed_path) pil_idx_arr np.array(pil_idx) print(f PIL mode: {pil_idx.mode}) print(f numpy shape: {pil_idx_arr.shape}) print(f numpy dtype: {pil_idx_arr.dtype}) print(f 像素值(索引):\n{pil_idx_arr})灰度图像读取结果PIL mode: L numpy shape: (5, 5) numpy dtype: uint8 像素值: [[0 0 0 1 1] [0 0 0 1 1] [2 2 2 1 1] [2 2 2 0 0] [2 2 2 0 0]]索引色图像读取结果PIL mode: P numpy shape: (5, 5) numpy dtype: uint8 像素值(索引): [[0 0 0 1 1] [0 0 0 1 1] [2 2 2 1 1] [2 2 2 0 0] [2 2 2 0 0]]结论 2P模式自动参数读出的pil_idx可以作为mask arrayPIL可以正确读取索引色图像。2.2 OpenCVimport cv2 import os OUT_DIR output_read_results os.makedirs(OUT_DIR, exist_okTrue) grayscale_path grayscale_test.png indexed_path indexed_test.png 读取灰度图像 # BGR读取默认 cv_gray_default cv2.imread(grayscale_path, cv2.IMREAD_COLOR) print(f 方式A - IMREAD_COLOR (默认):) print(f shape: {cv_gray_default.shape}) # (H, W, 3) print(f dtype: {cv_gray_default.dtype}) print(f 第一个像素 BGR: {cv_gray_default[0, 0]}) print(f B通道值(5x5):\n{cv_gray_default[..., 0]}) print(f G通道值(5x5):\n{cv_gray_default[..., 1]}) print(f R通道值(5x5):\n{cv_gray_default[..., 2]}) # 灰度读取 cv_gray_gray cv2.imread(grayscale_path, cv2.IMREAD_GRAYSCALE) print(f 方式B - IMREAD_GRAYSCALE:) print(f shape: {cv_gray_gray.shape}) # (H, W) print(f dtype: {cv_gray_gray.dtype}) print(f 像素值:\n{cv_gray_gray}) # 原始读取 cv_gray_unchanged cv2.imread(grayscale_path, cv2.IMREAD_UNCHANGED) print(f 方式C - IMREAD_UNCHANGED:) print(f shape: {cv_gray_unchanged.shape}) print(f dtype: {cv_gray_unchanged.dtype}) print(f 像素值:\n{cv_gray_unchanged}) 读取索引色模式图像 # BGR读取默认 cv_idx_default cv2.imread(indexed_path, cv2.IMREAD_COLOR) print(f 方式A - IMREAD_COLOR (默认):) print(f shape: {cv_idx_default.shape}) print(f dtype: {cv_idx_default.dtype}) print(f 第一个像素 BGR: {cv_idx_default[0, 0]}) print(f B通道值(5x5):\n{cv_idx_default[..., 0]}) print(f G通道值(5x5):\n{cv_idx_default[..., 1]}) print(f R通道值(5x5):\n{cv_idx_default[..., 2]}) # 灰度读取 cv_idx_gray cv2.imread(indexed_path, cv2.IMREAD_GRAYSCALE) print(f 方式B - IMREAD_GRAYSCALE:) print(f shape: {cv_idx_gray.shape}) print(f dtype: {cv_idx_gray.dtype}) print(f 像素值:\n{cv_idx_gray}) # 原始读取 cv_idx_unchanged cv2.imread(indexed_path, cv2.IMREAD_UNCHANGED) print(f 方式C - IMREAD_UNCHANGED:) print(f shape: {cv_idx_unchanged.shape}) print(f dtype: {cv_idx_unchanged.dtype}) print(f B通道值(5x5):\n{cv_idx_unchanged[..., 0]}) print(f G通道值(5x5):\n{cv_idx_unchanged[..., 1]}) print(f R通道值(5x5):\n{cv_idx_unchanged[..., 2]})灰度图像读取结果方式A - IMREAD_COLOR (默认): shape: (5, 5, 3) dtype: uint8 第一个像素 BGR: [0 0 0] B通道值(5x5): [[0 0 0 1 1] [0 0 0 1 1] [2 2 2 1 1] [2 2 2 0 0] [2 2 2 0 0]] G通道值(5x5): [[0 0 0 1 1] [0 0 0 1 1] [2 2 2 1 1] [2 2 2 0 0] [2 2 2 0 0]] R通道值(5x5): [[0 0 0 1 1] [0 0 0 1 1] [2 2 2 1 1] [2 2 2 0 0] [2 2 2 0 0]] 方式B - IMREAD_GRAYSCALE: shape: (5, 5) dtype: uint8 像素值: [[0 0 0 1 1] [0 0 0 1 1] [2 2 2 1 1] [2 2 2 0 0] [2 2 2 0 0]] 方式C - IMREAD_UNCHANGED: shape: (5, 5) dtype: uint8 像素值: [[0 0 0 1 1] [0 0 0 1 1] [2 2 2 1 1] [2 2 2 0 0] [2 2 2 0 0]]opencv读取灰度图像没有任何异常。索引色图像读取结果方式A - IMREAD_COLOR (默认): shape: (5, 5, 3) dtype: uint8 第一个像素 BGR: [ 0 0 255] B通道值(5x5): [[ 0 0 0 0 0] [ 0 0 0 0 0] [255 255 255 0 0] [255 255 255 0 0] [255 255 255 0 0]] G通道值(5x5): [[ 0 0 0 255 255] [ 0 0 0 255 255] [ 0 0 0 255 255] [ 0 0 0 0 0] [ 0 0 0 0 0]] R通道值(5x5): [[255 255 255 0 0] [255 255 255 0 0] [ 0 0 0 0 0] [ 0 0 0 255 255] [ 0 0 0 255 255]] 方式B - IMREAD_GRAYSCALE: shape: (5, 5) dtype: uint8 像素值: [[ 76 76 76 149 149] [ 76 76 76 149 149] [ 29 29 29 149 149] [ 29 29 29 76 76] [ 29 29 29 76 76]] 方式C - IMREAD_UNCHANGED: shape: (5, 5, 3) dtype: uint8 B通道值(5x5): [[ 0 0 0 0 0] [ 0 0 0 0 0] [255 255 255 0 0] [255 255 255 0 0] [255 255 255 0 0]] G通道值(5x5): [[ 0 0 0 255 255] [ 0 0 0 255 255] [ 0 0 0 255 255] [ 0 0 0 0 0] [ 0 0 0 0 0]] R通道值(5x5): [[255 255 255 0 0] [255 255 255 0 0] [ 0 0 0 0 0] [ 0 0 0 255 255] [ 0 0 0 255 255]]对于默认的BGR模式调色板的色彩信息被读入最终结果为三通道彩色图像。对于灰度模式可以发现opencv无法还原原始索引而是将色彩压缩成灰度遵从Gray 0.299R 0.587G 0.114B。这在mask上是比较严重的问题因为像素到类别的映射一般是预先在dataset类、配置文件等位置编码的将导致模型无法正确解析mask类别。对于原始读取仍只能解析到色彩信息。结论 3opencv并不适合从索引色图像解析mask并且在其它应用场景opencv也难以还原索引值对于opencv而言索引色图像基本上就是彩色图像。总结不要用索引色PNG图像制做mask仅在需要可视化时再进行转换。如果必须要处理则选择PIL。