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?.projectionTransform
est plus utile que le sceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrix
car 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 solvePnP
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 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:
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))
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:
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:
L'autre rotation ne fonctionne pas.
drawCircle(... rotation)
) 2. Je n'ai pas eu le temps de lire les spécifications 3. Idem 2Réponses:
Math (trig.):
Remarques: le bas est
l
(la longueur du code QR), l'angle gauche estk
et l'angle supérieur esti
(la caméra)la source
i
et la distance d'originel
i
? Si ce n'est pas le bon angle,l
il y a plus de maths pour trouver soitk
outheta
;i + k + theta = 180
.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'
Z
axe) se produit dans le système de coordonnées cartésiennes:L'espace de coordonnées du monde dans ARKit (ainsi que dans SceneKit et Vision) suit toujours un
right-handed convention
(l'Y
axe positif pointe vers le haut, l'Z
axe positif pointe vers le spectateur et l'X
axe 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.
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'Z
axe), puisyaw
(autour de l'Y
axe), puispitch
(autour de l'X
axe). Donc, l'ordre de rotation estZYX
.la source