1. Objetivo

Este exercício nos ajudará a aprofundarmos nos conceitos estudados em exercícios anteriores, como o Tilt-Shift, assim como iniciar nossos estudos com a manipulação de videos utilizando o OpenCV.

2. Stop Motion

A técnica do Stop Motion consiste em manipular um objeto para que ele pareça se mover sozinho 1. Muitos filmes feitos com massa de modelar empregaram essa técnica, como a A Fuga das Galinhas, Wallace e Gromit: A Batalha dos Vegetais e A noiva Cadáver. Essa técnica ser recriada digitalmente: se quisermos que um vídeo nosso aparente estar em StopMotion, basta que excluamos alguns quadros desse vídeo. Os resultados serão mostrados no fim desse tutorial.

A Fuga das Galinhas
Figura 1. A Fuga das Galinhas

3. O Código

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

#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){
  	VideoCapture video(argv[1]);      //Abrindo Arquivo de entrada
  	VideoWriter output_cap("output.mp4", video.get(CV_CAP_PROP_FOURCC), video.get(CV_CAP_PROP_FPS),
               			   cv::Size(video.get(CV_CAP_PROP_FRAME_WIDTH), video.get(CV_CAP_PROP_FRAME_HEIGHT))); //Arquivo de saída

  	if(!video.isOpened()) return -1;            //Teste para validar entradas e saídas
  	if(!output_cap.isOpened()) return -1;

  	int ratio, counter(0);
  	cout<<"Qual a razão que você quer no StopMotion?\n"; //Calculando a razão do StopMotion
  	cin>>ratio;

  	bool tempo;
  	cout<<"Manter tempo original? 1 - sim; 0 - Não\n";
  	cin>>tempo;

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

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

  	result = im.clone();
	L1_slider_max = im.rows;
  	L2_slider_max = im.rows;
  	cout<<"width: "<<im.cols<<endl;
  	cout<<"height: "<<im.rows<<endl;


	namedWindow("TiltShift",WINDOW_NORMAL);
	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);
  	destroyWindow("TiltShift");
  	waitKey(1);

  	while(1){
  		if(!video.read(im)) break;
  		counter++;
	  	if(counter == ratio){
	  		counter = 0;
	  		aux = im.clone();
		  	for(int i=0;i<30;i++){
	  			filter2D(aux, aux, im.depth(), mask, Point(1,1), 0);
	  		}
	  		juntar(im,aux);
	  		result.convertTo(result, CV_8UC3);
	  		output_cap.write(result);
	  	}
	  	if(tempo) output_cap.write(result);
  	}

  	waitKey(0);
  	return 0;
}

3.1. Código Base

Tomamos como base o código do Tilt Shift feito no exercício anterior. Aqui apenas serão explicadas as funções adicionais ao código.

3.2. Manipulando Vídeos

Para manipularmos vídeos, isto é, abrir-los, iremos utilizar a classe cv::VideoCapture. Quando passado ao construtor dessa classe uma string, ela irá abrir o vídeo no endereço contido nessa string. No nosso caso, será passado via terminal, como a seguir:

$ ./tiltshiftvideo entrada.mp4

Para salvarmos um vídeo teremos uma tarefa um pouco mais trabalhosa que apenas utilizar a função imwrite(). Criamos um objeto da classe cv::VideoWriter(string nome_do_arquivo, protocolo_de_compressão, fps, tamanho). A documentação dessa classe está disponível aqui. O protocolo de compressão pode ser XVid ou MPEG, por exemplo. A taxa de captura ou quantidade de frames por segundo(fps) também deve ser informada, e desse modo temos:

VideoCapture video(argv[1]);      //Abrindo Arquivo de entrada
VideoWriter output_cap("output.mp4", video.get(CV_CAP_PROP_FOURCC), video.get(CV_CAP_PROP_FPS),
               			cv::Size(video.get(CV_CAP_PROP_FRAME_WIDTH), video.get(CV_CAP_PROP_FRAME_HEIGHT)));

3.3. Taxa do Stop Motion

Para realizarmos o efeito do Stop Motion precisamos de uma taxa com a qual os quadros serão descartados. Por exemplo, uma taxa de 3, para o programa, significa que 2 quadros serão descartados e 1 será incluído no resultado final. Também pedimos ao usuário para informar se ele quer que o tempo do vídeo se mantenha constante. Em caso afirmativo, o vídeo parecerá em câmera lenta, e para isso basta repetirmos os quadros enquanto não chega a hora de atualizá-lo (com a taxa do stopmotion). Essas informações são pedidas utilizando as seguintes linhas do código:

int ratio, counter(0);
cout<<"Qual a razão que você quer no StopMotion?\n"; //Calculando a razão do StopMotion
cin>>ratio;

bool tempo;
cout<<"Manter tempo original? 1 - sim; 0 - Não\n";
cin>>tempo;

3.4. Criando a Janela Para o Ajuste

No início do programa, lemos o primeiro quadro do vídeo e pedimos para o usuário ajustar os limites do Tilt-Shift. Utilizamos métodos semelhantes ao do exercício anterior. Para fechar a janela utilizamos a função cv::destroyWindow().

tela ajuste

3.5. Ajustando o Vídeo Quadro a Quadro

while(1){
	if(!video.read(im)) break;
	counter++;
 	if(counter == ratio){
 		counter = 0;
  		aux = im.clone();
	  	for(int i=0;i<30;i++){
  			filter2D(aux, aux, im.depth(), mask, Point(1,1), 0);
  		}
  		juntar(im,aux);
	  	result.convertTo(result, CV_8UC3);
  		output_cap.write(result);
  	}
  	if(tempo) output_cap.write(result);
}

Começamos com um loop infinito while(1). O primeiro teste é if(!video.read(im)) break;. A função read() passa para a matriz im o valor do primeiro quadro de video. Caso isso não aconteça, o vídeo acabou, a função retorna -1 e o teste encerra o loop.

Incrementamos então o valor de um contador. Quando esse contador atingir o valor da taxa informada anteriormente (if(counter==ratio)) então executamos os seguintes processos:

  • Zerar o contador (recomeçamos a contagem)

  • Copiar o quadro para uma matriz auxiliar (aux = im.clone())

  • Aplicar um filtro de borramento na matriz auxiliar(x20) (filter2D)

  • Fazer a soma ponderada das duas imagens

  • Escrever o resultado no vídeo de saída (out.write())

Caso o tempo tenha que se manter constate, escrevemos a mesma matriz por diversas vezes, até que o contador atinja o valor da taxa e a matriz de saída deva ser atualizada if(tempo) output_cap.write(result);.

Precisamos converter de volta a matriz result para unsigned char para correta exibição. Convertemos as matrizes im e aux para float (CV_32FC3) e devemos retornar para o tipo inicial (CV_8UC3).

4. Código Auxiliar

Para cortarmos alguns vídeos retirados do youtube, utizamos o seguinte algoritmo:

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

int main(int argc,char* argv[]){
  // Load input video
  int counter = 0;
  cv::VideoCapture input_cap(argv[1]);
  if (!input_cap.isOpened())
  {
          std::cout << "!!! Input video could not be opened" << std::endl;
          return -1;
  }

  // Setup output video
  cv::VideoWriter output_cap(argv[2],
                 input_cap.get(CV_CAP_PROP_FOURCC),
                 input_cap.get(CV_CAP_PROP_FPS),
                 cv::Size(input_cap.get(CV_CAP_PROP_FRAME_WIDTH),
                 input_cap.get(CV_CAP_PROP_FRAME_HEIGHT)));

  if (!output_cap.isOpened())
  {
          std::cout << "!!! Output video could not be opened" << std::endl;
          return -1;
  }


  // Loop to read from input and write to output
  cv::Mat frame;

  while (true)
  {
      if (!input_cap.read(frame))
          break;
      if ((counter >= 10*input_cap.get(CV_CAP_PROP_FPS) && counter <= 20*input_cap.get(CV_CAP_PROP_FPS)) ||
          (counter >= 35*input_cap.get(CV_CAP_PROP_FPS) && counter <= 40*input_cap.get(CV_CAP_PROP_FPS))){
        output_cap.write(frame);
        //counter =0;
      }
      counter++;
  }

  input_cap.release();
  output_cap.release();
}

Nele, além dos conceitos abordados anteriormente, pegamos a taxa de captura (fps) do vídeo utilizando a seguinte função

input_cap.get(CV_CAP_PROP_FPS);

e a utilizamos para definir um tempo de corte. Se, por exemplo, quisermos cortar um vídeo aos 7 segundos, em um vídeo com 30 fps, aplicamos na seguinte fómula:

\$\text{Quadro} = \text{Frame_Rate}*\text{Quantidade_de_Segundos} = 30*7 = 210\$

Isso quer dizer que qualquer quadro antes do 210 não será incluído no vídeo final. Isso é feito com a seguinte condição:

while (true){
      if (!input_cap.read(frame)) break;
      if ((counter >= 10*input_cap.get(CV_CAP_PROP_FPS) && counter <= 20*input_cap.get(CV_CAP_PROP_FPS)) ||
          (counter >= 35*input_cap.get(CV_CAP_PROP_FPS) && counter <= 40*input_cap.get(CV_CAP_PROP_FPS))){
        output_cap.write(frame);
        //counter =0;
      }
      counter++;
}

5. Vídeos Utilizados

Utilizamos os seguintes vídeos, disponíveis no youtube, para o teste de nossos algoritmos.

The City - People walking on the street overhead view
Tokyo Stock Footage

6. Saída do Programa

A seguir são mostrados os vídeos de saída, em tempo normal e acelerado.

Stop Motion + TiltShift do video The City
Stop Motion + TiltShift (acelerado) do video The City
Stop Motion + TiltShift do video de Tokyo
Stop Motion + TiltShift (acelerado) do video de Tokyo

7. Resultados

O mais interessante da saída do nosso programa é que as imagens ficam se parecendo com miniaturas. Isso acontece porque o tiltShift engana o plano de projeção do nosso olho, dando a impressão de que os objetos no foco da imagem são pequenos e que os objetos distorcidos são maiores, como é mostrado na figura a seguir:

print