В данной статье будет рассказываться о применении библиотеки машинного зрения (openCV) для удаления эффекта радиального искажения (дисторсии) с фото и видео. Данный эффект также известен как эффект рыбьего глаза (fisheye) или distortion. Решение написать данную статью было принято после нескольких дней поиска информации в интернете. Не смотря на то, что есть гайды на английском языке, они не объясняют как правильно установить openCV, чтобы все работало. В статье присутствует готовый код.
Сразу привожу фото итогового результата. Слева оригинальное фото, справа — обработанное:
Сборка и установка openCV
Первое, что нужно сделать, это грамотно установить библиотеку openCV. Для этого скачиваем из официального репозитория два проекта — openCV и opencv_contrib.
git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git
Пока загружается openCV, устанавливаем видеокодек ffmpeg:
sudo apt-get install ffmpeg
Заходим в папку openCV, создаем подпапку buid и заходим в нее. Вся работа по сборке и установке библиотеки openCV будет производиться из этой директории.
cd opencv mkdir build cd build/
Для сборки библиотеки выполняем следующие команды:
cmake .. -DOPENCV_EXTRA_MODULES_PATH=/путь к папке opencv_contrib/modules/ /путь к папке opencv/ make -j5 sudo make install
У меня сборка заняла около полутора часов, установка — несколько минут. Обратите внимание: если у вас возникла ошибка при сборке(выполнение команды cmake), для нового запуска необходимо удалить файл CMakeCache.txt. После установки можем проверить все ли правильно получилось. Для этого можно вызвать рабочую среду python и импортировать библиотеку openCV. Если никаких ошибок не произошло, то вы все сделали правильно. Вторая строчка покажет, какая версия у вас установлена. На момент написания статьи я пользовался 3 версией библиотеки.
import cv2 print ("OpenCV version : {0}".format(cv2.__version__))
Калибровка камеры
Для того, чтобы убрать искажения, нам необходимо определить калибровочные коэффициенты для нашей камеры. Для этого необходимо скачать картинку с шахматной доской, сделать 5-6 снимков на камеру, изображения с которой мы хотим обработать. Все изображения необходимо конвертировать в формат png. Далее выполняем следующий код:
Определение поправочных коэффициентов
from __future__ import print_function import numpy as np import cv2 from common import splitfn import os if __name__ == '__main__': import sys import getopt from glob import glob args, img_mask = getopt.getopt(sys.argv[1:], '', ['debug=', 'square_size=']) args = dict(args) args.setdefault('--debug', '/рабочая директория/') args.setdefault('--square_size', 1.0) if not img_mask: img_mask = '/папка с изображениями/*.png' else: img_mask = img_mask[0] img_names = glob(img_mask) debug_dir = args.get('--debug') if not os.path.isdir(debug_dir): os.mkdir(debug_dir) square_size = float(args.get('--square_size')) pattern_size = (9, 6) pattern_points = np.zeros((np.prod(pattern_size), 3), np.float32) pattern_points[:, :2] = np.indices(pattern_size).T.reshape(-1, 2) pattern_points *= square_size obj_points = [] img_points = [] h, w = 0, 0 img_names_undistort = [] for fn in img_names: print('processing %s... ' % fn, end='') img = cv2.imread(fn, 0) if img is None: print("Failed to load", fn) continue h, w = img.shape[:2] found, corners = cv2.findChessboardCorners(img, pattern_size) if found: term = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_COUNT, 30, 0.1) cv2.cornerSubPix(img, corners, (5, 5), (-1, -1), term) if not found: print('chessboard not found') continue img_points.append(corners.reshape(-1, 2)) obj_points.append(pattern_points) print('ok') rms, camera_matrix, dist_coefs, rvecs, tvecs = cv2.calibrateCamera(obj_points, img_points, (w, h), None, None) print(" RMS:", rms) print("camera matrix: ", camera_matrix) print("distortion coefficients: ", dist_coefs.ravel()) cv2.destroyAllWindows()
В результате выполнения данного скрипта в консоли появится сообщение об обработанных фото и отобразятся два важных параметра — camera matrix и distortion coefficients. Это и есть те калибровочные коэффициенты, которые нам нужны.
Обработка фото и видео
Для обработки фото и/или видио необходимо выполнить скрипты, приведенные ниже. В скриптах нужно указать свои калибровочные параметры и рабочие папки.
Скрипт для обработки фото
from __future__ import print_function import numpy as np import cv2 import glob from matplotlib import pyplot as plt from common import splitfn import os img_names_undistort = [img for img in glob.glob("/путь до папки с фотографиями/*.png")] new_path = "/путь для сохранения обработанных изображений/" camera_matrix = np.array([[1.26125746e+03, 0.00000000e+00, 9.40592038e+02], [0.00000000e+00, 1.21705719e+03, 5.96848905e+02], [0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]); dist_coefs = np.array([-0.49181345, 0.25848255, -0.01067125, -0.00127517, -0.01900726]); i = 0 #for img_found in img_names_undistort: while i < len(img_names_undistort): img = cv2.imread(img_names_undistort[i]) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) h, w = img.shape[:2] newcameramtx, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_coefs, (w, h), 1, (w, h)) dst = cv2.undistort(img, camera_matrix, dist_coefs, None, newcameramtx) dst = cv2.cvtColor(dst, cv2.COLOR_BGR2RGB) # crop and save the image x, y, w, h = roi dst = dst[y:y+h-50, x+70:x+w-20] name = img_names_undistort[i].split("/") name = name[6].split(".") name = name[0] full_name = new_path + name + '.jpg' #outfile = img_names_undistort + '_undistorte.png' print('Undistorted image written to: %s' % full_name) cv2.imwrite(full_name, dst) i = i + 1
Скрипт для обработки видео
from __future__ import print_function import numpy as np import cv2 import glob from matplotlib import pyplot as plt from common import splitfn import os FILENAME_IN = "videoin.mp4" FILENAME_OUT = "videoout.mp4" CODEC = 'mp4v' camera_matrix = np.array([[1.26125746e+03, 0.00000000e+00, 9.40592038e+02], [0.00000000e+00, 1.21705719e+03, 5.96848905e+02], [0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]); dist_coefs = np.array([-3.18345478e+01, 7.26874187e+02, -1.20480816e-01, 9.43789095e-02, 5.28916586e-01]); print ("OpenCV version : {0}".format(cv2.__version__)) print((cv2.__version__).split('.')) # Load video video = cv2.VideoCapture(FILENAME_IN) fourcc = cv2.VideoWriter_fourcc(*list(CODEC)) fps = video.get(cv2.CAP_PROP_FPS) frame_count = video.get(cv2.CAP_PROP_FRAME_COUNT) size = (int(video.get(cv2.CAP_PROP_FRAME_WIDTH)), int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))) sizew = (1676, 846) writer = cv2.VideoWriter(FILENAME_OUT, fourcc, 25, sizew) newcameramtx, roi = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_coefs, (size[0], size[1]), 1, (size[0], size[1])) x, y, w, h = roi M = cv2.getRotationMatrix2D((size[0]/2,size[1]/2),5,1) while video.grab() is True: print("On frame %i of %i."%(video.get(cv2.CAP_PROP_POS_FRAMES), frame_count)) frame = video.retrieve()[1] frame = cv2.undistort(frame, camera_matrix, dist_coefs, None, newcameramtx) frame = frame[y:y+h-50, x+70:x+w-20] writer.write(frame) video.release() writer.release()