OpenCV: Time Lapse

link immagine: http://pixabay.com/p-73353/?no_redirect

link immagine: http://pixabay.com/p-73353/?no_redirect

Con questo post andremo a realizzare un time-lapse in OpenCV. Wikipedia fornisce la seguente definizione di time-lapse:

 “La fotografia time-lapse è una tecnica cinematografica nella quale la frequenza di cattura di ogni fotogramma è molto inferiore a quella di riproduzione. A causa di questa discrepanza, la proiezione con un frame rate standard di 24 fps fa sì che il tempo, nel filmato, sembri scorrere più velocemente del normale.”

In rete esistono diversi programmi per la creazione del time lapse, questo breve tutorial ha lo scopo di mostrarne una sua realizzazione usando una webcam e la libreria grafica di OpenCV.

La creazione del time lapse può essere riassunta in 3 passi:

   [1] Scatto di fotogrammi ad intervalli regolari
   [2] Correzione/modifica dei fotogrammi
   [3] Creazione del video

0

L’immagine mostra l’esecuzione della Main function la quale a seconda della scelta dell’utente si occupa di richiamare la funzione opportuna tramite il costrutto switch-case:

[cpp] #include "opencv2/opencv.hpp"
#include "iostream"

using namespace cv;
int idx = 0;

int molt(int a, int b){ return a * b; }
int divis(int a, int b){ return a / b; }

// al fine di rendere leggibile la struttura del codice
// il corpo delle funzioni è stato sostituito con {…}

// recupera il numero di webcam collegate
int getCamera(){…}

// ricerca delle risoluzioni
template<typename OP>
void scanResolution(std::vector<Point2i> &wh, VideoCapture &cap, Point2i wh_aux, OP op){…}

// scelta della risoluzione
int getResolution(VideoCapture &cap){…}

// salvataggio foto
int generateFrame(){…}

// composizione video
int generateVideo(){…}

// main function
int main(int, char**)
{
int opt;
std::cout << "************* Time Lapse AOS *********************\n\n";
std::cout << "Scegli cosa fare:\n\n";
std::cout << "[0] salva immagini\n[1] crea video dalle immagini\n\n";
std::cin >> opt;

switch (opt){
case 0 :
if (generateFrame() < 0) return -1;
break;
case 1 :
if (generateVideo() < 0) return -2;
break;
default:
std::cout << "Scelta non valida";
_sleep(1500);
return -3;
}

}
[/cpp]

Leggendo il codice si nota che il programma è stato strutturato in due parti: una che scatta le foto alla risoluzione e intervallo di scatto richiesti (scelta 0, chiamata alla funzione generateFrame()), l’altra che mette insieme le immagini scattate in un video il cui fps viene scelto dall’utente (scelta 1 chiamata a generateVideo()).

[1] SCATTO FOTOGRAMMI [1]

1

 

 

 

 

 

 

 

 

Le due immagini mostrano le finestre aperte in caso l’utente scelga l’opzione [0], la quale innesca la chiamata alla funzione generateFrame():

[cpp] // salvataggio foto
int generateFrame(){

Mat frame;
char key, filename[50];
int cam_num, wait_ms, i=0;

// recupero il numero di webcam collegate e
// apertura flusso video della camera selezionata
cam_num = getCamera();
if (cam_num < 0) return -1;
VideoCapture cap(cam_num);

// recupero delle risoluzioni supportate
if (getResolution(cap) < 0) return -1;

// acquisizione di un frame
cap >> frame;

// scelta dell’intervallo di scatto in [ms] std::cout << std::endl << "deltaT in [ms] \n";
std::cin >> wait_ms;

std::cout << std::endl << "Salvataggio (risoluzione" << frame.size() << ") in corso…" << std::endl;

// creazione della finestra di sincronizzazione e uscita
Mat aux = Mat::zeros(Size(500 – 40, 200), CV_8U);
putText(aux, "Premi un tasto per uscire", Point(0, aux.rows / 2), CV_FONT_HERSHEY_COMPLEX, 1, Scalar(255, 255, 255), 1, 8);
imshow("Finestra di uscita", aux);

// loop di salvataggio foto, arrestato nel momento in cui l’utente
// premete un tasto sulla finestra di uscita
for (;;){
cap >> frame;
sprintf(filename, "./%d.bmp", i);

// salvataggio immagine nella cartella contenente il programma
imwrite(filename, frame);

// intervallo di attesa utile per l’arresto dell’acquisizione
key = waitKey(wait_ms);
if (key >= 0) break;
i++;
}

cap.release();
return 0;
}
[/cpp]

Leggendo attentamente quello che viene stampato a schermo si nota che nella lista delle webcam disponibili viene visualizzato solo un [numero] senza alcun dettaglio sul tipo di webcam a cui si riferisce.

La ricerca delle webcam collegate così come la risoluzione di ciascuna di esse avviene attraverso la tecnica del “polling”, ovvero tramite un ciclo for si analizza fino a che indice lo stream video è aperto correttamente. Il primo indice per il quale il VideoCapture di OpenCV fallisce con l’apertura del flusso, corrisponde al numero di webcam collegate al computer. Il motivo di questa scelta risiede nella portabilità del codice e della semplicità della soluzione, visto che l’utilizzo di altre librerie restituiscono informazioni più dettagliate ma con l’uso maggiore di codice. Di seguito, il codice realizzato per la ricerca del numero di webcam (getCamera()) e la stampa delle risoluzioni supportate dalla webcam scelta (getResolution()):

[cpp] // recupera il numero di webcam collegate
int getCamera(){

vector<int> cameras;
int cam_num;

// Poll delle camere disponibili
for (int i = 0; i < 10; i++){
VideoCapture cap(i);
if (cap.isOpened()){
cameras.push_back(i);
cap.release();
}
}

// esci se nessuna camera è disponibile
if (cameras.size() == 0){
std::cout << "\nNessuna camera disponibile" << std::endl;
return -1;
}

// stampa a video delle camere disponibili
std::cout << "\nCamera individuate:" << std::endl;
for (int i = 0; i < cameras.size(); i++)
std::cout << "[" << i << "]: " << cameras[i] << std::endl;

// selezione di una delle camere
std::cout << std::endl << "Scegli una camera [i]" << std::endl;
std::cin >> cam_num;

// controllo input valido
if (cam_num>cameras.size() || cam_num<0){
std::cout << "scelta non valida!" << std::endl;
_sleep(1500);
return -1;
}

return cam_num;
}
[/cpp] [cpp] // scelta della risoluzione
int getResolution(VideoCapture &cap){

//vettore contenete le risoluzioni trovate
std::vector<Point2i> wh;
// vettore di punti(x,y) interi, ciascuno corrispondente
// a larghezza(width) e altezza(height) del frame
Point2i wh_aux;
Mat frame;

// ricerca delle soluzioni disponibili
std::cout << std::endl << "Ricerca risoluzioni disponibili…" << std::endl;

// acquisizione di un frame
cap >> frame;

//salvataggio risoluzione di partenza, in genere 640×480
wh_aux = Point2i(frame.size().width, frame.size().height);

// ha come parametro la funzione molt
scanResolution(wh, cap, wh_aux, molt);

// ripristino risoluzione di partenza e inserimento della risoluzione
// di partenza al fine di creare un vettore di risoluzioni di ordine decrescente
if (wh.back() != wh_aux){
wh.push_back(wh_aux);
idx++;
}

// ha come parametro la funzione divis
scanResolution(wh, cap, wh_aux, divis);

// stampa a video delle risoluzioni trovate e scelta della risoluzione desiderata
std::cout << std::endl << "Risoluzioni trovate:" << std::endl;
for (int i = 0; i < wh.size(); i++)
std::cout << "[" << i << "]: " << wh[i] << std::endl;

// input risoluzione desirata
int ris_num;
std::cout << "\nScegli risoluzione [i]" << std::endl;
std::cin >> ris_num;

// controllo input valido
if (ris_num>wh.size() || ris_num<0){
std::cout << "scelta non valida!" << std::endl;
_sleep(1500);
return -1;
}

// set della risoluzione richiesta
cap.set(CV_CAP_PROP_FRAME_WIDTH, wh[ris_num].x);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, wh[ris_num].y);

return 0;
}
[/cpp]

getResolution() si occupa di ritornare la risoluzione scelta dall’utente. Al suo interno viene chiamata due volte la funzione scanResolution(), la quale accettando come parametro una funzione (molt o divis), e partendo da una risoluzione di default (in genere 640 x 480), durante la prima chiamata, cerca le risoluzioni supportate maggiori rispetto a quella di default, mentre durante la seconda chiamata quelle minori.

Come per il caso di webcam disponibile, anche nella ricerca delle risoluzioni si è adottato un metodo approssimativo al fine di evitare l’uso di altre librerie; come reference si è considerato in un thread di stackoverflow. Il funzionamento di scanResolution() si basa sul metodo VideoCapture::set, il quale permette di settare diversi parametri dello stream video, tra questi anche la risoluzione di acquisizione. Qualora la risoluzione data in input fosse errata, il metodo set imposta l’acquisizione alla risoluzione che più si avvicina a quella data in input. In questo modo, come punto di partenza si considera la risoluzione di default settata da videocaputre per l’apertura dello stream, in genere 640 x 480. Successivamente si incrementa e decrementa la risoluzione di un determinato fattore  (2 in questo caso). Nel momento in cui non è possibile incrementare o decrementare la risoluzione (in quanto il metodo set imposta sempre la stessa risoluzione), si ha l’uscita della funzione scanResolution(). La particolarità di scanResolution è che accetta una funzione come parametro, ovvero OP op (template di typename OP). In questo modo è possibile riutilizzare lo stesso codice, chiamando la funzione con l’operatore opportuno. Di seguito il codice:

[cpp] int molt(int a, int b){ return a * b; }
int divis(int a, int b){ return a / b; }

// ricerca delle risoluzioni
template<typename OP>
void scanResolution(std::vector<Point2i> &wh, VideoCapture &cap, Point2i wh_aux, OP op){
Mat frame;

while (1){

// fattore 2 sia nella moltiplicazione che nella divisione
wh_aux.x = op(wh_aux.x,2);
wh_aux.y = op(wh_aux.y, 2);

cap.set(CV_CAP_PROP_FRAME_WIDTH, wh_aux.x);
cap.set(CV_CAP_PROP_FRAME_HEIGHT, wh_aux.y);

// test sull’effettivo set della risoluzione
cap >> frame;
wh_aux.x = frame.size().width;
wh_aux.y = frame.size().height;

if (idx>0)
if (wh_aux.x == wh[idx – 1].x && wh_aux.y == wh[idx – 1].y)
break;

wh.push_back(wh_aux);
idx++;
}
return;
}
[/cpp]

[2] CORREZIONE E MODIFICA DEI FOTOGRAMMI [2]

La libreria di OpenCV permette di effettuare qualsiasi opera di editing sulle foto, tuttavia questa parte non verrà trattata in questo post. D’altro canto in questo link ci sono diverse idee su come usare i programmi commerciali per effettuare post processing su più foto contemporaneamente.

 [3] CREAZIONE DEL VIDEO [3]

3

Una volta salvate le foto, ed eventualmente editate, si passa alla creazione del video con fps dato in input dall’utente. getVideo() è la funzione che si occupa di tale compito, di seguito il codice:

[cpp] // composizione video
int generateVideo(){

vector<string> fn;
int fps;
Mat frame;

std::cout << "\nScegli video fps\n";
std::cin >> fps;

std::string path = "./*.bmp";

// check cartella vuota
glob(path, fn, false);

if (fn.size() == 0){
std::cout << "\nDirectory vuota";
_sleep(1500);
return -1;
}

frame = imread("./1.bmp");

std::stringstream filename;
filename << "./timelapse" << fn.size() << ".avi";

// set del VideoWriter
cv::VideoWriter video(filename.str(),CV_FOURCC(‘D’, ‘I’, ‘V’, ‘X’),fps,frame.size());
if (!video.isOpened()){
std::cout << "\nImpossibile scrivere su file";
return -1;
}

std::cout << "file .avi composto da " << fn.size() << " immagini @"<< fps << " fps " << "durata prevista:" << fn.size()/fps << "s";
std::cout << "\nsalvataggio video…";

for (int i = 0; i < fn.size();i++){
// empty della string filename
filename.str(std::string());
// scrittura del filename
filename << "./" << i << ".bmp";
// lettura foto
frame = imread(filename.str());
//scrittura video
video.write(frame);
}

video.release();

std::cout << "\nVideo generato con successo!";
_sleep(1500);

return 0;
}
[/cpp]

Anche per la creazione di un video, OpenCV mette a disposizione una classe apposita: VideoWriter.

Prima di chiudere vi lasciamo al Time Lapse di AOS realizzato con il codice appena analizzato. Enjoy!

 

!!!! INSERIRE DOWNLOAD FILE

Francesco Celiberti
Ciao a tutti,

mi chiamo Francesco, sono laureato in Ing. Informatica e dell'Automazione. Sono attualmente coinvolto in un progetto di ricerca Europeo, MOTORIST. www.motorist-ptw.eu
Tags: ,
By Francesco Celiberti | aprile 13th, 2015 | LEAVE A COMMENT