Equalização de histograma em Python

Alvaro Leandro Cavalcante Carneiro
Data Hackers
Published in
7 min readOct 10, 2020

--

A equalização de histograma é muito utilizada em processamento digital de imagens (PDI), onde é possível ajustar os níveis de cinza de uma imagem automaticamente, garantindo um brilho e contraste balanceados de forma rápida e fácil.

Nesse artigo, iremos implementar o passo a passo para criação desse algoritmo em Python e mostrar exemplos práticos e limitações dessa técnica.

A equalização de histograma é uma técnica de transformação de intensidade que tem por objetivo balancear os níveis de cinza em uma imagem de forma automática, sem precisar de parâmetros e configurações adicionais. Dessa forma, imagens com um nível de brilho desbalanceados, ou seja, claras ou escuras demais atingem uma distribuição normalizada, o que garante um melhor contraste e visualização dos detalhes presentes na cena.

Implementação

Importando as bibliotecas

Como sempre, vamos importar algumas bibliotecas básicas que serão necessárias para a implementação do código.

import numpy as np
import matplotlib.pyplot as plt
from skimage import data
from PIL import Image
import math

A biblioteca skimage será utilizada para o carregamento das imagens a serem trabalhadas, como por exemplo, dessa xícara de café:

image = data.coffee()
plt.imshow(image, cmap='gray')
Imagem de exemplo.

Vamos criar um método para transformar essa imagem em escala de cinza, para fins de simplicidade, manipulando apenas uma matriz. Para mais detalhes de como essa conversão funciona, recomendo essa leitura:

def convert_to_gray(image, luma=False):
if luma:
params = [0.299, 0.589, 0.114]
else:
params = [0.2125, 0.7154, 0.0721]
gray_image = np.ceil(np.dot(image[...,:3], params))

# Saturando os valores em 255
gray_image[gray_image > 255] = 255

return gray_image
Imagem convertida para escala de cinza

Vamos começar a implementação da equalização, o qual foi dividida em diversos métodos pequenos e com responsabilidade única para deixar o entendimento mais simples.

O primeiro método serve para iniciar o histograma, que será um objeto com 256 valores, sendo cada um deles um dos níveis de intensidade que uma imagem pode assumir, pois o domínio de intensidade de um pixel varia entre 0 (para cor preta) até 255 (para cor branca).

def instantiate_histogram():    
hist_array= []

for i in range(0,256):
hist_array.append(str(i))
hist_array.append(0)

hist_dct = {hist_array[i]: hist_array[i + 1] for i in range(0, len(hist_array), 2)}

return hist_dct
histogram = instantiate_histogram()

Uma vez que o dicionário foi criado, basta apenas contar quantas vezes cada valor de intensidade aparece na imagem, percorrendo cada um dos pixels e contabilizando o número de aparições.

def count_intensity_values(hist, img):
for row in img:
for column in row:
hist[str(int(column))] = hist[str(int(column))] + 1

return hist
histogram = count_intensity_values(histogram, image)

A função plot_hist abaixo foi utilizada para exibir o histograma da imagem, seja individualmente ou uma comparação lado a lado entre dois histogramas.

def plot_hist(hist, hist2=''):
if hist2 != '':
figure, axarr = plt.subplots(1,2, figsize=(20, 10))
axarr[0].bar(hist.keys(), hist.values())
axarr[1].bar(hist2.keys(), hist2.values())
else:
plt.bar(hist.keys(), hist.values())
plt.xlabel("Níveis intensidade")
ax = plt.gca()
ax.axes.xaxis.set_ticks([])
plt.grid(True)
plt.show()
Histograma da imagem da xícara de café

Pronto! Com apenas isso já é possível gerar e visualizar o histograma da imagem. O eixo Y representa quantas vezes o valor apareceu na imagem enquanto o eixo X mostra os níveis de intensidade, que como foi dito, varia entre o domínio de 0 a 255. Na xícara de café mostrada anteriormente, é visível que a maior parte dos pixels da imagem são mais escuros (próximos a 0) do que claros (próximos a 255), portanto a distribuição do histograma está distorcida de forma a se concentrar mais para o lado esquerdo.

Uma vez com o histograma da imagem em mãos, podemos dar continuidade ao desenvolvimento, sendo necessário calcular agora um outro dicionário porém ao invés do número de vezes que determinado valor de intensidade aparece estamos interessados na probabilidade desse valor aparecer.

Um cálculo probabilístico é nada mais que a divisão de quantas vezes o valor apareceu pelo número total de pixels na imagem.

def get_hist_proba(hist, n_pixels):
hist_proba = {}
for i in range(0, 256):
hist_proba[str(i)] = hist[str(i)] / n_pixels

return hist_proba
n_pixels = image.shape[0] * image.shape[1]
hist_proba = get_hist_proba(histogram, n_pixels)

O próximo passo, ainda utilizando a estrutura de dicionário, é calcularmos a probabilidade acumulada, onde para cada iteração o valor do histograma é somado à probabilidade acumulada das iterações anteriores. A implementação e detalhes matemáticos foram retirados do livro de Gonzalez e Woods.

def get_accumulated_proba(hist_proba): 
acc_proba = {}
sum_proba = 0

for i in range(0, 256):
if i == 0:
pass
else:
sum_proba += hist_proba[str(i - 1)]

acc_proba[str(i)] = hist_proba[str(i)] + sum_proba
return acc_probaaccumulated_proba = get_accumulated_proba(hist_proba)

Com todas essas probabilidades podemos fazer o cálculo dos novos valores de cinza da imagem, ou seja, dado um pixel na posição (x,y) com nível de intensidade z, qual será seu novo valor de intensidade para que o histograma resultante seja equalizado.

Primeiro, calculamos um novo objeto que irá mapear os respectivos valores de cinza em novos valores equalizados.

def get_new_gray_value(acc_proba):
new_gray_value = {}

for i in range(0, 256):
new_gray_value[str(i)] = np.ceil(acc_proba[str(i)] * 255)
return new_gray_valuenew_gray_value = get_new_gray_value(accumulated_proba)

Por fim, basta aplicar os novos valores na imagem original.

def equalize_hist(img, new_gray_value):
for row in range(img.shape[0]):
for column in range(img.shape[1]):
img[row][column] = new_gray_value[str(int(img[row] [column]))]

return img

Pronto! Todos esses métodos formam o algoritmo de equalização de histograma que desejamos. Podemos aplicá-lo na imagem e conferir o resultado.

eq_img = equalize_hist(image.copy(), new_gray_value)
figure, axarr = plt.subplots(1,2, figsize=(20, 10))
axarr[0].imshow(image, cmap='gray')
axarr[1].imshow(eq_img, cmap='gray')
Imagem original e imagem equalizada, respectivamente.

As diferenças nesse caso são bem suaves, ainda assim é possível reparar que a imagem equalizada a direita é mais brilhante do que a imagem original. Vamos verificar seus histogramas.

Histograma da imagem original e histograma equalizado

Como é possível observar na comparação entre o histograma da imagem original (a esquerda) e o histograma equalizado, o segundo está com a frequência dos níveis de cinza melhor distribuídos, realçando principalmente o brilho da imagem, o qual estava com valores mais baixos, coincidindo com os efeitos visuais observados na comparação entre as imagens.

Vamos testar agora a capacidade desse algoritmo em situações um pouco mais desafiadoras. Para isso, vamos carregar novas imagens e gerar distorções propositais, como por exemplo, gerando um aumento no brilho na imagem da astronauta presente no skimage.

Imagem da astronauta com o brilho aumentado

É notável que a imagem está com o brilho muito elevado, afetando o contraste e visualização geral da mesma. Abaixo podemos ver o resultado da aplicação da equalização de histograma que criamos anteriormente.

Imagem equalizada

Ao apenas aplicar o algoritmo criado anteriormente conseguimos obter um resultado nitidamente melhor, com valores de intensidade bem distribuídos por toda a imagem.

Histograma da astronauta sem e com equalização respectivamente.

Essa diferença na distribuição dos níveis de intensidade fica clara ao comparar os dois histogramas.

Um outro exemplo abaixo mostra uma nave espacial que teve o brilho reduzido:

Nave espacial com os níveis de intensidade mais próximos a 0.

Ao equalizar o histograma da imagem podemos notar uma das limitações do algoritmo, que é uma possível distorção visual das cores e do brilho.

Imagem escurecida e imagem equalizada respectivamente.

O resultado obtido de fato melhorou consideravelmente a visualização dos detalhes da imagem em relação à versão escurecida, muito embora tenha reforçado muito as partes mais claras, o que é visível ao comparar com a imagem original:

Imagem original do foguete

Ainda assim, o resultado está bem aceitável e bom visualmente. Um último exemplo é de uma fórmula matemática escrita em um papel o qual teve seu brilho aumentado. Nesse caso, é possível verificar como a falta de semântica do algoritmo, ou seja, a preocupação em apenas balancear os níveis de intensidade sem se preocupar com outros aspectos pode acabar gerando uma perda de informação.

Texto com brilho aumentado e imagem equalizada, respectivamente.
Histograma do texto com brilho aumentado e equalizado.

Embora a imagem tenha sido corretamente equalizada com o algoritmo anteriormente criado, é possível ver que o resultado final acaba piorando a visualização, pois a introdução de níveis mais escuros de intensidade acabam borrando o conteúdo escrito.

Outras técnicas de equalização

Existem outras técnicas para equalizar o histograma que podem ser interessantes e efetivas em diferentes contextos. A primeira delas é a equalização local ao invés da equalização global que foi implementada nesse artigo. A equalização local, como o próprio nome sugere, utiliza uma pequena vizinhança de pixels para ajustar gradativamente os níveis de intensidade ao invés de considerar a intensidade de toda a imagem de uma vez, o que pode garantir o realce de detalhes que antes seriam ignorados.

Outra técnica interessante é a especificação de um histograma alvo, onde os valores de intensidade são pré-definidos para serem ajustados na imagem.

Cada uma dessas técnicas possuem suas vantagens e desvantagens e a melhor vai depender exclusivamente da sua aplicação.

Conclusão

A equalização de histograma pode ser um algoritmo bastante robusto e útil para ser aplicado nas mais diversas situações, onde sem nenhum esforço adicional é possível balancear os níveis de cinza de uma imagem garantindo uma melhor visualização. Ainda assim, a técnica deve ser usada com o devido cuidado, pois pode gerar algumas distorções indesejadas em situações específicas.

--

--

Alvaro Leandro Cavalcante Carneiro
Data Hackers

MSc. Computer Science | Data Engineer. I write about artificial intelligence, deep learning and programming.