Histograma é um conceito que está presente direta ou indiretamente em praticamente todas as aplicações de visão computacional.
Afinal, ao ajustar a distribuição dos valores de pixel, conseguimos realçar os detalhes e revelar características até então ocultas na imagem original.
Na equalização do histograma, queremos o espectro completo de intensidades, distribuindo os valores de pixel mais uniformemente ao longo do eixo x.
Por isso, entender o conceito e aprender como aplicar a técnica, é fundamental para trabalhar em problemas de processamento digital de imagem.
Neste tutorial, você vai aprender a teoria, e como equalizar histogramas em imagens digitais usando OpenCV e Python.
O que é um Histograma de Imagem
Um histograma de imagem é um tipo de representação gráfica que representa como as intensidades dos pixels de uma determinada imagem digital estão distribuídos.
Em termos simples, é o histograma que vai te dizer se uma imagem está corretamente exposta, se a iluminação está adequada e se o contraste permite ressaltar algumas características desejáveis.
Nós usamos histogramas para identificar assinaturas espectrais em imagens hiperspectrais de satélites – como distinguir entre plantações transgênicas e orgânicas.
Nós usamos histogramas para segmentar peças em uma esteira de fábrica – aplicando thresholding para isolar os objetos.
E olhando para diversos algoritmos como SIFT e HOG, o uso de histogramas de gradiente de imagem é fundamental para a detecção e descrição de características locais robustas.
Esta técnica é fundamental no processamento de imagens digitais e amplamente utilizada em diversos campos, incluindo a área médica, como em exames de raios X e tomografias computadorizadas (CT scans).
Além de ser uma ferramenta fundamental, uma vez que é muito simples de calcular, é uma alternativa bastante popular para aplicações que necessitam de processamento em tempo real.
Definição Formal
Um histograma de uma imagem digital cujas intensidades variam no intervalo é uma função discreta
onde é o valor de intensidade -ésimo e é o número total de pixels em com intensidade . De forma semelhante, o histograma normalizado de uma imagem digital é
onde e são as dimensões da imagem (linhas e colunas, respectivamente). É uma prática comum dividir cada componente de um histograma pelo número total de pixels para normalizá-lo.
Uma vez que é a probabilidade de ocorrência de um dado nível de intensidade em uma imagem, a soma de todos os componentes é igual a .
Como calcular um histograma
Calcular manualmente um histograma de imagens é um processo direto que envolve a contagem da frequência de cada valor de intensidade de pixel em uma imagem.
Apenas para fins didáticos, providenciei um pseudo-código e uma implementação usando o numpy
para demonstrar como fazer isso.
Primeiramente, inicializamos um vetor para armazenar o histograma com zeros. No caso, estou considerando uma imagem grayscale de 8 bits () valores possíveis para cada intensidade de pixel.
Em seguida, capturamos os atributos de altura
e largura
da imagem. Caso fosse necessário, poderíamos incluir ainda o atributo canais
.
Então, percorremos cada pixel da imagem, incrementando a posição correspondente no vetor do histograma com base no valor de intensidade de cada pixel. Dessa forma, ao final do processo, o vetor de histograma conterá a contagem de ocorrências de cada nível de cinza na imagem, proporcionando uma representação clara da distribuição de intensidades na imagem.
Cálculo de Histograma no NumPy
A biblioteca NumPy possui a função np.histogram()
, que permite calcular histogramas de dados de entrada. Esta função é bastante flexível e pode lidar com diferentes configurações de bins e intervalos.
numpy.histogram( a: np.ndarray, bins: Union[int, np.ndarray, str] = 10, range: Optional[Tuple[float, float]] = None, density: Optional[bool] = None, weights: Optional[np.ndarray] = None ) -> Tuple[np.ndarray, np.ndarray]
Veja o exemplo de utilização no código abaixo e perceba que quando usamos o np.histogram()
para calcular o histograma de uma imagem grayscale, definimos o número de bins como 256 para cobrir todos os valores de intensidade de pixel de 0 a 255.
No entanto, antes é preciso entender como a biblioteca calcula os bins. O NumPy os calcula em intervalos como , , até . Na prática, isso significa que haverá 257 elementos no array de bins (pois passamos o argumento na função).
Como não precisamos desse valor extra, afinal os valores de intensidade do pixel variam entre , podemos ignorá-lo.
import cv2 import numpy as np import matplotlib.pyplot as plt # Carrega uma foto em grayscale image_path = "carlos_hist.jpg" image = cv2.imread(image_path, 0) # Calcule o histograma hist, bins = np.histogram(image.flatten(), 256, [0, 256]) # Compute a função de distribuição acumulada cdf = hist.cumsum() cdf_normalized = cdf * hist.max() / cdf.max()
Inicialmente, nós carregamos e imagem carlos_hist.jpg
em grayscale usando a flag
em cv2.imread(image_path, 0)
. Com a imagem carregada, nosso próximo passo é calcular o histograma. Utilizamos a função np.histogram()
do NumPy para isso. Esta função conta a frequência de ocorrência de cada valor de intensidade de pixel.
Para entender melhor a distribuição das intensidades, também vamos calcular a função de distribuição acumulada (CDF). A CDF é essencial para técnicas como equalização de histograma.
# Plotar histograma e C.D.F. fig, axs = plt.subplots(1, 2, figsize=(10, 5)) # Mostrar a imagem em grayscale axs[0].imshow(image, cmap="gray", vmin=0, vmax=255) axs[0].axis("off") # Plotar o histograma e a CDF axs[1].plot(cdf_normalized, color="black", linestyle="--", linewidth=1) axs[1].hist(image.flatten(), 256, [0, 256], color="r", alpha=0.5) axs[1].set_xlim([0, 256]) axs[1].legend(("CDF", "Histograma"), loc="upper left") plt.show()
Agora, vamos visualizar o histograma e a CDF lado a lado usando Matplotlib e configurando a nossa figura com os subplots.
Visualmente, apenas olhando para a imagem, percebemos que a mesma parece “flat“, com pouco contraste. E isso de fato é corroborado pelo histograma à direita.
Como maioria dos valores de intensidade de pixel está concentrada em torno de 150, a imagem possui predominância de tons médios a claros, resultando em uma aparência menos vibrante e com pouca variação de contraste.
Equalização de Histograma para Imagens em Tons de Cinza
Como mencionamos no início do artigo, a equalização de histograma é uma técnica para ajustar o contraste de uma imagem, distribuindo os valores dos pixels de forma mais uniforme por toda a faixa de intensidade.
Existem várias técnicas possíveis, dependendo do contexto, quantidade de canais e aplicação. Nesta seção eu irei te mostrar como usar a função cv.equalizeHist() para realizar esse processo em imagens em tons de cinza.
cv2.equalizeHist(
src: np.ndarray
) -> np.ndarray:
A função cv2.equalizeHist
equaliza o histograma da imagem de entrada usando o seguinte algoritmo:
- Calcule o histograma para
src
. - Normalize o histograma de forma que a soma dos bins do histograma seja 255.
- Calcule o somatório do histograma:
- Transforme a imagem usando ( H’ ) como uma tabela de consulta (look-up table):
Para ver como essa função age para normalizar o brilho e aumentar o contraste da imagem original, vamos rodar o código abaixo.
# Aplica a equalização de histograma equalized = cv2.equalizeHist(image) # Cria uma figura com subplots para comparar as imagens plt.figure(figsize=(30, 10)) # Mostrar imagem equalizada plt.subplot(1, 3, 1) plt.imshow(equalized, cmap='gray', vmin=0, vmax=255) plt.title('Imagem Equalizada') # Comparar histogramas original e equalizado plt.subplot(1, 3, 2) plt.hist(image.flatten(), 256, [0, 256]) plt.title('Histograma Original') plt.subplot(1, 3, 3) plt.hist(equalized.flatten(), 256, [0, 256]) plt.title('Histograma Equalizado') plt.show()
Primeiro, aplicamos a equalização de histograma na imagem carregada usando a função cv2.equalizeHist
. Em seguida, criamos uma figura com subplots para analisar o resultado final.
O primeiro subplot exibe a imagem equalizada, enquanto os dois subplots seguintes comparam os histogramas da imagem original e da imagem processada, usando plt.hist
para plotá-los diretamente. Repare como a alteração da distribuição tornou a imagem mais agradável.
Equalização de Histograma para Imagens Coloridas
Se tentarmos realizar a equalização de histograma em imagens coloridas tratando cada um dos três canais separadamente, teremos um resultado ruim e inesperado.
O motivo é que, quando cada canal de cor é transformado de maneira não linear e independente, podem ser geradas cores completamente novas que não estão relacionadas de nenhuma maneira.
A maneira correta de realizar a equalização de histograma em imagens coloridas envolve uma etapa anterior, que é a conversão para um espaço de cor como o HSV, onde a intensidade está separada:
- Transforme a imagem para o espaço de cores HSV.
- Realize a equalização de histograma apenas no canal V (Valor).
- Transforme a imagem de volta para o espaço de cores RGB.
# Ler a astrofotografia astrofoto = cv2.imread('astrofoto.jpg') # Converter para o espaço de cores HSV hsv_astrofoto = cv2.cvtColor(astrofoto, cv2.COLOR_BGR2HSV) # Dividir os canais HSV h, s, v = cv2.split(hsv_astrofoto)
Primeiro, carregamos uma nova imagem colorida usando cv2.imread
. Então, convertemos a imagem carregada originalmente no formato BGR para o espaço de cores HSV com cv2.cvtColor
e separamos as informações de cor (Hue e Saturation) da intensidade (Value) usando cv2.split(hsv_astrofoto)
.
# Equalizar o canal V (valor) v_equalized = cv2.equalizeHist(v) # Mesclar os canais HSV de volta, com o canal V equalizado hsv_astrofoto = cv2.merge([h, s, v_equalized]) # Converter de volta para o espaço de cores RGB astrofoto_equalized = cv2.cvtColor(hsv_astrofoto, cv2.COLOR_HSV2RGB)
Após aplicamos a equalização de histograma apenas ao canal V, mesclamos os canais HSV de volta, mas substituindo o canal V pelo canal equalizado.
A redistribuição de valores de intensidade no canal V melhorou significativamente o contraste da imagem, destacando detalhes que podiam estar obscurecidos.
No entanto, a equalização de histograma que acabamos de ver pode não ser a melhor abordagem em muitos casos, por considerar o contraste global da imagem. Em situações onde há grandes variações de intensidade, com pixels muito claros e muito escuros presentes, ou onde desejaríamos melhorar apenas uma região da imagem, esse método pode fazer com que percamos muitas informações.
Para lidar com esses problemas, vamos conhecer uma técnica mais avançada chamada Contrast Limited Adaptive Histogram Equalization (CLAHE).
Contrast Limited Adaptive Histogram Equalization (CLAHE)
Contrast Limited Adaptive Histogram Equalization (CLAHE) é uma técnica que divide a imagem em pequenas regiões chamadas “tiles“ e aplica a equalização de histograma em cada uma dessas regiões de forma independente.
Isso permite que o contraste seja melhorado localmente, preservando detalhes e reduzindo o ruído. Além disso, o método CLAHE tem a capacidade de limitar o aumento do contraste (daí o termo “Contrast Limited”), impedindo a amplificação do ruído que pode ocorrer na técnica regular.
cv2.createCLAHE(
clipLimit: float = 40.0,
tileGridSize: Optional[Tuple[int, int]] = (8, 8)
) -> cv2.CLAHE:
A implementação do CLAHE no OpenCV é feita usando a função createCLAHE()
. Primeiro, um objeto CLAHE é criado com dois argumentos opcionais: clipLimit
e tileGridSize
.
Neste último exemplo, vamos usar uma foto que eu tirei com a minha esposa no Parque Nacional da Chapada dos Veadeiros, no estado de Goiás.
# Ler a imagem da chapada chapada = cv2.imread('chapada.png') # Converter para o espaço de cores HSV chapada_hsv = chapada.copy() chapada_hsv = cv2.cvtColor(chapada, cv2.COLOR_BGR2HSV) # Criar um objeto CLAHE clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) chapada_hsv[:, :, 2] = clahe.apply(chapada_hsv[:, :, 2]) # Converter de volta para o espaço de cores RGB chapada_equalized = cv2.cvtColor(chapada_hsv, cv2.COLOR_HSV2BGR)
Após carregar a imagem chapada.png
, vamos converter para o espaço de cor HSV a fim de manter a consistência. Depois, aplicamos o CLAHE ao canal V usando a função cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
. Finalmente, convertemos a imagem de volta para o espaço de cores RGB para visualizar o resultado.
Na imagem original, havia uma região bem iluminada (cerca, árvore, e eu e minha esposa). Se tivéssemos optado pela equalização de histograma global, toda a imagem teria sido ajustada uniformemente, potencialmente levando à perda de detalhes em áreas muito claras ou muito escuras.
No entanto, ao usar o CLAHE, o algoritmo utilizou o contexto local, adaptando a quantidade de tiles para ajustar o contraste de cada pequena região da imagem individualmente. Isso preservou os detalhes tanto nas áreas claras quanto nas escuras, resultando em uma imagem final com contraste aprimorado e cores mais naturais.
Resumo
- Histograma de Imagem: Um histograma de imagem representa graficamente a distribuição das intensidades dos pixels, indicando se uma imagem está corretamente exposta e ajudando a realçar detalhes ocultos.
- Utilização de Histogramas: Histogramas são usados para várias aplicações, incluindo a segmentação de peças em esteiras de fábrica, identificação de assinaturas espectrais em imagens hiperspectrais e detecção de características locais robustas com algoritmos como SIFT e HOG.
- Definição Formal: O histograma de uma imagem digital é uma função discreta que conta a frequência de cada valor de intensidade de pixel, podendo ser normalizado para representar a probabilidade de ocorrência de cada nível de intensidade.
- Equalização de Histograma em Tons de Cinza: A função
cv2.equalizeHist()
do OpenCV ajusta o contraste de uma imagem grayscale distribuindo os valores dos pixels de forma mais uniforme ao longo da faixa de intensidade. - Limitações da Equalização Global: A equalização de histograma global pode não ser ideal em casos com grandes variações de intensidade, pois pode levar à perda de informações em áreas muito claras ou escuras da imagem.
- Equalização de Histograma em Imagens Coloridas: Ao tratar imagens coloridas, a equalização de histograma deve ser aplicada no canal de intensidade (V) do espaço de cores HSV para evitar a geração de cores não naturais.
- CLAHE – Equalização de Histograma Adaptativa: CLAHE divide a imagem em pequenas regiões e aplica a equalização de histograma localmente, preservando detalhes e evitando a amplificação de ruído. Esta técnica é eficaz para melhorar o contraste de imagens com grandes variações de iluminação.
Cite este Post de Citação
Use a entrada abaixo para citar este post em sua pesquisa:
Carlos Melo. “Histogram Equalization with OpenCV and Python”, Sigmoidal.ai, 2024, https://sigmoidal.ai/equalizacao-de-histograma-com-opencv-e-python/.
@incollection{CMelo_EqualizacaoHistograma,
author = {Carlos Melo},
title = {Histogram Equalization with OpenCV and Python},
booktitle = {Sigmoidal.ai},
year = {2024},
url = {https://sigmoidal.ai/equalizacao-de-histograma-com-opencv-e-python/},
}