Estimación de pose de cámara desde homografía o con función solvePnP ()

Estoy tratando de construir una escena de realidad aumentada estática sobre una foto con 4 correspondencias definidas entre puntos coplanares en un plano y una imagen.

Aquí hay un flujo paso a paso:

El usuario agrega una imagen usando la cámara del dispositivo. Supongamos que contiene un rectángulo capturado con cierta perspectiva.El usuario define el tamaño físico del rectángulo, que se encuentra en el plano horizontal (YOZ en términos de SceneKit). Supongamos que su centro es el origen del mundo (0, 0, 0), por lo que podemos encontrar fácilmente (x, y, z) para cada esquina.El usuario define las coordenadas uv en el sistema de coordenadas de la imagen para cada esquina del rectángulo.La escena SceneKit se crea con un rectángulo del mismo tamaño y visible desde la misma perspectiva.Se pueden agregar y mover otros nodos en la escena.

También he medido la posición de la cámara del iPhone en relación con el centro del papel A4. Entonces, para esta toma, la posición se midió (0, 14, 42.5) en cm. Además, mi iPhone estaba ligeramente inclinado sobre la mesa (5-10 grados)

Usando estos datos que he configuradoSCNCamera para obtener la perspectiva deseada del plano azul en la tercera imagen:

let camera = SCNCamera()
camera.xFov = 66
camera.zFar = 1000
camera.zNear = 0.01

cameraNode.camera = camera
cameraAngle = -7 * CGFloat.pi / 180
cameraNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: Float(cameraAngle))
cameraNode.position = SCNVector3(x: 0, y: 14, z: 42.5)

Esto me dará una referencia para comparar mi resultado con.

Para construir AR con SceneKit necesito:

Ajuste el fov de SCNCamera, para que coincida con el fov de la cámara real.Calcule la posición y la rotación para el nodo de la cámara usando 4 correcciones de carga entre los puntos mundiales (x, 0, z) y los puntos de imagen (u, v)

H - homografía;K - matriz intrínseca;[R | t] - matriz extrínseca

Intenté dos enfoques para encontrar la matriz de transformación para la cámara: usar solvePnP de OpenCV y el cálculo manual de la homografía basada en 4 puntos coplanares.

Enfoque manual:

1. Descubre la homografía

Este paso se realiza con éxito, ya que las coordenadas UV de origen mundial parecen ser correctas.

2. matriz intrínseca

Para obtener una matriz intrínseca del iPhone 6, he usadoesta aplicación, que me dio el siguiente resultado de 100 imágenes de resolución 640 * 480:

Suponiendo que la imagen de entrada tiene una relación de aspecto de 4: 3, puedo escalar la matriz anterior dependiendo de la resolución

No estoy seguro, pero se siente como un problema potencial aquí. He usado cv :: CalibrationMatrixValues para verificar fovx para la matriz intrínseca calculada y el resultado fue ~ 50 °, mientras que debería estar cerca de 60 °.

3. Matriz de pose de cámara

func findCameraPose(homography h: matrix_float3x3, size: CGSize) -> matrix_float4x3? {
    guard let intrinsic = intrinsicMatrix(imageSize: size),
        let intrinsicInverse = intrinsic.inverse else { return nil }

    let l1 = 1.0 / (intrinsicInverse * h.columns.0).norm
    let l2 = 1.0 / (intrinsicInverse * h.columns.1).norm
    let l3 = (l1+l2)/2

    let r1 = l1 * (intrinsicInverse * h.columns.0)
    let r2 = l2 * (intrinsicInverse * h.columns.1)
    let r3 = cross(r1, r2)

    let t = l3 * (intrinsicInverse * h.columns.2)

    return matrix_float4x3(columns: (r1, r2, r3, t))
}

Resultado:

Como medí la posición y orientación aproximadas para esta imagen en particular, conozco la matriz de transformación, que daría el resultado esperado y es bastante diferente:

También estoy un poco preocupado por el elemento 2-3 de la matriz de rotación de referencia, que es -9.1, aunque debería estar cerca de cero, ya que hay una rotación muy leve.

Enfoque de OpenCV:

Hay unsolvePnP funciona en OpenCV para este tipo de problemas, así que traté de usarlo en lugar de reinventar la rueda.

OpenCV en Objective-C ++:

typedef struct CameraPose {
    SCNVector4 rotationVector;
    SCNVector3 translationVector; 
} CameraPose;

+ (CameraPose)findCameraPose: (NSArray<NSValue *> *) objectPoints imagePoints: (NSArray<NSValue *> *) imagePoints size: (CGSize) size {

    vector<Point3f> cvObjectPoints = [self convertObjectPoints:objectPoints];
    vector<Point2f> cvImagePoints = [self convertImagePoints:imagePoints withSize: size];

    cv::Mat distCoeffs(4,1,cv::DataType<double>::type, 0.0);
    cv::Mat rvec(3,1,cv::DataType<double>::type);
    cv::Mat tvec(3,1,cv::DataType<double>::type);
    cv::Mat cameraMatrix = [self intrinsicMatrixWithImageSize: size];

    cv::solvePnP(cvObjectPoints, cvImagePoints, cameraMatrix, distCoeffs, rvec, tvec);

    SCNVector4 rotationVector = SCNVector4Make(rvec.at<double>(0), rvec.at<double>(1), rvec.at<double>(2), norm(rvec));
    SCNVector3 translationVector = SCNVector3Make(tvec.at<double>(0), tvec.at<double>(1), tvec.at<double>(2));
    CameraPose result = CameraPose{rotationVector, translationVector};

    return result;
}

+ (vector<Point2f>) convertImagePoints: (NSArray<NSValue *> *) array withSize: (CGSize) size {
    vector<Point2f> points;
    for (NSValue * value in array) {
        CGPoint point = [value CGPointValue];
        points.push_back(Point2f(point.x - size.width/2, point.y - size.height/2));
    }
    return points;
}

+ (vector<Point3f>) convertObjectPoints: (NSArray<NSValue *> *) array {
    vector<Point3f> points;
    for (NSValue * value in array) {
        CGPoint point = [value CGPointValue];
        points.push_back(Point3f(point.x, 0.0, -point.y));
    }
    return points;
}

+ (cv::Mat) intrinsicMatrixWithImageSize: (CGSize) imageSize {
    double f = 0.84 * max(imageSize.width, imageSize.height);
    Mat result(3,3,cv::DataType<double>::type);
    cv::setIdentity(result);
    result.at<double>(0) = f;
    result.at<double>(4) = f;
    return result;
}

Uso en Swift:

func testSolvePnP() {
    let source = modelPoints().map { NSValue(cgPoint: $0) }
    let destination = perspectivePicker.currentPerspective.map { NSValue(cgPoint: $0)}

    let cameraPose = CameraPoseDetector.findCameraPose(source, imagePoints: destination, size: backgroundImageView.size);    
    cameraNode.rotation = cameraPose.rotationVector
    cameraNode.position = cameraPose.translationVector
}

Salida:

El resultado es mejor pero lejos de mis expectativas.

Algunas otras cosas que también he probado:

Esta pregunta es muy similar, aunque no entiendo cómo funciona la respuesta aceptada sin intrínsecos.descomponerHomographyMat tampoco me dio el resultado que esperaba

Estoy realmente atrapado con este problema, por lo que cualquier ayuda sería muy apreciada.

Respuestas a la pregunta(1)

Su respuesta a la pregunta