Calibration de caméra et reconstruction 3D

De Wiki du LAMA (UMR 5127)
Aller à la navigation Aller à la recherche

Noah CUNEO

Tuteur : Stéphane BREUILS

Introduction

Nous allons dans ce compte rendu, voir et comprendre ce qu’est la vision par ordinateur.

Plus particulièrement comment à partir d’une caméra nous pouvons traiter l’information d’une image.

Dans le but d’obtenir divers résultats tel que la création d’un panorama ou bien la reconstruction 3D d’un objet à partir de photos.

Pour cela, nous allons nous aider de la bibliothèque OpenCV.

Qui est une bibliothèque conçue spécifiquement pour la vision par ordinateur et le traitement d’images.

*Notons que par soucis de lisibilité, nous allons uniquement afficher des morceaux de code. Mais que nous ferons une redirection systématique vers le code correspondant sur GitHub

Présentation d'OpenCV

OpenCV est donc, comme dit dans l’introduction, une librairie Python axée sur la vision par ordinateur.
En effet, elle propose une grande variété de fonctions utiles au traitement d’images. Comme par exemple :

sift = cv.SIFT_create()
kp = sift.detect(gray,None)

Qui permet de détecter des points clés sur une image.

On également par exemple :

M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC,5.0)

Qui permet d’estimer une matrice d’homographie.

Mais nous verrons l’utilité de ces fonctions plus en détails ultérieurement.

OpenCV permet également, à l’instar de Tkinter ou Pygame, de générer une interface graphique.

Elle est cependant très limitée puisqu’elle à été créée seulement à l’intention d’afficher des images.

Voici un exemple de comment on affiche une image sur OpenCV :

image = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
cv2.imshow('Image', image)
cv2.waitKey(0)
cv2.destroyAllWindows()

Ce code va par exemple, afficher l’image « image.jpg » en nuance de gris de cette forme :


Photo d'immeuble en noir et blanc


Nous allons réutiliser cette image ultérieurement, afin que les modifications que nous allons étudier soient mieux compréhensibles.

Présentation de mon code

Lors de ma lecture de la documentation d'OpenCV. J'ai remarqué que certaines tâches pouvaient se répéter.

J'ai donc dans l'optique de rendre mon code plus compréhensible tout en évitant de répéter les tâches déjà effectuées, créé quelque classe permettant d'améliorer ces caractéristiques.

Organisation du projet sur GitHub

Présentation de mes classes

Voici la classe CameraParametre.

Je l'ai codé afin de stocker dans un fichier dictionnaire les paramètre intrinsèque de la caméra, ainsi que les paramètres extrinsèques pour les images données.

Le nom de la caméra sert ensuite à créer un fichier JSON avec la classe "Sauvegarde", ce qui permet ultérieurement d'obtenir ces paramètres sans avoir à les recalculer.

class CameraParametre() :
    """Permet de stocker les paramètres de la caméra"""
    def __init__(self, nom, ret, mtx, dist, rvecs, tvecs, proj) :
        self.nom = nom
        self.ret = ret
        self.mtx = mtx
        self.dist = dist
        self.rvecs = rvecs
        self.tvecs = tvecs
        self.proj = proj
    
    def save_camera(self, chemin:str) :
        """sauvegarde les parametres de la camra dans le chemin donné"""
        Sauvegarde.save(self.cam_to_dic(), chemin + self.nom + ".json")

    def get_camera(chemin:str) :
       """obtient les parametres de la camra dans le présent dans le dossier donné"""
       return CameraParametre.from_dic(Sauvegarde.load(chemin))

# Code complet sur GitHub

Comme vous pouvez le voir, Nous faisons quelques fois appel à la Classe "Sauvegarde".

Cette classe contient uniquement des fonctions et procédures statiques.

Permettant de créer des fichiers JSON à partir d'un dictionnaire, ou d'obtenir un fichier à partir de son nom.

class Sauvegarde() :
    """permet de stocker une valeur à clef dans un fichier JSON"""
    # Cette classe contient des fonctions statiques qui permettent de stocker et récupérer de s valeurs dans un fichier JSON
    def save(donnee, path):
        with open(path, "w") as json_file:
            json.dump(donnee, json_file, indent=4)

    def load(path):
        with open(path, "r") as json_file:
            data = json.load(json_file)
        return data

Calibration d'une caméra

Une caméra permet de capturer un phénomène physique (la lumière) en donnée digitale. Malheureusement, le procédé du fonctionnement d’une caméra induit une inexactitude de ce à quoi l’image ressemble réellement à la réalité (distorsion, colorimétrie).

C’est ainsi que nous allons voir à partir de plusieurs images, comment nous pouvons en extraire des paramètres afin de calibrer la caméra et de réajuster ces images.

Paramètres d'une caméra

Une caméra peut être décrite avec plusieurs variables, que l’on appelle paramètre.

Ces paramètre se distinguent en deux catégories.

Les premiers sont les paramètre intrinsèques, qui sont les paramètres relatif à la caméra, les voici :

  • Le centre optique, c'est le point théorique sur la caméra où les rayons lumineux convergent.
  • La distance focale, est la distance entre le centre optique et l'objet sujet nécessaire à avoir une image nette.
  • Les coefficients de distorsion, qui décrivent la distorsion radiale et tangentielle d'une image.

La deuxième catégorie sont les paramètres extrinsèques, qui correspondent à des valeurs inhérentes à l’image prise par la caméra. Ce sont donc des valeurs qui varient en fonction de la prise :

  • Un vecteur de translation, qui représente la position de la caméra par rapport au sujet plan.
  • Un vecteur de rotation, qui représente la rotation de la caméra par rapport au sujet plan.


Nous voulons donc dans un premier temps trouver ces paramètres.

Pour trouver ces paramètre, nous allons faire ce que l'on appelle un calibrage.

Calibrage

Pour faire un calibrage, nous allons prendre un motif connu.

Car avoir un motif que l'on connaît va nous permettre de "trouver les différences" entre l'image et la réalité, afin d'en extraire des paramètres.

Puisque si l'on sait qu'une ligne est droite dans la réalité mais qu'elle est courbée dans l'image, c'est qu'il y a eu une distorsion radiale.

Ou alors si deux lignes de même longueurs apparaissent de taille différentes, c'est que le plan est incliné par rapport à la caméra.

Nous allons donc prendre un damier. L'avantage du damier est que nous connaissons le nombre de carreaux et nous savons que ces carreaux sont bien droits (sans courbure).

Nous allons maintenant prendre en photo ce damier sous différents angles, ce qui va permettre à OpenCV d'estimer les paramètres de la caméra.

Voici les photos que j'ai prises :


Noah cuneo damier 0.jpg Noah cuneo damier 1.jpg Noah cuneo damier 2.jpg


Notons que plus nous allons donner d'images à OpenCV, plus les paramètres que l'on va obtenir vont être précis.

Mais que pour des soucis de performance, j'en ai fait que 3.

Maintenant que nous avons nos images, nous pouvons appelé ret, corners = cv.findChessboardCorners(gray, (7, 7), None)

Si ret vaut True après l'appel de cette fonction, c'est que OpenCV à trouvé le damier.

Dans ce cas, nous pouvons ajouter dans une liste les points objets (position des corner dans l'espace 3D) et les points images (ces même points sur l'image donc en 2D).

A partir de maintenant, nous pouvons appeler ret, mtx, dist, rvecs, tvecs = cv.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

Cette fonction est la fonction d'OpenCV qui va calculer les paramètres de la caméra.

Elle renvoi donc :

  • ret : True ou False en fonction de si la calibration a fonctionnée.
  • mtx : Appelé "Matrice de Caméra" Elle contient les paramètres intrinsèques, sauf les coefficients de distortions.
  • dist: Contient les coefficients de distortions.
  • tvecs : Contient une liste de de vecteur, représentant la translation de la caméra par rapport à chaque image.
  • rvecs : Contient une liste de de vecteur, représentant la rotation de la caméra par rapport à chaque image.

Notons que tvecs et rvecs sont généralement par la suite condensé dans ce qu'on appelle une matrice de projection, mais nous y reviendrons plus tard.

Pour la suite, étant donné que ma caméra ne fait pas subir de grosse distorsion à mes images. Je vais utiliser des images prisent d'internet pour illustrer mes propos.


Image de damier bien distordue, Prise sur internet


Grâce à ça, nous pouvons reconstruire l'image, sans la distorsion et autre effets de découlant la caméra.

Mais subsiste un problème. Lorsque nous allons dé-distosionner l'image, des zones noirs vont apparaître car l'image recourbé va laisser place à du vide.

Nous allons donc recalculer une matrice de caméra, spécifiquement conçu pour la résolution de l'image sans le noir.

newcameramtx, roi = cv.getOptimalNewCameraMatrix(camera.mtx, camera.dist, (w,h), alpha, (w,h))

Nous pouvons avec cette nouvelle matrice, dé-distorsionner l'image grâce au code : dst = cv.undistort(img, camera.mtx, camera.dist, None, newcameramtx)


À gauche, image dé-distortionnée avec la nouvelle matrice. À droite avec l'ancienne


Maintenant que nous avons vu comment calibrer une caméra, voyons ce quels traitements nous pouvons donner aux images.

Analyse et traitement d'une image


Matrice de projection

Trouver des descripteurs

Homographie et matrice d'homographie (panorama)

Reconstruction 3D

Matrice fondamentale