Sto cercando di stimare la posizione del mio dispositivo in relazione a un codice QR nello spazio. Sto usando ARKit e il framework Vision, entrambi introdotti in iOS11, ma la risposta a questa domanda probabilmente non dipende da loro.
Con il framework Vision, sono in grado di ottenere il rettangolo che delimita un codice QR nella cornice della fotocamera. Vorrei abbinare questo rettangolo alla traslazione e rotazione del dispositivo necessarie per trasformare il codice QR da una posizione standard.
Ad esempio, se osservo la cornice:
* *
B
C
A
D
* *
mentre se fossi a 1 metro di distanza dal codice QR, centrato su di esso e assumendo che il codice QR abbia un lato di 10 cm vedrei:
* *
A0 B0
D0 C0
* *
qual è stata la trasformazione del mio dispositivo tra questi due frame? Capisco che un risultato esatto potrebbe non essere possibile, perché forse il codice QR osservato è leggermente non planare e stiamo cercando di stimare una trasformazione affine su qualcosa che non è perfettamente uno.
Immagino che sceneView.pointOfView?.camera?.projectionTransform
sia più utile di sceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrix
poiché il secondo tiene già conto della trasformazione dedotta da ARKit a cui non sono interessato per questo problema.
Come mi riempirei
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
}
==== Modifica ====
Dopo aver provato un certo numero di cose, ho finito per optare per la stima della posa della fotocamera utilizzando la proiezione openCV e il risolutore prospettico, solvePnP
questo mi dà una rotazione e una traduzione che dovrebbero rappresentare la posa della fotocamera nel codice QR referenziale. Tuttavia, quando si utilizzano questi valori e si posizionano oggetti corrispondenti alla trasformazione inversa, dove il codice QR dovrebbe essere nello spazio della fotocamera, ottengo valori spostati imprecisi e non sono in grado di far funzionare la rotazione:
// 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
}
Ecco l'output:
dove A, B, C, D sono gli angoli del codice QR nell'ordine in cui vengono passati al programma.
L'origine prevista rimane in posizione quando il telefono ruota, ma viene spostata da dove dovrebbe essere. Sorprendentemente, se sposto i valori delle osservazioni, sono in grado di correggere questo:
// (imageSize.height * (1 - $0.y), imageSize.width * $0.x)
// replaced by:
(imageSize.height * (1.35 - $0.y), imageSize.width * ($0.x - 0.2))
e ora l'origine prevista rimane saldamente al suo posto. Tuttavia non capisco da dove provengano i valori di spostamento.
Infine, ho cercato di ottenere un orientamento fisso relativamente al codice QR referenziale:
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'orientamento va bene quando guardo il codice QR direttamente, ma poi si sposta di qualcosa che sembra essere correlato alla rotazione del telefono:
Le domande in sospeso che ho sono:
- Come risolvo la rotazione?
- da dove vengono i valori di spostamento di posizione?
- Quale semplice relazione verificano rotazione, traduzione, QRCornerCoordinatesInQRRef, osservazioni, intrisics? È O ~ K ^ -1 * (R_3x2 | T) Q? Perché se è così è sbagliato di pochi ordini di grandezza.
Se è utile, ecco alcuni valori numerici:
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
==== Modifica2 ====
Ho notato che la rotazione funziona bene quando il telefono rimane orizzontalmente parallelo al codice QR (ovvero la matrice di rotazione è [[a, 0, b], [0, 1, 0], [c, 0, d]] ), indipendentemente dall'orientamento effettivo del codice QR:
Altre rotazioni non funzionano.