web-dev-qa-db-fra.com

Comment inverser le projet de points 2D en 3D?

J'ai 4 points 2D dans l'espace d'écran, et je dois les projeter en arrière dans l'espace 3D. Je sais que chacun des 4 points est un coin d'un rectangle rigide tourné en 3D, et je connais la taille du rectangle. Comment puis-je obtenir des coordonnées 3D à partir de cela?

Je n'utilise aucune API particulière et je n'ai pas de matrice de projection existante. Je cherche juste des mathématiques de base pour ce faire. Bien sûr, il n'y a pas assez de données pour convertir un seul point 2D en 3D sans autre référence, mais j'imagine que si vous avez 4 points, vous savez qu'ils sont tous à angle droit les uns par rapport aux autres sur le même plan, et vous connaissez la distance entre eux, vous devriez être en mesure de le comprendre à partir de là. Malheureusement, je ne sais pas vraiment comment.

Cela pourrait tomber sous le parapluie de la photogrammétrie, mais les recherches Google pour cela ne m'ont pas conduit à des informations utiles.

56
Joshua Carmody

D'accord, je suis venu ici à la recherche d'une réponse et je n'ai pas trouvé quelque chose de simple et direct, alors j'ai continué et j'ai fait la chose stupide mais efficace (et relativement simple): l'optimisation de Monte Carlo.

En termes très simples, l'algorithme est le suivant: perturber aléatoirement votre matrice de projection jusqu'à ce qu'elle projette vos coordonnées 3D connues vers vos coordonnées 2D connues.

Voici une photo de Thomas the Tank Engine:

Thomas the Tank Engine

Disons que nous utilisons GIMP pour trouver les coordonnées 2D de ce que nous pensons être un carré sur le plan du sol (que ce soit vraiment un carré ou non dépend de votre jugement sur la profondeur):

With an outline of the square

J'obtiens quatre points dans l'image 2D: (318, 247), (326, 312), (418, 241) Et (452, 303).

Par convention, nous disons que ces points doivent correspondre aux points 3D: (0, 0, 0), (0, 0, 1), (1, 0, 0) Et (1, 0, 1). En d'autres termes, un carré d'unité dans le plan y = 0.

La projection de chacune de ces coordonnées 3D en 2D se fait en multipliant le vecteur 4D [x, y, z, 1] Par une matrice de projection 4x4, puis en divisant les composantes x et y par z pour obtenir la correction de perspective. C'est plus ou moins ce que fait gluProject () , sauf que gluProject() prend également en compte la fenêtre courante et prend en compte une matrice de visualisation de modèle distincte (nous pouvons simplement supposer que la matrice de visualisation de modèle est la matrice d'identité). Il est très pratique de regarder la documentation gluProject() car je veux en fait une solution qui fonctionne pour OpenGL, mais attention à la documentation qui manque la division par z dans la formule.

N'oubliez pas que l'algorithme doit commencer avec une matrice de projection et la perturber de manière aléatoire jusqu'à ce qu'elle donne la projection que nous voulons. Donc, ce que nous allons faire, c'est projeter chacun des quatre points 3D et voir à quel point nous nous rapprochons des points 2D que nous voulions. Si nos perturbations aléatoires font que les points 2D projetés se rapprochent de ceux que nous avons marqués ci-dessus, nous conservons cette matrice comme une amélioration par rapport à notre supposition initiale (ou précédente).

Définissons nos points:

# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)

Nous devons commencer par une matrice, la matrice d'identité semble un choix naturel:

mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]

Nous devons réellement implémenter la projection (qui est essentiellement une multiplication matricielle):

def project(p, mat):
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)

C'est essentiellement ce que gluProject() fait, 720 et 576 sont la largeur et la hauteur de l'image, respectivement (c'est-à-dire la fenêtre), et nous soustrayons de 576 pour compter le fait que nous avons compté les coordonnées y à partir du haut tandis qu'OpenGL les compte généralement par le bas. Vous remarquerez que nous ne calculons pas z, c'est parce que nous n'en avons pas vraiment besoin ici (bien que cela puisse être pratique pour s'assurer qu'il se situe dans la plage utilisée par OpenGL pour le tampon de profondeur).

Nous avons maintenant besoin d'une fonction pour évaluer à quel point nous sommes proches de la bonne solution. La valeur renvoyée par cette fonction est ce que nous allons utiliser pour vérifier si une matrice est meilleure qu'une autre. J'ai choisi de faire la somme des distances au carré, c'est-à-dire:

# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)

Pour perturber la matrice, nous choisissons simplement un élément à perturber d'une quantité aléatoire dans une certaine plage:

def perturb(amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)

(Il convient de noter que notre fonction project() n'utilise pas du tout mat[2], Car nous ne calculons pas z, et puisque toutes nos coordonnées y sont égales à 0, mat[*][1] Les valeurs ne sont pas pertinentes non plus. Nous pourrions utiliser ce fait et ne jamais essayer de perturber ces valeurs, ce qui donnerait une petite accélération, mais cela reste un exercice ...)

Pour plus de commodité, ajoutons une fonction qui fait l'essentiel de l'approximation en appelant perturb() encore et encore sur la meilleure matrice que nous ayons trouvée jusqu'à présent:

def approximate(mat, amount, n=100000):
    est = evaluate(mat)

    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est

Il ne reste plus qu'à l'exécuter ...:

for i in xrange(100):
    mat = approximate(mat, 1)
    mat = approximate(mat, .1)

Je trouve que cela donne déjà une réponse assez précise. Après avoir couru pendant un certain temps, la matrice que j'ai trouvée était:

[
    [1.0836000765696232,  0,  0.16272110011060575, -0.44811064935115597],
    [0.09339193527789781, 1, -0.7990570384334473,   0.539087345090207  ],
    [0,                   0,  1,                    0                  ],
    [0.06700844759602216, 0, -0.8333379578853196,   3.875290562060915  ],
]

avec une erreur d'environ 2.6e-5. (Remarquez que les éléments que nous avons dit ne pas avoir été utilisés dans le calcul n'ont pas été réellement modifiés par rapport à notre matrice initiale; c'est parce que le changement de ces entrées ne changerait pas le résultat de l'évaluation et donc le changement ne serait jamais poursuivi.)

Nous pouvons passer la matrice dans OpenGL en utilisant glLoadMatrix() (mais n'oubliez pas de la transposer d'abord, et n'oubliez pas de charger votre matrice modelview avec la matrice d'identité):

def transpose(m):
    return [
        [m[0][0], m[1][0], m[2][0], m[3][0]],
        [m[0][1], m[1][1], m[2][1], m[3][1]],
        [m[0][2], m[1][2], m[2][2], m[3][2]],
        [m[0][3], m[1][3], m[2][3], m[3][3]],
    ]

glLoadMatrixf(transpose(mat))

Maintenant, nous pouvons par exemple traduire le long de l'axe z pour obtenir différentes positions le long des pistes:

glTranslate(0, 0, frame)
frame = frame + 1

glBegin(GL_QUADS)
glVertex3f(0, 0, 0)
glVertex3f(0, 0, 1)
glVertex3f(1, 0, 1)
glVertex3f(1, 0, 0)
glEnd()

With 3D translation

C'est sûr que ce n'est pas très élégant d'un point de vue mathématique; vous n'obtenez pas une équation de forme fermée dans laquelle vous pouvez simplement brancher vos chiffres et obtenir une réponse directe (et précise). CEPENDANT, cela vous permet d'ajouter des contraintes supplémentaires sans avoir à vous soucier de compliquer vos équations; Par exemple, si nous voulions également incorporer la hauteur, nous pourrions utiliser ce coin de la maison et dire (dans notre fonction d'évaluation) que la distance entre le sol et le toit devrait être telle ou telle, et réexécuter l'algorithme. Alors oui, c'est une sorte de force brute, mais ça marche et ça marche bien.

Choo choo!

67
Vegard

C'est le problème classique de la réalité augmentée basée sur les marqueurs.

Vous disposez d'un marqueur carré (2D Barcode), et vous souhaitez retrouver sa Pose (translation & rotation par rapport à la caméra), après avoir trouvé les quatre bords du marqueur. vue d'ensemble

Je ne suis pas au courant des dernières contributions dans le domaine, mais au moins jusqu'à un certain point (2009) RPP était censé surpasser POSIT mentionné ci-dessus (et c'est en effet une approche classique pour cela) Veuillez voir les liens, ils ont aussi fournir la source.

(PS - Je sais que c'est un sujet un peu ancien, mais de toute façon, le message pourrait être utile à quelqu'un)

7
dim_tz

D. DeMenthon a conçu un algorithme pour calculer la pose d'un objet (sa position et son orientation dans l'espace) à partir des points caractéristiques d'une image 2D en connaissant le modèle de l'objet - c'est votre problème exact :

Nous décrivons une méthode pour trouver la pose d'un objet à partir d'une seule image. Nous supposons que nous pouvons détecter et faire correspondre dans l'image au moins quatre points caractéristiques non planaires de l'objet, et que nous connaissons leur géométrie relative sur l'objet.

L'algorithme est connu sous le nom de Posit et est décrit dans l'article classique "Pose d'objet basé sur un modèle en 25 lignes de code" (disponible sur son site Web , section 4).

Lien direct vers l'article: http://www.cfar.umd.edu/~daniel/daniel_papersfordownload/Pose25Lines.pdf Implémentation OpenCV: http://opencv.willowgarage.com/ wiki/Posit

L'idée est d'approximer à plusieurs reprises la projection en perspective par une projection orthographique à l'échelle jusqu'à converger vers une pose précise.

5
Julien-L

De l'espace 2D, il y aura 2 rectangles valides qui peuvent être construits. Sans connaître la projection matricielle d'origine, vous ne saurez pas laquelle est correcte. C'est la même chose que le problème "boîte": vous voyez deux carrés, l'un à l'intérieur de l'autre, avec les 4 sommets intérieurs connectés aux 4 sommets extérieurs respectifs. Vous regardez une boîte de haut en bas ou de bas en haut?

Cela étant dit, vous cherchez une transformation matricielle T où ...

{{x1, y1, z1}, {x2, y2, z2}, {x3, y3, z3}, {x4, y4, z4}} x T = {{x1, y1}, {x2, y2}, { x3, y3}, {x4, y4}}

(4 x 3) x T = (4 x 2)

T doit donc être une matrice (3 x 2). Nous avons donc 6 inconnues.

Maintenant, construisez un système de contraintes sur T et résolvez avec Simplex. Pour construire les contraintes, vous savez qu'une ligne passant par les deux premiers points doit être parallèle à la ligne passant par les deux seconds points. Vous savez qu'une ligne passant par les points 1 et 3 doit être parallèle aux lignes passant par les points 2 et 4. Vous savez qu'une ligne passant par les points 1 et 2 doit être orthogonale à une ligne passant par les points 2 et 3. Vous savez que la longueur de la ligne de 1 et 2 doit être égale à la longueur de la ligne de 3 et 4. Vous savez que la longueur de la ligne de 1 et 3 doit être égale à la longueur de la ligne de 2 et 4.

Pour rendre cela encore plus facile, vous connaissez le rectangle, vous connaissez donc la longueur de tous les côtés.

Cela devrait vous donner beaucoup de contraintes pour résoudre ce problème.

Bien sûr, pour revenir, vous pouvez trouver T-inverse.

@Rob: Oui, il existe un nombre infini de projections, mais pas un nombre infini de projets où les points doivent satisfaire aux exigences d'un rectangle.

@nlucaroni: Oui, ceci n'est résoluble que si vous avez quatre points dans la projection. Si le rectangle se projette sur seulement 2 points (c'est-à-dire que le plan du rectangle est orthogonal à la surface de projection), cela ne peut pas être résolu.

Hmmm ... Je devrais rentrer à la maison et écrire ce petit bijou. Cela semble amusant.

Mises à jour:

  1. Il existe un nombre infini de projections, sauf si vous corrigez l'un des points. Si vous fixez l'un des points du rectangle d'origine, il y a deux rectangles d'origine possibles.
4
Jarrett Meyer

Pour mon moteur OpenGL, le snip suivant convertira les coordonnées souris/écran en coordonnées 3D du monde. Lisez les commentaires pour une description réelle de ce qui se passe.

/* FONCTION: YCamera :: CalculateWorldCoordinates 
 ARGUMENTS: x souris x coordonnées 
 Y souris y coordonnées 
 Vec où stocker les coordonnées 
 RETOUR: n/a 
 DESCRIPTION: Convertir les coordonnées de la souris en coordonnées mondiales 
 */

void YCamera :: CalculateWorldCoordinates(float x, float y, YVector3 *vec) { // START GLint viewport[4]; GLdouble mvmatrix[16], projmatrix[16];

GLint real_y;
GLdouble mx, my, mz;

glGetIntegerv(GL_VIEWPORT, viewport);
glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
glGetDoublev(GL_PROJECTION_MATRIX, projmatrix);

real_y = viewport[3] - (GLint) y - 1;   // viewport[3] is height of window in pixels
gluUnProject((GLdouble) x, (GLdouble) real_y, 1.0, mvmatrix, projmatrix, viewport, &mx, &my, &mz);

/*  'mouse' is the point where mouse projection reaches FAR_PLANE.
    World coordinates is intersection of line(camera->mouse) with plane(z=0) (see LaMothe 306)

    Equation of line in 3D:
        (x-x0)/a = (y-y0)/b = (z-z0)/c      

    Intersection of line with plane:
        z = 0
        x-x0 = a(z-z0)/c  <=> x = x0+a(0-z0)/c  <=> x = x0 -a*z0/c
        y = y0 - b*z0/c

*/
double lx = fPosition.x - mx;
double ly = fPosition.y - my;
double lz = fPosition.z - mz;
double sum = lx*lx + ly*ly + lz*lz;
double normal = sqrt(sum);
double z0_c = fPosition.z / (lz/normal);

vec->x = (float) (fPosition.x - (lx/normal)*z0_c);
vec->y = (float) (fPosition.y - (ly/normal)*z0_c);
vec->z = 0.0f;
</code>

}

4
user14208

Pour suivre l'approche de Rons: Vous pouvez trouver vos valeurs z si vous savez comment vous avez fait pivoter votre rectangle.

L'astuce consiste à trouver la matrice projective qui a fait la projection. Heureusement, cela est possible et même bon marché. Les mathématiques pertinentes peuvent être trouvées dans l'article "Mappages projectifs pour la déformation d'image" par Paul Heckbert.

http://pages.cs.wisc.edu/~dyer/cs766/readings/heckbert-proj.pdf

De cette façon, vous pouvez récupérer la partie homogène de chaque sommet qui a été perdue lors de la projection.

Maintenant, il vous reste quatre lignes au lieu de points (comme l'explique Ron). Puisque vous connaissez la taille de votre rectangle d'origine, rien n'est perdu. Vous pouvez maintenant brancher les données de la méthode de Ron et de l'approche 2D dans un solveur d'équation linéaire et résoudre pour z. Vous obtenez ainsi les valeurs z exactes de chaque sommet.

Remarque: cela fonctionne simplement parce que:

  1. La forme originale était un rectangle
  2. Vous connaissez la taille exacte du rectangle dans l'espace 3D.

C'est vraiment un cas particulier.

J'espère que ça aide, Nils

2
Nils Pipenbrinck

En supposant que les points font effectivement partie d'un rectangle, je donne une idée générique:

Trouvez deux points avec une distance maximale entre eux: ceux-ci définissent très probablement une diagonale (exception: cas particuliers où le rectangle est presque parallèle au plan YZ, laissé à l'élève). Appelez-les A, C. Calculez les angles BAD, BCD. Ceux-ci, par rapport aux angles droits, vous donnent une orientation dans l'espace 3D. Pour connaître la distance z, vous devez corréler les côtés projetés avec les côtés connus, puis, sur la base de la méthode de projection 3D (est-ce 1/z?), Vous êtes sur la bonne voie pour connaître les distances.

2
tzot

Je sortirai mon livre d'algèbre linéaire quand je rentrerai si personne ne répond. Mais @ D G, toutes les matrices ne sont pas inversibles. Les matrices singulières ne sont pas inversibles (lorsque déterminant = 0). Cela se produira en fait tout le temps, car une matrice de projection doit avoir des valeurs propres de 0 et 1, et être carrée (car elle est idempotente, donc p ^ 2 = p).

Un exemple simple est, [[0 1] [0 1]] puisque le déterminant = 0, et c'est une projection sur la ligne x = y!

1
nlucaroni

La projection que vous avez sur la surface 2D contient une infinité de rectangles 3D qui se projetteront sur la même forme 2D.

Pensez-y de cette façon: vous avez quatre points 3D qui composent le rectangle 3D. Appelez-les (x0, y0, z0), (x1, y1, z1), (x2, y2, z2) et (x3, y3, z3). Lorsque vous projetez ces points sur le plan x-y, vous déposez les coordonnées z: (x0, y0), (x1, y1), (x2, y2), (x3, y3).

Maintenant, vous voulez projeter à nouveau dans l'espace 3D, vous devez inverser l'ingénierie de ce que z0, .., z3 étaient. Mais tout ensemble de coordonnées z qui a) garde la même distance x-y entre les points, et b) garde la forme qu'un rectangle fonctionnera. Ainsi, tout membre de cet ensemble (infini) fera: {(z0 + i, z1 + i, z2 + i, z3 + i) | i <- R}.

Edit @Jarrett: Imaginez que vous avez résolu cela et que vous vous êtes retrouvé avec un rectangle dans l'espace 3D. Maintenant, imaginez faire glisser ce rectangle de haut en bas sur l'axe z. Ces quantités infinies de rectangles traduits ont tous la même projection x-y. Comment savez-vous que vous avez trouvé la "bonne"?

Edit # 2: D'accord, cela vient d'un commentaire que j'ai fait sur cette question - une approche plus intuitive pour raisonner à ce sujet.

Imaginez tenir un morceau de papier au-dessus de votre bureau. Imaginez que chaque coin du papier est doté d'un pointeur laser léger qui pointe vers le bureau. Le papier est l'objet 3D et les points du pointeur laser sur le bureau sont la projection 2D.

Maintenant, comment pouvez-vous dire à quelle hauteur du papier le papier est en regardant juste les points du pointeur laser?

Tu ne peux pas. Déplacez le papier vers le haut et vers le bas. Les pointeurs laser brillent toujours aux mêmes endroits sur le bureau, quelle que soit la hauteur du papier.

Trouver les coordonnées z dans la projection inverse, c'est comme essayer de trouver la hauteur du papier sur la base des points du pointeur laser sur le bureau seul.

1
Rob Dickerson

Si vous savez que la forme est un rectangle dans un plan, vous pouvez considérablement limiter davantage le problème. Vous ne pouvez certainement pas déterminer "quel" plan, vous pouvez donc choisir qu'il se trouve sur le plan où z = 0 et l'un des coins est à x = y = 0, et les bords sont parallèles à l'axe x/y.

Les points en 3d sont donc {0,0,0}, {w, 0,0}, {w, h, 0} et {0, h, 0}. Je suis presque certain que la taille absolue ne sera pas trouvée, donc seul le rapport w/h est relâché, c'est donc un inconnu.

Par rapport à ce plan, la caméra doit être à un certain point cx, cy, cz dans l'espace, doit pointer dans une direction nx, ny, nz (un vecteur de longueur un, donc l'un d'eux est redondant), et avoir une longueur focale/largeur_image facteur de w. Ces nombres se transforment en matrice de projection 3x3.

Cela donne un total de 7 inconnues: w/h, cx, cy, cz, nx, ny et w.

Vous avez un total de 8 connus: les 4 paires x + y.

Cela peut donc être résolu.

L'étape suivante consiste à utiliser Matlab ou Mathmatica.

1
spitzak

Oui, Monte Carlo fonctionne, mais j'ai trouvé une meilleure solution à ce problème. Ce code fonctionne parfaitement (et utilise OpenCV):

Cv2.CalibrateCamera(new List<List<Point3f>>() { points3d }, new List<List<Point2f>>() { points2d }, new Size(height, width), cameraMatrix, distCoefs, out rvecs, out tvecs, CalibrationFlags.ZeroTangentDist | CalibrationFlags.FixK1 | CalibrationFlags.FixK2 | CalibrationFlags.FixK3);

Cette fonction prend les points 3d et 2d connus, la taille de l'écran et renvoie la rotation (rvecs [0]), la traduction (tvecs [0]) et la matrice des valeurs intrinsèques de la caméra. C'est tout ce dont vous avez besoin.

1
Inflight

Lorsque vous projetez de 3D en 2D, vous perdez des informations.

Dans le cas simple d'un point unique, la projection inverse vous donnerait un rayon infini à travers l'espace 3D.

La reconstruction stéréoscopique commencera généralement avec deux images 2D et sera projetée toutes les deux en 3D. Recherchez ensuite une intersection des deux rayons 3D produits.

La projection peut prendre différentes formes. Orthogonale ou en perspective. Je suppose que vous supposez une projection orthogonale?

Dans votre cas, en supposant que vous aviez la matrice d'origine, vous auriez 4 rayons dans l'espace 3D. Vous pourrez alors contraindre le problème par vos dimensions de rectangle 3D et tenter de le résoudre.

La solution ne sera pas unique car une rotation autour de l'un ou l'autre axe qui est parallèle au plan de projection 2D sera ambiguë dans la direction. En d'autres termes, si l'image 2D est perpendiculaire à l'axe Z, la rotation du rectangle 3D dans le sens horaire ou antihoraire autour de l'axe X produira la même image. De même pour l'axe y.

Dans le cas où le plan du rectangle est parallèle à l'axe z, vous avez encore plus de solutions.

Comme vous n'avez pas la matrice de projection d'origine, une ambiguïté supplémentaire est introduite par un facteur d'échelle arbitraire qui existe dans n'importe quelle projection. Vous ne pouvez pas distinguer entre une mise à l'échelle dans la projection et une translation en 3D dans la direction de l'axe z. Ce n'est pas un problème si vous ne vous intéressez qu'aux positions relatives des 4 points dans l'espace 3D lorsqu'ils sont liés les uns aux autres et non au plan de la projection 2D.

Dans une perspective de projection, les choses deviennent plus difficiles ...

1
morechilli

Merci à @Vegard pour une excellente réponse. J'ai nettoyé un peu le code:

import pandas as pd
import numpy as np

class Point2:
    def __init__(self,x,y):
        self.x = x
        self.y = y

class Point3:
    def __init__(self,x,y,z):
        self.x = x
        self.y = y
        self.z = z

# Known 2D coordinates of our rectangle
i0 = Point2(318, 247)
i1 = Point2(326, 312)
i2 = Point2(418, 241)
i3 = Point2(452, 303)

# 3D coordinates corresponding to i0, i1, i2, i3
r0 = Point3(0, 0, 0)
r1 = Point3(0, 0, 1)
r2 = Point3(1, 0, 0)
r3 = Point3(1, 0, 1)

mat = [
    [1, 0, 0, 0],
    [0, 1, 0, 0],
    [0, 0, 1, 0],
    [0, 0, 0, 1],
]

def project(p, mat):
    #print mat
    x = mat[0][0] * p.x + mat[0][1] * p.y + mat[0][2] * p.z + mat[0][3] * 1
    y = mat[1][0] * p.x + mat[1][1] * p.y + mat[1][2] * p.z + mat[1][3] * 1
    w = mat[3][0] * p.x + mat[3][1] * p.y + mat[3][2] * p.z + mat[3][3] * 1
    return Point2(720 * (x / w + 1) / 2., 576 - 576 * (y / w + 1) / 2.)

# The squared distance between two points a and b
def norm2(a, b):
    dx = b.x - a.x
    dy = b.y - a.y
    return dx * dx + dy * dy

def evaluate(mat): 
    c0 = project(r0, mat)
    c1 = project(r1, mat)
    c2 = project(r2, mat)
    c3 = project(r3, mat)
    return norm2(i0, c0) + norm2(i1, c1) + norm2(i2, c2) + norm2(i3, c3)    

def perturb(mat, amount):
    from copy import deepcopy
    from random import randrange, uniform
    mat2 = deepcopy(mat)
    mat2[randrange(4)][randrange(4)] += uniform(-amount, amount)
    return mat2

def approximate(mat, amount, n=1000):
    est = evaluate(mat)
    for i in xrange(n):
        mat2 = perturb(mat, amount)
        est2 = evaluate(mat2)
        if est2 < est:
            mat = mat2
            est = est2

    return mat, est

for i in xrange(1000):
    mat,est = approximate(mat, 1)
    print mat
    print est

L'appel approximatif avec .1 n'a pas fonctionné pour moi, alors je l'ai retiré. Je l'ai couru pendant un moment aussi, et la dernière fois que j'ai vérifié c'était à

[[0.7576315397559887, 0, 0.11439449272592839, -0.314856490473439], 
[0.06440497208710227, 1, -0.5607502645413118, 0.38338196981556827], 
[0, 0, 1, 0], 
[0.05421620936883742, 0, -0.5673977598434641, 2.693116299312736]]

avec une erreur autour de 0,02.

1
BBDynSys