Estoy tratando de estimar la posición de mi dispositivo relacionada con un código QR en el espacio. Estoy usando ARKit y el marco Vision, ambos introducidos en iOS11, pero la respuesta a esta pregunta probablemente no dependa de ellos.
Con el marco Vision, puedo obtener el rectángulo que delimita un código QR en el marco de la cámara. Me gustaría hacer coincidir este rectángulo con la traducción y rotación del dispositivo necesarias para transformar el código QR desde una posición estándar.
Por ejemplo, si observo el marco:
* *
B
C
A
D
* *
mientras que si estuviera a 1 m del código QR, centrado en él, y asumiendo que el código QR tiene un lado de 10 cm, vería:
* *
A0 B0
D0 C0
* *
¿Cuál ha sido la transformación de mi dispositivo entre esos dos marcos? Entiendo que un resultado exacto podría no ser posible, porque tal vez el código QR observado no sea un poco plano y estamos tratando de estimar una transformación afín en algo que no lo es perfectamente.
Supongo que sceneView.pointOfView?.camera?.projectionTransform
es más útil que el, sceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrix
ya que el último ya tiene en cuenta la transformación inferida del ARKit que no me interesa para este problema.
¿Cómo llenaría
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
}
==== Editar ====
Después de probar varias cosas, terminé optando por la estimación de la pose de la cámara usando la proyección openCV y el solucionador de perspectiva. solvePnP
Esto me da una rotación y traducción que debería representar la pose de la cámara en el código QR referencial. Sin embargo, al usar esos valores y colocar objetos correspondientes a la transformación inversa, donde el código QR debería estar en el espacio de la cámara, obtengo valores desplazados inexactos y no puedo hacer que la rotación funcione:
// 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
}
Aquí está el resultado:
donde A, B, C, D son las esquinas del código QR en el orden en que se pasan al programa.
El origen predicho permanece en su lugar cuando el teléfono gira, pero se desplaza de donde debería estar. Sorprendentemente, si cambio los valores de las observaciones, puedo corregir esto:
// (imageSize.height * (1 - $0.y), imageSize.width * $0.x)
// replaced by:
(imageSize.height * (1.35 - $0.y), imageSize.width * ($0.x - 0.2))
y ahora el origen predicho se mantiene firmemente en su lugar. Sin embargo, no entiendo de dónde provienen los valores de cambio.
Finalmente, intenté fijar una orientación en relación con el código QR referencial:
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)
La orientación está bien cuando miro el código QR directamente, pero luego cambia por algo que parece estar relacionado con la rotación del teléfono:
Las preguntas pendientes que tengo son:
- ¿Cómo resuelvo la rotación?
- ¿De dónde provienen los valores de cambio de posición?
- ¿Qué relación simple verifican rotación, traslación, QRCornerCoordinatesInQRRef, observaciones, intrínsecas? ¿Es O ~ K ^ -1 * (R_3x2 | T) Q? Porque si es así, eso está desviado por un orden de magnitud.
Si eso es útil, aquí hay algunos valores numéricos:
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
==== Editar2 ====
Noté que la rotación funciona bien cuando el teléfono permanece horizontalmente paralelo al código QR (es decir, la matriz de rotación es [[a, 0, b], [0, 1, 0], [c, 0, d]] ), sin importar cuál sea la orientación real del código QR:
Otra rotación no funciona.