如何使用OpenCV重建PIL调色板图像?(与P模式的混淆)

0 投票
1 回答
72 浏览
提问于 2025-04-14 17:51

我正在尝试用OpenCV替换掉原本用PIL写的部分代码。理想情况下,我希望能完全去掉PIL,或者至少让输入(first_frame)变成OpenCV的数组。

原始的(PIL)代码:

from PIL import Image
import numpy as np
import cv2

first_frame_path = "00000.png"
image2 = Image.open(first_frame_path)
print(image2.mode) # out: P <---

image2_p = image2.convert("P")
image2_pil = np.array(image2_p)
print(image2_pil.mean()) # out: 0.039107 <---

OpenCV代码:

from PIL import Image
import numpy as np
import cv2

first_frame_path = "00000.png"

image = cv2.imread(first_frame_path, cv2.IMREAD_COLOR)

image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image_from_array = Image.fromarray(image)
print(image_from_array.mode) # out: RGB <---

image_p = image_from_array.convert("P")
image_pil = np.array(image_p)
print(image_pil.mean()) # out: 0.48973 <---

我该如何调整我的OpenCV代码,使得image_pilimage2_pil的值相同呢?

我计算了mean(),只是为了展示这两个数组是不同的,而我的目标是得到相同的结果。

我正在使用一个简单的掩码:

在此输入图片描述

我明白,差异的原因在于原始代码中掩码是直接以P格式加载的,而在OpenCV代码中是RGB格式。不幸的是,我不知道该如何解决这个问题。我尝试指定:

Image.fromarray(image, mode="P")

但是,我得到了一个错误:

发生异常:ValueError 维度过多:3 > 2。

你能告诉我该怎么做才能得到和原始代码一样的NumPy数组吗?

1 个回答

3

这个方法可以实现你想要的效果。它依赖于让PIL(Python Imaging Library)将图像转换为RGB格式,然后再转换回调色板模式,每次都会以自己的方式(希望是可预测的)创建(或重新创建)调色板。

总的来说,你现在做的事情其实不是特别推荐……

#!/usr/bin/env python3

import cv2 as cv
import numpy as np
from PIL import Image

# Load original image
im = Image.open('26dQH.png')

# Convert to RGB, then back to palette so that **PIL decides the palette**
im = im.convert('RGB').convert('P', palette=Image.Palette.ADAPTIVE)

# Get its colours
print(f'im.getcolors(): {im.getcolors()}')
# prints im.getcolors(): [(5367, 0), (5297, 1), (399256, 2)]

# Print first 3 palette entries
print(np.array(im.getpalette()).reshape((-1,3))[:3])

# Prints:
# [[  0 128   0]
#  [128   0   0]
#  [  0   0   0]]

# Read same image using OpenCV and convert to palette-mode PIL Image
na = cv.imread('26dQH.png', cv.IMREAD_COLOR)
pi = Image.fromarray(na).convert('P', palette=Image.Palette.ADAPTIVE)

# Get its colours
print(f'pi.getcolors(): {pi.getcolors()}') 
# prints pi.getcolors(): [(5367, 0), (5297, 1), (399256, 2)]

# Print first 3 palette entries
print(np.array(im.getpalette()).reshape((-1,3))[:3])
# Prints:
# [[  0 128   0]
#  [128   0   0]
#  [  0   0   0]]

注意,你可以用pngcheck来检查PNG的调色板,方法如下:

pngcheck -p  26dQH.png
zlib warning:  different version (expected 1.2.11, using 1.2.12)

File: 26dQH.png (2063 bytes)
  PLTE chunk: 256 palette entries
      0:  (  0,  0,  0) = (0x00,0x00,0x00)
      1:  (128,  0,  0) = (0x80,0x00,0x00)
      2:  (  0,128,  0) = (0x00,0x80,0x00)
      3:  (128,128,  0) = (0x80,0x80,0x00)
      4:  (  0,  0,128) = (0x00,0x00,0x80)
      5:  (128,  0,128) = (0x80,0x00,0x80)
    ...
    ...
    ...
    253:  (224, 96,192) = (0xe0,0x60,0xc0)
    254:  ( 96,224,192) = (0x60,0xe0,0xc0)
    255:  (224,224,192) = (0xe0,0xe0,0xc0)
OK: 26dQH.png (854x480, 8-bit palette, non-interlaced, 99.5%).

或者你可以使用ImageMagick,这个工具非常有用,它可以让你看到每个调色板条目的频率/数量(在第一列):

magick 26dQH.png -format %c histogram:info: 

    399256: (0,0,0) #000000 black
      5367: (0,128,0) #008000 green
      5297: (128,0,0) #800000 maroon

或者用exiftool,方法如下:

exiftool -b -palette  26dQH.png | xxd -g 1 -c 3    
00000000: 00 00 00  ...   # entry 0
00000003: 80 00 00  ...   # entry 1
00000006: 00 80 00  ...   # entry 2
00000009: 80 80 00  ...
...
...

撰写回答