iOS rétablit la projection de la caméra

87

J'essaie d'estimer la position de mon appareil par rapport à un code QR dans l'espace. J'utilise ARKit et le framework Vision, tous deux introduits dans iOS11, mais la réponse à cette question ne dépend probablement pas d'eux.

Avec le framework Vision, je peux obtenir le rectangle qui délimite un code QR dans le cadre de la caméra. Je voudrais faire correspondre ce rectangle à la translation et à la rotation de l'appareil nécessaires pour transformer le code QR à partir d'une position standard.

Par exemple si j'observe le cadre:

*            *

    B
          C
  A
       D


*            *

alors que si j'étais à 1 m du code QR, centré dessus, et en supposant que le code QR a un côté de 10 cm, je verrais:

*            *


    A0  B0

    D0  C0


*            *

quelle a été la transformation de mon appareil entre ces deux cadres? Je comprends qu'un résultat exact pourrait ne pas être possible, car peut-être que le code QR observé est légèrement non planaire et que nous essayons d'estimer une transformation affine sur quelque chose qui n'en est pas parfaitement une.

Je suppose que le sceneView.pointOfView?.camera?.projectionTransformest plus utile que le sceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrixcar ce dernier prend déjà en compte la transformation déduite de l'ARKit qui ne m'intéresse pas pour ce problème.

Comment pourrais-je remplir

func get transform(
  qrCodeRectangle: VNBarcodeObservation,
  cameraTransform: SCNMatrix4) {
  // qrCodeRectangle.topLeft etc is the position in [0, 1] * [0, 1] of A0

  // expected real world position of the QR code in a referential coordinate system
  let a0 = SCNVector3(x: -0.05, y: 0.05, z: 1)
  let b0 = SCNVector3(x: 0.05, y: 0.05, z: 1)
  let c0 = SCNVector3(x: 0.05, y: -0.05, z: 1)
  let d0 = SCNVector3(x: -0.05, y: -0.05, z: 1)

  let A0, B0, C0, D0 = ?? // CGPoints representing position in
                          // camera frame for camera in 0, 0, 0 facing Z+

  // then get transform from 0, 0, 0 to current position/rotation that sees
  // a0, b0, c0, d0 through the camera as qrCodeRectangle 
}

==== Modifier ====

Après avoir essayé un certain nombre de choses, j'ai fini par essayer d'estimer la pose de la caméra en utilisant la projection openCV et le solveur de perspective.Cela solvePnPme donne une rotation et une traduction qui devraient représenter la pose de la caméra dans le référentiel de code QR. Cependant, lorsque vous utilisez ces valeurs et que vous placez des objets correspondant à la transformation inverse, où le code QR doit être dans l'espace de la caméra, j'obtiens des valeurs décalées inexactes et je ne parviens pas à faire fonctionner la rotation:

// some flavor of pseudo code below
func renderer(_ sender: SCNSceneRenderer, updateAtTime time: TimeInterval) {
  guard let currentFrame = sceneView.session.currentFrame, let pov = sceneView.pointOfView else { return }
  let intrisics = currentFrame.camera.intrinsics
  let QRCornerCoordinatesInQRRef = [(-0.05, -0.05, 0), (0.05, -0.05, 0), (-0.05, 0.05, 0), (0.05, 0.05, 0)]

  // uses VNDetectBarcodesRequest to find a QR code and returns a bounding rectangle
  guard let qr = findQRCode(in: currentFrame) else { return }

  let imageSize = CGSize(
    width: CVPixelBufferGetWidth(currentFrame.capturedImage),
    height: CVPixelBufferGetHeight(currentFrame.capturedImage)
  )

  let observations = [
    qr.bottomLeft,
    qr.bottomRight,
    qr.topLeft,
    qr.topRight,
  ].map({ (imageSize.height * (1 - $0.y), imageSize.width * $0.x) })
  // image and SceneKit coordinated are not the same
  // replacing this by:
  // (imageSize.height * (1.35 - $0.y), imageSize.width * ($0.x - 0.2))
  // weirdly fixes an issue, see below

  let rotation, translation = openCV.solvePnP(QRCornerCoordinatesInQRRef, observations, intrisics)
  // calls openCV solvePnP and get the results

  let positionInCameraRef = -rotation.inverted * translation
  let node = SCNNode(geometry: someGeometry)
  pov.addChildNode(node)
  node.position = translation
  node.orientation = rotation.asQuaternion
}

Voici la sortie:

entrez la description de l'image ici

où A, B, C, D sont les coins du code QR dans l'ordre où ils sont passés au programme.

L'origine prédite reste en place lorsque le téléphone tourne, mais elle est décalée de l'endroit où elle devrait être. Étonnamment, si je décale les valeurs des observations, je suis en mesure de corriger ceci:

  // (imageSize.height * (1 - $0.y), imageSize.width * $0.x)
  // replaced by:
  (imageSize.height * (1.35 - $0.y), imageSize.width * ($0.x - 0.2))

entrez la description de l'image ici

et maintenant l'origine prédite reste solidement en place. Cependant, je ne comprends pas d'où viennent les valeurs de changement.

Enfin, j'ai essayé de fixer une orientation par rapport au référentiel du code QR:

    var n = SCNNode(geometry: redGeometry)
    node.addChildNode(n)
    n.position = SCNVector3(0.1, 0, 0)
    n = SCNNode(geometry: blueGeometry)
    node.addChildNode(n)
    n.position = SCNVector3(0, 0.1, 0)
    n = SCNNode(geometry: greenGeometry)
    node.addChildNode(n)
    n.position = SCNVector3(0, 0, 0.1)

L'orientation est bonne quand je regarde le code QR directement, mais ensuite il se décale par quelque chose qui semble être lié à la rotation du téléphone:entrez la description de l'image ici

Les questions en suspens que j'ai sont:

  • Comment résoudre la rotation?
  • d'où viennent les valeurs de décalage de position?
  • Quelle relation simple la rotation, la translation, QRCornerCoordinatesInQRRef, les observations, les intrisics vérifient-elles? Est-ce O ~ K ^ -1 * (R_3x2 | T) Q? Parce que si c'est le cas, c'est de quelques ordres de grandeur.

Si cela est utile, voici quelques valeurs numériques:

Intrisics matrix
Mat 3x3
1090.318, 0.000, 618.661
0.000, 1090.318, 359.616
0.000, 0.000, 1.000

imageSize
1280.0, 720.0
screenSize
414.0, 736.0

==== Edit2 ====

J'ai remarqué que la rotation fonctionne bien lorsque le téléphone reste horizontalement parallèle au code QR (c'est-à-dire que la matrice de rotation est [[a, 0, b], [0, 1, 0], [c, 0, d]] ), quelle que soit l'orientation réelle du code QR:

entrez la description de l'image ici

L'autre rotation ne fonctionne pas.

Guig
la source
Hé, essayez-vous d'obtenir la distance des appareils via le code QR? Si oui, voyez ma réponse ci-dessous.
Ephellon Dantzler
EDIT: pour vos questions en suspens, 1. Il semble qu'il y ait simplement une valeur inutile insérée. Peut-être dans la méthode de cartographie appelée, ou toute autre chose traitant des cercles en cours de dessin (comme drawCircle(... rotation)) 2. Je n'ai pas eu le temps de lire les spécifications 3. Idem 2
Ephellon Dantzler
Serez-vous capable de partager du code?
Michal Zaborowski

Réponses:

1

Math (trig.):

Équation

Remarques: le bas est l(la longueur du code QR), l'angle gauche est ket l'angle supérieur est i(la caméra)

Image

Ephellon Dantzler
la source
bien sûr, mais je ne connais que l'angle observé iet la distance d'originel
Guig
c'est bien, y a-t-il un moyen de trouver le contraire de i? Si ce n'est pas le bon angle, lil y a plus de maths pour trouver soit kou theta; i + k + theta = 180.
Ephellon Dantzler
1
Pour faire fonctionner la trigonométrie, j'ai besoin soit de deux distances et d'un angle, soit de deux angles et d'une distance. Il n'y a aucun moyen d'obtenir tout à partir d'un seul angle et d'une seule distance
Guig
Cela aide-t-il que le code QR soit carré, de sorte que vous puissiez observer deux angles, à la fois vertical et horizontal?
Bob Wakefield
1

Je suppose que le problème n'est pas dans la matrice. C'est dans le placement des sommets. Pour suivre les images 2D, vous devez placer les sommets ABCD dans le sens inverse des aiguilles d'une montre (le point de départ est un sommet situé à l' origine imaginaire x:0, y:0 ). Je pense que la documentation Apple sur la classe VNRectangleObservation (informations sur les régions rectangulaires projetées détectées par une demande d'analyse d'image) est vague. Vous avez placé vos sommets dans le même ordre que dans la documentation officielle:

var bottomLeft: CGPoint
var bottomRight: CGPoint
var topLeft: CGPoint
var topRight: CGPoint

Mais ils doivent être placés de la même manière que la direction de rotation positive (autour de l' Zaxe) se produit dans le système de coordonnées cartésiennes:

entrez la description de l'image ici

L'espace de coordonnées du monde dans ARKit (ainsi que dans SceneKit et Vision) suit toujours un right-handed convention(l' Yaxe positif pointe vers le haut, l' Zaxe positif pointe vers le spectateur et l' Xaxe positif pointe vers la droite du spectateur), mais est orienté en fonction de la configuration de votre session . La caméra fonctionne dans l'espace de coordonnées local.

Le sens de rotation autour de n'importe quel axe est positif (sens anti-horaire) et négatif (sens horaire). Pour le suivi dans ARKit et Vision, c'est d'une importance cruciale.

entrez la description de l'image ici

L'ordre de rotation a également un sens. ARKit, ainsi que SceneKit, applique la rotation par rapport à la propriété pivot du nœud dans l'ordre inverse des composants: d'abord roll(autour de l' Zaxe), puis yaw(autour de l' Yaxe), puis pitch(autour de l' Xaxe). Donc, l'ordre de rotation est ZYX.

En outre, il existe un article utile sur les opérations de matrice sur Nukepedia.

Andy Fedoroff
la source