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.

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()
.

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:
#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.
6. Saída do Programa
A seguir são mostrados os vídeos de saída, em tempo normal e acelerado.
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:
