Saltar al contenido

OpenCV – Mapa de profundidad del sistema estéreo no calibrado

Solución:

TLDR; Use StereoSGBM (Semi Global Block Matching) para imágenes con bordes más suaves y use un filtrado posterior si lo desea aún más suave

OP no proporcionó imágenes originales, así que estoy usando Tsukuba del conjunto de datos de Middlebury.

Resultado con StereoBM regular

stereobm

Resultado con StereoSGBM (sintonizado)

stereosgbm

El mejor resultado que pude encontrar en la literatura.

ingrese la descripción de la imagen aquí

Consulte la publicación aquí para obtener más detalles.

Ejemplo de filtrado de publicaciones (ver enlace a continuación)

ejemplo de filtro posterior

Teoría / Otras consideraciones de la pregunta de OP

Las grandes áreas negras de sus imágenes rectificadas calibradas me llevarían a creer que para ellas, la calibración no se hizo muy bien. Hay una variedad de razones por las que podría estar en juego, tal vez la configuración física, tal vez la iluminación cuando realizó la calibración, etc., pero hay muchos tutoriales de calibración de la cámara para eso y tengo entendido que está pidiendo una forma de obtener un mapa de profundidad mejor con una configuración no calibrada (esto no es 100% claro, pero el título parece respaldar esto y creo que eso es lo que la gente vendrá aquí a intentar encontrar).

Su enfoque básico es correcto, pero los resultados definitivamente pueden mejorarse. Esta forma de mapeo de profundidad no se encuentra entre las que producen mapas de la más alta calidad (especialmente si no están calibrados). La mayor mejora probablemente provendrá del uso de un algoritmo de coincidencia estéreo diferente. La iluminación también puede tener un efecto significativo. La imagen de la derecha (al menos a simple vista) parece estar menos iluminada, lo que podría interferir con la reconstrucción. Primero puede intentar iluminarlo al mismo nivel que el otro, o recopilar nuevas imágenes si es posible. De ahora en adelante, asumiré que no tiene acceso a las cámaras originales, por lo que consideraré recopilar nuevas imágenes, alterar la configuración o realizar la calibración para estar fuera de alcance. (Si tiene acceso a la configuración y las cámaras, le sugiero que verifique la calibración y utilice un método calibrado, ya que esto funcionará mejor).

Usaste StereoBM para calcular su disparidad (mapa de profundidad) que funciona, pero StereoSGBM es mucho más adecuado para esta aplicación (maneja mejor los bordes más suaves). Puedes ver la diferencia a continuación.

Este artículo explica las diferencias con más profundidad:

La coincidencia de bloques se enfoca en imágenes de alta textura (piense en la imagen de un árbol) y la coincidencia de bloques semi-global se enfocará en la coincidencia de niveles de subpíxeles e imágenes con texturas más suaves (piense en una imagen de un pasillo).

Sin ningún parámetro intrínseco explícito de la cámara, detalles específicos sobre la configuración de la cámara (como distancia focal, distancia entre las cámaras, distancia al sujeto, etc.), una dimensión conocida en la imagen o movimiento (para usar la estructura del movimiento), puede solo obtenga reconstrucción 3D hasta una transformación proyectiva; tampoco tendrá un sentido de escala ni necesariamente de rotación, pero aún puede generar un mapa de profundidad relativa. Es probable que sufra algunas distorsiones de barril y otras que podrían eliminarse con la calibración adecuada de la cámara, pero puede obtener resultados razonables sin ella siempre que las cámaras no sean terribles (el sistema de lentes no esté demasiado distorsionado) y estén bien configuradas. cerca de la configuración canónica (lo que básicamente significa que están orientados de manera que sus ejes ópticos estén lo más paralelos posible y sus campos de visión se superpongan lo suficiente). Sin embargo, esto no parece ser el problema de los OP, ya que logró obtener imágenes bien rectificadas con el método sin calibrar.

Procedimiento básico

  1. Encuentre al menos 5 puntos bien emparejados en ambas imágenes que pueda usar para calcular la Matriz Fundamental (puede usar cualquier detector y comparador que desee, mantuve FLANN pero usé ORB para hacer la detección ya que SIFT no está en la versión principal de OpenCV para 4.2.0)
  2. Calcule la matriz fundamental, F, con findFundamentalMat
  3. Anula las distorsiones de tus imágenes con stereoRectifyUncalibrated y warpPerspective
  4. Calcular la disparidad (mapa de profundidad) con StereoSGBM

Los resultados son mucho mejores:

Coincide con ORB y FLANN

Partidos

Imágenes sin distorsiones (izquierda, luego derecha)

izquierda sin distorsiones
derecho sin distorsiones

Disparidad

StereoBM

Este resultado se parece a los problemas de los OP (manchas, huecos, profundidades incorrectas en algunas áreas).

stereobm

StereoSGBM (sintonizado)

Este resultado se ve mucho mejor y utiliza aproximadamente el mismo método que el OP, menos el cálculo de disparidad final, lo que me hace pensar que el OP vería mejoras similares en sus imágenes, si se hubieran proporcionado.

stereosgbm

Post filtrado

Hay un buen artículo sobre esto en los documentos de OpenCV. Recomiendo mirarlo si necesita mapas realmente fluidos.

Las fotos de ejemplo de arriba son el fotograma 1 de la escena. ambush_2 en el conjunto de datos MPI Sintel.

ejemplo de filtro posterior

Código completo (probado en OpenCV 4.2.0):

import cv2
import numpy as np
import matplotlib.pyplot as plt

imgL = cv2.imread("tsukuba_l.png", cv2.IMREAD_GRAYSCALE)  # left image
imgR = cv2.imread("tsukuba_r.png", cv2.IMREAD_GRAYSCALE)  # right image


def get_keypoints_and_descriptors(imgL, imgR):
    """Use ORB detector and FLANN matcher to get keypoints, descritpors,
    and corresponding matches that will be good for computing
    homography.
    """
    orb = cv2.ORB_create()
    kp1, des1 = orb.detectAndCompute(imgL, None)
    kp2, des2 = orb.detectAndCompute(imgR, None)

    ############## Using FLANN matcher ##############
    # Each keypoint of the first image is matched with a number of
    # keypoints from the second image. k=2 means keep the 2 best matches
    # for each keypoint (best matches = the ones with the smallest
    # distance measurement).
    FLANN_INDEX_LSH = 6
    index_params = dict(
        algorithm=FLANN_INDEX_LSH,
        table_number=6,  # 12
        key_size=12,  # 20
        multi_probe_level=1,
    )  # 2
    search_params = dict(checks=50)  # or pass empty dictionary
    flann = cv2.FlannBasedMatcher(index_params, search_params)
    flann_match_pairs = flann.knnMatch(des1, des2, k=2)
    return kp1, des1, kp2, des2, flann_match_pairs


def lowes_ratio_test(matches, ratio_threshold=0.6):
    """Filter matches using the Lowe's ratio test.

    The ratio test checks if matches are ambiguous and should be
    removed by checking that the two distances are sufficiently
    different. If they are not, then the match at that keypoint is
    ignored.

    https://stackoverflow.com/questions/51197091/how-does-the-lowes-ratio-test-work
    """
    filtered_matches = []
    for m, n in matches:
        if m.distance < ratio_threshold * n.distance:
            filtered_matches.append(m)
    return filtered_matches


def draw_matches(imgL, imgR, kp1, des1, kp2, des2, flann_match_pairs):
    """Draw the first 8 mathces between the left and right images."""
    # https://docs.opencv.org/4.2.0/d4/d5d/group__features2d__draw.html
    # https://docs.opencv.org/2.4/modules/features2d/doc/common_interfaces_of_descriptor_matchers.html
    img = cv2.drawMatches(
        imgL,
        kp1,
        imgR,
        kp2,
        flann_match_pairs[:8],
        None,
        flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS,
    )
    cv2.imshow("Matches", img)
    cv2.imwrite("ORB_FLANN_Matches.png", img)
    cv2.waitKey(0)


def compute_fundamental_matrix(matches, kp1, kp2, method=cv2.FM_RANSAC):
    """Use the set of good mathces to estimate the Fundamental Matrix.

    See  https://en.wikipedia.org/wiki/Eight-point_algorithm#The_normalized_eight-point_algorithm
    for more info.
    """
    pts1, pts2 = [], []
    fundamental_matrix, inliers = None, None
    for m in matches[:8]:
        pts1.append(kp1[m.queryIdx].pt)
        pts2.append(kp2[m.trainIdx].pt)
    if pts1 and pts2:
        # You can play with the Threshold and confidence values here
        # until you get something that gives you reasonable results. I
        # used the defaults
        fundamental_matrix, inliers = cv2.findFundamentalMat(
            np.float32(pts1),
            np.float32(pts2),
            method=method,
            # ransacReprojThreshold=3,
            # confidence=0.99,
        )
    return fundamental_matrix, inliers, pts1, pts2


############## Find good keypoints to use ##############
kp1, des1, kp2, des2, flann_match_pairs = get_keypoints_and_descriptors(imgL, imgR)
good_matches = lowes_ratio_test(flann_match_pairs, 0.2)
draw_matches(imgL, imgR, kp1, des1, kp2, des2, good_matches)


############## Compute Fundamental Matrix ##############
F, I, points1, points2 = compute_fundamental_matrix(good_matches, kp1, kp2)


############## Stereo rectify uncalibrated ##############
h1, w1 = imgL.shape
h2, w2 = imgR.shape
thresh = 0
_, H1, H2 = cv2.stereoRectifyUncalibrated(
    np.float32(points1), np.float32(points2), F, imgSize=(w1, h1), threshold=thresh,
)

############## Undistort (Rectify) ##############
imgL_undistorted = cv2.warpPerspective(imgL, H1, (w1, h1))
imgR_undistorted = cv2.warpPerspective(imgR, H2, (w2, h2))
cv2.imwrite("undistorted_L.png", imgL_undistorted)
cv2.imwrite("undistorted_R.png", imgR_undistorted)

############## Calculate Disparity (Depth Map) ##############

# Using StereoBM
stereo = cv2.StereoBM_create(numDisparities=16, blockSize=15)
disparity_BM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_BM, "gray")
plt.colorbar()
plt.show()

# Using StereoSGBM
# Set disparity parameters. Note: disparity range is tuned according to
#  specific parameters obtained through trial and error.
win_size = 2
min_disp = -4
max_disp = 9
num_disp = max_disp - min_disp  # Needs to be divisible by 16
stereo = cv2.StereoSGBM_create(
    minDisparity=min_disp,
    numDisparities=num_disp,
    blockSize=5,
    uniquenessRatio=5,
    speckleWindowSize=5,
    speckleRange=5,
    disp12MaxDiff=2,
    P1=8 * 3 * win_size ** 2,
    P2=32 * 3 * win_size ** 2,
)
disparity_SGBM = stereo.compute(imgL_undistorted, imgR_undistorted)
plt.imshow(disparity_SGBM, "gray")
plt.colorbar()
plt.show()

Puede haber varios problemas posibles que resulten en una baja calidad Depth Channel y Disparity Channel lo que nos lleva a una secuencia estéreo de baja calidad. Aquí hay 6 de esos problemas:

Posible problema I

  • Fórmula incompleta

Como una palabra uncalibrated implica, stereoRectifyUncalibrated El método de instancia calcula una transformación de rectificación por usted, en caso de que no conozca o no pueda conocer los parámetros intrínsecos de su par estéreo y su posición relativa en el entorno.

cv.StereoRectifyUncalibrated(pts1, pts2, fm, imgSize, rhm1, rhm2, thres)

dónde:

# pts1    –> an array of feature points in a first camera
# pts2    –> an array of feature points in a first camera
# fm      –> input fundamental matrix
# imgSize -> size of an image
# rhm1    -> output rectification homography matrix for a first image
# rhm2    -> output rectification homography matrix for a second image
# thres   –> optional threshold used to filter out outliers

Y tu método se ve de esta manera:

cv2.StereoRectifyUncalibrated(p1fNew, p2fNew, F, (2048, 2048))

Entonces, no tiene en cuenta tres parámetros: rhm1, rhm2 y thres. Si un threshold > 0, todos los pares de puntos que no cumplen con un epipolar la geometría se rechaza antes de calcular las homografías. De lo contrario, todos los puntos se consideran inliers. Esta fórmula se ve así:

(pts2[i]^t * fm * pts1[i]) > thres

# t   –> translation vector between coordinate systems of cameras

Por lo tanto, creo que pueden aparecer inexactitudes visuales debido a un cálculo de fórmula incompleto.

Puede leer Calibración de la cámara y Reconstrucción 3D en un recurso oficial.

Posible problema II

  • Distancia Interaxial

Un robusto interaxial distance entre las lentes de la cámara izquierda y derecha deben not greater than 200 mm. Cuando el interaxial distance es más grande que el interocular distancia, el efecto se llama hyperstereoscopy o hyperdivergence y resulta no solo en una exageración profunda en la escena, sino también en las molestias físicas del espectador. Lea el informe técnico sobre creación de películas estereoscópicas de Autodesk para obtener más información sobre este tema.

ingrese la descripción de la imagen aquí

Posible problema III

  • Modo de cámara paralelo vs toed-in

Se produjeron inexactitudes visuales Disparity Map puede ocurrir debido a un cálculo incorrecto del modo de cámara. Muchos estereógrafos prefieren Toe-In camera mode pero Pixar, por ejemplo, prefiere Parallel camera mode.

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Posible problema IV

  • Alineamiento vertical

En estereoscopía, si se produce un desplazamiento vertical (incluso si una de las vistas se desplaza 1 mm hacia arriba), se arruina una experiencia estéreo sólida. Entonces, antes de generar Disparity Map debe asegurarse de que las vistas izquierda y derecha de su par estéreo estén alineadas en consecuencia. Consulte el documento técnico estereoscópico en tecnicolor sobre 15 problemas comunes en estéreo.

Matriz de rectificación estéreo:

   ┌                  ┐
   |  f   0   cx  tx  |
   |  0   f   cy  ty  |   # use "ty" value to fix vertical shift in one image
   |  0   0   1   0   |
   └                  ┘

Aquí está un StereoRectify método:

cv.StereoRectify(cameraMatrix1, cameraMatrix2, distCoeffs1, distCoeffs2, imageSize, R, T, R1, R2, P1, P2, Q=None, flags=CV_CALIB_ZERO_DISPARITY, alpha=-1, newImageSize=(0, 0)) -> (roi1, roi2)

Posible problema V

  • Distorsión de la lente

La distorsión de la lente es un tema muy importante en la composición estéreo. Antes de generar un Disparity Map debe anular la distorsión de las vistas izquierda y derecha, después de esto, generar un canal de disparidad y, a continuación, volver a distorsionar ambas vistas.

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Posible problema VI

  • Canal de profundidad de baja calidad sin suavizado

Para crear una alta calidad Disparity Map necesitas izquierda y derecha Depth Channels que debe ser generado previamente. Cuando trabaja en un paquete 3D, puede renderizar un canal de profundidad de alta calidad (con bordes nítidos) con solo un clic. Pero generar un canal de profundidad de alta calidad a partir de una secuencia de video no es fácil porque el par estéreo tiene que moverse en su entorno para producir datos iniciales para el futuro algoritmo de profundidad a partir del movimiento. Si no hay movimiento en un cuadro, el canal de profundidad será extremadamente pobre.

ingrese la descripción de la imagen aquí

También, Depth canal en sí tiene un inconveniente más: sus bordes no coinciden con los bordes del RGB porque no tiene anti-aliasing.

ingrese la descripción de la imagen aquí

Fragmento de código de canal de disparidad:

Aquí me gustaría representar un enfoque rápido para generar un Disparity Map:

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

imageLeft = cv.imread('paris_left.png', 0)
imageRight = cv.imread('paris_right.png', 0)
stereo = cv.StereoBM_create(numDisparities=16, blockSize=15)
disparity = stereo.compute(imageLeft, imageRight)
plt.imshow(disparity, 'gray')
plt.show()

ingrese la descripción de la imagen aquí

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *