web-dev-qa-db-fra.com

iOS inverser la projection de la caméra

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 cadre 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é sur lui, 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 images? Je comprends qu'un résultat exact pourrait ne pas être possible, car le code QR observé est peut-être légèrement non plan et nous essayons d'estimer une transformation affine sur quelque chose qui n'est pas parfaitement.

Je suppose que le sceneView.pointOfView?.camera?.projectionTransform est plus utile que sceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrix puisque la dernière 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 opter pour l'estimation de la pose de la caméra en utilisant la projection openCV et le solveur de perspective, solvePnP Cela me 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 j'utilise ces valeurs et que je place 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 suis pas en mesure de 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:

enter image description here

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

L'origine prévue 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 cela:

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

enter image description here

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

Enfin, j'ai essayé d'obtenir une orientation fixe relativement au référentiel de 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 correcte lorsque je regarde le code QR directement, mais il décale ensuite quelque chose qui semble être lié à la rotation du téléphone: enter image description here

Les questions en suspens que j'ai sont:

  • Comment résoudre la rotation?
  • d'où viennent les valeurs de changement de position?
  • Quelle relation simple la rotation, la traduction, QRCornerCoordinatesInQRRef, observations, intrisics vérifient-elles? Est-ce O ~ K ^ -1 * (R_3x2 | T) Q? Parce que si c'est le cas, c'est désactivé 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 correctement 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:

enter image description here

Les autres rotations ne fonctionnent pas.

86
Guig

Je suppose que le problème n'est pas dans la matrice. C'est dans le placement des sommets. Pour suivre des images 2D, vous devez placer les sommets ABCD dans le sens antihoraire (le point de départ est un sommet situé dans origine imaginaire x:0, y:0). Je pense que la classe Apple Documentation sur 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 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 (environ Z axe) se produit dans le système de coordonnées cartésiennes:

enter image description here

L'espace de coordonnées mondiales dans ARKit (ainsi que dans SceneKit et Vision) suit toujours un right-handed convention (l'axe positif Y pointe vers le haut, l'axe positif Z pointe vers le spectateur et l'axe positif X 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 (anti-horaire) et négatif (horaire). Pour le suivi dans ARKit et Vision, il est extrêmement important.

enter image description here

L'ordre de rotation est également logique. ARKit, ainsi que SceneKit, applique une rotation par rapport à la propriété pivot du nœud dans l'ordre inverse des composants: d'abord roll (environ Z axe), puis yaw (environ Y axe), puis pitch (environ X axe). Ainsi, l'ordre de rotation est ZYX.

En outre, il y a un article utile sur Opérations matricielles sur Nukepedia.

1
ARGeo

Math (Trig.):

Equation

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

Picture

1
Ephellon Dantzler