python

Убираем радиальное искажение с фото и видео при помощи библиотеки openCV и языка python

  • воскресенье, 29 октября 2017 г. в 03:12:57
https://habrahabr.ru/post/341160/
  • Работа с видео
  • Обработка изображений
  • Python


В данной статье будет рассказываться о применении библиотеки машинного зрения (openCV) для удаления эффекта радиального искажения (дисторсии) с фото и видео. Данный эффект также известен как эффект рыбьего глаза (fisheye) или distortion. Решение написать данную статью было принято после нескольких дней поиска информации в интернете. Не смотря на то, что есть гайды на английском языке, они не объясняют как правильно установить openCV, чтобы все работало. В статье присутствует готовый код.


Сразу привожу фото итогового результата. Слева оригинальное фото, справа — обработанное:


beforeafter



Сборка и установка 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. Это и есть те калибровочные коэффициенты, которые нам нужны.


coef

Обработка фото и видео


Для обработки фото и/или видио необходимо выполнить скрипты, приведенные ниже. В скриптах нужно указать свои калибровочные параметры и рабочие папки.


Скрипт для обработки фото
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()