Software de Processamento de Imagens - C# VisualStudio

1.Introdução

Este trabalho me possibilitou uma ótima experiência de aprendizado, pude trabalhar um pouco com processamento de imagens, pude aprender uma linguagem que não havia tido muito contato C#, entendido um pouco mais sobre formatos das imagens, usei o Visual Studio uma das melhores ferramentas na minha opinião, mas que nem sempre é possível usar nos trabalhos escolares. Também estou tendo a oportunidade de melhorar um pouco meu site pessoal com mais conteúdo. Neste relatório vou mostrar o desenvolvimento e o resultado dos 5 itens pedidos na definição do trabalho. Mas antes gostaria de explicar seu funcionamento e ressaltar que este programa é possivelmente o mais intuitivo de se usar já feito para esta disciplina. Tudo acontece em uma janela auto dimensionável, com todas as opções visíveis para o usuário. Quando você escolhe uma opção ele abre uma janela para que você selecione uma imagem, após selecionar uma imagem ele mostra ela na janela 1 e o resultado da opção escolhida na janela 2. Você pode salvar esta imagem digitando CRTL + S ou simplesmente Arquivo -> Salvar. Para a quantização existe um campo de texto no próprio menu com o número que você deseja escolher, o valor default é 8. O programa funciona com vários tipos de imagens .jpg, .png, .bmp, entretanto ele salva apenas no formato .jpg. O programa também mantém o valor de Alfa das imagens, ou seja, não modifica sua transparência. Ele irá funcionar em qualquer sistema operacional Windows que tenha o net.framework4.0 instalado. Agora o programa também mostra o Histograma, Brilho, Contraste, Negativo e Equaliza a imagem. A interface continua intuitiva no entanto esta pouco prática, mas o objetivo é justamente ver os conceitos de processamento de imagens e não de se vender o software.Então considero que a intuitividade é mais importante que a prática. Para finalizar este super programa ele foi incrementado com funções de Zoom, IN/OUT, Rotação Horária e Anti-Horária. O programa também recebeu filtros para se aplicar nas imagens.

Vejamos abaixo a janela auto dimensionável:



Não alteração do fator alpha (imagem .png):





2.Desenvolvimento

2.1 Espelhamento Horizontal

Ambos espelhamentos foram feitos baseados na troca pixel por pixel, pois a biblioteca usada não tinha uma função que nos desse a linha ou coluna inteira da matriz, se armazenássemos os pixels numa matriz antes de trocar as linhas e colunas seria mais demorado pois teríamos que percorrer todos pixels uma vez de qualquer jeito para poder armazena-los.

Código Fonte:

pictureBox1.Image = Image.FromFile(openFileDialog1.FileName);
imagem = new Bitmap(Image.FromFile(openFileDialog1.FileName));
nome = openFileDialog1.FileName;
int h = imagem.Width;
int v = imagem.Height;
nova_imagem = new Bitmap(imagem.Width, imagem.Height);
int i, u;
for (i = 0; i < v; i++)
{
for (u = 0; u < h; u++)
{
nova_imagem.SetPixel(h - u - 1, i, imagem.GetPixel(u, i));
}
}
pictureBox2.Image = nova_imagem;

Resultado:



2.2 Espelhamento Vertical:

Código Fonte:

pictureBox1.Image = Image.FromFile(openFileDialog1.FileName);
imagem = new Bitmap(Image.FromFile(openFileDialog1.FileName));
nome = openFileDialog1.FileName;
int h = imagem.Width;
int v = imagem.Height;
nova_imagem = new Bitmap(imagem.Width, imagem.Height);
int i, u;
for (u = 0; u < h; u++)
{
for (i = 0; i < v; i++)
{
nova_imagem.SetPixel(u, v - i -1, imagem.GetPixel(u, i));
}
}
pictureBox2.Image = nova_imagem;

Resultado:



2.3 Imagem em Tons de Cinza

Foi utilizado a fórmula para conversão RGB-YCbCr e o valor de Y foi colocado para todos os canais.


Código Fonte:

Bitmap grayScale = new Bitmap(image.Width, image.Height);
for (Int32 y = 0; y < grayScale.Height; y++)
for (Int32 x = 0; x < grayScale.Width; x++)
{
Color c = image.GetPixel(x, y);
Int32 gs = (Int32)(c.R * 0.3 + c.G * 0.59 + c.B * 0.11);
int trasn = imagem.GetPixel(x, y).A;
grayScale.SetPixel(x, y, Color.FromArgb(trasn,gs, gs, gs));
}
return grayScale;

Resultado:



2.4 Quantização

Dependendo do valor do pixel ele recebe o valor intermediário para o grupo ao qual ele pertence, o número de grupos depende do usuário que pode escolher de 1 a 256. Se ele escolher 1 a imagem vai ficar toda cinza se escolher 256 não haverá nenhuma alteração.


Código Fonte:

try
{
int quan = quan = Convert.ToInt32(valor.Text);

if (quan > 256)
quan = 256;
else if (quan <= 0)
quan = 1;

int dist = 256 / quan;

if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
pictureBox1.Image = Image.FromFile(openFileDialog1.FileName);
imagem = new Bitmap(Image.FromFile(openFileDialog1.FileName));
nome = openFileDialog1.FileName;
int h = imagem.Width;
int v = imagem.Height;
int novo_valorR = 0;
int novo_valorG = 0;
int novo_valorB = 0;
int faixaR = 0;
int faixaG = 0;
int faixaB = 0;
nova_imagem = new Bitmap(imagem.Width, imagem.Height);
int i, u;
for (i = 0; i < v; i++)
{
for (u = 0; u < h; u++)
{
faixaR = imagem.GetPixel(u, i).R / dist;
faixaG = imagem.GetPixel(u, i).G / dist;
faixaB = imagem.GetPixel(u, i).B / dist;
int trasn = imagem.GetPixel(u, i).A;
novo_valorR = faixaR * dist + (dist / 2);
novo_valorG = faixaG * dist + (dist / 2);
novo_valorB = faixaB * dist + (dist / 2);
if (novo_valorR > 255)
novo_valorR = 255;
if (novo_valorG > 255)
novo_valorG = 255;
if (novo_valorB > 255)
novo_valorB = 255;
nova_imagem.SetPixel(u, i, Color.FromArgb(trasn,novo_valorR, novo_valorG, novo_valorB));
}
}
pictureBox2.Image = nova_imagem;
}
}
catch { }

Resultado para 8 tons:

Resultado para 2 tons:

Resultado para 2 tons sobre imagem colorida:



2.5 Salvamento

A opção salvar esta disponível e pelo que vi toda vez que salvamos um .jpg ele perde qualidade, pois é aplicada algum tipo de compreensão. Mas no meu caso o como eu recupero todos os pixels e depois salvo de novo e provavelmente minha biblioteca não esta realizando a melhor compressão o tamanho das imagens aumenta. Num teste inicial eu apenas copiei o conteúdo de um arquivo de imagem para outro arquivo neste caso como era de se esperar o tamanho se manteve o mesmo.



2.6 Exibir Histograma

A imagem é convertida para tons de cinza usando a função do trabalho anterior só que agora o valor dos tons de cinza são postos em um vetor que é passado ao novo formulário que irá exibir o histograma.


Código Fonte:


pictureBox1.Image = Image.FromFile(openFileDialog1.FileName);
imagem = new Bitmap(Image.FromFile(openFileDialog1.FileName));
nome = openFileDialog1.FileName;
int h = imagem.Width;
int v = imagem.Height;
nova_imagem = GrayScaleFilter(imagem);
pictureBox2.Image = nova_imagem;
Form2 newForm2 = new Form2();
newForm2.Vcin = Vcin;
newForm2.Show();

Resultado:

Veja que o histograma também é dimensionável:



2.7 Alterar Brilho

Código Fonte:


nova_imagem = new Bitmap(imagem.Width, imagem.Height);
int i, u;
for (i = 0; i < v; i++)
{
for (u = 0; u < h; u++)
{
faixaR = imagem.GetPixel(u, i).R + briR;
if (faixaR > 255)
faixaR = 255;
else if (faixaR < 0)
faixaR = 0;
faixaG = imagem.GetPixel(u, i).G + briG;
if (faixaG > 255)
faixaG = 255;
else if (faixaG < 0)
faixaG = 0;
faixaB = imagem.GetPixel(u, i).B + briB;
if (faixaB > 255)
faixaB = 255;
else if (faixaB < 0)
faixaB = 0;

int trasn = imagem.GetPixel(u, i).A;

nova_imagem.SetPixel(u, i, Color.FromArgb(trasn, faixaR, faixaG, faixaB)); }}

Resultado: (Alterando o brilho do canal Vermelho)



2.8 Contraste

Código Fonte:


for (i = 0; i < v; i++)
{
for (u = 0; u < h; u++)
{
faixaR = Convert.ToInt32(imagem.GetPixel(u, i).R * conR);
if (faixaR > 255)
faixaR = 255;
else if (faixaR < 0)
faixaR = 0;

faixaG = Convert.ToInt32(imagem.GetPixel(u, i).G * conG);
if (faixaG > 255)
faixaG = 255;
else if (faixaG < 0)
faixaG = 0;

faixaB = Convert.ToInt32(imagem.GetPixel(u, i).B * conB);
if (faixaB > 255)
faixaB = 255;
else if (faixaB < 0)
faixaB = 0;

int trasn = imagem.GetPixel(u, i).A;

nova_imagem.SetPixel(u, i, Color.FromArgb(trasn, faixaR, faixaG, faixaB));}}

Resultado:



2.9 Negativo

Código Fonte:


for (i = 0; i < v; i++)
{
for (u = 0; u < h; u++)
{
faixaR = 255 - imagem.GetPixel(u, i).R;

faixaG = 255 - imagem.GetPixel(u, i).G;

faixaB = 255 - imagem.GetPixel(u, i).B;

int trasn = imagem.GetPixel(u, i).A;

nova_imagem.SetPixel(u, i, Color.FromArgb(trasn, faixaR, faixaG, faixaB));

}
}

Resultado:



2.10 Equalizar Histograma

É realizado a contagem dos tons de red, blue e green para a imagem, encima deles é aplicado a fórmula fornecida em sala de aula, para gerar o novo histograma de cores red, blue e green. Nós gráficos o que é mostrado é o histograma de escalas de cinza apenas


Código Fonte:


try {
int h=0, v=0, i =0;

if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
pictureBox1.Image = Image.FromFile(openFileDialog1.FileName);
imagem = new Bitmap(Image.FromFile(openFileDialog1.FileName));
nome = openFileDialog1.FileName;
h = imagem.Width;
v = imagem.Height;
nova_imagem = GrayScaleFilter(imagem);

Form2 newForm2 = new Form2();
newForm2.Vcin = Vcin;
newForm2.Show();
}



double a = 255.0 / (Convert.ToDouble(h) * Convert.ToDouble(v));
int [] Vcin_cumR = new int[256];
int [] Vcin_cumG = new int[256];
int [] Vcin_cumB = new int[256];
int[] VcinR= new int[256];
int[] VcinG = new int[256];
int[] VcinB = new int[256];
int x=0,y=0;
for (y = 0; y < imagem.Height; y++)
for (x = 0; x < imagem.Width; x++)
{
Color c = imagem.GetPixel(x, y);
VcinR[c.R]++;
VcinG[c.G]++;
VcinB[c.B]++;
}

Vcin_cumR[0] = Convert.ToInt32(a * Convert.ToDouble(VcinR[0]));
for (i = 1; i <= 255; i++)
{
Vcin_cumR[i] = Vcin_cumR[i - 1] + Convert.ToInt32(a * Convert.ToDouble(VcinR[i]));
if (Vcin_cumR[i] > 255)
Vcin_cumR[i] = 255;
else if (Vcin_cumR[i] < 0)
Vcin_cumR[i] = 0;
}

Vcin_cumG[0]= Convert.ToInt32(a * Convert.ToDouble(VcinG[0]));
for (i = 1; i <= 255; i++)
{
Vcin_cumG[i] = Vcin_cumG[i - 1] + Convert.ToInt32(a * Convert.ToDouble(VcinG[i]));
if (Vcin_cumG[i] > 255)
Vcin_cumG[i] = 255;
else if (Vcin_cumG[i] < 0)
Vcin_cumG[i] = 0;
}


Vcin_cumB[0] = Convert.ToInt32(a * Convert.ToDouble(VcinB[0]));
for (i = 1; i <= 255; i++)
{
Vcin_cumB[i] = Vcin_cumB[i - 1] + Convert.ToInt32(a * Convert.ToDouble(VcinB[i]));
if (Vcin_cumB[i] > 255)
Vcin_cumB[i] = 255;
else if (Vcin_cumB[i] < 0)
Vcin_cumB[i] = 0;
}


nova_imagem1 = new Bitmap(h, v);

for (x = 0; x < h; x++){
for (y = 0; y < v; y++) {
int trasn = imagem.GetPixel(x, y).A;
int auxR = imagem.GetPixel(x, y).R;
int auxG = imagem.GetPixel(x, y).G;
int auxB = imagem.GetPixel(x, y).B;
int r = Vcin_cumR[auxR];
int g = Vcin_cumG[auxG];
int b = Vcin_cumB[auxB];
nova_imagem1.SetPixel(x, y, Color.FromArgb(trasn, r, g, b));}}

pictureBox2.Image = nova_imagem1;

GrayScaleFilter2(nova_imagem1);
Form3 newForm3 = new Form3();
newForm3.Vcin1 = Vcin2;
newForm3.Show();

}
catch { }

Resultado:



2.11 Zoom Out

O usuário deve passar como entrada o tamanho da nova imagem a ser gerada, o programa calcula os fatores, é interessante que a nova imagem tenha suas dimensões divisores das dimensões originais, isto gera fatores inteiros e a redução é perfeita, do contrário a imagem poderá sofre um pequeno corte, para se evitar esse corte poderia se ter usado uma abordagem matemática considerando fatores racionais, mas por simplicidade de programação foi usado fatores inteiros.


Código Fonte:


if (hori <= h)
{
if (verti <= v)
{
nova_imagem = new Bitmap(hori, verti);
int fator_x = h / hori;
int fator_y = v / verti;
while (i < verti)
{
while (u < hori)
{
while (i2 < fator_y)
{
if (acumulado_i + i2 <= v)
{
mediaR = mediaR + imagem.GetPixel(acumulado_u, acumulado_i + i2).R;
mediaG = mediaG + imagem.GetPixel(acumulado_u, acumulado_i + i2).G;
mediaB = mediaB + imagem.GetPixel(acumulado_u, acumulado_i + i2).B;
mediaA = mediaA + imagem.GetPixel(acumulado_u, acumulado_i + i2).A;
a++;
}
i2 = i2 + 1;
}
while (u2 < fator_x)
{
if (acumulado_u + u2 <= h)
{
mediaR = mediaR + imagem.GetPixel(acumulado_u + u2, acumulado_i).R;
mediaG = mediaG + imagem.GetPixel(acumulado_u + u2, acumulado_i).G;
mediaB = mediaB + imagem.GetPixel(acumulado_u + u2, acumulado_i).B;
mediaA = mediaA + imagem.GetPixel(acumulado_u + u2, acumulado_i).A;
a++;
}
u2 = u2 + 1;
}
mediaA = mediaA / a;
mediaR = mediaR / a;
mediaG = mediaG / a;
mediaB = mediaB / a;
nova_imagem.SetPixel(u, i,Color.FromArgb(Convert.ToInt32(mediaA), Convert.ToInt32(mediaR),
Convert.ToInt32(mediaG), Convert.ToInt32(mediaB)));

mediaA = 0;
mediaR = 0;
mediaG = 0;
mediaB = 0;
i2 = 0;
u2 = 0;
a = 0;
u = u + 1;
acumulado_u = acumulado_u + fator_x;
}
u = 0;
acumulado_u = 0;
i = i + 1;
acumulado_i = acumulado_i + fator_y;
}
}
else
{
MessageBox.Show("Tamanho vertical maior que imagem
original");
}
}
else
{
MessageBox.Show("Tamanho horizontal maior que imagem
original");
}
}

Resultado:

Você deve salvar a imagem e ver seu tamanho reduzido no diretório, pois o software redimensiona ela para caber no "picture box", mas é possível ver como ela perde qualidade em função da diminuição da resolução.



2.12 Zoom In 2x2

Código Fonte:


int i = 0;
int u = 0;
int i2 = 0;
int u2 = 0;
int mediaR = 0;
int mediaG = 0;
int mediaB = 0;
int mediaA = 0;
pictureBox1.Image = Image.FromFile(openFileDialog1.FileName);
imagem = new Bitmap(Image.FromFile(openFileDialog1.FileName));
nome = openFileDialog1.FileName;
int h = imagem.Width;
int v = imagem.Height;
nova_imagem = new Bitmap(2 * h, 2 * v);
while (i < v)
{
while (u < h)
{
nova_imagem.SetPixel(u2, i2, imagem.GetPixel(u, i));
u = u + 1;
u2 = u2 + 2;
}
u = 0;
u2 = 0;
i = i + 1;
i2 = i2 + 2;
}
u = 0;
i = 0;
while (i < 2 * v)
{
while (u < 2 * h)
{
if (u % 2 == 1)
{
if (u + 1 >= 2 * h)
{
mediaR = (nova_imagem.GetPixel(u - 1, i).R);
mediaG = (nova_imagem.GetPixel(u - 1, i).G);
mediaB = (nova_imagem.GetPixel(u - 1, i).B);
mediaA = (nova_imagem.GetPixel(u - 1, i).A);
}
else
{
mediaR = (nova_imagem.GetPixel(u - 1, i).R +nova_imagem.GetPixel(u + 1, i).R) / 2;
mediaG = (nova_imagem.GetPixel(u - 1, i).G +nova_imagem.GetPixel(u + 1, i).G) / 2;
mediaB = (nova_imagem.GetPixel(u - 1, i).B +nova_imagem.GetPixel(u + 1, i).B) / 2;
mediaA = (nova_imagem.GetPixel(u - 1, i).A +nova_imagem.GetPixel(u + 1, i).A) / 2;
}
nova_imagem.SetPixel(u, i, Color.FromArgb(mediaA,mediaR, mediaG, mediaB));
}
u = u + 1;
}
u = 0;
i = i + 2;
}
u = 0;
i = 0;
while (u < 2 * h)
{
while (i < 2 * v)
{
if (i % 2 == 1)
{
if (i + 1 >= 2 * v)
{
mediaR = (nova_imagem.GetPixel(u, i - 1).R);
mediaG = (nova_imagem.GetPixel(u, i - 1).G);
mediaB = (nova_imagem.GetPixel(u, i - 1).B);
mediaA = (nova_imagem.GetPixel(u, i - 1).A);
}
else
{
mediaR = (nova_imagem.GetPixel(u, i - 1).R + nova_imagem.GetPixel(u, i + 1).R) / 2;
mediaG = (nova_imagem.GetPixel(u, i - 1).G + nova_imagem.GetPixel(u, i + 1).G) / 2;
mediaB = (nova_imagem.GetPixel(u, i - 1).B + nova_imagem.GetPixel(u, i + 1).B) / 2;
mediaA = (nova_imagem.GetPixel(u, i - 1).A + nova_imagem.GetPixel(u, i + 1).A) / 2;
}
nova_imagem.SetPixel(u, i, Color.FromArgb(mediaA,mediaR, mediaG, mediaB));
}
i = i + 1;
}
i = 0;
u = u + 1;
}
pictureBox2.Image = nova_imagem;
MessageBox.Show("Salve a imagem para ver o seu tamanho no diretório!");
}
}
catch { }

Resultado:



2.13 Rotação 90 graus

É feita a rotação onde os pixels são deslocados para posição oposta ao qual estão em sua linha ou coluna. Para aplicar mais de uma rotação salve a imagem e execute de novo.


Código Fonte:



//Anti-horário

while (i < v)
{
while (u < h)
{
nova_imagem.SetPixel(i, h-u-1, imagem.GetPixel(u,i));
u = u + 1;
}
u = 0;
i = i + 1;
}

//Horário

while (u < h)
{
while (i < v)
{
nova_imagem.SetPixel(v-i-1, u, imagem.GetPixel(u,i));
i = i + 1;
}
i = 0;
u = u + 1;
}

Resultado:



2.14 Filtros

Os filtros são aplicados segundo o código fonte abaixo, a única mudança são os valores da convolação e o somar 127 para os filtros de relevo. As linhas e colunas das bordas são desprezadas na aplicação do filtro e seu valor permanece o mesmo.


Código Fonte:


while (i < v)
{
while (u < h)
{
if (u == 0 || i == 0 || u == h-1 || i == v-1)
{
nova_imagem.SetPixel(u, i, imagem.GetPixel(u,i));
}
else
{
colorR = imagem.GetPixel(u - 1, i - 1).R * 0.0625+
imagem.GetPixel(u, i - 1).R * 0.125 +
imagem.GetPixel(u + 1, i - 1).R * 0.0625+
imagem.GetPixel(u - 1, i).R * 0.125 +
imagem.GetPixel(u, i).R * 0.25 +
imagem.GetPixel(u + 1, i).R * 0.125 +
imagem.GetPixel(u - 1, i + 1).R * 0.0625+
imagem.GetPixel(u, i + 1).R * 0.125 +
imagem.GetPixel(u + 1, i + 1).R *0.0625;

colorG = imagem.GetPixel(u - 1, i - 1).G *0.0625 +
imagem.GetPixel(u, i - 1).G * 0.125 +
imagem.GetPixel(u + 1, i - 1).G * 0.0625+
imagem.GetPixel(u - 1, i).G * 0.125 +
imagem.GetPixel(u, i).G * 0.25 +
imagem.GetPixel(u + 1, i).G * 0.125 +
imagem.GetPixel(u - 1, i + 1).G * 0.0625+
imagem.GetPixel(u, i + 1).G * 0.125 +
imagem.GetPixel(u + 1, i + 1).G *0.0625;

colorB = imagem.GetPixel(u - 1, i - 1).B *0.0625 +
imagem.GetPixel(u, i - 1).B * 0.125 +
imagem.GetPixel(u + 1, i - 1).B * 0.0625+
imagem.GetPixel(u - 1, i).B * 0.125 +
imagem.GetPixel(u, i).B * 0.25 +
imagem.GetPixel(u + 1, i).B * 0.125 +
imagem.GetPixel(u - 1, i + 1).B * 0.0625+
imagem.GetPixel(u, i + 1).B * 0.125 +
imagem.GetPixel(u + 1, i + 1).B * 0.0625;

if (colorB > 255)
colorB = 255;
else if(colorB < 0)
colorB = 0;
if (colorR > 255)
colorR = 255;
else if (colorR < 0)
colorR = 0;
if (colorG > 255)
colorG = 255;
else if (colorG < 0)
colorG = 0;
nova_imagem.SetPixel(u, i,Color.FromArgb(imagem.GetPixel(u, i).A, Convert.ToInt32(colorR),
Convert.ToInt32(colorG), Convert.ToInt32(colorB)));
}
u = u + 1;
}
u = 0;
i = i + 1;
}

Resultados:




3.Conclusão

Pude trabalhar vários conceitos, fazer um programa divertido, usar aquilo que eu julgo o melhor para trabalhar com software. Enfim usei muitos conhecimentos que aprendi durante a faculdade, foi realmente muito bom este trabalho e prazeroso.