1. Objetivo

Esse projeto deverá nos permitir nos aprofundar nos conceitos abordados em projetos anteriores, assim como aplicá-los em um tipo de filtro que simula um tipo muito caro de lente: a lente Tilt Shift.

2. Tilt Shift

O pricípio do Tilt Shift baseia-se em dar uma pequena inclinação à lente da câmera; em vez da lente ser paralela ao plano de foco da câmera, há uma pequena inclinação entre os dois eixos. Isso faz com que a o foco da câmera fique somente em uma das regiões da imagem, deixando as outras partes com distorção ou borramento.

exemplo
Figura 1. Diferença entre as Lentes Normal e TiltShift

Esse feito dá a aparência de miniatura às imagens, conforme será mostrado na saída da imagem, em seguida. Para simularmos digitalmente o efeito da imagem, somaremos a imagem original com uma cópia sua borrada, poderadas pela seguinte função

\$\alpha(x) = \frac{1}{2}*(tanh(\frac{x-L1}{d}) - tanh(\frac{x-L2}{d}))\$

exemplo2
Figura 2. Função de Ponderamento

Isso dará o efeito de degradê à saída da função. Ajustaremos os parâmetros L1 e L2 (pontos onde a função retorna aproximadamente 0.5) e o valor de d, que é o decaimento (inclinação do decaimento da função).

3. O Código

O código é mostrado abaixo, e é explicado logo em seguida.

tiltshift.cpp
#include <iostream>
#include <opencv2/opencv.hpp>
#include <cmath>

using namespace cv;
using namespace std;

double d;
int L1_slider = 0;
int L1_slider_max;

int L2_slider = 0;
int L2_slider_max;

int d_slider = 0;
int d_slider_max = 100;

int contrast_slider = 0;
int contrast_slider_max = 100;
int previous_contrast = 0;

Mat im,aux,result;

char TrackbarName[50];

double alpha(double x,double l1,double l2,double d1){
	float retorno;
	float k11 = (x-l1)/d1;
	float k22 = (x-l2)/d1;
	retorno = 0.5*(tanh(k11) - tanh(k22));
	return retorno;
}

void juntar(Mat& src1 , Mat& src2){
	for(int i=2;i<src1.rows;i++){
		double alfa = alpha(i,L1_slider,L2_slider,d);
		addWeighted(src1.row(i),alfa, src2.row(i),1-alfa,0.0,result.row(i));
	}
}

void on_trackbar_d(int, void*){
	d = (double) d_slider;
	juntar(im,aux);
	result.convertTo(result, CV_8UC3);
	imshow("TiltShift",result);
}

void on_trackbar_L1(int, void*){
	juntar(im,aux);
	result.convertTo(result, CV_8UC3);
	imshow("TiltShift",result);
}

void on_trackbar_L2(int, void*){
	juntar(im,aux);
	result.convertTo(result, CV_8UC3);
	imshow("TiltShift",result);
}

float media[] = {1,1,1,
			     1,1,1,
				 1,1,1};
Mat mask = Mat(3,3,CV_32F,media),mask1;

int main(int argvc, char** argv){
	im = imread(argv[1],CV_LOAD_IMAGE_COLOR);
  	namedWindow("TiltShift", WINDOW_NORMAL);

  	cout<<"Melhorar cores?";
	bool escolha;
	cin>>escolha;
	if(escolha){
		cvtColor(im,im,CV_BGR2HSV);
		vector<Mat> planes;
		split(im,planes);
		//equalizeHist(planes[2],planes[2]);
		planes[1].convertTo(planes[1], CV_8UC1,1,30);
		merge(planes,im);
		cvtColor(im,im,CV_HSV2BGR);
	}
  	aux = im.clone();
  	cout<<"width: "<<im.cols<<endl;
  	cout<<"height: "<<im.rows<<endl;

  	scaleAdd(mask, 1/9.0, Mat::zeros(3,3,CV_32F), mask1);
	mask = mask1;

  	for(int i=0;i<25;i++){
  		filter2D(aux, aux, im.depth(), mask, Point(1,1), 0);
  	}
  	L1_slider_max = im.rows;
  	L2_slider_max = im.rows;

  	im.convertTo(im,CV_32FC3);
  	aux.convertTo(aux,CV_32FC3);
  	result = im.clone();

  	sprintf( TrackbarName, "decaimento x %d", d_slider_max );
  	createTrackbar( TrackbarName, "TiltShift", &d_slider, d_slider_max, on_trackbar_d );
  	on_trackbar_d(d_slider, 0 );

  	sprintf( TrackbarName, "Linha Superior x %d", L1_slider_max );
  	createTrackbar( TrackbarName, "TiltShift", &L1_slider, L1_slider_max, on_trackbar_L1 );
  	on_trackbar_L1(L1_slider, 0 );

  	sprintf( TrackbarName, "Linha Inferior x %d", L2_slider_max );
  	createTrackbar( TrackbarName, "TiltShift", &L2_slider, L2_slider_max, on_trackbar_L2 );
  	on_trackbar_L2(L2_slider, 0 );

  	waitKey(0);
  	return 0;
}

3.1. Criando Trackbars

A criação de trackbars é bem explicada em uma página de referência do OpenCV. Criamos 3 Trackbars:

  • Ajuste do limite superior (L1)

  • Ajuste do limite inferior (L2)

  • Ajuste do Decaimento (d)

a criação é feita a partir das seguintes linhas:

sprintf( TrackbarName, "decaimento x %d", d_slider_max );
createTrackbar( TrackbarName, "TiltShift", &d_slider, d_slider_max, on_trackbar_d );
on_trackbar_d(d_slider, 0 );

sprintf( TrackbarName, "Linha Superior x %d", L1_slider_max );
createTrackbar( TrackbarName, "TiltShift", &L1_slider, L1_slider_max, on_trackbar_L1 );
on_trackbar_L1(L1_slider, 0 );

sprintf( TrackbarName, "Linha Inferior x %d", L2_slider_max );
createTrackbar( TrackbarName, "TiltShift", &L2_slider, L2_slider_max, on_trackbar_L2 );
on_trackbar_L2(L2_slider, 0 );

Juntamente com as respectivas funções que são chamadas quando as barras são deslocadas:

void on_trackbar_d(int, void*){
	d = (double) d_slider;
	juntar(im,aux);
	imshow("TiltShift",result);
	result.convertTo(result, CV_8UC3);
}

void on_trackbar_L1(int, void*){
	juntar(im,aux);
	imshow("TiltShift",result);
	result.convertTo(result, CV_8UC3);
}

void on_trackbar_L2(int, void*){
	juntar(im,aux);
	imshow("TiltShift",result);
	result.convertTo(result, CV_8UC3);
}

Os valores máximos são iguais ao número de linhas da imagen, começando a partir do zero (padrão do OpenCV). Desse modo, podemos nos deslocar livremente pela imagem.

L1_slider_max = im.rows;
L2_slider_max = im.rows;

3.2. Borrando a Imagem

Para fazer o borramento da imagem, criamos uma imagem auxiliar (cv::Mat aux;) além da imagem que já havíamos criado (cv::Mat im), cujo valor é o mesmo da imagem lida. Essa imagem auxiliar é, primeiramente, uma cópia da imagem original, e a ela aplicamos o filtro da média (cerca de 20-30 vezes) para que ela fique borrada.

float media[] = {1,1,1,
			     1,1,1,
				 1,1,1};
Mat mask = Mat(3,3,CV_32F,media),mask1;

.
.
.

aux = im.clone();

scaleAdd(mask, 1/9.0, Mat::zeros(3,3,CV_32F), mask1);
mask = mask1;

for(int i=0;i<30;i++){
	filter2D(aux, aux, im.depth(), mask, Point(1,1), 0);
}

3.3. Aplicando o Ponderamento às Imagens

Aplicamos o poderamento utilizando a função void juntar(Mat &,Mat &), mostrada abaixo.

void juntar(Mat& src1 , Mat& src2){
	for(int i=2;i<src1.rows;i++){
		double alfa = alpha(i,L1_slider,L2_slider,d);
		addWeighted(src1.row(i),alfa, src2.row(i),1-alfa,0.0,result.row(i));
	}
}

Essa função realiza o ponderamento das imagens, linha a linha (utilizando a função cv::Mat.row()), com os valores retornados pela função double alpha(double x,double L1, double L2, double d), como mostrado a seguir:

float alpha(double x,double l1,double l2,double d1){
	float retorno;
	float k11 = (x-l1)/d1;
	float k22 = (x-l2)/d1;
	retorno = 0.5*(tanh(k11) - tanh(k22));
	return retorno;
}

Os valores utilizados por essa função são processados pela função cv::addWeighted(), da seguinte forma:

\$\text{result.row(x)} = \alpha*\text{src1.row(x)} + (1-\alpha)*\text{src2.row(x)}\$

Para utilizarmos a função tanh(), ou seja, a tangente hiperbólica de um determinado valor, utilizamos a biblioteca <cmath>

3.4. Melhorando as Cores

Podemos aumentar a saturação das cores e seu contraste para dar um efeito de miniaturas. Brinquedos, em geral, tem cores muito saturadas. Isso pode ser feito utilizando a função "cvtColor()", modificando a componente "Saturation" da matriz (formato HSV), como mostrado a seguir:

cout<<"Melhorar cores?";
bool escolha;
cin>>escolha;
if(escolha){
	cvtColor(im,im,CV_BGR2HSV);
	vector<Mat> planes;
	split(im,planes);
	//equalizeHist(planes[2],planes[2]);
	planes[1].convertTo(planes[1], CV_8UC1,1,30);
	merge(planes,im);
	cvtColor(im,im,CV_HSV2BGR);
}

Essa função só é executada no início do programa e pode ser melhorada para que o ganho de saturação seja também definido pelo usuário, com uma trackbar. O algoritmo foi sugeido por um usuário do StackOverflow.

3.5. A Interface do Usuário

O usuário tem acesso às 3 trackbars, como definidas anteriormente, assim como é capaz de redimensionar a janela, devido à inclusão do parâmetro WINDOW_NORMAL na função namedWindow() (criação da janela).

namedWindow("TiltShift",WINDOW_NORMAL);

O redimensionamento da janela é útil quando trabalhamos com imagens muito grandes. A interface é mostrada a seguir:

userHUB
Figura 3. Interface do Usuário

4. A Saída do Programa

Escolhemos uma imagem qualquer pesquisando-a no Google. A seguir mostramos a saída do nosso programa. O mais interessante dessa aplicação é o efeito de miniatura que a imagem ganha.

Aeroporto
Figura 4. Imagem Original
Aeroporto
Figura 5. Efeito Til-Shift Aplicado
Times Square
Figura 6. Imagem Original
Times Square
Figura 7. Efeito Tilt-Shift
Carrefour_view
Figura 8. Imagem Original
Carrefour_view
Figura 9. Efeito Tilt-Shift

5. Referências

Tomamos como base, para a execução do nosso programa, a listagem feita pelo professor Agostinho, disponível em seu GitHub: