Come si fa a procurarsi un alibi con le webcam (C#/C++/Qt)

di Andrea Zani, in Software,

Dopo che ho inserito, in quell'ammasso di inutilità che è Facabook, una fotografia dove venivo immortalato da una webcam della mia zona mentre ero in bici, una persona, che invece di godersi le ferie e farsi un po' di fatti suoi, mi ha chiesto come avessi fatto.

Innanzitutto non ero d'accordo con nessuno. Ho banalmente scritto un software che ogni n minuti cattura l'immagine di una webcam pubblica e la salva su disco. Prima di partire in bici ho avviato il programmino e al mio ritorno mi sono trovato su disco un centinaio di immagini con quella in cui mi procuravo l'alibi per ieri pomeriggio. Per semplice curiosità, ecco l'immagine catturata (ridotta):

Webcam in valle camonica

Ovviamente sono poco distinguibile e in un processo non mi basterebbe per evitare la forca. Il software lo avevo scritto in due linguaggi di programmazione: in C# con il Framework .Net di Microsoft, e in C++ con il toolkit Qt. Per semplicità di codice e velocità di realizzazione non c'è paragone ovviamente e l'ago della bilancia si volge verso il C#. Vediamo il codice con questa tecnologia:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Timers;

namespace WebCam
{
    class Program
    {
        private static System.Timers.Timer _t;
        const string url ="xxx"
; // Url dell'immagine della webcam
        static void Main(string[] args)
        {
            Directory.CreateDirectory(Path.Combine(
                AppDomain.CurrentDomain.BaseDirectory,
                "photo"
                ));
            _t = new System.Timers.Timer();
            _t.AutoReset = false;
            _t.Interval = CalcolaIntervallo();
            _t.Elapsed += new ElapsedEventHandler(TakePhoto);
            _t.Start();

            Console.Write("Press 'enter' to exit... ");
            Console.ReadLine();
            _t.Stop();
        }
        static void TakePhoto(object sender, ElapsedEventArgs e)
        {
            System.Drawing.Image imagesource;
            Stream str = null;
            try
            {
                HttpWebRequest wReq = (HttpWebRequest)WebRequest.Create(url);
                HttpWebResponse wRes = (HttpWebResponse)(wReq).GetResponse();
                str = wRes.GetResponseStream();

                imagesource = System.Drawing.Image.FromStream(str);
                imagesource.Save(Path.Combine(
                    AppDomain.CurrentDomain.BaseDirectory,
                    "photo/" +
                    DateTime.Now.ToString("yyyyMMddHHmmss") + ".jpg"));
            }
            catch { }
            _t.Interval = GetIntervall();
            _t.Start();
        } 
        private static double GetIntervall()
        {
            DateTime next_date = DateTime.Now;
            next_date = new DateTime(next_date.Year, next_date.Month, next_date.Day, next_date.Hour, next_date.Minute, 0).AddMinutes(1);
           TimeSpan diff = next_date.Subtract(DateTime.Now);
            return diff.Ticks / 10000;
        }
    }
}

Questa console application si basa su un timer che ogni minuto esegue il codice della funzione TakePhoto che con una WebRequest si salva in locale l'immagine del server remoto. Quindi ricalcola quanto tempo dopo il timer deve scattare nuovamente per la prossima immagine.

Le cose con il C++ si complicano, ovviamente anche se il toolkit Qt aiuta molto. Utilizzando gli oggetti di Qt sono obbligato ad utilizzare le sue regole, e scegliendo come tipo di applicativo la console application sono obbligato a utilizzare il QCoreApplication in modo che da poter gestire gli eventi; inizialmente il suo utilizzo comporta delle piccole adattazioni visto che dobbiamo creare una classe che comunichi con esso utilizzando sempre gli eventi. Ecco il corpo del file principale main.cpp:

#include <QObject>
#include <QtCore/QCoreApplication>
#include <QTimer>
#include "webcam.h"
#include <stdio.h>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv); 

    webcam wc;
    QObject::connect(&wc, SIGNAL( done() ), &a, SLOT( quit() ), Qt::QueuedConnection);
    QTimer::singleShot(0, &wc, SLOT( takePhoto() ));
    return a.exec();
}

La gestione degli eventi viene implementata grazie ai SIGNAL e SLOT. In questo mio post ne spiego a grandi linee il funzionamento. Dopo la dichiarazione dell'oggetto webcam collego il suo signal done con lo slot quit del QCoreApplication. Dopo il collegamento con il QTimer si avvia la funzione principale di webcam, takePhoto che sarà spiegato dopo. Questo permette di uscire correttamente dal programma alla fine dell'esecuzione della procedura takePhoto in webcam. Ulteriori informazioni si possono trovare qui.

Ecco i due file della classe webcam il primo webcam.h:

#ifndef WEBCAM_H
#define WEBCAM_H

#include <QObject>
#include <stdio.h>
#include <iostream>
#include <qtextstream.h>
#include <qtimer.h>
#include <qnetworkaccessmanager.h>
#include <qnetworkrequest.h>
#include <qnetworkreply.h>
#include <qfile.h>
#include <qurl.h>
#include <qeventloop.h>
#include <qdatetime.h>

#include "thread.h"
using namespace std;

class webcam : public QObject
{
    Q_OBJECT
public:
    explicit webcam(QObject *parent = 0);

signals:
    void done();

public slots:
    void takePhoto();
};

#endif // HELLOWORLD_H

E il file webcam.cpp:

#include "webcam.h"

webcam::webcam(QObject *parent) :
    QObject(parent)
{
}

void webcam::takePhoto() {
    Thread *th=new Thread;
    th->start();
    cout << "Press 'enter' to exit... ";
    QTextStream qtin(stdin);
    QString line = qtin.readLine();
    //th->terminate();
    th->stop();
    th->wait();
    delete th;
    done();
}

La funzione takePhoto istanzia la classe thread. Questa deriva dalla classe QThread e permette di creare un thread parallelo che sarà utilizzato per il download dell'immagine della webcam, thread avviato richiamando la sua funzione start. Quindi scrive nel terminal il messaggio che spiega come terminare il programma e aspetta la pressione del tasto enter prima di distruggere i vari oggetti e richiamato il signal done che, come si ricorderà, essendo collegato all'oggetto QCoreApplication, chiuderà il programma correttamente.

Non rimane che vedere la classe thread. Ecco il file thread.h:

#ifndef THREAD_H
#define THREAD_H

#include <QObject>
#include <QThread>
#include <QDateTime>
#include <QUrl>
#include <QNetworkRequest>
#include <QNetworkAccessManager>
#include <QEventLoop>
#include <QNetworkReply>
#include <QFile>

class Thread : public QThread
{
Q_OBJECT
public:
    explicit Thread(QObject *parent = 0);
    void stop();
    virtual void run();
protected:
    int getInterval();
private:
    QString messageStr;
    volatile bool stopped;
signals:
public slots:
};
#endif // THREAD_H

E il file thread.cpp:

#include "thread.h"

Thread::Thread(QObject *parent) :
    QThread(parent)
{
    stopped=false;
}
void Thread::stop()
{
    stopped = true;
}
void Thread::run()
{
    while (!stopped)
    {
        QUrl url("xx"
); // url della webcam
        QNetworkRequest request(url);
        QNetworkAccessManager manager;
        QEventLoop loop;
        QNetworkReply *reply = manager.get( request );
        QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
        loop.exec();
        QFile file("./" + QDateTime::currentDateTime().toString("yyyyMMddHHmmss")+".jpg");
        file.open(QIODevice::WriteOnly);
        file.write(reply->readAll());
        file.close();
        delete reply; 

        QThread::sleep(getInterval());
        //QThread::sleep(10); // only for test
    }
}
int Thread::getInterval()
{
    QDateTime dt=QDateTime::currentDateTime();
    QString qq=dt.toString("yyyy, MM, dd, HH, mm, 00");
    QDateTime dt2=QDateTime::fromString(qq,"yyyy, MM, dd, HH, mm, ss").addSecs(60);
    return dt.secsTo(dt2);
}

Il funzionamento è molto differente con quello scritto in C#: prima veniva utilizzato il timer per l'attesa tra il download dell'immagine, qui un thread che girando continuamente calcola quanto la funzione sleep deve addormentare il thread prima del nuovo download dell'immagine.

La versione in C++ non è proprio perfetta perché alla pressione del tasto enter, prima di chiudere il programma, attende la fine dello sleep della procedura. La cosa si può risolvere con poche righe di codice di controllo, ma il programmino funzionava bene anche così e non avevo alcuna pretesa di perfezione dello stesso viste le mie conoscenza limitate del toolkit Qt.

La salita è questa: link.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese