processamento digital de imagens… 28/Mar/2008
Posted by Timóteo in Programação.Tags: C, Equalização de Histograma, Histograma, PDI, Processamento Digital de Imagens
trackback
Devido ao início das aulas e de conseguir WoWar via Wine, fiz uma pequena pausa nos estudos de Ruby on Rails. Logo será retomado…
Enquanto isso, a gente tem trabalho de faculdade pra fazer, né??? O primeiro foi exatamente nessa cadeira: PDI. O assunto: equalização de histograma (legal, né?). Vamos ao trabalho:
Criar um programa em C/C++ que irá receber 2 parâmetros: o nome do arquivo de entrada e do arquivo de saída, respectivamente. O programa deverá ler o arquivo de entrada (arquivo de imagem no formato PGM ), realizar a equalização do histograma dos pixels e gravar a imagem equalizada no arquivo de saída.
Vamos ao programa:
um cabeçalho básico
#include <stdio.h> #include <stdlib.h> include <string.h>
mais a biblioteca para alocação dinâmica de memória
#include <malloc.h>
um vetorzão pra armazenar os pixels da imagem PGM, mais os protótipos das funções que utilizo no programa: uma pra processar PGM Ascii e outra pra processar PGM binário)
int **vValoresObtidos; int arredonda(float valor); int processaAscii(int cinzas, int largura, int altura, FILE *entrada, FILE *saida); int processaBinario(int cinzas, int largura, int altura, FILE *entrada, FILE *saida);
A função main, que recebe os nomes dos arquivos pela linha de comando, escreve o cabeçalho do novo arquivo (Esse cabeçalho é em ascii, mesmo nos arquivos PGM binários). Os comentários no código explicam bem o que eu quiz fazer:
int main(int argc, char** argv) {
//declaração das variáveis
FILE *entrada, *saida; //arquivos de entrada e saída
int tonsDeCinza=0, largura=0, altura=0; //os nomes são auto-explicativos
//variáveis auxiliares
char param[5]; //pra ler do arquivo
int p2p5=0; //pra verificar se o arquivo é ascii ou binário
//se o número de parâmetros passados não for exatamente igual a 3, sai do programa
if(argc != 3) return (EXIT_FAILURE)
//verificação de que os nomes dos arquivos estão corretos
printf("Arquivo de entrada é %s\n", argv[1]);
printf("Arquivo de saída será %s\n", argv[2]);
//verifica se os arquivos podem ser acessados:
//arquivo de entrada, somente leitura
if(!(entrada=fopen(argv[1], "r"))) {
printf("arquivo %s não existe!!!\n", argv[1]);
return(EXIT_FAILURE);
}
//arquivo de saída, leitura-escrita
if(!(saida=fopen(argv[2], "w+"))){
printf("Erro ao acessar o arquivo %s", argv[2]);
return(EXIT_FAILURE);
}
/* Uma observação sobre a passagem de argumentos de linha de comando em C:
* C armazena em argc o número de parâmetros passados na linha de comando,
* incluindo a chamada do próprio programa, por isso, se chamarmos o nosso programa
* da forma correta, argc terá o valor 3 e argv, que armazena os parâmetros passados,
* terá os valores: argv[0]: nome do programa, argv[1]: arquivo de entrada, argv[2]: arquivo de saída
*/
// criando o cabeçalho do novo arquivo:
// a condição de parada deste loop é chegar ao final do arquivo,
// isso é só uma garantia pra que um arquivo vazio ou mal formado não seja lido
while(!feof(entrada)){
fgets(param, 5, entrada); //lê até cinco caracteres do arquivo
if(param[0]=='#') {
//ignora os comentários
while(getc(entrada)!='\n');
continue;
}
if(param[0]=='P'){
//verifica se o arquivo é ascii ou binário...
//P2 diz que os dados estão em formato ascii, P5 diz que é binário
p2p5=param[1]=='2'?2:5;
}
if(contaParam==1){
//pega o primeiro parâmetro válido e atribui à largura
largura=atoi(param);
}else if (contaParam==2){
//pega o segundo parâmetro válido e atribui à altura
altura=atoi(param);
}else if(contaParam==3){
//pega o terceiro parâmetro válido e atribui aos tons de cinza
tonsDeCinza=atoi(param);
}
//escreve os parâmetros no arquivo de saída
//os arquivos PGM, mesmo que sejam no formato binário, têm seu cabeçalho em ASCII
fprintf(saida, "%s", param);
contaParam++;
//depois de pegar os quatro primeiros parâmetros sai do laço
if(contaParam >3)break;
}
//chama a função correta de interpretação
if(p2p5==2){
//como eu escrevi anteriormente, P2 é ASCII...
processaAscii(tonsDeCinza, largura, altura, entrada, saida);
}else if (p2p5==5){
//e P5 é binário
processaBinario(tonsDeCinza, largura, altura, entrada, saida);
}else{
//mensagem de erro. Enquanto estou escrevendo este post,
//percebi que essa mensagem nunca será exibida,
//por causa do operador ternário que eu utilizei na atribuição de p2p5
printf("Erro de formato!!! O arquivo está mal definido!!!\n");
return(EXIT_FAILURE);
}
//fecha os arquivos antes de sair
fclose(entrada);
fclose(saida);
return (EXIT_SUCCESS);
}//fim do main
Agora, a função que faz o arredondamento de um número float. Ela retorna o valor inteiro mais próximo, somando 0.5 e cortando os decimais.
int arredonda(float valor){
return (valor+0.5);
}
As funções processaAscii e processaBinário são praticamente iguais, a única diferença é na leitura e escrita no arquivo Vou utilizar a processaAscii como exemplo e citar o que é diferente na processaBinário.
int processaAscii(int cinzas, int largura, int altura, FILE *entrada, FILE *saida){
//alocação de memória para os ponteiros de cada linha do arquivo
vValoresObtidos=malloc(sizeof(int*)*largura);
if(!vValoresObtidos) {printf("erro no malloc1"); exit(EXIT_FAILURE);}
//declaração dos vetores
int vCinzas[cinzas+1];
float vProbabilidades[cinzas+1];
float vAcumulado[cinzas+1];
int vNormal[cinzas+1];
//variáveis auxiliares
int i, j;
char parm[5]; //variável para ler caracteres do arquivo ascii. Não existe no processaBinário
//alocação dos vetores de valores obtidos
for(i=0;i<largura;i++){
vValoresObtidos[i]=malloc(sizeof(int)*altura);
if(!vValoresObtidos){
printf("erro no malloc2");
exit(EXIT_FAILURE);
}
}
//inicialização dos vetores
for(i=0;i<=cinzas;i++){
vCinzas[i]=0;
vProbabilidades[i]=0;
vAcumulado[i]=0;
vNormal[i]=0;
}
//lê os valores do arquivo e armazena na matriz que alocamos dinamicamente
for(i=0;i<largura;i++){
for(j=0;j<altura;j++){
int valor;
fgets(parm, 5, entrada);//não existe no processaBinario
valor=atoi(parm);//fica assim: valor=fgetc(entrada);
vValoresObtidos[i][j]=valor;
vCinzas[valor]++;
}
}
//agora os cálculos
//cálculo das probabilidades e do acumulado
vProbabilidades[0]=(float)(vCinzas[0])/(largura*altura);
vAcumulado[0]=vProbabilidades[0];
for(i=1;i<=cinzas;i++){
vProbabilidades[i]=(float)(vCinzas[i])/(largura*altura);
vAcumulado[i]=vAcumulado[i-1]+vProbabilidades[i];
}
//normalização
for(i=0;i<=cinzas;i++){
vNormal[i]=arredonda(vAcumulado[i]*cinzas);
}
//escrever novos valores
for(i=0;i<largura;i++){
for(j=0;j<altura;j++){
char valor[5];
sprintf(valor, "%d\n", vNormal[vValoresObtidos[i][j]]);
fprintf(saida, valor);
/* no código que processa binários, todo este corpo dos dois laços for
* é substituído por
* fputc(vNormal[vValoresObtidos[i][j]],saida);
*/
}
}
//libera a memória e tremina a função
for(i=0;i<largura;i++){
free(vValoresObtidos[i]);
}
free(vValoresObtidos);
return EXIT_SUCCESS;
} //fim da função processaAscii
Agora, é só compilar e rodar:
$ gcc equaliza.c -o equaliza $ ./equaliza <img_original> <nova_img>
Os resultados, utilizando a famosa imagem da lena (tive que converter pra png por causa do wordpress). A de cima é a original. Nas de baixo, a primeira fopi a equalizada pelo código e a segunda foi equalizada pelo GIMP:


Bom, o código não está perfeito. Estou trabalhando nele, portando pra Java e fazendo outro código para detecção de bordas aplicando o operador de Sobel. Valeu!!!
Olá!
Parabens pelo codigo…
vc tem ele em java?
se tiver, poderia me enviar?
preciso criar um histograma de uma imagem e equaliza-lo e nao estou conseguindo.
Obrigada
Josi Godoy (josiggodoy@hotmail.com)
olá Josi. Infelizmente não portei o código para Java, mas a ideia deve ser basicamente a mesma, com orientação a objetos…