numpy
image processing
python
data visualization
array manipulation

Saving a Numpy array as an image

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When working with image data in Python, you frequently represent images as NumPy arrays. Whether you are processing photographs, generating visualizations, or working with scientific imaging data, at some point you need to save that array back to disk as an image file. Python offers several libraries for this task, each with different strengths and data format expectations. Understanding which library to use and how to prepare your array data will prevent corrupted or incorrectly colored output images.

Data Type Considerations

Before saving any array as an image, you need to understand the expected data format. Most image formats expect pixel values as unsigned 8-bit integers (uint8) in the range 0 to 255. If your array uses floating-point values (common in scientific computing), you must convert it first:

python
1import numpy as np
2
3# Float array with values between 0.0 and 1.0
4float_array = np.random.rand(100, 100, 3)
5
6# Convert to uint8 by scaling to 0-255
7uint8_array = (float_array * 255).astype(np.uint8)

Skipping this conversion is the single most common mistake. Libraries handle mismatched types differently: some silently produce garbled images, others throw errors.

Using Pillow (PIL)

Pillow is the most widely used library for basic image I/O in Python. The Image.fromarray() method converts a NumPy array to a PIL Image, which you can then save:

python
1from PIL import Image
2import numpy as np
3
4# Create a 100x100 RGB image with random pixel values
5array = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
6
7# Save as PNG
8img = Image.fromarray(array)
9img.save("output.png")

For grayscale images, pass a 2D array or specify the mode:

python
gray_array = np.random.randint(0, 256, (100, 100), dtype=np.uint8)
img = Image.fromarray(gray_array, mode='L')
img.save("grayscale.png")

For RGBA images (with transparency), use a shape of (height, width, 4):

python
rgba_array = np.random.randint(0, 256, (100, 100, 4), dtype=np.uint8)
img = Image.fromarray(rgba_array, mode='RGBA')
img.save("transparent.png")

Pillow requires the array to be uint8. If you pass a float array, you will get unexpected results or an error.

Using Matplotlib

Matplotlib's imsave function is convenient when you are already using matplotlib for plotting. It handles float arrays in the 0.0-1.0 range natively:

python
1import matplotlib.pyplot as plt
2import numpy as np
3
4# Float array works directly with matplotlib
5float_array = np.random.rand(100, 100, 3)
6plt.imsave("output_mpl.png", float_array)
7
8# For grayscale, specify a colormap
9gray_array = np.random.rand(100, 100)
10plt.imsave("gray_mpl.png", gray_array, cmap='gray')

One important detail: plt.imsave always saves RGBA PNGs by default, even for grayscale input. This means the file size may be larger than expected. Matplotlib also clips float values outside the 0.0-1.0 range, which can silently alter your data.

Using OpenCV

OpenCV is the standard for computer vision tasks. Its imwrite function saves arrays to disk. The critical difference is that OpenCV uses BGR channel ordering by default, not RGB:

python
1import cv2
2import numpy as np
3
4# Create an RGB array
5rgb_array = np.random.randint(0, 256, (100, 100, 3), dtype=np.uint8)
6
7# Convert RGB to BGR before saving with OpenCV
8bgr_array = cv2.cvtColor(rgb_array, cv2.COLOR_RGB2BGR)
9cv2.imwrite("output_cv2.png", bgr_array)

If you skip the RGB-to-BGR conversion, your reds and blues will be swapped in the saved image. For grayscale, no conversion is needed:

python
gray_array = np.random.randint(0, 256, (100, 100), dtype=np.uint8)
cv2.imwrite("gray_cv2.png", gray_array)

OpenCV also supports saving 16-bit images, which is useful for scientific and medical imaging:

python
depth_array = np.random.randint(0, 65535, (100, 100), dtype=np.uint16)
cv2.imwrite("depth.png", depth_array)

Deprecated Approach: scipy.misc.imsave

Older code and tutorials may reference scipy.misc.imsave. This function was deprecated in SciPy 1.0 and removed in SciPy 1.2. If you encounter it, replace with Pillow:

python
1# Old (deprecated, will not work in modern SciPy)
2# from scipy.misc import imsave
3# imsave("output.png", array)
4
5# Modern replacement
6from PIL import Image
7Image.fromarray(array).save("output.png")

Common Pitfalls

  • Saving a float array without converting to uint8 first, producing an all-black or garbled image
  • Forgetting the BGR channel order when using OpenCV, resulting in swapped red and blue channels
  • Using matplotlib's imsave on uint8 arrays in the 0-255 range without realizing it expects 0.0-1.0 for floats
  • Passing a 2D array to Image.fromarray without specifying mode='L', which can misinterpret the data
  • Using the deprecated scipy.misc.imsave in modern Python environments where it no longer exists
  • Not matching array shape to the intended format (e.g., (height, width, 3) for RGB vs (height, width) for grayscale)

Summary

  • Use Pillow (Image.fromarray) for general-purpose image saving with uint8 arrays
  • Use matplotlib (plt.imsave) when working with float arrays in the 0.0-1.0 range or when you need colormap support
  • Use OpenCV (cv2.imwrite) for computer vision workflows, but remember the BGR channel order
  • Always verify your array's dtype and value range before saving
  • Convert float arrays to uint8 with (array * 255).astype(np.uint8) when using Pillow
  • Avoid scipy.misc.imsave as it has been removed from modern SciPy versions

Course illustration
Course illustration

All Rights Reserved.