Desdistorsionar imágenes usando OpenCV

Ammar Ali 15 febrero 2024
Desdistorsionar imágenes usando OpenCV

Este tutorial tratará sobre cómo eliminar la distorsión de una imagen usando la función desdistorsionar() de OpenCV.

Desdistorsionar imágenes usando OpenCV

Al capturar imágenes con cámaras, algunas imágenes se distorsionan. La distorsión añadida en las imágenes se debe principalmente a la cámara utilizada para capturar la imagen.

La distorsión presente en una imagen puede ser radial o tangencial. En la distorsión radial, las líneas rectas de una imagen se vuelven curvas y las líneas se vuelven más curvas si están en las esquinas de la imagen.

Si las líneas están en el centro de la imagen, su distorsión será pequeña y no se notará. Por ejemplo, vea la imagen distorsionada a continuación.

tablero de ajedrez

En la imagen de arriba, los bordes del tablero de ajedrez están conectados con una línea roja recta, por lo que podemos ver fácilmente la distorsión presente en la imagen. Podemos ver que las líneas de borde en la imagen de arriba son curvas, y cuanto más lejos está la línea del centro de la imagen, más curvas se vuelven.

En la distorsión tangencial, el plano de la imagen y la lente utilizada para tomar la imagen no son paralelos entre sí y, por eso, algunas áreas presentes en la imagen se verán más lejos y algunas áreas se verán más cerca de lo que se ven en la imagen real. . Por ejemplo, vea la imagen a continuación, que muestra una distorsión tangencial.

distorsión tangencial

En la imagen anterior, la cuadrícula roja muestra el plano de la imagen de salida y la cuadrícula azul muestra el plano de la imagen de entrada. La distorsión tangencial depende del ángulo de la lente utilizada para tomar la fotografía.

Si el ángulo de la lente cambia, la distorsión cambiará y la distorsión aumentará si aumenta el ángulo de la lente. Debemos encontrar cinco parámetros conocidos como coeficientes de distorsión para no distorsionar una imagen.

También necesitamos encontrar información sobre la cámara utilizada para capturar las imágenes, como la distancia focal de la lente y los centros ópticos. Usando los valores de la distancia focal y del centro óptico, podemos hacer una matriz de cámara que se usará para desdistorsionar una imagen.

Podemos reutilizar una matriz de cámara en otras imágenes si las toma la misma cámara, pero si la cámara cambia, necesitamos encontrar la matriz nuevamente porque los valores de distancia focal y centro óptico pueden cambiar dependiendo de la cámara. La matriz de la cámara será una matriz de 3 por 3 como la matriz que se muestra a continuación.

matriz de cámara

En la matriz de cámara anterior, la variable fx y fy contiene los valores de distancia focal y la variable cx y cy contiene los valores del centro óptico de la lente utilizada para capturar las imágenes. La distancia focal y los valores del centro óptico son los parámetros intrínsecos de una cámara.

También necesitamos encontrar los parámetros extrínsecos de una cámara que muestren los vectores de rotación y traslación utilizados para trasladar un punto en el sistema de coordenadas 3D. Para encontrar el parámetro de distorsión, necesitamos usar algunas imágenes distorsionadas de muestra, y necesitamos usar algunos puntos específicos presentes en estas imágenes y su posición.

Todas las imágenes de muestra deben ser tomadas por la misma cámara para que se calcule una matriz de cámara precisa, y las imágenes distorsionadas de esa cámara no deben estar distorsionadas usando la matriz de cámara. Por ejemplo, tomemos 10 imágenes de tablero de ajedrez y extraigamos los puntos 3D del mundo real y sus coordenadas 2D, que contienen la ubicación de los puntos 3D.

Estos puntos se toman del tablero de ajedrez donde dos cuadrados negros se tocan. Usaremos estos puntos para calibrar la cámara utilizada para capturar las imágenes de muestra y la imagen que queremos calibrar.

Un punto 3D del mundo real contiene tres valores x, y y z. Para simplificar el procedimiento, tomaremos las imágenes de muestra a una distancia fija de la cámara, eliminando el valor z de los puntos de palabra real 3D.

Ahora pasaremos la ubicación de todos los puntos presentes en la imagen en los puntos 3D del mundo real como (0, 0), (1, 0), y así sucesivamente. Estos puntos representan la ubicación de los cuadrados en la imagen del tablero de ajedrez.

Si conocemos el tamaño de los cuadrados en la imagen del ajedrez, podemos omitir algunos puntos en lugar de sumar todos los puntos. Por ejemplo, si el cuadrado es de 30 milímetros, podemos sumar puntos como (0, 0), (30, 0), etc.

Sepa que debemos encontrar las esquinas de los cuadrados en el tablero de ajedrez. Podemos usar cv2.findChessboardCorners() para encontrar las esquinas de los cuadrados.

Tenemos que pasar el patrón que estamos buscando dentro de la función cv2.findChessboardCorners() como una cuadrícula de 8 por 8. Un tablero de ajedrez normalmente tiene cuadrados de 8 por 8, pero en este ejemplo usaremos una cuadrícula de 7 por 6.

La función devolverá los puntos de las esquinas colocados en un orden que comienza de izquierda a derecha y de arriba a abajo. La función también devolverá un booleano, que será true si se obtiene el patrón que buscamos.

Es posible que las imágenes de muestra que queremos usar no contengan el patrón requerido, por lo que debemos usar las imágenes que contienen el patrón requerido para encontrar los puntos de las esquinas.

Podemos usar la salida booleana de cv2.findChessboardCorners() para comprobar si se obtiene o no el patrón, y si se obtiene el patrón, almacenaremos los puntos de las esquinas, y si no se encuentran, no almacenaremos la esquina. puntos y pasar a la siguiente imagen.

Consulte este enlace para obtener más detalles sobre la función cv2.findChessboardCorners(). Para aumentar la precisión de los puntos de las esquinas, podemos usar la función cv2.cornerSubPix().

Consulte este enlace para obtener más detalles sobre la función cv2.cornerSubPix(). También podemos usar la función cv2.drawChessboardCorners() para dibujar los puntos de las esquinas en las imágenes del tablero de ajedrez.

Consulte este enlace para obtener más detalles sobre la función cv2.drawChessboardCorners(). Por ejemplo, leamos 10 imágenes de tablero de ajedrez de muestra, encontremos los puntos de imagen 2D y del mundo real en 3D y dibujemos en las imágenes.

Vea el código a continuación.

import numpy as np
import cv2

term_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
obj_points = np.zeros((6 * 7, 3), np.float32)
obj_points[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)
real_points = []
img_points = []
chess_images = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for name in chess_images:
    chess_img = cv2.imread(str(name) + ".png")
    chess_gray = cv2.cvtColor(chess_img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(chess_gray, (7, 6), None)

    if ret == True:
        real_points.append(obj_points)
        corners2 = cv2.cornerSubPix(
            chess_gray, corners, (11, 11), (-1, -1), term_criteria
        )
        img_points.append(corners)
        cv2.drawChessboardCorners(chess_img, (7, 6), corners2, ret)
        cv2.imshow("img", chess_img)
        cv2.waitKey(0)
cv2.destroyAllWindows()

Producción:

puntos de esquina

En el código anterior, usamos la función zeros() de la biblioteca numpy para crear una matriz de ceros y luego usamos la función mgrid() para poner valores en la matriz que se usará como el real 3D. -puntos del mundo.

Las variables real_points e img_points almacenan los puntos 3D del mundo real y los puntos de imagen 2D de todas las imágenes de muestra.

Usamos un bucle for para procesar cada imagen por separado. Leeremos cada imagen usando la función imread() y luego convertiremos cada imagen a escala de grises usando la función cv2.cvtColor().

Usamos la sentencia if para comprobar si se obtiene o no el patrón, y si se obtiene, almacenaremos los puntos en las variables real_points e img_points.

Usamos la función cv2.cornerSubPix() para aumentar la precisión de los puntos de las esquinas y pasar los criterios de terminación que hemos definido anteriormente dentro de la función junto con la imagen en escala de grises y sus esquinas para aumentar la precisión de los puntos de las esquinas. Usamos la función append() para agregar los puntos de las esquinas a los puntos de la imagen 2D.

Podemos usar los puntos 3D del mundo real y los puntos de imagen 2D para encontrar los 5 parámetros de distorsión usando la función cv2.calibrateCamera(). Consulte este enlace para obtener detalles sobre la función cv2.calibrateCamera().

Podemos usar dos parámetros de los 5 parámetros de distorsión y el tamaño de la imagen de entrada para encontrar la matriz de la cámara usando la función cv2.getOptimalNewCameraMatrix().

La función cv2.getOptimalNewCameraMatrix() también devolverá la región de interés, que se puede utilizar para recortar la imagen. Ahora, podemos usar los parámetros de distorsión y la matriz de la cámara para eliminar la distorsión de una imagen usando la función cv2.undistort() de OpenCV.

Consulte este enlace para obtener más detalles sobre la función cv2.getOptimalNewCameraMatrix(). Por ejemplo, leamos una imagen de las 10 imágenes de muestra distorsionadas y deshagámosla usando la función cv2.undistort().

Vea el código a continuación.

import numpy as np
import cv2

term_criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
obj_points = np.zeros((6 * 7, 3), np.float32)
obj_points[:, :2] = np.mgrid[0:7, 0:6].T.reshape(-1, 2)
real_points = []
img_points = []
chess_images = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for name in chess_images:
    chess_img = cv2.imread(str(name) + ".png")
    chess_gray = cv2.cvtColor(chess_img, cv2.COLOR_BGR2GRAY)
    ret, corners = cv2.findChessboardCorners(chess_gray, (7, 6), None)
    if ret == True:
        real_points.append(obj_points)
        corners2 = cv2.cornerSubPix(
            chess_gray, corners, (11, 11), (-1, -1), term_criteria
        )
        img_points.append(corners)

ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
    real_points, img_points, chess_gray.shape[::-1], None, None
)
img = cv2.imread("5.png")
h, w = img.shape[:2]
newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
dst = cv2.undistort(img, mtx, dist, None, newcameramtx)
x, y, w, h = roi
dst = dst[y : y + h, x : x + w]

cv2.imshow("Undistorted Image", dst)
cv2.imshow("distorted image", img)
cv2.waitKey(0)
cv2.destroyAllWindows()

Producción:

imagen no distorsionada

En la salida anterior, la imagen de la izquierda es la imagen distorsionada y la imagen de la derecha es la imagen no distorsionada. En el código anterior, usamos la función cv2.imshow() para mostrar la imagen y almacenar la imagen sin distorsiones usando la función cv2.imwrite().

Consulte este enlace para obtener más detalles sobre la función cv2.undistort(). En el ejemplo anterior, usamos cuadrículas cuadradas para encontrar el patrón, pero si las cuadrículas son circulares, podemos usar la función cv2.findCirclesGrid() para encontrar el patrón.

Consulte este enlace para obtener más detalles sobre la función cv2.findCirclesGrid().

Autor: Ammar Ali
Ammar Ali avatar Ammar Ali avatar

Hello! I am Ammar Ali, a programmer here to learn from experience, people, and docs, and create interesting and useful programming content. I mostly create content about Python, Matlab, and Microcontrollers like Arduino and PIC.

LinkedIn Facebook