Merge branch 'feature/basic-pipeline' into dev

This commit is contained in:
Oscar Blue 2022-04-05 20:11:37 +01:00
commit 708d7058e7
3 changed files with 267 additions and 74 deletions

12
README.md Normal file
View file

@ -0,0 +1,12 @@
# Autophotographer
Autophotographer is a tool that helps users filter the best images from a video.
```
.
├── docs Report and Project proposal
├── img images for the repository
├── README.md
├── src project source code
└── terraform terraform IaC
```

View file

@ -1,87 +1,239 @@
import cv2 import cv2
import sys
import argparse import argparse
import os import os
import pathlib from os.path import abspath
import numpy import pandas
import time import matplotlib.pyplot as plt
import numpy as np
from skimage.exposure import is_low_contrast
import yaml
# Process arguments # Import local packages
def parse_arguments(argv=None): from focusdetection.focusdetection import fast_fourier
parser = argparse.ArgumentParser()
parser.add_argument('-i', '--input', dest='inputfile', type=pathlib.Path, help='Specify a video file')
parser.add_argument('-o', '--output', dest='outputfolder', type=pathlib.Path, help='Specify a folder to save frames to')
return parser.parse_args()
# Convert video to frames #import sys
def video_to_frames(): #import pathlib
startTime = time.time() #import numpy
print("Converting video to frames...") #import time
capture = cv2.VideoCapture(str(inputfile))
success,image = capture.read()
count = 0
while success:
outputfile = outputfolder + "/frame%d.jpg" % count
# print(outputfile)
cv2.imwrite(outputfile, image)
success,image = capture.read()
count +=1
endTime = time.time()
totalTime = endTime - startTime
print("Exported " + str(count) + " frames in " + str(totalTime) + " seconds.")
# Shrink set based on filesize # accepted image formats
def display_file_sizes(): image_formats = (".jpg", ".jpeg", ".png")
filesizes = []
for filename in os.listdir(outputfolder):
filepath = outputfolder + "/" + filename
filesize = os.path.getsize(filepath)
print(filepath + ": " + str(filesize))
filesizes.append(filesize)
# work out average
average = sum(filesizes)/len(filesizes)
print ("Average is: " + str(average))
# delete files below average
count = 0
for filename in os.listdir(outputfolder):
filepath = outputfolder + "/" + filename
if filesizes[count] < average:
# print(filepath + ": " + str(filesizes[count]))
os.remove(filepath)
count += 1
#def remove_similar_frames(): # accepted video formats
def order_frames_by_filesize(): video_formats = (".mp4", ".mov", ".avi", ".flv", ".mkv")
frames = os.listdir(outputfolder)
frames = sorted(frames, key = lambda x: os.stat(os.path.join(outputfolder, x)).st_size, reverse = True) # load config file
for frame in frames: def load_config(path=os.path.join(os.path.dirname(__file__), "./config.yml")):
filesize = os.stat(os.path.join(outputfolder, frame)).st_size abs_path = os.path.abspath(path)
if filesize > 1024:
filesize = filesize / 1024 # check if file exists
print(frame + ": " + str(filesize) + " KB") if os.path.exists(abs_path):
if abs_path.lower().endswith((".yml", ".yaml")):
print("[INFO] Loading config...")
# attempt to open file
with open(abs_path) as file:
return yaml.safe_load(file)
else: else:
print(frame + ": " + str(filesize)) print("[ERRO] Please specify a file with extension '.yml' or '.yaml'.")
quit()
else:
print("[ERRO] Path does not exist")
quit()
def order_frames_by_brightness(): def filter_to_function(imagefilter, prettyName=False):
frames_path = os.listdir(outputfolder) if imageFilter == "brightness":
frames_path = sorted(frames_path, key = return_frame_brightness) # filter_brightness()
for frame_path in frames_path: print("[INFO] Filtering based on brightness...")
print(frame_path + ": " + str(return_frame_brightness(frame_path))) elif imageFilter == "filesize":
# filter_filesize()
print("[INFO] Filtering based on filesize...")
elif imageFilter == "contrast":
# filter_contrast()
print("[INFO] Filtering based on contrast...")
elif imageFilter == "focus":
# filter_focus()
print("[INFO] Filtering based on focus...")
else:
print("[WARN] Filter not recognised. Ignoring...")
def return_frame_brightness(frame_path): # filter paths by accepted file extensions
frame = cv2.imread(os.path.join(outputfolder, frame_path)) def filter_paths(paths):
hsv_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) print("[INFO] Filtering paths by filetype...")
h, s, v = cv2.split(hsv_frame) filtered_paths = []
average_v = numpy.average(v) for path in paths:
return average_v if path.lower().endswith(video_formats + image_formats):
filtered_paths.append(path)
return filtered_paths
args = parse_arguments() # load frames from images
inputfile = str(args.inputfile.absolute()) def load_video(path):
outputfolder = str(args.outputfolder.absolute()) print("[INFO] Loading video...")
stream = cv2.VideoCapture(path)
frames = []
while True:
(retrieved, frame) = stream.read()
if not retrieved:
break
frames.append(frame)
return frames
# Convert video to frames # load images
video_to_frames() def load_image(path):
print("[INFO] Loading image...")
image = cv2.imread(path)
return image
display_file_sizes() # load images respective of their file type
#order_frames_by_filesize() def load_files(paths):
#order_frames_by_brightness() print("[INFO] Loading files...")
# set loaded counters to 0
images_loaded = 0
videos_loaded = 0
videos = []
images = []
# load in all image/video files
for path in paths:
# if file is an image, just load it
if path.lower().endswith(image_formats):
image = load_image(path)
images_loaded += 1
images.append(image)
# if file is a video, break into frames and load them
elif path.lower().endswith(video_formats):
frames = load_video(path)
videos.append(frames)
videos_loaded += 1
else:
print("[INFO] Skipped non-image/non-video file.")
# return information on number of files loaded
if videos_loaded == 0 and images_loaded == 0:
print("[INFO] No valid images or videos found.")
else:
print("[INFO] {} image(s) and {} video(s) loaded.".format(images_loaded, videos_loaded))
total_frames = 0
for video in videos:
total_frames += len(video)
total_images = total_frames + len(images)
print("[INFO] Total images loaded (including video frames): {}".format(total_images))
return (images, videos)
# filter images based on filesize
def filter_filesize(paths):
print("[INFO] Filtering by filesize...")
filesizes = []
filtered_paths = []
# calculate avg filesize
for path in paths:
filesize = os.path.getsize(path)
filesizes.append(filesize)
filesize_avg = sum(filesizes)/len(filesizes)
filesize_std = pandas.Series(filesizes).std(ddof=0)
filesize_min = min(filesizes)
filesize_max = max(filesizes)
filesize_filter = filesize_avg - filesize_std
print("[INFO] min size: {}, max size: {}, avg size: {}, std size: {}".format(
filesize_min, filesize_max, filesize_avg, filesize_std))
# remove images that are 3 std below mean
for path in paths:
if os.path.getsize(path) > filesize_filter:
filtered_paths.append(path)
return filtered_paths
# plot the distribution of file sizes for data insight
def plot_filesizes(paths):
filesizes = []
for path in paths:
if path.lower().endswith(image_formats):
filesize = os.path.getsize(path)
filesizes.append(filesize)
sorted_fs = sorted(filesizes)
data = np.array(sorted_fs)
fig = plt.figure(figsize =(10, 7))
plt.boxplot(data)
plt.show()
# resize image and turn to greyscale
def process_image(path, width):
print("[INFO] Processing image...")
image = cv2.imread(path)
height = int(image.shape[0] * (width / image.shape[0]))
image = cv2.resize(image, (width, height))
image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
return image
# filter images based on contrast
def filter_contrast(paths, thresh=0.35):
print("[INFO] Filtering by contrast...")
filtered_paths = []
for path in paths:
if path.lower().endswith(video_formats):
continue
print(path)
image = process_image(path, 500)
if is_low_contrast(image, thresh):
print("[INFO] Low contrast")
else:
print("[INFO] High contrast")
filtered_paths.append(path)
return filtered_paths
# filter images based on brightness
def filter_brightness(paths):
print("[INFO] Filtering by brightness...")
# filter images based on focus/blurriness
def filter_focus(paths):
print("[INFO] Filtering by focus/blurriness...")
filtered_paths = []
# read images in greyscale
for path in paths:
if path.lower().endswith(video_formats):
continue
print(path)
image = process_image(path, 500)
(mean, blurry) = fast_fourier(image, size=60, thresh=30)
blurStats = "Blurry ({:.4f})" if blurry else "Not blurry ({:.4f})"
if blurry:
print(("[INFO] {}: " + blurStats).format(path, mean))
else:
filtered_paths.append(path)
return filtered_paths
# rank remaining images using a CNN
def rank_images():
print("[INFO] Ranking images using machine learning...")
# parse commande line arguments
parser = argparse.ArgumentParser()
parser.add_argument("-i", "--input", type=os.path.abspath, required=True, nargs="+",
help="path to video or image folder")
parser.add_argument("-c", "--config", type=os.path.abspath, help="path to config file")
args = vars(parser.parse_args())
paths = filter_paths(args["input"])
# load in config file
if args["config"] is not None:
autophotoConf = load_config(args["config"])
else:
autophotoConf = load_config()
# Order and selection of operations from config file
for imageFilter in autophotoConf["filters"]:
n_of_images_before = len(paths)
filter_to_function(imageFilter)
n_of_images_after = len(paths)
# calculate set difference after filtering
diff = n_of_images_before - n_of_images_after
print("[INFO] Filtered {}/{} images via {} filtering.".format(
n_of_images_after, n_of_images_before, imageFilter))
if autophotoConf["CNNrank"]:
print("[INFO] Running CNN ranking...")

29
src/config.yml Normal file
View file

@ -0,0 +1,29 @@
---
# Config file for autophotographer
# List of filters to apply in order
filters:
- brightness
- filesize
- contrast
- focus
# Whether or not to apply CNN ranking
CNNrank: True
# Options for focus filter
brightness_options:
threshold: 0.35
# Options for focus filter
filesize_options:
threshold: 0.35
# Options for focus filter
contrast_options:
threshold: 0.35
# Options for focus filter
focus_options:
threshold: 0.35
...