Site : tvaira.free.fr

Mise en oeuvre d’OpenCV

OpenCV

Présentation

OpenCV (Open Computer Vision) est une bibliothèque graphique libre, initialement développée par Intel, spécialisée dans le traitement d’images en temps réel. Cette bibliothèque est distribuée sous licence BSD.

La bibliothèque OpenCV met à disposition de nombreuses fonctionnalités très diversifiées permettant de créer des programmes partant des données brutes pour aller jusqu’à la création d’interfaces graphiques basiques. Elle propose la plupart des opérations classiques en traitement bas niveau des images et des vidéos. Cette bibliothèque s’est imposée comme un standard dans le domaine de la recherche parce qu’elle propose un nombre important d’outils issus de l’état de l’art en vision des ordinateurs.

La documentation d’OpenCV (cf. OpenCV API Reference).

Retour au sommaire

Installation (Ubuntu 12.04)

Pour installer les bibliothèques de développement d’OpenCV, il faudra faire :

$ sudo apt-get install libcv-dev libhighgui-dev libopencv-dev

Les options de compilation seront :

$ pkg-config --cflags --libs opencv
-I/usr/include/opencv  -lopencv_core -lopencv_imgproc -lopencv_highgui -lopencv_ml -lopencv_video 
-lopencv_features2d -lopencv_calib3d -lopencv_objdetect -lopencv_contrib -lopencv_legacy -lopencv_flann

Retour au sommaire

Exemple en C

OpenCV fournit une API C pour l’acquisition vidéo en provenance d’une caméra ou d’un fichier. En C, on utilisera donc les appels cvCaptureFromCAM() ou cvCaptureFromFile() qui retournent un pointeur sur une structure CvCapture. En fin de programme, il ne faudra pas oublier de le libérer en appelant cvReleaseCapture().

Dans notre cas, la caméra n’étant raccordée physiquement à l’ordinateur, on ne pourra pas utiliser l’appel cvCaptureFromCAM(). En effet, on accéde au flux vidéo via un script CGI par le réseau. On doit donc utiliser l’appel cvCaptureFromFile(). OpenCV détecte le type des données image par l’extension du fichier. La documentation de la caméra nous informe que le format des données vidéos est du type MJPEG. Au final, il faudra donc passer en paramètre de l’appel cvCaptureFromFile() : l’adresse (adresse-ip-camera:99) qui pointe vers le script CGI (c’est-à-dire videostream.cgi) fournissant les données vidéos ET l’extension .mjpg, soit :

"http://adresse-ip-camera:99/videostream.cgi?user=admin&pwd=&resolution=32&rate=0&.mjpg"

Pour réaliser une ‘capture’, l’API C met à notre disposition 3 fonctions :

Pour l’affichage, on utilisera l’appel cvNamedWindow() pour créer une fenêtre et cvShowImage() pour visualiser l’image (frame). Opencv fournit aussi des fonctions pour sauvegarder les images comme cvSaveImage().

cf. la documentation de l’API sur docs.opencv.org.

#include <stdio.h>
#include <cv.h> // contient les déclarations des structures et fonctions de manipulation d'images
#include <highgui.h> // contient déclarations des fonctions d'affichage des images

#define DEBUG

// gcc -O2 -Wall -o opencv-0 opencv-0.c `pkg-config --cflags --libs opencv`

int main(int argc, char *argv[])
{
    // cf. http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html
    CvCapture *capture = cvCaptureFromFile(
    "http://192.168.52.14:99/videostream.cgi?user=admin&pwd=&resolution=32&rate=0&.mjpg");
    if(!capture)
    {
        printf("Erreur d'initialisation !\n");
        exit(1);
    }

    //cvNamedWindow("Wanscam");
    cvNamedWindow("Wanscam", CV_WINDOW_AUTOSIZE);

    while(1)
    {    
        #ifdef DEBUG
        double t1 = (double)cvGetTickCount();
        #endif

        // Méthode 1 :
        /*if(!cvGrabFrame(capture))
        {
            printf("Erreur d'acquisition !\n");
            exit(1);
        }
        IplImage *img = cvRetrieveFrame(capture);*/

        // Méthode 2 :
        IplImage *img = cvQueryFrame(capture);
        if(img == NULL)
        {
            printf("Erreur de lecture !\n");
            exit(1);
        }

        #ifdef DEBUG
        printf("%dx%d pixels (%d canaux couleurs)\n", img->width, img->height, img->nChannels); 
        double t2 = (double)cvGetTickCount();
        printf("time: %gms  fps: %.2g\n", 
               (t2-t1)/(cvGetTickFrequency()*1000.), 1000./((t2-t1)/(cvGetTickFrequency()*1000.)));
        #endif    

        cvShowImage("Wanscam", img);

        // Si on veut sauvegarder l'image
        //cvSaveImage("foo.jpg", img);

        //cvReleaseImage(&img);

        // Appuyez sur une touche pour sortir
        if(cvWaitKey(0) >= 0) break;
    }    

    cvReleaseCapture(&capture);

    return 0;
}

Le source de l’exemple C.

Retour au sommaire

Exemple en C++

En C++, OpenCV fournit une classe VideoCapture pour l’acquisition vidéo en provenance d’une caméra ou d’un fichier. On utilisera la méthode open() pour ouvrir le flux vidéo de la caméra.

Pour rappel, on accéde au flux vidéo via un script CGI par le réseau. OpenCV détecte le type des données image par l’extension du fichier. La documentation de la caméra nous informe que le format des données vidéos est du type MJPEG. Au final, il faudra donc passer en paramètre de l’appel open() : l’adresse (adresse-ip-camera:99) qui pointe vers le script CGI (c’est-à-dire videostream.cgi) fournissant les données vidéos ET l’extension .mjpg, soit :

"http://adresse-ip-camera:99/videostream.cgi?user=admin&pwd=&resolution=32&rate=0&.mjpg"

Pour réaliser une ‘capture’, l’API C++ met à notre disposition 3 méthodes :

Pour manipuler des images, OpenCV utilise une classe Mat (cf. cv::Mat).

Pour l’affichage des images, on utilisera la méthode cv::imshow(). Opencv fournit aussi des méthodes pour lire des images comme imread() et pour les sa uvegarder avec imwrite().

cf. la documentation de l’API sur docs.opencv.org.

#include <iostream>
#include <cv.h> // contient les déclarations des classes de manipulation d'images
#include <highgui.h> // contient déclarations des fonctions d'affichage des images

#define DEBUG

using namespace cv;
using namespace std;

// g++ -o opencv-1 opencv-1.cpp `pkg-config --cflags --libs opencv`

int main(int, char**) 
{
    VideoCapture capture;
    Mat img;

    const string adresseFluxVideo = 
    "http://192.168.52.14:99/videostream.cgi?user=admin&pwd=&resolution=32&rate=0&.mjpg"; 

    if(!capture.open(adresseFluxVideo)) 
    {
        cout << "Erreur d'initialisation !" << endl;
        return 1;
    }

    #ifdef DEBUG
    double dWidth = capture.get(CV_CAP_PROP_FRAME_WIDTH);
    double dHeight = capture.get(CV_CAP_PROP_FRAME_HEIGHT);
    cout << dWidth << " x " << dHeight << endl;
    #endif    

    //namedWindow("Wanscam");
    namedWindow("Wanscam", CV_WINDOW_AUTOSIZE);

    while(1)
    {            
        // Méthode 1 :
        /*if(!capture.grab())
        {
            cout << "Erreur d'acquisition !" << endl;            
            exit(1);
        }
        if(!capture.retrieve(img))
        {
            cout << "Erreur de récupération !" << endl;            
            exit(1);
        }*/

        // Méthode 2 :
        if(!capture.read(img)) 
        {
            cout << "Erreur de lecture !" << endl;
            return 1;
        }

        imshow("Wanscam", img);

        // Si on veut sauvegarder l'image
        //imwrite("foo.jpg", img);

        // Appuyez sur une touche pour sortir
        if(cv::waitKey(0) >= 0) break;
    }       

    return 0;
}

OpenCV fournit de très nombreuses fonctionnalités. Voici un exemple de deux transformations possibles sur des objets image de type Mat :

// ...

namedWindow("Flip", CV_WINDOW_AUTOSIZE);
namedWindow("Negative", CV_WINDOW_AUTOSIZE);

while(1)
{
    // ...
    flip(img, img, 1);
    imshow("Flip", img);

    Mat gray, edge, draw;
    cvtColor(img, gray, CV_BGR2GRAY); 
    Canny(gray, edge, 50, 150, 3); 
    edge.convertTo(draw, CV_8U);
    imshow("Negative", draw);        

    // ...
}

// ...

Le source de l’exemple C++.

Retour au sommaire

Exemple en Qt

Sous Qt, on utilisera un objet QLabel et un objet QImage pour assurer l’affichage des frames dans l’IHM. Le principe est le suivant :

QLabel *imageLabel = new QLabel;
imageLabel->setBackgroundRole(QPalette::Base);

QImage image("vide.png");
imageLabel->setPixmap(QPixmap::fromImage(image));
imageLabel->resize(imageLabel->pixmap()->size());

On utilise le code C++ ci-dessus (voir Exemple en C++) que l’on adapte à Qt pour la capture. À partir d’une frame de type Mat ou IplImage, il faut pouvoir la convertir dans un type Qt (ici QImage ). Pour cela, on va se servir de la méthode Ipl2QImage() qui permet de convertir une image OpenCV de type IplImage vers une image Qt de type QImage.

Mat frame;

// capture de la frame (voir plus haut) ... puis :
IplImage *img = new IplImage(frame);

QImage image = Ipl2QImage(img);
imageLabel->setPixmap(QPixmap::fromImage(image));
imageLabel->resize(imageLabel->pixmap()->size());

Pour la gestion de la caméra, on peut prendre un objet QNetworkAccessManager et sa méthode get() pour accéder aux scripts CGI de la caméra. Voici le principe pour la commande flip :

class X : public QWidget
{
    Q_OBJECT

public:
    X( QWidget *parent = 0 );
    ~X();

private:
    QNetworkAccessManager *manager;
    QNetworkReply         *reply;    
    //...
    QTextEdit             *journal;

public slots:    
    void flip();
    void replyFinished(QNetworkReply *reply);    
    //...
};

X::X( QWidget *parent ) : QWidget( parent )
{
    // ...
    manager = new QNetworkAccessManager(this);
    connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
}

void X::flip()
{
    //QString URL = "http://" + adresseIP + ":" + QString::number(port) + 
    // "/camera_control.cgi?param=5&value=1" + "&user=" + user + "&pwd=" + pwd;
    QString URL = "http://192.168.52.14:99/camera_control.cgi?param=5&value=1&user=admin&pwd=";
    manager->get(QNetworkRequest(QUrl(URL)));
}

void X::noflip()
{
    //QString URL = "http://" + adresseIP + ":" + QString::number(port) + 
    // "/camera_control.cgi?param=5&value=0" + "&user=" + user + "&pwd=" + pwd;
    QString URL = "http://192.168.52.14:99/camera_control.cgi?param=5&value=0&user=admin&pwd=";
    manager->get(QNetworkRequest(QUrl(URL)));
}

void X::replyFinished(QNetworkReply *reply)
{
    QByteArray datas = reply->readAll();
    qDebug() << QString::fromUtf8("<X::replyFinished()> reply : ") << datas;

    QString infos(datas);
    journal->append(infos);
}    

Le source de l’exemple Qt.

Retour au sommaire