1. Objetivo

Por meio deste trabalho devemos ser capazes de entender como funciona um dos filtros mais comuns em câmeras digitais atuais e programas de edição de imagens - o negativo - e nos aprofundarmos um pouco mais no assunto da manipulação de imagens.

2. Obtendo o Negativo de Imagens

Primeiramente, para podermos manipular adequadamente uma imagem, devemos saber quantos bits essa imagem possui. O mais comum é que imagens em escala de cinza possuam 8 bits. Isso quer dizer que elas possuem \$2^8 = 256\$ tons de cinza possíveis. Desse modo, temos que o valor máximo que podemos atribuir a um pixel é 255, pois a contagem começa no 0.

O próximo passo é sabermos qual é a cor complementar de uma cor. A cor complementar é aquela que quando somada a uma cor temos o valor máximo, que para o nosso caso é 255. desse modo:

\$\text{cor_complementar} = 255 - {cor_do_pixel}\$

E assim definimos a cor negativa de uma cor como sendo a sua complementar. Veremos, a partir da saída do programa, que essa definição é correta e corresponde ao negativo que estamos acostumados a ver usualmente.

É importante também lembrarmos que, por definição:

  • Preto é definido como sendo 0

  • Branco é definido como sendo 255

  • Os tons de cinza (do mais escuro para o mais claro) estão nesse intervalo de 1 a 254

3. O Código

negative.cpp
#include <iostream>
#include <opencv2/opencv.hpp>

using namespace std;
using namespace cv;

int main(int argc, char* argv[]){
	Mat im = imread(argv[1],CV_LOAD_IMAGE_COLOR);
	if(!im.data) cout<<"Nao abriu a imagem!\n";

	CvPoint cp1, cp2;
	namedWindow("janela",WINDOW_AUTOSIZE);
	cout<<"Tamanho da imagem: "<<im.cols<<'x'<<im.rows<<endl;
	cout<<"Diga dois pontos: \nX1: ";
	cin>>cp1.y; cout<<"Y1: "; cin>>cp1.x;
	cout<<"X2: "; cin>>cp2.y; cout<<"Y2: "; cin>>cp2.x;
	int maiorx,menorx,maiory,menory;
	if(cp1.x>=cp2.x){
		maiorx = cp1.x;
		menorx = cp2.x;
	}
	else{
		maiorx = cp2.x;
		menorx = cp1.x;
	}
	if(cp1.y>=cp2.y){
		maiory = cp1.y;
		menory = cp2.y;
	}
	else{
		maiory = cp2.y;
		menory = cp1.y;
	}
	for(int i=menorx;i<maiorx;i++){
		for(int j=menory;j<maiory;j++){
			im.at<Vec3b>(i,j)[0] = 255 - im.at<Vec3b>(i,j)[0];
			im.at<Vec3b>(i,j)[1] = 255 - im.at<Vec3b>(i,j)[1];
			im.at<Vec3b>(i,j)[2] = 255 - im.at<Vec3b>(i,j)[2];
		}
	}
	imshow("janela",im);
	imwrite("saida.jpg",im);
	waitKey();

}

4. Explicando o Funcionamento do Código

Aqui é importante lembrarmos que grande parte das funções aqui utilizadas foram apresentadas e explicadas no Primeiro Projeto. Iremos detalhar somente aqueles que forem cuja utilização é novidade.

4.1. Pedindo ao Usuário os Pontos

O código tem a função de pedir ao usuário pontos de início e fim, para caso não se deseje negativar a imagem completa mas somente uma parte dela. Como esses pontos serão necessários adiante, fazemos testes para que eles sejam colocados em ordem crescente: \$x2>=x1\$ e \$y2>=y1\$, ou, na linguagem do programa, menorx<maiorx e menory<maiory.. O programa também mostra a dimensão da imagem, para que sirva de referência para o usuário, utilizando os parâmetros cv::Mat.rows e cv::Mat.cols, da classe Mat, para linhas e colunas, respectivamente.

cout<<"Tamanho da imagem: "<<im.cols<<'x'<<im.rows<<endl;
	cout<<"Diga dois pontos: \nX1: ";
	cin>>cp1.y; cout<<"Y1: "; cin>>cp1.x;
	cout<<"X2: "; cin>>cp2.y; cout<<"Y2: "; cin>>cp2.x;
	int maiorx,menorx,maiory,menory;
	if(cp1.x>=cp2.x){
		maiorx = cp1.x;
		menorx = cp2.x;
	}
	else{
		maiorx = cp2.x;
		menorx = cp1.x;
	}
	if(cp1.y>=cp2.y){
		maiory = cp1.y;
		menory = cp2.y;
	}
	else{
		maiory = cp2.y;
		menory = cp1.y;
	}
As entradas dos x’s e y’s estão trocadas na função std::cin>> pois as coordenadas utilizadas nas classes cv::CvPoint e cv::Mat também estão trocadas. Isso corrige o erro.

4.2. Efetuando a Negativação da Imagem

Esse processo é feito utilizando a equação mostrada na seção 2. Como estamos agora trabalhando com uma imagem RGB, devemos executar a operação em cada uma das componentes. Para uma imagem RGB, cada componente também é de 8 bits, e assim a equação apresentada anteriormente não muda. Desse modo:

for(int i=menorx;i<maiorx;i++){
	for(int j=menory;j<maiory;j++){
		im.at<Vec3b>(i,j)[0] = 255 - im.at<Vec3b>(i,j)[0];
		im.at<Vec3b>(i,j)[1] = 255 - im.at<Vec3b>(i,j)[1];
		im.at<Vec3b>(i,j)[2] = 255 - im.at<Vec3b>(i,j)[2];
	}
}

As submatrizes da hipermatriz im são as componentes RGB da matriz, porém não nessa ordem. As matrizes estão na ordem B,R e G, algo feito por definição na elaboração das funções do opencv. Temos também o Vec3b, que é um typedef definido por: typedef Vec<uchar, 3> Vec3b. O Vec3b é utilizado template da função at(), e indica que temos um vetor de unsigned char de 3 posições (por isso podemos acessar cada uma das submatrizes utilizando o operador []).

4.3. Execução e Saída

Para executarmos o programa no terminal, devemos rodar o arquivo Makefile no terminal, e em seguida passarmos uma imagem como parâmetro:

$ make negative
$ ./negative bplathi.jpg
bplathi
Figura 1. Livro de Sinais e Sistemas Lineares

Durante a execução do programa, é essa a interface a que temos acesso:

Tamanho da imagem: 292x292
Diga dois pontos:
X1: 0
Y1: 0
X2: 150
Y2: 150

Aqui passamos como parâmetros os pontos \$(0,0)\$ e \$(150,150)\$, e obtivemos a seguinte saída:

saida
Figura 2. Saída do programa negative.cpp
A imagem foi gravada utilizando a função Mat::imwrite(string nome, Mat imagem);.