Source code for sensors.Utilities.frame_decoder

import cv2
import numpy as np
import os
import subprocess
import uuid
from time import time, sleep
import copy
from colorama import Fore
from queue import Queue


[docs]class FrameDecoder: """Class to decode frames from h264 video. :param config: configuration including data.json template :type config: object :param video_path: folder containing video file :type video_path: string :param data_json: json dictionary with import/output-format for KInsecta web app :type data_json: dict """ def __init__(self, config, video_path, data_json): self.config = config self.executor = config.executor self.video_path = video_path self.video_filename = self.get_video_filename() self.convert_to_mp4() self.stream = cv2.VideoCapture(self.video_filename) self.data_json = data_json self.trigger_save = False self.stopped = False # initialize the queue used to store frames read from # the video file self.Q = Queue(maxsize=self.number_of_frames) self.skip_read = False
[docs] def get_video_filename(self): """Returns the filename of the video. :return: filename :rtype: string """ f = [] for (dir_path, dir_names, filenames) in os.walk(self.video_path): f.extend(filenames) break return os.path.join(self.video_path, f[0])
[docs] def convert_to_mp4(self): """Convert ``h264`` to ``mp4`` with `GPAC` and get number of frames from its stdout. """ # convert h264 to mp4 video, dependencies gpac video_filename = self.video_filename print(Fore.RED + 'Converting h264 to mp4 video with MP4Box(gpac)!' + Fore.WHITE) cmd = '{} {} {} {} {}'.format('MP4Box -fps', self.config.camera_fps, '-add', video_filename, video_filename.replace('.h264', '.mp4')) process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) _, stdout = process.communicate() self.number_of_frames = int(stdout.decode('utf-8').split('Slices:')[1].split(' ')[1]) os.system('{} {}'.format('rm', video_filename)) self.video_filename = video_filename.replace('.h264', '.mp4')
[docs] def start(self): """Start decoding thread to fill queue with frames. """ # start a thread to read frames from the file video stream self.executor.submit(self.update) return self
[docs] def update(self): """Main decoding thread, that fills queue with decoded frames. Every second frame the faster grab method is \ used instead of decoding a frame. """ # keep looping infinitely while True: # if the thread indicator variable is set, stop the # thread if self.stopped: self.stream.release() return # otherwise, ensure the queue has room in it if not self.Q.full(): if self.skip_read: # read the next frame from the file (grabbed, frame) = self.stream.read() if not grabbed: self.stop() return self.Q.put(frame) else: grabbed = self.stream.grab() if not grabbed: self.stop() return self.skip_read = not self.skip_read sleep(0.001)
[docs] def more(self): """Check if frame queue is not empty or if the decoding process is not stopped yet. :return: flag to decode next frame :rtype: boolean """ # return True if there are still frames in the queue return self.Q.qsize() > 0 or not self.stopped
[docs] def stop(self): """Stop the decoding thread and insert `None` as last queue item. Set corresponding flag. """ # indicate that the thread should be stopped self.Q.put(None) self.stopped = True
[docs] def create_filename(self): """Creates UUID filename for frame and write this information to ``data.json``. """ UUID = uuid.uuid1() UUID_filename = os.path.join(self.video_path, '{}.{}'.format(UUID, 'png')) images_copy = copy.deepcopy(self.config.data["images"])[0] images_copy["filename"] = '{}.{}'.format(UUID, 'png') self.data_json["images"].append(images_copy) return UUID_filename
[docs] @staticmethod def write_frame(frame, UUID_filename): """Saves the frame with UUID filename. :param frame: frame to save to disc :type frame: numpy array :param UUID_filename: filename of frame :type UUID_filename: string """ cv2.imwrite(UUID_filename, frame)
[docs] def frames(self): """Loops over all the video frames and computes the mean pixel values and the difference to the mean of \ the preceded frame in each loop. The root mean square (rms) of the mean differences gets updated in each loop \ and if a frame's mean difference is five times higher than the current rms, a sequence of frames is saved to \ disc. If no such trigger event occurs, a sequence of frames from the middle of the video gets saved. """ # empty images list and add list entries for multiple images by appending self.data_json["images"] = [] count = 1 saved_frames_count, mean_old, rms = 0, 0, 0 index_start = self.number_of_frames // 4 center_frames = [] triggered_frames = [] start_time = time() while self.more(): frame = self.Q.get(block=True) if frame is None: break if not self.trigger_save: # approx 0.05 sec per mean calculation res_frame = np.resize(frame, (320, 240, 1)) mean_new = np.mean(res_frame) # skip first loop due to non existing difference if count != 1: mean_diff = mean_new - mean_old # set reasonable starting value for rms if count == 2: rms = np.abs(mean_diff) if rms > 5: rms = 5 if rms < 0.05: rms = 0.5 if index_start <= count < index_start + self.config.number_frames: center_frames.append(frame) # trigger save frames sequence if mean_diff > 5 * rms: self.trigger_save = True # print("{:1.4f}".format(mean_diff), "{:1.4f}".format(rms)) print(Fore.GREEN + 'Trigger event detected! ' + Fore.WHITE) print('Saving sequence of %d frames!' % self.config.number_frames) # ignore great negative differences and don't update rms elif mean_diff < - 5 * rms: pass # update rms else: rms = np.sqrt((np.square(rms) * (count - 1) + np.square(mean_diff)) / count) # print("{:1.4f}".format(mean_diff), "{:1.4f}".format(rms)) mean_old = mean_new else: if saved_frames_count < self.config.number_frames: triggered_frames.append(frame) saved_frames_count += 1 else: self.stop() break count += 1 # check if save sequence got triggered while iterating through frames. If not then frames from # the center of the mp4 video get saved if self.trigger_save: save_frames = triggered_frames starting_frame = 2 * (count - 1) - 1 else: print(Fore.RED + 'No trigger event detected!' + Fore.WHITE + ' Decode frames from center of the video!') print('Saving sequence of %d frames!' % self.config.number_frames) save_frames = center_frames starting_frame = self.number_of_frames // 2 await_save_first_frame = True for frame in save_frames: UUID_filename = self.create_filename() if await_save_first_frame: await_save_first_frame = False self.plot_png_filename = os.path.join('/sensors/static/data/img', os.path.basename(os.path.dirname(UUID_filename)), os.path.basename(UUID_filename)) self.write_frame(frame, UUID_filename) else: self.executor.submit(self.write_frame, frame, UUID_filename) decoding_save_time = "{:2.1f}".format(time() - start_time) print('From {} total video frames, decoded {} frames (starting frame #{}) and saved in {} seconds!'. format(self.number_of_frames, self.config.number_frames, starting_frame, decoding_save_time))