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.
drawCircle(... rotation)
) 2. No he tenido tiempo de leer las especificaciones 3. Igual que 2Respuestas:
Matemáticas (Trig.):
Notas: la parte inferior es
l
(la longitud del código QR), el ángulo izquierdo esk
y el ángulo superior esi
(la cámara)fuente
i
y la distancia originall
i
? Si no es un ángulo recto,l
entonces hay más matemáticas involucradas para encontrark
otheta
;i + k + theta = 180
.Supongo que el problema no está en matrix. Está en la ubicación de los vértices. Para rastrear imágenes 2D, debe colocar vértices ABCD en sentido antihorario (el punto de partida es un vértice ubicado en el origen imaginario
x:0, y:0
). Creo que la documentación de Apple en la clase VNRectangleObservation (información sobre regiones rectangulares proyectadas detectadas por una solicitud de análisis de imagen) es vaga. Colocó sus vértices en el mismo orden que en la documentación oficial:var bottomLeft: CGPoint var bottomRight: CGPoint var topLeft: CGPoint var topRight: CGPoint
Pero deben colocarse de la misma manera que la dirección de rotación positiva (alrededor del
Z
eje) ocurre en el sistema de coordenadas cartesianas:World Coordinate Space en ARKit (así como en SceneKit y Vision) siempre sigue a
right-handed convention
(elY
eje positivo apunta hacia arriba, elZ
eje positivo apunta hacia el espectador y elX
eje positivo apunta hacia la derecha del espectador), pero está orientado según la configuración de su sesión . La cámara funciona en el espacio de coordenadas locales.La dirección de rotación sobre cualquier eje es positiva (en sentido antihorario) y negativa (en sentido horario). Para el seguimiento en ARKit y Vision, es de vital importancia.
El orden de rotación también tiene sentido. ARKit, así como SceneKit, aplica la rotación relativa a la propiedad de pivote del nodo en el orden inverso de los componentes: primero
roll
(sobre elZ
eje), luegoyaw
(sobre elY
eje), luegopitch
(sobre elX
eje). Entonces el orden de rotación esZYX
.fuente