ML pipelines, OpenCV and scientific code all work on arrays, so moving images in and out of Pillow is routine.
Pillow images implement the array interface, so np.asarray(im) gives an array without writing any conversion code:
- RGB becomes shape
(height, width, 3), dtypeuint8 - L becomes
(height, width); RGBA(height, width, 4) 1mode becomes aboolarray;I;16givesuint16,Fgivesfloat32
Note the axis order flips relative to im.size, which is (width, height). The array from np.asarray is read-only; use np.array(im) for a writable copy.
Image.fromarray(arr) goes the other way, inferring the mode from shape and dtype: uint8 with 3 channels becomes RGB, 2-D uint8 becomes L, float32 becomes F. If your array is float64 in the range 0 to 1, as many pipelines produce, scale and convert first: (arr * 255).clip(0, 255).astype(np.uint8). Using fromarray's mode= argument to reinterpret the data as a different type is deprecated; convert the array's dtype instead, or call .convert() afterwards.
OpenCV uses BGR channel order while Pillow uses RGB, so reverse the last axis when crossing between them: Image.fromarray(bgr[..., ::-1]).
For performance, prefer whole-array NumPy operations over getpixel/putpixel loops, then convert back once.
Example
import numpy as np
from PIL import Image
im = Image.new("RGB", (4, 2), (10, 20, 30))
arr = np.asarray(im)
print(arr.shape, arr.dtype, arr.flags.writeable) # (2, 4, 3) uint8 False
work = np.array(im) # writable copy
work[:, :2] = [255, 0, 0] # left half red
out = Image.fromarray(work)
print(out.mode, out.size, out.getpixel((0, 0))) # RGB (4, 2) (255, 0, 0)
prob = np.random.default_rng(0).random((2, 4)) # float64 in [0, 1)
gray = Image.fromarray((prob * 255).astype(np.uint8))
print(gray.mode) # L
print(Image.fromarray(prob.astype(np.float32)).mode) # F
bgr = work[..., ::-1] # pretend OpenCV array
print(Image.fromarray(np.ascontiguousarray(bgr[..., ::-1])).getpixel((0, 0))) # (255, 0, 0)Remember
np.asarray(im)gives (h, w, channels); it is read-only,np.arraycopiesImage.fromarrayinfers the mode from shape and dtype- Scale float images to uint8 before making RGB/L images
- Swap BGR and RGB when moving between OpenCV and Pillow
Watch out for
- Passing a float64 array in 0..1 to Image.fromarray and getting an F-mode or black image instead of a photo.