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.