# -*- coding: utf-8 -*-
#
# This module is part of pylapsy.
# It is licensed under a GPL-3.0 license, for details see LICENSE file.
#
# Author: Jonas Gliß
# Copyright (C) 2019 Jonas Gliss (jonasgliss@gmail.com)
# GitHub: jgliss
# Email: jonasgliss@gmail.com
import cv2
import numpy as np
import os
from pylapsy import utils, ImageList, logger, print_log
from functools import partial
from pylapsy.speedup_helpers import find_shifts_fast, shift_crop_list
[docs]class Deshaker(object):
"""Interface for deshaking a series of images
"""
def __init__(self, imglist=None, outdir=None):
self._imglist = None
# make sure input is provided properly by going through decorator
# assignment method
if imglist is not None:
self.imglist = imglist
self.results = None
self._init_results()
def _init_results(self):
self.results = dict(
dx=None,
dy=None,
matrices=None)
@property
def imglist(self):
"""List of images (instance of :class:`pylapsy.ImageList`)"""
return self._imglist
@imglist.setter
def imglist(self, val):
if not isinstance(val, ImageList):
val = ImageList(val) # raises error if invalid
self._imglist = val
[docs] def find_shifts(self, ref_index=None, parallel=True):
"""Find shifts for all images in :attr:`imglist`
Parameters
----------
ref_index : int
index of reference image in image list (shifts are computed wrt
that image)
Returns
-------
dict
dictionary containing shifts (`dx, dy`) and transformation
matrices (`matrices`) for each image in :attr:`imglist`.
"""
if ref_index is None:
ref_index = 0
imglist = self.imglist
ref = imglist[ref_index].to_gray(inplace=False).img
if parallel:
res = find_shifts_fast(imglist.files, ref)
dx, dy, matrices = list(zip(*res))
else:
dx, dy, matrices = self._find_shifts(imglist, ref)
self.results['dx'] = dx
self.results['dy'] = dy
self.results['matrices'] = matrices
return self.results
@staticmethod
def _find_shifts(imglist, ref):
dx, dy, matrices = [],[],[]
totnum = len(imglist)
disp_each = int(totnum/4)
print_log.info('Finding image shifts for {} images'.format(totnum))
for i, img in enumerate(imglist):
if totnum > 10 and i%disp_each == 0:
print_log.info("{} %".format(i/totnum*100))
gray = img.to_gray(inplace=False)
(_dx, _dy), da, M = utils.find_shift(ref, gray.img)
matrices.append(M)
dx.append(_dx)
dy.append(_dy)
logger.info('Image {}, dx={:.3f} dy={:.3f}'.format(i, _dx, _dy))
return (dx, dy, matrices)
[docs] def deshake(self, outdir=None, ref_index=None, sequence_id=None,
save_preview_video=False, parallel=True):
"""Method that deshakes images sequence and saves result
Parameters
----------
outdir : str, optional
output directory. If None, a subdirectory will be created in the
current directory
ref_index : int, optional
Index of reference image in sequence (all images are adjusted wrt
to this image, defaults to 0).
sequence_id : str, optional
name of the sequence (for output directory)
save_preview_video : bool
if True, a preview video is saved (currently not working)
"""
if sequence_id is None:
sequence_id = 'pylapsy'
if outdir is None:
outdir = 'output_{}'.format(sequence_id)
if not os.path.exists(outdir):
os.mkdir(outdir)
imglist = self.imglist
# get image width and height
h,w = imglist.current_img.shape[:2]
# Find dx and dy shifts for all images
results = self.results
if results['dx'] is None:
results = self.find_shifts(ref_index=ref_index,
parallel=parallel)
dx, dy = results['dx'], results['dy']
matrices = results['matrices']
# determine image crop for output images in order to avoid black
# borders (based on maximum and minimum shifts)
crop = utils.get_crop(dx, dy, w, h)
shift_crop_list(imglist.files,
matrices,
crop,
outdir,
multiproc=parallel,
multithread=False)
if save_preview_video:
raise NotImplementedError
print_log.info('Results are stored at {}'.format(outdir))
[docs] def deshake_v0(self, outdir=None, ref_index=None, sequence_id=None,
save_images=True, save_preview_video=True,
preview_fps=24):
"""Method that deshakes images sequence and saves result
Parameters
----------
outdir : str, optional
output directory. If None, a subdirectory will be created in the
current directory
ref_index : int, optional
Index of reference image in sequence (all images are adjusted wrt
to this image, defaults to 0).
sequence_id : str, optional
name of the sequence (for output directory)
save_images : bool
if True, corrected images are saved
save_preview_video : bool
if True, a preview video is saved
preview_fps : int
fps of preview video (if applicable)
"""
if sequence_id is None:
sequence_id = 'pylapsy'
if outdir is None:
outdir = 'output_{}'.format(sequence_id)
if not os.path.exists(outdir):
os.mkdir(outdir)
imglist = self.imglist
# get image width and height
h,w = imglist.current_img.shape[:2]
# Find dx and dy shifts for all images
results = self.results
if results['dx'] is None:
results = self.find_shifts()
dx, dy = results['dx'], results['dy']
matrices = results['matrices']
# determine image crop for output images in order to avoid black
# borders (based on maximum and minimum shifts)
x0,x1,y0,y1 = utils.get_crop(dx, dy, w, h)
clip = None
if save_preview_video:
wnew = x1 - x0
hnew = y1 - y0
videopath = os.path.join(outdir, 'preview_{}.avi'.format(sequence_id))
try:
clip = cv2.VideoWriter(videopath,
cv2.VideoWriter_fourcc('M','J','P','G'),
preview_fps, (wnew, hnew))
except Exception as e:
save_preview_video = False
print_log.warning('Failed to init VideoWriter. Reason: {}'
.format(repr(e)))
totnum = len(imglist)
disp_each = int(totnum/4)
print_log.info('Shifting {} images'.format(totnum))
for i, img in enumerate(imglist):
if totnum > 10 and i%disp_each == 0:
print_log.info("{} %".format(i/totnum*100))
shifted = utils.shift_image(img.img, matrices[i])
shifted_crop = shifted[y0:y1, x0:x1]
if save_preview_video:
clip.write(shifted_crop)
if save_images:
fp = os.path.join(outdir, os.path.basename(imglist.files[i]))
utils.imsave(shifted_crop, fp)
if save_preview_video:
clip.release()
print_log.info('Results are stored at {}'.format(outdir))
if __name__=='__main__':
import pylapsy as ply
from time import time
DIR = "C:\\Users\\Jonas\\Jonas\\photography\\timelapse\\lrt_out\\LRT_20190504_sunset_noklevann\\"
files = ply.io.get_testimg_files_deshake()
#files = ply.io.find_image_files(DIR,file_pattern='*.jpg')
ds = Deshaker(files)
ds.deshake()