iOS revertir la proyección de la cámara

87

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?.projectionTransformes más útil que el, sceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrixya 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. solvePnPEsto 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:

ingrese la descripción de la imagen aquí

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))

ingrese la descripción de la imagen aquí

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:ingrese la descripción de la imagen aquí

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:

ingrese la descripción de la imagen aquí

Otra rotación no funciona.

Guig
fuente
Oye, ¿estás tratando de obtener la distancia de los dispositivos a través del código QR? Si es así, vea mi respuesta a continuación.
Ephellon Dantzler
EDITAR: para sus preguntas pendientes, 1. Parece que simplemente se ha insertado un valor innecesario. Posiblemente en el método de mapeo llamado, o cualquier otra cosa relacionada con los círculos que se dibujan (como drawCircle(... rotation)) 2. No he tenido tiempo de leer las especificaciones 3. Igual que 2
Ephellon Dantzler
¿Podrás compartir algún código?
Michal Zaborowski

Respuestas:

1

Matemáticas (Trig.):

Ecuación

Notas: la parte inferior es l(la longitud del código QR), el ángulo izquierdo es ky el ángulo superior es i(la cámara)

Imagen

Ephellon Dantzler
fuente
seguro, pero solo conozco el ángulo observado iy la distancia originall
Guig
eso está bien, ¿hay alguna manera de encontrar lo contrario de i? Si no es un ángulo recto, lentonces hay más matemáticas involucradas para encontrar ko theta; i + k + theta = 180.
Ephellon Dantzler
1
Para que la trigonometría funcione, necesito dos distancias y un ángulo, o dos ángulos y una distancia. No hay forma de obtener todo desde un solo ángulo y una distancia
Guig
¿Ayuda que el código QR sea cuadrado, de modo que puedas observar dos ángulos, tanto vertical como horizontal?
Bob Wakefield
1

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 Zeje) ocurre en el sistema de coordenadas cartesianas:

ingrese la descripción de la imagen aquí

World Coordinate Space en ARKit (así como en SceneKit y Vision) siempre sigue a right-handed convention(el Yeje positivo apunta hacia arriba, el Zeje positivo apunta hacia el espectador y el Xeje 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.

ingrese la descripción de la imagen aquí

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 el Zeje), luego yaw(sobre el Yeje), luego pitch(sobre el Xeje). Entonces el orden de rotación es ZYX.

Además, hay una publicación útil sobre Matrix Operations en Nukepedia.

Andy Fedoroff
fuente