iOS reverter projeção da câmera

Estou tentando estimar a posição do meu dispositivo relacionada a um código QR no espaço. Estou usando o ARKit e a estrutura Vision, ambos introduzidos no iOS11, mas a resposta a esta pergunta provavelmente não depende deles.

Com a estrutura do Vision, sou capaz de obter o retângulo que limita um código QR no quadro da câmera. Gostaria de combinar esse retângulo com a tradução e a rotação do dispositivo necessárias para transformar o código QR de uma posição padrão.

Por exemplo, se eu observar o quadro:

*            *

    B
          C
  A
       D


*            *

enquanto eu estivesse a 1 m de distância do código QR, centralizado nele, e assumindo que o código QR tivesse um lado de 10 cm, eu veria:

*            *


    A0  B0

    D0  C0


*            *

qual foi a transformação do meu dispositivo entre esses dois quadros? Entendo que um resultado exato pode não ser possível, porque talvez o código QR observado seja um pouco não plano e estamos tentando estimar uma transformação afim em algo que não é perfeitamente perfeito.

Eu acho que osceneView.pointOfView?.camera?.projectionTransform é mais útil que osceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrix já que o último já leva em conta a transformação inferida no ARKit que não me interessa esse problema.

Como eu preencheria

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

Depois de tentar várias coisas, acabei indo para a estimativa de pose da câmera usando a projeção openCV e o solucionador de perspectiva,solvePnP Isso me dá uma rotação e tradução que deve representar a pose da câmera no referencial do código QR. No entanto, ao usar esses valores e colocar objetos correspondentes à transformação inversa, onde o código QR deve estar no espaço da câmera, recebo valores deslocados imprecisos e não consigo fazer a rotação funcionar:

// 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
}

Aqui está a saída:

onde A, B, C, D são os cantos do código QR na ordem em que são passados para o programa.

A origem prevista permanece no lugar quando o telefone gira, mas mudou de onde deveria estar. Surpreendentemente, se eu alterar os valores das observações, sou capaz de corrigir isso:

  // (imageSize.height * (1 - $0.y), imageSize.width * $0.x)
  // replaced by:
  (imageSize.height * (1.35 - $0.y), imageSize.width * ($0.x - 0.2))

e agora a origem prevista permanece robusta no lugar. No entanto, eu não entendo de onde vêm os valores das mudanças.

Por fim, tentei corrigir uma orientação relativamente ao referencial do código 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)

A orientação é boa quando olho diretamente para o código QR, mas depois muda de algo que parece estar relacionado à rotação do telefone:

As perguntas pendentes que tenho são:

Como faço para resolver a rotação?De onde vêm os valores de mudança de posição?Que relação simples a rotação, a tradução, o QRCornerCoordinatesInQRRef, as observações e os intrísicos verificam? É O ~ K ^ -1 * (R_3x2 | T) Q? Porque se sim, isso ocorre por uma ordem de magnitude.

Se isso for útil, aqui estão alguns 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

==== Edit2 ====

Notei que a rotação funciona bem quando o telefone fica horizontalmente paralelo ao código QR (ou seja, a matriz de rotação é [[a, 0, b], [0, 1, 0], [c, 0, d]] ), independentemente da orientação atual do código QR:

Outra rotação não funciona.

questionAnswers(2)

yourAnswerToTheQuestion