Calibración de cámara, proyección inversa de píxel a dirección.

Estoy usando OpenCV para estimar la matriz intrínseca de una cámara web a partir de una serie de imágenes de tablero de ajedrez, como se detalla en este tutorial , y proyectar un píxel en una dirección (en términos de acimut / angularjs de elevación).

El objective final es permitir al usuario seleccionar un punto en la imagen, estimar la dirección de este punto en relación con el centro de la cámara web y usarlo como DOA para un algoritmo de formación de haz.

Así que una vez que haya estimado la matriz intrínseca, invierto el proyecto del píxel seleccionado por el usuario (ver código a continuación) y lo muestro como angularjs de acimut / elevación.

result = [0, 0, 0] # reverse projected point, in homogeneous coord. while 1: _, img = cap.read() if flag: # If the user has clicked somewhere result = np.dot(np.linalg.inv(mtx), [mouse_x, mouse_y, 1]) result = np.arctan(result) # convert to angle flag = False cv2.putText(img, '({},{})'.format(mouse_x, mouse_y), (20, 440), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2, cv2.LINE_AA) cv2.putText(img, '({:.2f},{:.2f})'.format(180/np.pi*result[0], 180/np.pi*result[1]), (20, 460), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2, cv2.LINE_AA) cv2.imshow('image', img) if cv2.waitKey(1) & 0xFF == ord('q'): break 

Mi problema es que no estoy seguro de si mis resultados son coherentes. La mayor incoherencia es que, el punto de la imagen correspondiente al ángulo {0,0} está notablemente fuera del centro de la imagen, como se ve a continuación (la imagen de la cámara ha sido reemplazada por un fondo negro por razones de privacidad): Punto central proyectado inverso

Realmente no veo una manera simple pero eficiente de medir la precisión (el único método que se me ocurrió fue usar un servomotor con un láser, justo debajo de la cámara y apuntarlo a la dirección calculada).

Aquí está la matriz intrínseca después de la calibración con 15 imágenes:

Matriz intrisica

Recibo un error de alrededor de 0.44 RMS que parece satisfactorio.

Mi código de calibración:

 nCalFrames = 12 # number of frames for calibration nFrames = 0 criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) # termination criteria objp = np.zeros((9*7, 3), np.float32) objp[:, :2] = np.mgrid[0:9, 0:7].T.reshape(-1, 2) objpoints = [] # 3d point in real world space imgpoints = [] # 2d points in image plane. cap = cv2.VideoCapture(0) previousTime = 0 gray = 0 while 1: # Capture frame-by-frame _, img = cap.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Find the chess board corners ret, corners = cv2.findChessboardCorners(gray, (9, 7), None) # If found, add object points, image points (after refining them) if ret: corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) if time.time() - previousTime > 2: previousTime = time.time() imgpoints.append(corners2) objpoints.append(objp) img = cv2.bitwise_not(img) nFrames = nFrames + 1 # Draw and display the corners img = cv2.drawChessboardCorners(img, (9, 7), corners, ret) cv2.putText(img, '{}/{}'.format(nFrames, nCalFrames), (20, 460), cv2.FONT_HERSHEY_SIMPLEX, 2, (0, 255, 0), 2, cv2.LINE_AA) cv2.putText(img, 'press \'q\' to exit...', (255, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, cv2.LINE_AA) # Display the resulting frame cv2.imshow('Webcam Calibration', img) if nFrames == nCalFrames: break if cv2.waitKey(1) & 0xFF == ord('q'): break RMS_error, mtx, disto_coef, _, _ = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None) 

EDITAR: otro método de prueba sería usar una pizarra con puntos de angularjs conocidos y estimar el error comparándolo con los resultados experimentales, pero no sé cómo configurar un sistema de este tipo

Con respecto a su primera preocupación, es normal tener el punto principal fuera del centro de la imagen. El punto estimado, que es el punto de elevación cero y acimut, es el que minimiza los coeficientes de distorsión radial, y para una lente de gran angular de bajo valor (por ejemplo, la de una cámara web típica) puede desactivarse fácilmente por una cantidad notable.

Su calibración debe estar bien hasta la llamada a calibrateCamera . Sin embargo, en su fragmento de código parece que ignora los coeficientes de distorsión. Lo que falta es initUndistortRectifyMap , que también le permite volver a centrar el punto principal si eso importa.

 h, w = img.shape[:2] # compute new camera matrix with central principal point new_mtx,roi = cv2.getOptimalNewCameraMatrix(mtx,disto_coef,(w,h),1,(w,h)) print(new_mtx) # compute undistort maps mapx,mapy = cv2.initUndistortRectifyMap(mtx,disto_coef,None,new_mtx,(w,h),5) 

Básicamente, hace que la distancia focal sea igual en ambas dimensiones y centra el punto principal (consulte la documentación de OpenCV sobre los parámetros).

Luego, en cada

 _, img = cap.read() 

Debes desvincular la imagen antes de renderizar.

 # apply the remap img = cv2.remap(img,mapx,mapy,cv2.INTER_LINEAR) # crop the image x,y,w,h = roi img = img[y:y+h, x:x+w] 

Aquí, pongo fondo verde para enfatizar la distorsión del cañón. La salida podría ser algo como esto (imagen de cámara reemplazada por tablero de ajedrez por razones de privacidad):

distorsión

Si hace todo esto, su objective de calibración es preciso y sus muestras de calibración llenan toda el área de la imagen, por lo que debe estar bastante seguro del cálculo. Sin embargo, para validar el azimut y la elevación medidos con respecto a las lecturas de píxeles de la imagen sin distorsión, tal vez sugiera una cinta métrica desde el primer punto principal de la lente y una placa de calibración colocada en un ángulo normal justo delante de la cámara. Allí puedes calcular los angularjs esperados y comparar.

Espero que esto ayude.