Estou tentando estimar a posição do meu dispositivo em relação a um código QR no espaço. Estou usando o ARKit e o framework Vision, ambos introduzidos no iOS11, mas a resposta a essa pergunta provavelmente não depende deles.
Com o framework Vision, consigo obter o retângulo que delimita um código QR no frame da câmera. Eu gostaria de combinar este retângulo com a translação e rotação do dispositivo necessária para transformar o código QR de uma posição padrão.
Por exemplo, se eu observar o quadro:
* *
B
C
A
D
* *
enquanto se eu estivesse a 1m de distância do código QR, centralizado nele, e assumindo que o código QR tem um lado de 10 cm eu veria:
* *
A0 B0
D0 C0
* *
qual foi a transformação do meu dispositivo entre esses dois quadros? Eu entendo que um resultado exato pode não ser possível, porque talvez o código QR observado seja ligeiramente não plano e estejamos tentando estimar uma transformação afim em algo que não é perfeito.
Acho que o sceneView.pointOfView?.camera?.projectionTransform
é mais útil do que o, sceneView.pointOfView?.camera?.projectionTransform?.camera.projectionMatrix
visto que o último já leva em consideração a transformação inferida do ARKit na qual não estou interessado para esse problema.
Como eu encheria
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 optando por estimar a pose da câmera usando projeção openCV e solucionador de perspectiva. solvePnP
Isso me dá uma rotação e translaçã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 deveria 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á o resultado:
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 é deslocada de onde deveria estar. Surpreendentemente, se eu mudar os valores das observações, posso 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, não entendo de onde vêm os valores de mudança.
Finalmente, tentei obter uma orientação fixa em relação 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 eu olho para o código QR diretamente, mas então ele muda para algo que parece estar relacionado à rotação do telefone:
As dúvidas 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, translação, QRCornerCoordinatesInQRRef, observações e intrísicos verificam? É O ~ K ^ -1 * (R_3x2 | T) Q? Porque se for assim, isso está errado por algumas ordens 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 permanece horizontalmente paralelo ao código QR (ou seja, a matriz de rotação é [[a, 0, b], [0, 1, 0], [c, 0, d]] ), não importa qual seja a orientação real do código QR:
Outra rotação não funciona.