I am trying to replace part of the code originally written with PIL by OpenCV. Ideally, I would like to eliminate PIL or at least make that input (first_frame) is OpenCV array.
Original (PIL) code:
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 code:
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 <---
How should I adapt my OpenCV code to make that image_pil has the same values as image2_pil?
I computed the mean() only to present that these two arrays are different, and my goal is to obtain same results.
I am working with simple mask:
I understand that the difference comes from the fact that in original code mask is directly loaded as P, contradicting to OpenCV code, where it is RGB. Unfortunately, I don't have an idea how to fix it. I tried to specify:
Image.fromarray(image, mode="P")
However, then I am getting error:
Exception has occurred: ValueError Too many dimensions: 3 > 2.
Could you please tell me what can I do to obtain the same NumPy array as in original code?
This does what you want. It relies on allowing PIL to convert the image to RGB and thence back to palette-mode creating (or recreating) the palette in its own (hopefully deterministic) way each time.
In general, what you are doing is really rather ill-advised...
#!/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]]
Note that you can check the palette of a PNG with pngcheck
like this:
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%).
Or you can use ImageMagick - which very usefully gets you the frequencies/counts of each palette entry (in the first column) too:
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
Or with exiftool
like this:
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 ...
...
...