How to Display an Image in Python
Displaying images in Python is a fundamental skill for data visualization, computer vision projects, and user interface development. Whether you're working with machine learning models, creating photo editing applications, or simply learning to manipulate visual data, knowing how to properly render images in Python opens up countless possibilities for your programming projects.
Python offers several powerful libraries for image display, each with its own strengths and use cases. Here's the thing — the most popular options include Matplotlib for scientific visualization, PIL/Pillow for image processing, OpenCV for computer vision tasks, and Tkinter for graphical user interfaces. Understanding these different approaches will help you choose the right tool for your specific needs It's one of those things that adds up..
Understanding Image Representation in Python
Before diving into display methods, it helps to understand how Python represents images internally. Images are typically stored as multi-dimensional arrays where each pixel contains numerical values representing color information. Grayscale images use a single value per pixel (0-255), while RGB images use three values (red, green, blue channels) per pixel.
The moment you load an image into Python, it becomes a NumPy array that can be manipulated mathematically. This representation allows for powerful image processing capabilities, but also means you need appropriate tools to convert these arrays back into visible images on your screen.
Method 1: Using Matplotlib
Matplotlib is one of the most versatile libraries for displaying images in Python, particularly popular in data science and machine learning workflows It's one of those things that adds up. And it works..
Basic Image Display
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
# Load the image
img = mpimg.imread('your_image.jpg')
# Display the image
plt.imshow(img)
plt.axis('off') # Remove axis labels
plt.show()
Working with NumPy Arrays
Matplotlib works easily with NumPy arrays, making it ideal for processed images:
import numpy as np
import matplotlib.pyplot as plt
# Create a simple colored image
height, width = 100, 100
image_array = np.zeros((height, width, 3))
# Set red channel
image_array[:, :, 0] = 1.0
# Set green channel
image_array[:, :, 1] = 0.5
# Blue channel remains 0
plt.imshow(image_array)
plt.title("Custom Colored Image")
plt.show()
Advanced Matplotlib Features
Matplotlib provides extensive customization options:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
img = mpimg.imread('your_image.png')
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Original image
axes[0].imshow(img)
axes[0].set_title('Original')
# Grayscale version
gray_img = img[:, :, :3].mean(axis=2)
axes[1].imshow(gray_img, cmap='gray')
axes[1].set_title('Grayscale')
# Flipped image
flipped_img = np.fliplr(img)
axes[2].imshow(flipped_img)
axes[2].set_title('Flipped')
for ax in axes:
ax.axis('off')
plt.tight_layout()
plt.show()
Method 2: Using PIL/Pillow
The Python Imaging Library (PIL), now maintained as Pillow, is specifically designed for image processing and display.
Basic Usage
from PIL import Image
# Open an image file
img = Image.open('your_image.jpg')
# Display using default viewer
img.show()
Converting Between Formats
Pillow makes format conversion straightforward:
from PIL import Image
# Open image
img = Image.open('input.jpg')
# Convert to different modes
grayscale = img.convert('L') # Grayscale
rgba = img.convert('RGBA') # Add alpha channel
rgb = img.convert('RGB') # Ensure RGB format
# Save converted images
grayscale.save('grayscale.png')
rgba.save('with_alpha.png')
Image Manipulation Before Display
from PIL import Image, ImageEnhance
img = Image.open('your_image.jpg')
# Enhance brightness
enhancer = ImageEnhance.Brightness(img)
bright_img = enhancer.enhance(1.5) # 50% brighter
# Enhance contrast
enhancer = ImageEnhance.Contrast(img)
high_contrast = enhancer.enhance(2.0) # Double contrast
# Display enhanced images
bright_img.show()
high_contrast.show()
Method 3: Using OpenCV
OpenCV is the go-to library for computer vision applications and provides strong image display capabilities And that's really what it comes down to..
Basic Display
import cv2
# Read image (OpenCV reads in BGR format)
img = cv2.imread('your_image.jpg')
# Display image in a window
cv2.imshow('Image Window', img)
cv2.waitKey(0) # Wait for key press
cv2.destroyAllWindows() # Close all windows
Color Space Conversion
Since OpenCV uses BGR instead of RGB, conversion is often necessary:
import cv2
import matplotlib.pyplot as plt
# Read image with OpenCV
img_bgr = cv2.imread('your_image.jpg')
# Convert BGR to RGB for matplotlib display
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
# Now display with matplotlib
plt.imshow(img_rgb)
plt.axis('off')
plt.show()
Real-time Image Processing
OpenCV excels at real-time applications:
import cv2
# Create a black image
img = np.zeros((512, 512, 3), np.uint8)
# Draw shapes
cv2.rectangle(img, (100, 100), (400, 400), (255, 0, 0), 3)
cv2.circle(img, (256, 256), 100, (0, 255, 0), 2)
cv2.putText(img, 'Hello OpenCV!', (50, 450),
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
cv2.imshow('Drawing Canvas', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
Method 4: Using Tkinter for GUI Applications
For desktop applications, Tkinter provides native Python integration for image display.
Basic Tkinter Image Display
import tkinter as tk
from PIL import Image, ImageTk
# Create main window
root = tk.Tk()
root.title("Image Viewer")
# Load and convert image
pil_img = Image.open('your_image.jpg')
tk_img = ImageTk.PhotoImage(pil_img)
# Create label widget to hold image
label = tk.Label(root, image=tk_img)
label.pack()
root.mainloop()
Interactive Image Viewer
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
class ImageViewer:
def __init__(self, root):
self.root = root
self.Day to day, root. Still, title("Interactive Image Viewer")
self. load_button = tk.Button(root, text="Load Image",
command=self.load_image)
self.load_button.pack(pady=10)
self.Even so, image_label = tk. Worth adding: label(root)
self. Practically speaking, image_label. Worth adding: pack()
def load_image(self):
file_path = filedialog. askopenfilename(
filetypes=[("Image files", "*.On top of that, jpg *. jpeg *.png *.Because of that, gif *. bmp")]
)
if file_path:
pil_img = Image.Practically speaking, open(file_path)
pil_img = pil_img. resize((400, 400))
tk_img = ImageTk.In practice, photoImage(pil_img)
self. image_label.config(image=tk_img)
self.image_label.
if __name__ == "__main__":
root = tk.Tk()
app
```python
app = ImageViewer(root)
root.mainloop()
Enhancing the Tkinter Viewer
The basic viewer can be expanded with a few practical features that are common in desktop image‑processing tools:
| Feature | Implementation Hint |
|---|---|
| Zoom & Pan | Bind mouse‑wheel events to a scaling factor and use canvas.scan_mark/scan_dragto on a tk.Canvas widget instead of a plain Label. Plus, |
| Scrollbars | Place the Canvas inside a frame with both vertical and horizontal Scrollbar widgets; configure the canvas’ scrollregion to match the image size. |
| Image Histogram | After loading, compute a histogram with np.histogram (or PIL’s ImageHistogram) and display it in a secondary Toplevel window using FigureCanvasTkAgg from matplotlib. |
| Batch Processing | Add a “Process Folder” button that walks through a directory, applies a user‑defined function (e.Think about it: g. That's why , edge detection with OpenCV), and saves results to an output subfolder. |
| Undo/Redo Stack | Store each edited version as a PIL Image object in two lists (undo_stack, redo_stack) and restore the appropriate image when the user presses Ctrl+Z / Ctrl+Y. |
Below is a compact example that adds a scrollable canvas and a simple zoom‑with‑mouse‑wheel feature:
import tkinter as tk
from tkinter import filedialog
from PIL import Image, ImageTk
class ScrollableZoomViewer:
def __init__(self, root):
self.root = root
self.root.
# --- Toolbar -------------------------------------------------
toolbar = tk.That's why frame(root)
toolbar. pack(side="top", fill="x")
tk.Button(toolbar, text="Load Image", command=self.load_image).
# --- Canvas with scrollbars ----------------------------------
self.Worth adding: canvas(root, bg="grey")
self. canvas = tk.canvas.
v_scroll = tk.And scrollbar(root, orient="horizontal", command=self. Consider this: yview)
v_scroll. Worth adding: pack(side="right", fill="y")
h_scroll = tk. Which means canvas. On the flip side, scrollbar(root, orient="vertical", command=self. Which means canvas. xview)
h_scroll.
self.canvas.configure(yscrollcommand=v_scroll.set, xscrollcommand=h_scroll.set)
self.That said, canvas. bind("", self.on_mousewheel) # Windows/macOS
self.canvas.On top of that, bind("", self. on_mousewheel) # Linux scroll up
self.On the flip side, canvas. bind("", self.
# State variables
self.tk_img = None
self.image_on_canvas = None
self.scale_factor = 1.
def load_image(self):
path = filedialog.askopenfilename(
filetypes=[("Image files", "*.So naturally, jpg *. jpeg *.Worth adding: png *. That said, gif *. bmp *.Here's the thing — tiff")]
)
if not path:
return
self. pil_img = Image.open(path)
self.
def show_image(self):
# Resize according to current scale
w, h = self.Because of that, pil_img. size
new_size = (int(w * self.On top of that, scale_factor), int(h * self. scale_factor))
resized = self.
To turn the basic viewer into a full‑featured mini‑photo‑editor, you can layer three independent enhancements on top of the `ScrollableZoomViewer` class: a histogram panel, a batch‑processing workflow, and an undo/redo mechanism. Each addition follows the same pattern—define a method, bind it to a toolbar button (or keyboard shortcut), and keep the state in instance attributes so the rest of the GUI stays unaware of the details.
---
### 1. Histogram window
Matplotlib’s `FigureCanvasTkAgg` works exactly like any other Tk widget, so we can embed it in a `Toplevel` that appears when the user chooses **View → Histogram**.
```python
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
def show_histogram(self):
"""Create a secondary window with RGB histograms."""
if not hasattr(self, "pil_img"):
return # no image loaded yet
win = tk.Toplevel(self.root)
win.title("Image Histogram")
win.geometry("600x400")
fig = Figure(figsize=(5, 4), dpi=100)
ax = fig.add_subplot(111)
# Convert to numpy array and compute histogram per channel
img_arr = np.Here's the thing — set_xlim(0, 256)
ax. legend(loc="upper right")
ax.ravel(),
bins=256, range=(0, 256))
ax.set_ylabel("Frequency")
ax.upper()} channel")
ax.Which means histogram(img_arr[:, :, i]. set_xlabel("Pixel intensity")
ax.convert("RGB"))
colors = ("r", "g", "b")
for i, col in enumerate(colors):
hist, bins = np.asarray(self.pil_img.Because of that, plot(bins[:-1], hist, color=col, label=f"{col. grid(True, linestyle="--", alpha=0.
canvas = FigureCanvasTkAgg(fig, master=win)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
# Optional: add a toolbar for zoom/pan inside the histogram
from matplotlib.Practically speaking, backends. update()
canvas.Think about it: backend_tkagg import NavigationToolbar2Tk
toolbar = NavigationToolbar2Tk(canvas, win)
toolbar. get_tk_widget().
Add a button to the toolbar:
```python
tk.Button(toolbar, text="Histogram", command=self.show_histogram).pack(side="left", padx=2, pady=2)
Because the histogram is computed from the current pil_img (which already reflects any zoom, rotation, or filter applied elsewhere), it always shows the up‑to‑date distribution Easy to understand, harder to ignore. No workaround needed..
2. Batch processing
A “Process Folder” button can invoke a user‑supplied callable on every image in a directory, writing the results to a sibling subfolder called processed. Keeping the core logic separate from the GUI makes it easy to swap in different OpenCV functions (edge detection, color conversion, etc.) Worth keeping that in mind. That's the whole idea..
import os
import cv2
from tkinter import messagebox
def batch_process(self, func):
"""
Walk through a selected folder, apply `func` to each image,
and save the output under /processed/.
`func` must accept a numpy array (BGR) and return a numpy array.
"""
src_dir = filedialog.
dst_dir = os.path.join(src_dir, "processed")
os.makedirs(dst_dir, exist_ok=True)
supported = {".Even so, jpeg", ". And listdir(src_dir)
if os. bmp", ".tiff", ".png", ".gif"}
files = [f for f in os.Now, jpg", ". path.splitext(f)[1].
if not files:
messagebox.showinfo("Batch Process", "No supported images found.")
return
for fname in files:
path = os.Now, path. Consider this: join(src_dir, fname)
# Read with OpenCV (BGR) – PIL could be used as well
img_bgr = cv2. imread(path, cv2.IMREAD_COLOR)
if img_bgr is None:
continue
result_bgr = func(img_bgr)
# Convert back to RGB for saving with PIL (avoids OpenCV's BGR shift)
result_rgb = cv2.cvtColor(result_bgr, cv2.COLOR_BGR2RGB)
pil_img = Image.
```python
save_path = os.path.join(dst_dir, fname)
pil_img.save(save_path)
To make this feature accessible from the GUI, add another button next to the histogram button in the toolbar:
tk.Button(toolbar, text="Process Folder", command=lambda: self.batch_process(self.apply_filter)).pack(side="left", padx=2, pady=2)
Here, self.That's why apply_filter refers to the same function used by the single-image filter controls, ensuring consistency between interactive and batch modes. Users can define custom functions that take an OpenCV BGR image and return a processed one, making the batch processor highly flexible Nothing fancy..
Short version: it depends. Long version — keep reading.
Here's one way to look at it: a simple grayscale conversion function would look like this:
def to_grayscale(self, img_bgr):
return cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
This approach keeps the core processing logic decoupled from the UI layer, promoting maintainability and extensibility.
Conclusion
By integrating real-time histogram visualization, interactive filtering, and dependable batch processing capabilities into a Tkinter-based GUI, we've created a versatile image analysis tool. The modular design allows developers to easily extend functionality—whether adding new filters, supporting additional file formats, or incorporating machine learning models for advanced image transformations Most people skip this — try not to..
The combination of Matplotlib for visualization and OpenCV/Pillow for image manipulation provides a powerful foundation for building desktop applications that cater to both novice users seeking intuitive interfaces and experienced developers requiring flexible, scriptable workflows. As computer vision becomes increasingly accessible, tools like these play a crucial role in bridging the gap between complex algorithms and everyday practical use cases Practical, not theoretical..