463

How do I convert a PIL Image back and forth to a NumPy array so that I can do faster pixel-wise transformations than PIL's PixelAccess allows? I can convert it to a NumPy array via:

pic = Image.open("foo.jpg")
pix = numpy.array(pic.getdata()).reshape(pic.size[0], pic.size[1], 3)

But how do I load it back into the PIL Image after I've modified the array? pic.putdata() isn't working well.

Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
akdom
  • 32,264
  • 27
  • 73
  • 79
  • 9
    Note that `pic.size[0]` and `pic.size[1]` should be swapped (ie. `reshape(pic.size[1], pic.size[0], 3)`), since `size` is `width x height` or `x * y`, while matrix ordering is `rows x columns`. – foges Mar 21 '18 at 15:16

9 Answers9

457

You're not saying how exactly putdata() is not behaving. I'm assuming you're doing

>>> pic.putdata(a)
Traceback (most recent call last):
  File "...blablabla.../PIL/Image.py", line 1185, in putdata
    self.im.putdata(data, scale, offset)
SystemError: new style getargs format but argument is not a tuple

This is because putdata expects a sequence of tuples and you're giving it a numpy array. This

>>> data = list(tuple(pixel) for pixel in pix)
>>> pic.putdata(data)

will work but it is very slow.

As of PIL 1.1.6, the "proper" way to convert between images and numpy arrays is simply

>>> pix = numpy.array(pic)

although the resulting array is in a different format than yours (3-d array or rows/columns/rgb in this case).

Then, after you make your changes to the array, you should be able to do either pic.putdata(pix) or create a new image with Image.fromarray(pix).

wovano
  • 4,543
  • 5
  • 22
  • 49
dF.
  • 74,139
  • 30
  • 130
  • 136
  • 44
    That page lists `numpy.asarray(pic)` as the "proper" way to convert, not `numpy.array(pic)`. As per [this answer](http://stackoverflow.com/questions/14415741/numpy-array-vs-asarray) `array` will make a copy whereas `asarray` will not (but then the `asarray` result will be read-only). – Arthur Tacca Nov 30 '16 at 10:13
  • 3
    The second answer is better. – Nathan majicvr.com Jun 02 '20 at 17:42
  • 1
    Type check warns: 'Expected type 'Union[ndarray, Iterable, int, float]', got 'Image' instead' – David Taub Nov 15 '22 at 14:11
348

Open I as an array:

>>> I = numpy.asarray(PIL.Image.open('test.jpg'))

Do some stuff to I, then, convert it back to an image:

>>> im = PIL.Image.fromarray(numpy.uint8(I))

Source: Filter numpy images with FFT, Python

If you want to do it explicitly for some reason, there are pil2array() and array2pil() functions using getdata() on this page in correlation.zip.

Josiah Yoder
  • 3,321
  • 4
  • 40
  • 58
endolith
  • 25,479
  • 34
  • 128
  • 192
  • 2
    @ArditS.: Did you `import Image` first? Do you have PIL installed? – endolith Nov 09 '13 at 17:04
  • 9
    Is the `uint8` conversion necessary? – Neil Traft Jan 16 '14 at 05:33
  • 1
    you might need to use numpy.copy to have I be editable: `I = numpy.copy(numpy.asarray(Image.open('test.jpg')))` – Nick Nov 05 '14 at 06:57
  • 1
    Thanks for this, I was using np.array(image.getdata(),...) which is incredibly slow compared to this. – Rebs Nov 25 '14 at 10:08
  • 1
    @NeilTraft No, sorry. Note that if your image is a float array from 0 to 1, you need to scale it first: `Image.fromarray(J*255)` – endolith Dec 27 '14 at 21:04
  • 6
    `numpy.asarray(Image.open(filename))` seems to work for .jpg images but not for .png. The result displays as `array(, dtype=object)`. There seem to be no obviously-named methods of the `PngImagePlugin.PngImageFile` object for solving this. Guess I should ask this as a new question but it's very relevant to this thread. Anybody understand what's going wrong here? – jez Dec 01 '15 at 23:59
  • 5
    @Rebs: here's the reason *why* this is so much faster: `getdata()` returns a sequence like object (http://pillow.readthedocs.io/en/3.4.x/reference/Image.html#PIL.Image.Image.getdata), but a pillow image implements the `__array_interface__` which `numpy` can use to access the raw bytes of an image without having to pass through an iterator (see https://github.com/python-pillow/Pillow/blob/730cf93c32ffb747c018afffe597ef9ae264a20a/PIL/Image.py#L633 and https://docs.scipy.org/doc/numpy/reference/arrays.interface.html). You can even just use `numpy.array(PIL.Image.open('test.jpg'))` – tdp2110 Jul 28 '17 at 17:34
  • 6
    @jez Check if the Image object is closed before you convert it to numpy. The same happened to me and I found I closed the image object somewhere. – Shaohua Li Aug 18 '18 at 14:34
  • 1
    @jez I use `numpy.asarray(PIL.Image.open('test.png').convert('RGB'))` which works for even for .png images – Mohit Lamba Aug 08 '20 at 05:17
127

I am using Pillow 4.1.1 (the successor of PIL) in Python 3.5. The conversion between Pillow and numpy is straightforward.

from PIL import Image
import numpy as np
im = Image.open('1.jpg')
im2arr = np.array(im) # im2arr.shape: height x width x channel
arr2im = Image.fromarray(im2arr)

One thing that needs noticing is that Pillow-style im is column-major while numpy-style im2arr is row-major. However, the function Image.fromarray already takes this into consideration. That is, arr2im.size == im.size and arr2im.mode == im.mode in the above example.

We should take care of the HxWxC data format when processing the transformed numpy arrays, e.g. do the transform im2arr = np.rollaxis(im2arr, 2, 0) or im2arr = np.transpose(im2arr, (2, 0, 1)) into CxHxW format.

Daniel
  • 2,195
  • 3
  • 14
  • 24
  • 6
    This is about the cleanest example, including import statements (thanks for that detail). Let's vote this answer up to increase visibility. – David Parks Apr 07 '18 at 15:17
  • 1
    I found that when I converted a PIL drawn image to a numpy array, when using matplotlib imshow on the array, it showed it upside down requiring a `np.flipud` to fix. Although my PIL image was created from scratch using `ImageDraw.Draw`. I think one must be careful where the origin of their coordinates comes from. – CMCDragonkai Apr 13 '18 at 07:21
  • 1
    Bless you!! I have been looking for this answer for half a day. It solves my problem of restoring the original axis after of the plot image to the original one. – Tinkerbell Jun 27 '18 at 10:21
38

You need to convert your image to a numpy array this way:

import numpy
import PIL

img = PIL.Image.open("foo.jpg").convert("L")
imgarr = numpy.array(img) 
Billal Begueradj
  • 20,717
  • 43
  • 112
  • 130
35

Convert Numpy to PIL image and PIL to Numpy

import numpy as np
from PIL import Image

def pilToNumpy(img):
    return np.array(img)

def NumpyToPil(img):
    return Image.fromarray(img)
Kamran Gasimov
  • 1,445
  • 1
  • 14
  • 11
3

The example, I have used today:

import PIL
import numpy
from PIL import Image

def resize_image(numpy_array_image, new_height):
    # convert nympy array image to PIL.Image
    image = Image.fromarray(numpy.uint8(numpy_array_image))
    old_width = float(image.size[0])
    old_height = float(image.size[1])
    ratio = float( new_height / old_height)
    new_width = int(old_width * ratio)
    image = image.resize((new_width, new_height), PIL.Image.ANTIALIAS)
    # convert PIL.Image into nympy array back again
    return array(image)
Uki D. Lucas
  • 516
  • 6
  • 4
0

If your image is stored in a Blob format (i.e. in a database) you can use the same technique explained by Billal Begueradj to convert your image from Blobs to a byte array.

In my case, I needed my images where stored in a blob column in a db table:

def select_all_X_values(conn):
    cur = conn.cursor()
    cur.execute("SELECT ImageData from PiecesTable")    
    rows = cur.fetchall()    
    return rows

I then created a helper function to change my dataset into np.array:

X_dataset = select_all_X_values(conn)
imagesList = convertToByteIO(np.array(X_dataset))

def convertToByteIO(imagesArray):
    """
    # Converts an array of images into an array of Bytes
    """
    imagesList = []

    for i in range(len(imagesArray)):  
        img = Image.open(BytesIO(imagesArray[i])).convert("RGB")
        imagesList.insert(i, np.array(img))

    return imagesList

After this, I was able to use the byteArrays in my Neural Network.

plt.imshow(imagesList[0])
Charles Vogt
  • 121
  • 1
  • 4
0

I can vouch for svgtrace, I found it both super simple and relatively fast. Find it here: https://pypi.org/project/svgtrace/

This is how I used it:

from svgtrace import trace

asset_path = 'image.png'
save_path = 'traced_image.svg'

Path(save_path).write_text(trace(asset_path), encoding='utf-8')

It took an average of 3 seconds for a 1080x1080px image on my machine. (MacBook Pro 2017)

-4
def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()

You can transform the image into numpy by parsing the image into numpy() function after squishing out the features( unnormalization)

Thiyagu
  • 49
  • 2
  • 6
  • This is for conversion between numpy and a tensor in e.g. PyTorch. This question is about PIL. – moi Mar 08 '22 at 16:47