processamento digital de imagens…


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:

Lena Original

Lena Equalizada por este Código Lena 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!!!

2 ideias sobre “processamento digital de imagens…

  1. Josi Godoy

    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)

    Resposta
    1. Timóteo Autor do post

      olá Josi. Infelizmente não portei o código para Java, mas a ideia deve ser basicamente a mesma, com orientação a objetos…

      Resposta

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s