OpenCVMiddleCoding

Что такое гистограмма в обработке изображений и как её вычислять и выравнивать?

cv2.calcHist вычисляет гистограмму по каналам; cv2.equalizeHist выравнивает глобально (только uint8 grayscale); для локального и щадящего выравнивания используйте CLAHE через cv2.createCLAHE.

Гистограммы в обработке изображений: вычисление и выравнивание

Гистограмма изображения показывает, сколько пикселей имеют каждое значение яркости (0–255). Она используется для оценки экспозиции, контраста, а также как основа для нормализации и порогового метода Otsu.

Вычисление гистограммы через cv2.calcHist

import cv2
import numpy as np
import matplotlib.pyplot as plt

img = cv2.imread("photo.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# cv2.calcHist(images, channels, mask, histSize, ranges)
hist = cv2.calcHist(
    [gray],         # список изображений
    [0],            # индекс канала (0 для одноканального)
    None,           # маска (None = всё изображение)
    [256],          # число бинов
    [0, 256]        # диапазон значений
)
# hist.shape == (256, 1), dtype == float32

plt.plot(hist)
plt.xlabel("Яркость")
plt.ylabel("Количество пикселей")
plt.title("Гистограмма")
plt.show()

Гистограмма для цветного изображения (по каналам)

colors = {"b": 0, "g": 1, "r": 2}
for color_name, ch_idx in colors.items():
    hist = cv2.calcHist([img], [ch_idx], None, [256], [0, 256])
    plt.plot(hist, color=color_name, label=color_name.upper())

plt.legend()
plt.title("Гистограмма по каналам BGR")
plt.show()

Глобальное выравнивание гистограммы: cv2.equalizeHist

Перераспределяет яркости так, чтобы гистограмма стала примерно равномерной. Работает только с одноканальным uint8-изображением.

equalized = cv2.equalizeHist(gray)

# Сравнение до/после
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes[0, 0].imshow(gray, cmap="gray")
axes[0, 0].set_title("Оригинал")
axes[0, 1].imshow(equalized, cmap="gray")
axes[0, 1].set_title("После equalizeHist")
axes[1, 0].plot(cv2.calcHist([gray], [0], None, [256], [0, 256]))
axes[1, 0].set_title("Гистограмма оригинала")
axes[1, 1].plot(cv2.calcHist([equalized], [0], None, [256], [0, 256]))
axes[1, 1].set_title("Гистограмма после выравнивания")
plt.tight_layout()
plt.show()

Адаптивное выравнивание: CLAHE

Глобальное выравнивание может пересветить одни области и затемнить другие. CLAHE (Contrast Limited Adaptive Histogram Equalization) работает на тайлах и ограничивает усиление контраста.

# Создать объект CLAHE
clahe = cv2.createCLAHE(
    clipLimit=2.0,         # максимальное усиление контраста в тайле
    tileGridSize=(8, 8)    # изображение разбивается на сетку 8x8 тайлов
)

clahe_result = clahe.apply(gray)

# Для цветного изображения применяем только к L-каналу в LAB
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
l_clahe = clahe.apply(l)
lab_clahe = cv2.merge([l_clahe, a, b])
result_bgr = cv2.cvtColor(lab_clahe, cv2.COLOR_LAB2BGR)

Сравнение методов

  • equalizeHist: быстро, глобально, агрессивно — хорошо для документов и QR-кодов.
  • CLAHE: адаптивно, плавнее — рекомендуется для медицинских снимков, лиц, природных сцен.
  • cv2.normalize: просто растягивает диапазон до [0,255] — самый мягкий вариант.
# normalize как простейшая альтернатива
norm = cv2.normalize(gray, None, 0, 255, cv2.NORM_MINMAX)

Подводные камни

  • equalizeHist только для uint8: передача float32 или uint16 вызовет ошибку или неверный результат.
  • Применение к BGR напрямую: equalizeHist/CLAHE к каждому BGR-каналу независимо искажает цвет — переводите в LAB или HSV и обрабатывайте только яркостный канал.
  • clipLimit слишком мал: clipLimit=1.0 в CLAHE почти не меняет изображение; слишком большой (>4.0) — вносит шум.
  • tileGridSize больше объектов: если тайл содержит только фон, выравнивание усилит шум, а не детали.
  • Маска в calcHist: маска должна быть uint8 с значениями 0 и 255, не bool — иначе результат неверен.
  • histSize и ranges несогласованы: histSize=[256] с ranges=[0,256] — стандарт; но histSize=[128] с ranges=[0,256] даст бины по 2 единицы — нужно знать, что считаете.
  • Нет визуального сравнения: оценивать выравнивание только по метрикам (std яркости) недостаточно — артефакты могут быть незаметны в числах, но очевидны визуально.
  • Применение к уже нормализованным данным: если изображение уже имеет равномерное распределение яркостей, повторное equalizeHist введёт артефакты.

Common mistakes

  • Объяснять histograms equalization только синтаксисом без shape, dtype, состояния или режима выполнения.
  • Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
  • Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.

What the interviewer is testing

  • Может ли связать histograms equalization с реальным контрактом входов и выходов.
  • Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
  • Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.

Sources

Related topics