Убираем радиальное искажение с фото и видео при помощи библиотеки openCV и языка python
- воскресенье, 29 октября 2017 г. в 03:12:57
В данной статье будет рассказываться о применении библиотеки машинного зрения (openCV) для удаления эффекта радиального искажения (дисторсии) с фото и видео. Данный эффект также известен как эффект рыбьего глаза (fisheye) или distortion. Решение написать данную статью было принято после нескольких дней поиска информации в интернете. Не смотря на то, что есть гайды на английском языке, они не объясняют как правильно установить 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("\nRMS:", rms)
print("camera matrix:\n", 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()