2025, Oct 23 03:00

Fix blurry NumPy array displays in Matplotlib: convert PIL crops to grayscale for crisp output and better EasyOCR

Learn why PIL images look sharp while NumPy arrays look blurry in Matplotlib, and how grayscale conversion fixes it for clean EasyOCR input and OCR results.

When you display a PIL image directly with matplotlib and then render the exact same content after converting it to a numpy array, the two visuals can look surprisingly different. In one scenario the first plot is crisp, while the array-based view becomes barely legible. If that image is destined for EasyOCR, you understandably want the sharper version to go in.

What triggered the question

I am curious what the PIL library does value scaling and normalization wise to show me crisp image and why just doing matplotlib on the extracted numpy value looks really bad.

Reproducing the issue

The behavior shows up with a straightforward crop, display, array conversion, conditional replacement, and re-display. The only difference between the two visualizations is whether matplotlib receives a PIL image or a numpy array.

from PIL import Image
import numpy as np
import matplotlib.pyplot as plt

img_handle = Image.open(tmp_img_path)
crop_roi = img_handle.crop((520, 965, 565, 1900))

plt.imshow(crop_roi, cmap='gray')
plt.show()

arr_view = np.array(crop_roi, dtype=np.uint8)
arr_view[np.where(arr_view == 48)] = 255
plt.imshow(arr_view, cmap='gray')
plt.show()

What is actually going on

The direct PIL image display appears crisp, while the array-based one does not. The practical fix in this case is to explicitly convert the cropped image to grayscale before further steps. That conversion aligns the data in a way that produces a crisp result when shown as a numpy array as well.

The fix

Applying grayscale conversion to the PIL image makes the array rendering look as clear as the direct display.

from PIL import Image, ImageOps
import numpy as np
import matplotlib.pyplot as plt

img_handle = Image.open(tmp_img_path)
crop_roi = img_handle.crop((520, 965, 565, 1900))

crop_roi = ImageOps.grayscale(crop_roi)

plt.imshow(crop_roi, cmap='gray')
plt.show()

arr_view = np.array(crop_roi, dtype=np.uint8)
arr_view[np.where(arr_view == 48)] = 255
plt.imshow(arr_view, cmap='gray')
plt.show()

Why this matters

In OCR workflows such as EasyOCR, small visual differences can translate into big accuracy differences. If the numpy view is what you ultimately feed into the model, you want that representation to be as clean and consistent as the image you see when you preview results. Ensuring the grayscale conversion before array conversion helps you avoid a mismatch between what looks right on screen and what the model actually receives.

Takeaways

If you notice that a PIL image looks crisp when shown directly but the numpy array view becomes hard to read, convert the image to grayscale with ImageOps.grayscale before displaying or array conversion. This simple step aligns the output and gives you a reliable preview of exactly what downstream code, including EasyOCR, will process.

The article is based on a question from StackOverflow by El Dude and an answer by El Dude.