Processamento
Digital de Imagens
Operações entre Imagens
Digitais
Nesta aula abordaremos os principais
conceitos relacionados com algumas operaçoes lógicas e
aritméticas que podem ser aplicadas às imagens digitais.
Importante: O resumo abaixo deve ser complementado,
pelo aluno, com a leitura dos textos sugeridos na bibliografia do curso.
Tópicos
Introdução - Conceitos iniciais
- Consideraremos operações
entre imagens digitais a
aplicação de operações matemáticas,
aritméticas e lógicas, entre os níveis digitais de
duas ou mais imagens digitais, ou bandas de uma
imagem multiespectral.
- As operações aqui
consideradas são
operações pixel a pixel, ou seja, operações
locais que envolvem os pixels das imagens que tem
correspondência espacial na cena.
- O resultado dessas operações
é sempre uma
nova imagem digital. A figura abaixo ilustra a operação
entre um pixel de uma imagem X
com o pixel colocalizado da imagem Y gerando o
pixel correspondente na imagem Z.
O processo é realizado para
todos os pixels que compõem as imagens.
Figura: Ilustração
das
operações entre duas imagens de entrada, X e Y,
gerando
uma imagem de saída Z.
- Objetivos das
Operações com imagens digitais.
- Entre os principais objetivos de se operar com imagens digitais
podemos destacar:
- Eliminação de ruídos sistemáticos.
- Melhora da qualidade visual geral de uma imagem digital.
- Realçe de alvos ou de objetos específicos
contidos
em uma imagem digital.
Operações Aritméticas
- Idéias iniciais:
- A princípio qualquer operador matemático pode ser
aplicado aos níveis digitais das imagens ou bandas envolvidas
nas operações.
- Operadores aritméticos, como por exemplo, soma,
subtração, divisão e multiplicação
podem ser usados. Também podem ser usados operadores
lógicos como veremos no próximo item. Vamos abordar,
neste
texto, algumas operações com maior
utilização.
- Operações
aritméticas para eliminação de ruídos.
São aplicados geralmente para se eliminar
ruídos sistemáticos que aparecem igualmente na bandas de
uma
multiespectral.
- Subtração de
imagens ou bandas digitais. Aplicada quando os ruídos
estão somados aos valores digitais das imagens ou
bandas. Supondo-se que as imagens X
e Y estejam contaminadas pelo
mesmo ruído aditivo R,
podemos dizer que:
X =
X´+R ; Y = Y´+R e Z = X-Y =
X´-Y´
- Assim a imagem resultante Z é proporcional as imagens
X´ e Y´ e não está contaminado pelo erro R.
- Divisão de imagens ou
bandas digitais. Aplicada quando os ruídos estão
multiplicados aos valores digitais das
imagens ou bandas. Supondo-se que as imagens X e Y
estejam contaminadas pelo mesmo ruído multiplicativo R, podemos dizer que:
X =
X´*R ; Y = Y´*R e Z = X/Y =
X´/Y´
- Assim a imagem resultante Z é proporcional as imagens
X´ e Y´ e não está contaminado pelo erro R.
Por exemplo, as sombras, decorrentes da iluminação do
terreno, que aparecem numa imagem multiespectral de sensoriamento
remoto, estão presentes igualmente em todas as bandas como se
fossem ruídos multiplicativos.
- Resumo das principais
aplicações das operações aritméticas
- Considerando-se as mesmas duas imagens de entrada X e Y
e uma imagem de saída Z,
resultante da operação de X por Y, a tabela abaixo,
apresentada na página 41 do livro Processamento Digital de Imagens
de Oge Marques Filho e Hugo
Vieira Neto, mostra um resumo dos efeitos de cada
operação e quais as aplicações
típicas para cada tipo de operação.
Operação
|
Efeito
sobre a Imagem
|
Aplicações
|
Adição
|
Z
é o resultado da soma dos valores de intensidade de
X e Y. Se Y for um escalar positivo, Z será uma versão
mais clara de X; o acréscimo de intensidade será o
próprio valor de Y.
|
Normalização de
brilho de imagens (adequar a faixa total de níveis de cinza a um
intervalo prédefinido)
Remoção de ruídos (ver técnica da filtragem
pela média de múltiplas imagens na subseção
4.2.4 do livro texto)
|
Subtração
|
Z é o resultado da
diferença dos valores de intensidade de X e Y. Se Y for
um escalar positivo, Z será uma versão mais escura de X;
o decréscimo de
intensidade será o próprio valor de Y. |
Detecção de
diferenças entre duas imagens (eventualmente adquiridas de
forma consecutiva) da mesma cena
|
Multiplicação
|
Z é o produto dos valores
de intensidade de X e Y. Se Y for
um escalar positivo, os valores de intensidade de Z serão
diretamente proporcionais a X por um fator Y. |
Calibração
de brilho (adequação a diferentes valores de
iluminância sobre uma mesma cena, por exemplo)
|
Divisão
|
Z é a razão dos
valores de intensidade de X pelos valores correspondentes em Y. Se Y
for
um escalar positivo, os valores de intensidade de Z serão
inversamente proporcionais a X por um fator Y |
Normalização do
brilho
|
Tabela: Efeitos e
aplicações das operações aritméticas
sobre imagens (retirado do livro
Processamento
Digital de Imagens de
Oge
Marques Filho e Hugo Vieira Neto, pagina 41)
- Além das aplicações relacionadas na tabela
acima pode-se citar que:
- a subtração é capas de realçar
diferenças espectrais entre imagens;
- a adição pode ser usada para a
obtenção da média entre imagens;
- a divisão, ou razão, aumenta o contraste entre
objetos de uma cena quando estes objetos apresentam respostas
espectrais muito diferentes nas duas imagens de entrada.
- A divisão entre bandas de uma imagem multiespectral
é também muito usada para obtenção de
imagens de Índice de Diferença de
Vegetação
(IDV ou VDI - Vegetation Difference
Index ). A idéia é
utilizar uma banda com alta sensibilidade à resposta da
vegetação e dividir essa banda por outra com baixa
sensibilidade à vegetação. Como exemplo
prático é comum utilizar-se a banda 4 e a banda 3 do
sensor TM (Thematic Mapper) do satélite LandSat para se
obter o IDV por:
IDV=banda4/banda3.
- Também pode-se calcular o esse mesmo
índice, agora normalizado, através da
relação:
IDVN=(banda4-banda3)/(banda4+banda3).
- Neste caso
os valores da imagem de saída estão normalizados para
valores entre -1 e 1 porque estão divididos pela soma
(banda4+banda3).
- O Índice de Diferença de Vegetação Normalizado ( IDVN ou NVDI em inglês) além
de aumentar o contraste espectral entre a vegetação e o
solo, tem os efeitos
de iluminação, declividade da superfície e
geometria de "visada"
parcialmente compensados pelo índice. A figura abaixo ilustra a
geração de uma banda IDVN a partir das bandas 3 e 4 do
sensor TM Landsat. Na imagem (c) resultante, as áreas
escuras representam índice de vegetação baixo
enquanto que as áreas
claras representam índice de vegetação alto.
Quanto mais branco maior a
densidade da vegetação no local. Por consequência
as regiões com valores mais escuros na imagem representam
ausencia de vegetação.
(a)
|
(b)
|
Figura: Banda IDVN de índice de
vegetação de diferença normalizada (a) obtida a
partir das bandas 4 (b)
e 3 (c) do sensor Thematic Mapper (TM) do satélite LandSat.
- Problemas com o resultado das operações
aritméticas entre imagens.
- O resultado das operações aritméticas
entre imagens, ou bandas de uma imagem multiespectral, pode ser
menor que o valor mínimo (underflow) ou exceder o valor
máximo (overflow) permitido na
codificação de seus níveis digitais. Por exemplo,
numa imagem com radiometria codificada com 8 bits podemos ter valores
resultantes negativos, menores que 0, e maiores que 255.
- Ainda que o resultado final da operação entre
essas imagens, ou bandas de uma imagem, esteja entre os valores
mínimo e máximo da codificação, a
imagem resultante pode estar pouco realçada.
- Solução:
Mapeamento do resultado para os valores
digitais do domínio de codificação das imagens.
Transformação linear da imagem de saída para os
valores mínimos e máximos da codificação.
Procedimento idêntico ao apresentado no capítulo de realce
por histograma.
- Sejam duas imagens X e
Y, codificadas com 8
bits, definidas
como abaixo. A imagem Z
é o resultado da subtração
de X por Y.
132
|
120
|
138
|
-
|
152
|
92
|
107 |
=
|
-20
|
28
|
31
|
84
|
110
|
200
|
80 |
124 |
210 |
4
|
-14
|
-10
|
255
|
92
|
177
|
230
|
100 |
164 |
25
|
-8
|
13
|
X |
-
|
Y |
=
|
Z |
- O realce da imagem de saída Z, para os valores
mínimo e máximo
da codificação (0, 255), pode ser realizado pela
mesma metodologia
apresentada na apostila de realce por histograma, ou seja:
- Da análise dos valores da imagem Z obtem-se:
Zmin =
-20 e Zmax = 31
a
= (28-1)/(Zmax - Zmin) = 255/51 = 5
b =
-a*Zmin = -5*(-20) = 100
- Lembrete: Numa transformação linear, o valor de a,
multiplicativo, é também chamado ganho e o valor de b, aditivo, é chamado de offset.
- linear Aplicando-se os valores de a e b, calculados
acima, da
relação S = a*Z + b = 5*Z + 100,
em cada elemento da
imagem Z, obtemos
a imagem S, abaixo, que foi
realçada com valores mínimo e
máximo iguais a 0 e 255, respectivamente.
0
|
240
|
255
|
120
|
30
|
50
|
225
|
60
|
165
|
S
|
- Importante: A imagem
(c) da figura acima está
realçada por um realce por histograma pois seus níveis de
cinza originais estão na faixa entre -1 e 1.
- Sugestão: Fazer exercícios de
operações aritméticas no SPRING usando as bandas 3
e 4 do sensor TM LandSat (veja exercício 1 abaixo)
|
Operações Lógicas
- Da mesma forma que aplicamos operações
aritméticas, também podemos aplicar
operações lógicas, ou booleanas, entre diferentes
imagens ou bandas de uma imagem multiespectral.
- A figura abaixo ilustra a aplicação das
operações lógicas E (AND), OU (OR), OU Exclusivo
(XOR) e NEGAÇÃO (NOT).
Figura: Exemplos de
operações lógicas entre sobre imagens (retirado do
livro Processamento
Digital de Imagens de Oge
Marques Filho e Hugo Vieira Neto, pagina 45)
- Na figura os operadores lógicos ou booleanos foram
aplicados bit a bit utilizando as tabelas verdades das
operações booleanas.
Entradas
|
Operador
Lógico
|
A
|
B
|
AND
|
OR
|
XOR
|
NOT
A
|
0
|
0
|
0
|
0
|
0
|
1
|
0
|
1
|
0
|
1
|
1
|
1
|
1
|
0
|
0
|
1
|
1
|
0
|
1
|
1
|
1
|
1
|
0
|
0
|
- Sejam duas imagens X e Y, codificadas com 8 bits, definidas
como abaixo. A imagem Z é o resultado da subtração
de X por Y.
X
|
AND
|
Y
|
=
|
Z
|
132
|
120
|
138
|
152
|
92
|
107 |
128
|
88
|
10
|
84
|
110
|
200
|
80 |
124 |
210 |
80
|
108
|
192
|
255
|
92
|
177
|
230
|
100 |
164 |
25
|
68
|
160
|
X
|
AND
|
Y
|
=
|
Z
|
10000100
|
01111000
|
10001010
|
10011000
|
01011100
|
01101011 |
10000000
|
01011000
|
00001010
|
01010100
|
01101110
|
11001000
|
01010000 |
01111100 |
11010010 |
01010000
|
01101100
|
11000000
|
11111111
|
01011100
|
10110001
|
11100110
|
01100100 |
10100100 |
11100110
|
01000100
|
10100000
|
- Importante: Diferentemente das operações
aritméticas,
quando se aplicam operações lógicas entre imagens
digitais, não se obtem valores menores que o mínimo
(underflow) ou maiores que o máximo (overflow) da
codificação. Porém pode-se aplicar um realce por
histograma sobre a imagem de saída ( da mesma forma como foi
aplicada ao resultado das operações aritméticas).
Exercícios
Laboratório 5 - Aplicação de
operações aritméticas entre duas imagens digitais
ou entre duas bandas de uma imagem digital
- Faça um programa em Java para operar sobre duas imagens
do
tipo jpeg (.jpg) e calcular o índice de vegetação
normalizado como definido na teoria acima. Dica: Utilize o programa em
java do apendice 1, abaixo, como ponto de partida para o seu
programa (utilize as imagens TM_3.jpg e TM_4.jpg abaixo, que podem
estar disponíveis também em http://www.dpi.inpe.br/tmp/Carlos
). Neste caso a imagem IDVN de saída é o resultado
da seguinte operação (TM_4-TM_3)/(TM_4+TM_3).
.
Imagens TM_3.jpg (esquerda) e TM_4.jpg (direita)
- Visualize a imagem de saída.O
que
você observou?
- Voce utilizou algum realce para a imagem de saída? Se
sim, explique os fatores de ganho e offset utilizados.
- Faça um relatório sobre o laboratório,
incluindo a resposta da pergunta acima e
envie para o professor.
Observação: O
relatório deve ser digital e deve ser enviado para o e-mail do
professor (
carlos@dpi.inpe.br
) contendo:
Identificação do aluno (
nome, nro de matrícula, outros...) e data.
Nome do experimento do laboratório.
Uma introdução explicitando o objetivo do trabalho do
laboratório.
Uma descrição da metodologia utilizada para
resolver o problema.
Uma descrição dos resultados obtidos com análises
pertinentes.
Uma seção de conclusão com opiniões
próprias de cada
aluno e sugestões gerais relacionadas com o experimento.
Exercícios gerais
1. Faça operações
utilizando o SPRING (Ative um Plano de Informação da
categoria Imagem. Ative a função de
operações aritméticas no menu Imagem do
SPRING). Utilize essa função para executar
operações aritméticas sobre duas bandas presentes
no seu banco de dados. Aplique operações de realce
por contraste na banda de saída, quando necessário.
2. Suponha que você tenha as imagens X e Y abaixo codificadas com
3 bits. Calcule a imagem de saída Z aplicando sobre X e Y as
operações: (a) soma; (b) subtração; (c)
divisão; (d) OU e (e) AND. Calcule os parâmetros
da
equação linear que realce a imagem de saída
supondo que ela esteja codificada com 5 bits.
1 5 3 6
6 3 2 4
X = 3 7 0 2 Y = 4 5 7 0
6 5 5 1
7 1 3 2
3. Explique como você faria para
aplicar
operações matemáticas em imagens coloridas, cada
qual com a s suas
3 componentes R, G e B.
4. A imagem resultante de operação booleana AND entre
duas imagens de entrada deverá conter média menor ou
igual à menor média das imagens de entrada. Voce concorda
com essa afirmativa? Explique.
5. A imagem resultante de operação booleana OR entre duas
imagens de
entrada deverá conter média menor ou igual à menor
média das imagens de
entrada. Voce concorda com essa afirmativa? Explique.
Apêndice1: Trabalhando com a
classe BufferedImage
// importação de pacotes do AWT para trabalhar com classe
BufferedImage
import
java.awt.image.BufferedImage;
import
java.awt.image.Raster;
import
java.awt.image.WritableRaster;
//
Instanciação
de um objeto da classe BufferedImage
BufferedImage ima_in,
ima_out;
//
Carregando de uma imagem através da classe BufferedImage
BufferedImage
ima_in = null;
String image_name =
"nemo500-270.jpg";
File file =
new File(image_name);
try {
ima_in= ImageIO.read(file);
} catch(Exception e) {
System.out.println("Imagem
'" + image_name + "' nao existe.");
System.exit(0);
}
// Lendo
informações do cabeçalho de uma imagem aberta
int ncol = ima_in.getWidth();
int nlin
=
ima_in.getHeight();
int type =
ima_in.getType();
// Criando
uma imagem de saída através da classe BufferedImage
BufferedImage
ima_out
= new BufferedImage(ncol, nlin,
BufferedImage.TYPE_BYTE_GRAY);
// 10 - TYPE_BYTE_GRAY
// Obtendo acesso aos dados (matriz) de uma BufferedImage
Raster raster =
ima_in.getRaster(); // declara e instancia objeto raster só para
leitura
WritableRaster wraster =
ima_out.getRaster(); // declara e instancia objeto raster para escrita
// Lendo dados das bandas de uma imagem aberta
int r =
raster.getSample(x,y,0); // le dado da
banda 0 da imagem de entrada
int g =
raster.getSample(x,y,1); // le dado da
banda 1 da imagem de entrada
int b =
raster.getSample(x,y,2); // le dado da
banda 2 da imagem de entrada
// Escrevendo dados de uma
banda
de uma imagem criada
int valor_saida =
(r+g+b)/3;
wraster.setSample(x,y,0,valor_saida);
|
Apêndice2: Programa Java
ProcessaeMostraImagem.java
O programa abaixo deve servir de base para o aluno desenvolver um
programa que implemente a operação de cálculo do
IVDN utilizando duas imagens digitais de entrada.
// Este programa em Java possibilita realizar vários
processamentos em uma imagem
// de entrada (nivel de cinza ou colorida) armazenada no formato jpeg
(.jpg)
// O resultado do processamento é uma imagem em níveis de
cinza codificada com 8 bits.
// O Programa permite ler a imagem de entrada, processa-la e mostrar a
imagem processada em um frame.
// pacote de classes de Entrada e Saida
import java.io.File;
// pacotes do AWT
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
// pacote Swing para definir interface gráfica
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.ImageIcon;
// leitura em modo imediato - para J2SE 1.4+
import javax.imageio.ImageIO;
public class ProcessaeMostraImagem extends JFrame {
public static void main(String args[]) {
// Mostra JFrame decorado pelo Swing
JFrame.setDefaultLookAndFeelDecorated(true);
// Carrega e Visualiza imagem original
String ima_name_in =
"nemo500-270.jpg";
String ima_name_out =
"nemo500-270ncpond.jpg";
BufferedImage ima_in, ima_out;
ima_in =
CarregaImagem(ima_name_in);
ProcessaeMostraImagem frame1 =
new ProcessaeMostraImagem();
frame1.MostraImagem(ima_in,
ima_name_in);
// Carrega e processa imagem original e mostra
imagem processada
ima_out =
ProcessaImagem(ima_in);
ProcessaeMostraImagem
frame2 = new ProcessaeMostraImagem();
frame2.MostraImagem(ima_out,
ima_name_out);
SalvaImagem(ima_out,
ima_name_out);
}
// public ProcessaeMostraImagem (){ };
public static BufferedImage CarregaImagem(String
image_name) {
// Associa objeto BufferedImage com
<arquivo_imagem>
BufferedImage ima_in = null;
// Carrega imagem
File file = new File(image_name);
try {
ima_in= ImageIO.read(file);
} catch(Exception e) {
System.out.println("Imagem '" + image_name + "' nao existe.");
System.exit(0);
}
System.out.println("Nome da
Imagem: "+image_name+" Tipo da Imagem: "+ima_in.getType());
System.out.println("Tamanho da
Imagem: Colunas "+ima_in.getWidth()+" Linhas "+ima_in.getHeight());
return ima_in;
}
public static void SalvaImagem(BufferedImage dest,
String image_name) {
try {
ImageIO.write(dest, "jpg", new File(image_name));
} catch (Exception e) {
System.out.println("Problema gravando arquivo. ");
System.exit(0);
}
}
public static BufferedImage ProcessaImagem
(BufferedImage ima_in) {
// Associa objeto BufferedImage com
<arquivo_imagem> níveis de cinza (10)
BufferedImage ima_out = new
BufferedImage(ima_in.getWidth(),ima_in.getHeight(),BufferedImage.TYPE_BYTE_GRAY);
// 10 - TYPE_BYTE_GRAY
// Recupera matriz das imagens de entrada e
saida
Raster raster =
ima_in.getRaster(); // declara e instancia objeto raster soh para
leitura
WritableRaster wraster =
ima_out.getRaster(); // declara e instancia objeto raster para escrita
// Processa valores da imagem de entrada e armazena
na imagem de saida
int r, g, b, valorn;
for(int y=0;
y<ima_in.getHeight(); y++)
for(int x=0;
x<ima_in.getWidth(); x++){
r = raster.getSample(x,y,0); // le dado da
banda 0 da imagem de entrada
g = raster.getSample(x,y,1); // le dado da
banda 1 da imagem de entrada
b = raster.getSample(x,y,2); // le dado da
banda 2 da imagem de entrada
//
valorn = r; // Transfere Banda 0 (r) da imagem
//
valorn = g; // Transfere Banda 1 (g) da imagem
//
valorn = b; // Transfere Banda 2 (b) da imagem
//
Criacao de Imagens níveis de Cinza a partir
das bandas r, g e b
//
valorn = (r+g+b)/3; // Pela
média das 3 bandas
//
valorn = (Maximo(r,g,b)+Minimo(r,g,b))/2; // Pela
Média entre Maximo e Minimo
valorn = (int)(.299*(double)r + .587*(double)g +
.114*(double)b); // Pela Média Ponderada das 3 bandas
//
valorn = 255-valorn; // Inverte Imagem
//
if(valorn<127)valorn = 0; else valorn=255; //
Binariza imagem pelo nivel 127
wraster.setSample(x,y,0,valorn);
if(y<=3 && x<=3)
System.out.println(y+"
"+x+" Valores "+r+" "+g+" "+b+" "+valorn);
}
return ima_out;
};
public void MostraImagem(BufferedImage ima, String
image_name) {
// Define GUI com objetos do Swing
JLabel lsrc2 = new
JLabel(new ImageIcon(ima));
getContentPane().add(new
JScrollPane(lsrc2));
// Atribui nome e tamanho ao frame
setTitle("Java2DImageDisplay: =>" + image_name);
setSize(ima.getWidth()+40,
ima.getHeight()+40);
setVisible(true);
// Encerra a aplicação clicando no
"close"
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static int Minimo(int a, int b, int c){
int min=a;
if(b<min)min=b; if(c<min)min=c;
return min;
}
public static int Maximo(int a, int b, int c){
int max=a;
if(b>max)max=b; if(c>max)max=c;
return max;
}
}
Apêndice3: Estrutura básica de um
Programa em C para implementar operações
matemáticas entre imagens digitais.
#include <stdio.h>
#include <malloc.h>
main()
{
FILE *fpiX, *fpiY, *fpo;
unsigned char *bufferX, *bufferY;
int nlin,ncol,l,c,cor,Xmin=10000, Xmax=-1000;
float ganho, offset, result;
// Abre arquivo de entrada com imagem X
fpiX = fopen("tm3_or_393x433.raw","rb");
if(fpiX==NULL){
printf("Arquivo da imagem Xnao existe");
getchar(); return 1;
}
// Abre arquivo de entrada com imagem Y
// COMPLETAR
// Cria arquivo de saida
fpo = fopen("IDVN.raw","wb");
if(fpo==NULL){
printf("Arquivo nemosaida.raw nao existe");
getchar(); return 1;
}
// Le nro de linhas e colunas do usuario
printf("\nEntre com o nro de
linhas da imagem: ");
scanf("%d",&nlin);
printf("\nEntre com o nro de
colunas da imagem: ");
scanf("%d",&ncol);
// Le valores de ganho e offset
printf("\nEntre com o valor do
ganho: ");
scanf("%f",&ganho);
printf("\nEntre com o valor do
offset: ");
scanf("%f",&offset);
// Aloca dinamicamente o buffer de cada linha da imagem X
bufferX =
malloc(ncol*sizeof(char));
if( bufferX == NULL)
{
printf("Nao alocou memoria para o buffer");
getchar(); return 1;
}
// Aloca dinamicamente o buffer de cada linha da imagem Y
// COMPLETAR
// Aplica a operação de IDVN sobre as imagens de
entrada e escreve resultado na imagem de saida
for(l=0; l<nlin; l++)
{
// Le linha l do
arquivo de entrada da imagem X em bufferX
fread(bufferX, sizeof(char), ncol,
fpo);
// Le linha l do arquivo de
entrada da imagem Y em bufferY
// COMPLETAR
// Opera sobre as linhas das
duas imagens de entrada
for(c=0;
c<ncol; c++){
result =
((float)bufferY[c]-(float)bufferX[c])/((float)bufferY[c]+(float)bufferX[c]);
bufferX[c] = (unsigned char) (ganho*result + offset);
}
// Escreve linha l no arquivo
de saida
fwrite(bufferX,
sizeof(char), ncol,
fpo);
}
// Fecha arquivos e libera memória
alocada
fclose (fpiX);
fclose (fpiY);
fclose (fpo);
free (bufferX);
free(bufferY);
getchar();
}
|