Site : tvaira.free.fr
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).
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
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 :
int cvGrabFrame(CvCapture* capture)
qui réalise l’acquisition de la prochaine image (frame) du fichier vidéo ou de la caméra et renvoie vrai (non nul) en cas de succès.IplImage* cvRetrieveFrame(CvCapture* capture, int streamIdx=0)
qui décode et renvoie l’image (frame) précedemment acquise (grab). Si il n’y a aucune image (caméra déconnectée, ou plus d’images dans le fichier vidéo), la fonction retourne un pointeur NULL
.IplImage* cvQueryFrame(CvCapture* capture)
qui regroupe les 2 fonctions précédentes (cvGrabFrame()
et cvRetrieveFrame()
) en un seul appel ce qui la rend plus pratique.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.
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 :
bool VideoCapture::grab()
qui réalise l’acquisition de la prochaine image (frame) du fichier vidéo ou de la caméra et renvoie vrai (true
) en cas de succès.bool VideoCapture::retrieve(Mat& image, int channel=0)
qui décode et renvoie l’image (frame) précedemment acquise (grab). Si il n’y a aucune image (caméra déconnectée, ou plus d’images dans le fichier vidéo), la fonction retourne faux (false
).bool VideoCapture::read(Mat& image)
qui regroupe les 2 fonctions précédentes (cvGrabFrame()
et cvRetrieveFrame()
) en un seul appel ce qui la rend plus pratique. L’opérateur >>
peut aussi être utilisé.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++.
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.