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.


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 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 cam_to_dic(self) -> dict :
        """transforme la classe en dictionnaire toléré par json"""
        dico = {"nom":self.nom ,"ret":self.ret, "mtx":self.mtx, "dist":self.dist, "rvecs":self.rvecs, "tvecs":self.tvecs, "proj":self.proj}
        dico["mtx"] = dico["mtx"].tolist()
        dico["dist"] = dico["dist"].tolist()
        dico["rvecs"] = tuple(np.array(arr).tolist() for arr in dico["rvecs"])
        dico["tvecs"] = tuple(np.array(arr).tolist() for arr in dico["tvecs"])

        dico["proj"] = [matrix.flatten().tolist() for matrix in dico["proj"]]

        return dico

    def from_dic(dico:dict):
        """fonction statique qui charge dans la classe les paramètres stockés dans un dictionanire"""
        dico["mtx"] = np.array(dico["mtx"])
        dico["dist"] = np.array(dico["dist"])
        dico["rvecs"] = tuple(np.array(arr) for arr in dico["rvecs"])
        dico["tvecs"] = tuple(np.array(arr) for arr in dico["tvecs"])

        dico["proj"] = [np.array(matrix).reshape((3, 4)) for matrix in dico["proj"]]

        return CameraParametre(dico["nom"], dico["ret"],dico["mtx"],dico["dist"],dico["rvecs"],dico["tvecs"],dico["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))

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.

Ray-tracing.png Ray-tracing.png Ray-tracing.png

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

Analyse et traitement d'une image

Matrice de projection

Trouver des descripteurs

Homographie et matrice d'homographie (panorama)

Reconstruction 3D

Matrice fondamentale