1. Objetivo
Este projeto visa conhecermos melhor os histogramas e como podemos utilizá-los para realçar imagens, enxergando uma maior riqueza de detalhes.
2. Histograma
O histograma é um vetor que armazena as ocorrências de cada tom de cinza presentes. Geralmente é mostrado em um gráfico com as frequências de cada tom de cor, como o mostrado abaixo.

A equalização de histogramas é uma operação que melhora o contraste, uniformizando o histograma da imagem de forma automática, redistribuindo os níveis de cinza existentes, e mapeando-os para novos níveis. Embora os picos e vales do histograma sejam mantidos, eles deslocados após a equalização. Este procedimento fará com que o número de intensidades na imagem resultante seja igual ou menor que na imagem original.
A equalização de histogramas segue o seguinte algoritmo:
onde \$h(r_k)\$ é o valor do histograma na posição k, e \$r_k\$ é a intensidade. \$L\$ é geralmente a quantidade de bits da imagem, e \$r = 0\$ representa o preto e \$r = L -1\$ representa o branco.
3. O Código
O código é mostrado abaixo, e será explicado logo em seguida.
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
int main(int argc, char** argv){
Mat image, out;
int width, height;
bool color;
vector<Mat> channels;
cout<<"Voce quer uma imagem colorida?\n";
cin>>color;
char key;
if(argc>1){
image = imread(argv[1],CV_LOAD_IMAGE_COLOR);
if(!image.data){
cout<<"Nao abriu a imagem!\n";
return -1;
}
width = image.cols;
height = image.rows;
cout << "largura = " << width << endl;
cout << "altura = " << height << endl;
if(color){
cvtColor(image,out,CV_RGB2HSV);
split(out,channels);
equalizeHist(channels[2], channels[2]);
merge(channels,out);
cvtColor(out,out,CV_HSV2RGB);
}
else{
cvtColor(image,image,CV_RGB2GRAY);
equalizeHist(image, out );
}
namedWindow("source");
namedWindow("output");
imshow("souce", image);
imshow("output",out);
imwrite("output.png",out);
waitKey();
}
else{
VideoCapture cap;
cap.open(0);
if(!cap.isOpened()){
cout << "cameras indisponiveis";
return -1;
}
width = cap.get(CV_CAP_PROP_FRAME_WIDTH);
height = cap.get(CV_CAP_PROP_FRAME_HEIGHT);
cout << "largura = " << width << endl;
cout << "altura = " << height << endl;
cout << "Pressione p para printar e ESC para sair!\n";
while(1){
cap >> image;
flip(image,image,1);
if(color){
cvtColor(image,out,CV_BGR2HSV);
split(out,channels);
equalizeHist(channels[2], channels[2]);
merge(channels,out);
cvtColor(out,out,CV_HSV2BGR);
}
else{
cvtColor(image,image,CV_BGR2GRAY);
equalizeHist(image, out );
}
namedWindow("source");
namedWindow("output");
imshow("souce", image);
imshow("output",out);
key = waitKey(30);
if(key==27) break;
if(key==112){
imwrite("saida.png",out);
imwrite("entrada.png",image);
}
}
}
return 0;
}
3.1. As Variáveis Utilizadas
Mat image, out;
int width, height;
bool color;
vector<Mat> channels;
cout<<"Voce quer uma imagem colorida?\n";
cin>>color;
Para a execução correta do programa, criamos duas variáveis da classe cv::Mat
: uma de entrada e uma de saída (as matrizes image
e out
, respectivamente). Também temos a variável channels, um vetor de cv::Mat
, que servirá para manipularmos os canais de cores da imagem, caso desejado. Por último, temos uma variável definida pelo usuário bool color;
, que indica se o usuário deseja manipular as imagens em preto e branco(0) ou utilizando cores (1).
3.2. Trabalhando com Imagens e Vídeos
O programa é capaz de trabalhar com imagens e vídeos. Para isso, o usuário deve passar uma imagem como parâmetro caso deseje manipular uma imagem, e nenhum parâmetro caso deseje manipular um vídeo. O teste é feito com o primeiro if()
do programa:
if(argc>1)
Caso opte por trabalhar com vídeo, um objeto da classe cv::VideoCapture
será criado. Essa classe é utilizada para trabalharmos com vídeos, capturando-os de arquivos, sequências de imagens e câmeras. A documentação, para referência, pode ser encontrada aqui.
A função VideoCapture.open()
faz com que o elemento da classe abra uma câmera. Utilizamos o parâmetro 0
para abrir a câmera padrão do notebook (webcam). Caso não seja possível abrir, o teste (utilizando a função .isOpened()
- está aberto? ) deverá retornar falso e a execução do programa será parada. Por último, fazemos a atribuição do quadro do vídeo à variável image
, com cap >> image
.
VideoCapture cap;
cap.open(0);
if(!cap.isOpened()){
cout << "cameras indisponiveis";
return -1;
}
.
.
.
while(1){
cap >> image;
flip(image,image,1);
.
.
.
if(waitKey(30) >= 0) break;
A condição while(1)
garante que o programa continue sendo executado (captura contínua de vídeo) até que o usurário pressione um botão (if(waitKey(30) >= 0) break;
).
Utilizamos a função flip(entrada,saida,parâmetro) para inverter horizontalmente a imagem, devido a estarmos trabalhando com uma câmera frontal (webcam nativa do notebook).
|
3.3. Trabalhando com As Funções do OpenCV
Iremos detalhar a manipulação como foi feita para imagens coloridas e no modo vídeo, devido aos modos de imagem e em grayscale possuírem pequenas modificações.
if(color){
cvtColor(image,out,CV_BGR2HSV);
split(out,channels);
equalizeHist(channels[2], channels[2]);
merge(channels,out);
cvtColor(out,out,CV_HSV2BGR);
}
Trabalhando com imagens coloridas, primeiramente devemos converter a imagem para outro padrão de cores, como o HSV ou YCrCb. Apesar do YCrCb ser mais adequado para trabalhar com as imagens (é um modelo bem mais usual para trabalho com imagens digitais e optimizado para tal) estamos mais habituados ao HSV. Mas por que precisamos converter a imagem de RGB para outro padrão de cores?
Fazemos isso pois a equalização de histograma não é um processo linear, e desse modo não é correto aplicá-la individualmente às componentes R,G e B; ou seja, a equalização não está relacionada às componentes de cor, mas sim à intensidade de cada pixel presente na imagem e, desse modo, o balanço de cores não deve ser alterado. Assim utilizamos o HSV, que nos permite mexer nas intesidades (V - Value) sem alterar o balanço de cores (H - Hue).
Para imagens em grayscale (P/B) só possuímos a componente de intensidade, e desse modo podemos aplicar diretamente a equalização. |
Após termos convertido a imagem, utilizando a função cvtColor(entrada,saida,parâmetro)
, cuja documentação está disponível aqui, separamos as componentes de cor HSV utilizando a função split(entrada,vetor_de_componentes)
, que divide os canais em 3 elementos do vetor de matrizes channels[]
. Em seguida, equalizamos o histograma das intensidades (que é o terceiro canal do HSV - channels[2]
) utilizando a função equalizeHist(entrada,saida), cuja documentação também pode ser encontrada no site do OpenCV.
A equalização de histogramas segue o processo descrito acima, e como já temos uma função pronta do OpenCV decidimos por não implementá-la. O OpenCV possui uma base de usuários muito forte e os algoritmos são otimizados. Implementar a função seria somente um meio de expansão do aprendizado.
Após equalizado, juntamos novamente os canais da matriz utilizando a função merge(entrada,saida)
. A matriz de saída out
é então convertida novamente para RGB (para correta exibição) e em seguida a saída é mostrada através da função imshow()
.
namedWindow("source");
namedWindow("output");
imshow("souce", image);
imshow("output",out);
}
4. Saída do Programa
Utilizamos a função waitKey()
, que nos retorna o valor da tabela ascii que foi pressionada no teclado. O valor correspondente à tecla p é 112, conforme a tabela ascii. Caso o usuário pressione a tecla "p", é salvo um print da câmera, utilizando a função imwrite()
.
char key;
.
.
.
key = waitKey(30);
if(key==27) break;
if(key==112){
imwrite("saida.png",out);
imwrite("entrada.png",image);
}
Testamos a saída do programa utilizando a webcam do computador em que foi rodado o programa, utilizando uma imagem colorida, e com uma imagem retirada da internet. As duas imagens de entrada e as duas de saída são mostradas a seguir.




É visível a melhoria do contraste na segunda imagem, mas não tanto na primeira, devido à condições de câmera e iluminação.