2025, Oct 23 03:16

PIL vs NumPy в matplotlib: как добиться четкого изображения для EasyOCR

Почему PIL выглядит четче, чем массив NumPy в matplotlib, и как это исправить: конвертируйте в оттенки серого (ImageOps.grayscale) перед подачей в EasyOCR.

Когда вы показываете изображение PIL напрямую в matplotlib, а затем выводите тот же самый контент после преобразования в массив numpy, результаты могут заметно отличаться. В одном случае первый график выглядит четким, тогда как представление на основе массива становится едва читаемым. Если изображение отправляется в EasyOCR, логично захотеть передать именно более резкую версию.

Что подтолкнуло к вопросу

Мне интересно, что именно делает библиотека PIL с масштабированием и нормализацией значений, чтобы показать четкое изображение, и почему простая визуализация в matplotlib извлеченного массива numpy выглядит так плохо.

Воспроизведение проблемы

Поведение проявляется при простых шагах: кадрирование, отображение, преобразование в массив, условная замена и повторный показ. Единственное различие между двумя визуализациями — что именно получает matplotlib: объект PIL или массив numpy.

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()

Что происходит на самом деле

Прямой показ изображения PIL выглядит четко, а вариант на основе массива — нет. На практике помогает явное преобразование вырезанного фрагмента в оттенки серого перед следующими шагами. Такая конвертация выравнивает данные так, что при показе как массива numpy результат тоже выглядит четким.

Решение

Преобразование изображения PIL в градации серого делает вывод массива таким же ясным, как и прямой показ.

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()

Почему это важно

В OCR-пайплайнах вроде EasyOCR небольшие визуальные отличия способны заметно повлиять на точность. Если в итоге в модель попадает именно представление в виде numpy-массива, важно, чтобы оно было таким же чистым и стабильным, как картинка на предпросмотре. Предварительное преобразование в градации серого до конвертации в массив помогает избежать рассогласования между тем, что хорошо выглядит на экране, и тем, что реально получает модель.

Выводы

Если прямой показ изображения PIL выглядит четко, а вид в виде numpy-массива — размытым и трудночитаемым, перед отображением или преобразованием в массив конвертируйте изображение в градации серого с помощью ImageOps.grayscale. Этот простой шаг выравнивает результат и дает достоверный предпросмотр того, что на самом деле будет обрабатываться последующим кодом, включая EasyOCR.

Статья основана на вопросе с StackOverflow от El Dude и ответе от El Dude.