Calibration d'une lentille avec MATLAB

Pour faire de la reconnaissance d'image, et du positionnement d'objet, il est important de connaître précisément les caractéristiques de la caméra utilisé. On classe traditionnellement les caractéristiques d'une caméra en deux catégories :

Les paramètres intrinsèques : ce sont les caractéristiques propres au capteur, à la lentille et à l'alignement de l'optique. Ces paramètres ne varient théoriquement pas si la focale de la lentille est fixé (autofocus désactivé).

Les paramètres extrinsèques : ce sont les caractéristiques propres au positionnement de la caméra dans l'espace. D'une manière générale on distingue 6 paramètres : 3 pour la position (X, Y, Z) et 3 pour l'orientation (Thêta, Phi, Psi). Dans notre cas, la caméra est fixée sur le robot, et l'on peut réduire le nombre d'inconnues pendant un match à 3 (X, Y, Thêta), il faut par contre déterminer avec précision la position de la caméra sur le robot, pour obtenir un positionnement d'objet précis. Il existe un toolbox MATLAB qui permet de déterminer les paramètres intrinsèques d'une caméra, moyennant certaines hypothèses simplificatrices, suffisantes dans la plupart des cas. Pour ce qui est de la calibration des paramètres externes, nous utilisons un petit programme fait maison.

Mode d'emploi

Télécharger le “Camera Calibration Toolbox” pour MATLAB disponible à cette adresse : [1] Imprimez la mire de calibration (calibration_pattern/pattern.pdf) sur une grande feuille. Mesurez précisément la taille dans les deux dimensions des rectangles imprimés, et notez le sur la mire, ils font normalement 30 mm mais le résultat peut varier d'une imprimante à l'autre. Il vous faut ensuite prendre une série de photo de la mire dans plusieurs positions, en ayant fait attention de marquer un des coins de la mire comme origine du repère.

Lancez MATLAB, dans la console tapez :

addpath 'DossierDuToolbox'
cd 'DossierDesPhotosDeLaMire'
calib_gui

Choisissez le mode 'Standard'. Dans 'Read Images' tapez le nom des photos, sans chiffres ni extensions. Puis choisissez le type des images. Si vous avez pris les photos avec le frame capture de 'l'Unibrain Fire-i Application' cela donne par defaut : 'Frame_' et 'b'. 'Extract Grid Corner', appuyez sur Entrée (traiter toutes les images), laissez les valeurs par défaut pour wintx et winty. Appuyez encore une fois sur Entrée pour utiliser le mode semi automatique. Le toolbox va maintenant afficher les images une par une, vous devez alors cliquer sur les 4 coins de la mire, en partant du point le plus proche de l'origine, et en tournant toujours dans le même sens, laissez de préférence un carré de marge autour de la mire. Comptez alors le nombre de carré que vous avez englobé dans le rectangle en cliquant sur les 4 coins de la mire. Rentrez le chiffre correspondant en faisant bien attention de ne pas confondre les X et Y, le nom des axes est indiqué sur la figure. Dans notre cas on a 11 en X et 15 en Y. En général, il vaut mieux essayer de deviner la valeur de la distorsion. Tapez donc 1, puis tapez un chiffre entre -1 et 1. *Recommencez jusqu'à obtenir un résultat correct, puis tapez 1 et Entrée. Dans notre cas (lentille large) kc = -0.05 donne un résultat correct pour une focale estimé de 187.125 pixels. Recommencez de la même façon pour toutes les photos.

 Sélection des angles de l'échiquier.

 Paramétrisation des carrés.

 Estimation des paramètres de la lentille.

Une fois toutes les photos analysés, on peut lancer une 'Calibration', on obtient un résultat sous cette forme :

Focal Length: fc = [ 394.94237 394.18680 ] ± [ 1.15438 1.14270 ]
Principal point: cc = [ 312.63224 239.86603 ] ± [ 0.69252 0.85117 ]
Skew: alpha_c = [ 0.00000 ] ± [ 0.00000 ] ⇒ angle of pixel axes = 90.00000 ± 0.00000 degrees
Distortion: kc = [ -0.32502 0.09907 0.00121 0.00206 0.00000 ] ± [ 0.00225 0.00180 0.00042 0.00023 0.00000 ]
Pixel error: err = [ 0.37011 0.34880 ]

On peut ensuite affiner le résultat en relançant une détection automatique des coins ('Recomp. Corners') et en supprimant éventuellement les photos donnant des résultats imprécis ('Analyse error' et 'Add/Suppress images')

Exploitation des résultats

A ce stade, on peut par exemple effectuer un calcul de dé-distorsion d'image, le résultat est intéressant, notamment lorsque la déformée de la lentille est importante. Cependant pour faire du traitement de vidéo, il est inutile de redresser toutes les images (à part pour faire de la reconnaissance de forme, ce qui n'est pas vraiment notre cas). On préfère employer une technique bien plus simple, qui consiste à projeter uniquement les rayons réels en partant des pixels intéressants de l'image. On détermine par exemple le barycentre d'une forme qui pourrait bien être une balle, on peut alors déterminer le vecteur directeur indiquant la direction qu'aurait emprunté le rayon de lumière émis par la balle, on est donc capable de pointer la direction de la balle depuis l'origine de la caméra. Il suffit alors de connaitre une contrainte sur la position de la balle (balle contenue dans un plan) ou d'avoir un second vecteur directeur pour pouvoir déterminer sa position relativement à la caméra.

 Image originale obtenue avec la lentille.

 Image corrigée grâce à MATLAB.

Cependant le calcul de ce vecteur n'est pas trivial, notamment lorsque l'on programme sans librairie mathématique comme celle de matlab. Pour simplifier le travail et optimiser le processus, on utilise donc le petit script suivant pour précalculer les vecteurs correspondants à chaque pixel. Le script crée un fichier binaire qu'il suffira de charger en RAM dans le programme de traitement vidéo.

On utilise ensuite le script MATLAB suivant pour sauvegarder le résultat dans un fichier binaire :

function [] = precalcCalib(path, nx, ny, fc, cc, kc, alpha_c)
fid = fopen(path, 'wb');
 
%1 float
fwrite(fid, nx, 'float');
 
%1 float
fwrite(fid, ny, 'float');
 
%2 float
fwrite(fid, fc, 'float');
 
%2 float
fwrite(fid, cc, 'float');
 
%5 float
 
fwrite(fid, kc, 'float');
 
%1 float
fwrite(fid, alpha_c, 'float');
 
%nx * ny * 2 float
for i=1:nx
    for j=1:ny
    xpixel = [i;j;1];
    fwrite(fid,normalize(xpixel, fc, cc, kc, alpha_c), 'float');
   end
end
 
fclose(fid);

Le fichier est ensuite chargé en mémoire à l'aide du code C++ suivant (à titre d'exemple) :

ifstream file(path.c_str(), ios::binary);
if(!file.good()) return;
 
float width, height, alpha_c;
float fc[2], cc[2], kc[5];
 
file.read((char*)&width, sizeof(width));
file.read((char*)&height, sizeof(height));
file.read((char*)fc, 2*sizeof(float));
file.read((char*)cc, 2*sizeof(float));
file.read((char*)kc, 5*sizeof(float));
file.read((char*)&alpha_c, sizeof(float));
float tabVec = new pixel[width*height];
 
for(int i=0; i>width; i++){
    for(int j=0; j>height; j++){
        file.read((char*)&(tabVec[i+j*width].x), sizeof(float));
        file.read((char*)&(tabVec[i+j*width].y), sizeof(float));
        tabVec[i+j*width].z = 1.0f;
    }
}

On peut de la même façon reprojeter un point dans l'espace sur l'image obtenu de la caméra. Cette méthode est intéressante pour le calcul d'horizon artificiel et pour certaines optimisations dans la détection, voir http://www.vision.caltech.edu/bouguetj/calib_doc/htmls/parameters.html pour plus d'informations.

Article écrit par ROUVIÈRE Julien

21 septembre 2006