ios - OpenCV: problemas de detección de solvePnP
camera markers (3)
Tengo un problema con la detección precisa de marcadores usando OpenCV.
Grabé un video presentando ese problema: http://youtu.be/IeSSW4MdyfU
Como puede ver, los marcadores que estoy detectando se mueven ligeramente en algunos ángulos de cámara. He leído en la web que esto puede ser un problema de calibración de la cámara, así que les diré a ustedes que estoy calibrando la cámara, y ¿podrían decirme qué estoy haciendo mal?
Al principio estoy recopilando datos de varias imágenes y almacenando las esquinas de calibración en el vector _imagePoints
como este
std::vector<cv::Point2f> corners;
_imageSize = cvSize(image->size().width, image->size().height);
bool found = cv::findChessboardCorners(*image, _patternSize, corners);
if (found) {
cv::Mat *gray_image = new cv::Mat(image->size().height, image->size().width, CV_8UC1);
cv::cvtColor(*image, *gray_image, CV_RGB2GRAY);
cv::cornerSubPix(*gray_image, corners, cvSize(11, 11), cvSize(-1, -1), cvTermCriteria(CV_TERMCRIT_EPS+ CV_TERMCRIT_ITER, 30, 0.1));
cv::drawChessboardCorners(*image, _patternSize, corners, found);
}
_imagePoints->push_back(_corners);
Entonces, después de recopilar suficientes datos, estoy calculando la matriz de la cámara y los coeficientes con este código:
std::vector< std::vector<cv::Point3f> > *objectPoints = new std::vector< std::vector< cv::Point3f> >();
for (unsigned long i = 0; i < _imagePoints->size(); i++) {
std::vector<cv::Point2f> currentImagePoints = _imagePoints->at(i);
std::vector<cv::Point3f> currentObjectPoints;
for (int j = 0; j < currentImagePoints.size(); j++) {
cv::Point3f newPoint = cv::Point3f(j % _patternSize.width, j / _patternSize.width, 0);
currentObjectPoints.push_back(newPoint);
}
objectPoints->push_back(currentObjectPoints);
}
std::vector<cv::Mat> rvecs, tvecs;
static CGSize size = CGSizeMake(_imageSize.width, _imageSize.height);
cv::Mat cameraMatrix = [_userDefaultsManager cameraMatrixwithCurrentResolution:size]; // previously detected matrix
cv::Mat coeffs = _userDefaultsManager.distCoeffs; // previously detected coeffs
cv::calibrateCamera(*objectPoints, *_imagePoints, _imageSize, cameraMatrix, coeffs, rvecs, tvecs);
Los resultados son como los que has visto en el video.
¿Qué estoy haciendo mal? ¿Es eso un problema en el código? Cuántas imágenes debo usar para realizar la calibración (en este momento estoy tratando de obtener 20-30 imágenes antes del final de la calibración).
¿Debo usar imágenes que contengan esquinas de tablero de ajedrez incorrectamente detectadas, como esta:
o debería usar solo tableros de ajedrez detectados de la siguiente manera:
He estado experimentando con la cuadrícula de círculos en lugar de tableros de ajedrez, pero los resultados fueron mucho peores que ahora.
En caso de preguntas cómo estoy detectando el marcador: Estoy usando la función solvepnp
:
solvePnP(modelPoints, imagePoints, [_arEngine currentCameraMatrix], _userDefaultsManager.distCoeffs, rvec, tvec);
con modelPoints especificados así:
markerPoints3D.push_back(cv::Point3d(-kMarkerRealSize / 2.0f, -kMarkerRealSize / 2.0f, 0));
markerPoints3D.push_back(cv::Point3d(kMarkerRealSize / 2.0f, -kMarkerRealSize / 2.0f, 0));
markerPoints3D.push_back(cv::Point3d(kMarkerRealSize / 2.0f, kMarkerRealSize / 2.0f, 0));
markerPoints3D.push_back(cv::Point3d(-kMarkerRealSize / 2.0f, kMarkerRealSize / 2.0f, 0));
y los imagePoints
son coordenadas de las esquinas del marcador en la imagen de procesamiento (estoy usando un algoritmo personalizado para hacerlo)
Para aquellos que todavía estén interesados: esta es una vieja pregunta, pero creo que su problema no es la mala calibración. Desarrollé una aplicación AR para iOS, usando OpenCV y SceneKit, y tuve su mismo problema.
Creo que su problema es la posición de renderización incorrecta del cubo: el solvePnP de OpenCV devuelve las coordenadas X, Y, Z del centro del marcador, pero desea representar el cubo sobre el marcador, a una distancia específica a lo largo del eje Z del marcador, exactamente a la mitad del tamaño del lado del cubo. Por lo tanto, debe mejorar la coordenada Z del vector de traducción de marcador de esta distancia.
De hecho, cuando ve su cubo desde la parte superior, el cubo se procesa correctamente. He hecho una imagen para explicar el problema, pero mi reputación impide publicarlo.
Para depurar adecuadamente tu problema, necesitaría todo el código :-)
Supongo que está siguiendo el enfoque sugerido en los tutoriales ( calibración y pose ) citados por KobeJohn en su comment y para que su código siga estos pasos:
- recoger varias imágenes del objetivo del tablero de ajedrez
- encontrar esquinas del tablero de ajedrez en las imágenes del punto 1)
- Calibre la cámara (con
cv::calibrateCamera
) y obtenga como resultado los parámetros intrínsecos de la cámara (llamémoslosintrinsic
) y los parámetros de distorsión de la lente (llamémoslesdistortion
) - Recoja una imagen de su propio objetivo personalizado (el objetivo se ve en 0:57 en su video ) y se muestra en la siguiente figura y encuentre algunos puntos relevantes en él (llamemos al punto que encontró en la imagen
image_custom_target_vertices
yworld_custom_target_vertices
los puntos 3D correspondientes). - estima la matriz de rotación (llamémosla
R
) y el vector de traducción (llamémoslot
) de la cámara desde la imagen de tu propio objetivo personalizado que obtienes en el punto 4), con una llamada acv::solvePnP
como estecv::solvePnP
cv::solvePnP(world_custom_target_vertices,image_custom_target_vertices,intrinsic,distortion,R,t)
- dando el cubo de 8 esquinas en 3D (llamémosles
world_cube_vertices
) obtienes los 8 puntos de imagen 2D (llamémoslosimage_cube_vertices
) por medio de una llamada acv2::projectPoints
como estecv::projectPoints(world_cube_vertices,R,t,intrinsic,distortion,image_cube_vertices)
- dibuje el cubo con su propia función de
draw
.
Ahora, el resultado final del procedimiento de sorteo depende de todos los datos computados anteriores y tenemos que encontrar dónde se encuentra el problema:
Calibración : como observó en su answer , en 3) debe descartar las imágenes donde las esquinas no se detectan correctamente. Necesita un umbral para el error de reproyección a fin de descartar las imágenes "malas" del tablero de ajedrez. Citando del tutorial de calibración :
Error de re-proyección
El error de re-proyección da una buena estimación de cuán exactos son los parámetros encontrados. Esto debería ser lo más cercano posible a cero. Dadas las matrices intrínsecas, de distorsión, de rotación y de traducción, primero transformamos el punto del objeto en punto de imagen usando cv2.projectPoints (). Luego calculamos la norma absoluta entre lo que obtuvimos con nuestra transformación y el algoritmo de búsqueda de esquina. Para encontrar el error promedio calculamos la media aritmética de los errores calculados para todas las imágenes de calibración.
Por lo general, encontrará un umbral adecuado con algunos experimentos. Con este paso adicional obtendrá mejores valores de intrinsic
y distortion
.
Encontrar su propio objetivo personalizado : no me parece que explique cómo encuentra su propio objetivo personalizado en el paso I etiquetado como punto 4). ¿ image_custom_target_vertices
el esperado image_custom_target_vertices
? ¿Desecha las imágenes donde los resultados son "malos"?
Pose de la cámara : creo que en 5) usas intrinsic
encontrado en 3), ¿estás seguro de que nada cambia en la cámara mientras tanto? En referencia a la segunda calibración de la regla de la cámara de Callari :
Calibración de la segunda regla de la cámara: "No tocará la lente después de la calibración". En particular, no puede volver a enfocar ni cambiar el f-stop, ya que tanto el enfoque como el iris afectan la distorsión no lineal de la lente y (aunque menos, dependiendo de la lente) el campo de visión. Por supuesto, usted es completamente libre de cambiar el tiempo de exposición, ya que no afecta en absoluto la geometría de la lente.
Y luego puede haber algunos problemas en la función de draw
.
Por lo tanto, he experimentado mucho con mi código, y todavía no he solucionado el problema principal (objetos desplazados), pero he logrado responder algunas de las preguntas de calibración que he formulado.
En primer lugar, para obtener buenos resultados de calibración , debe usar imágenes con elementos de cuadrícula / posiciones de círculos correctamente detectados. . El uso de todas las imágenes capturadas en el proceso de calibración (incluso aquellas que no se detectan correctamente) dará como resultado una mala calibración.
He experimentado con varios patrones de calibración:
- El patrón de círculos asimétricos (
CALIB_CB_ASYMMETRIC_GRID
) da resultados mucho peores que cualquier otro patrón. Por peores resultados quiero decir que produce muchas esquinas detectadas incorrectamente como estas:
Experimenté con CALIB_CB_CLUSTERING
y no me ayudó mucho; en algunos casos (entorno de luz diferente) mejoró, pero no mucho.
- Patrón de círculos simétricos (
CALIB_CB_SYMMETRIC_GRID
): mejores resultados que la cuadrícula asimétrica, pero aún tengo resultados mucho peores que la grilla estándar (tablero de ajedrez). A menudo produce errores como estos:
- Tablero de ajedrez (encontrado usando la función
findChessboardCorners
) - este método está produciendo los mejores resultados posibles - no produce esquinas desalineadas muy a menudo, y casi todas las calibraciones están produciendo resultados similares a los mejores resultados posibles de la cuadrícula de círculos simétricos
Para cada calibración he estado usando 20-30 imágenes que vienen desde diferentes ángulos. Lo he intentado incluso con más de 100 imágenes, pero no ha producido un cambio notable en los resultados de la calibración en comparación con una cantidad menor de imágenes. Vale la pena observar que una mayor cantidad de imágenes de prueba aumenta el tiempo necesario para calcular los parámetros de la cámara de forma no lineal (100 imágenes de prueba con una resolución de 480x360 computan 25 minutos en iPad4, en comparación con 4 minutos con ~ 50 imágenes)
También he experimentado con los parámetros de solvePNP
, pero tampoco me ha dado ningún resultado aceptable: probé los 3 métodos de detección ( ITERATIVE
, EPNP
y P3P
), pero no he visto un cambio notable.
También lo he intentado con useExtrinsicGuess
establecido en true
, y he usado rvec
y tvec
de detección previa, pero este resultó con la desaparición completa del cubo detectado.
Me he quedado sin ideas. ¿Qué otra cosa podría estar afectando estos problemas cambiantes?