Source code for Video

######################################################################
#          O  o   o o-o   o--o   o-o  o   o o--o o-o   o--o          #
#         / \ |\  | |  \  |   | o   o |\ /| |    |  \  |             #
#        o---o| \ | |   O O-Oo  |   | | O | O-o  |   O O-o           #
#        |   ||  \| |  /  |  \  o   o |   | |    |  /  |             #
#        o   oo   o o-o   o   o  o-o  o   o o--o o-o   o--o          #
######################################################################
#
# ANDROMEDE
# Copyright (C) 2023 Toulouse INP
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details :
# <http://www.gnu.org/licenses/>.
#
######################################################################

""" This module opens video and create class video. It defines all functions needed for visualization on GUI.
"""

import os

import cv2
import numpy as np
from PySide6.QtCore import Signal, QObject
from PySide6.QtGui import QImage, QPixmap
from PySide6.QtWidgets import QDialog
import glob
from ThirdParty import PhotogrammetryFunctions as P_funcs
from ui_files.imagefps import Ui_ImageFps
from ui_files.renameimage import Ui_RenameImageWindow


[docs] @staticmethod def copy_and_rename_image(filepath, filepattern_out): """Create new image with name compatible with openCv video reader :return proj_table: table of the ground reference point if exist (file name must be projection.txt in the video folder) :rtype: array :return interior_orient: intrinsic camera parameters (file name must be interiorGeometry.txt in the video folder) :rtype: class object (defined in PhotogrammetryFunctions.py) """ extension = os.path.splitext(filepath)[1] dir_path = os.path.dirname(str(filepath)) flist = sorted(glob.glob((os.path.join(dir_path, '*' + extension)))) for i, filename in enumerate(flist): new_name = filepattern_out + '_' + '0' * (4 - len(str(i))) + str(i) + extension os.rename(filename, new_name) flist = sorted(glob.glob((os.path.join(dir_path, '*' + extension)))) return flist, extension
# noinspection PyAttributeOutsideInit
[docs] class Video(QObject): __slots__ = ('storable', 'outputConsole', 'process_status', 'prop', 'masks', 'stab_dict', 'process_dict', 'cap', 'filepath', 'current_cap', 'imgs', 'current_img', 'manual_objs') process_status_changed = Signal(bool) def __init__(self, output_console): super(Video, self).__init__() self.storable = ('prop', 'stab_dict', 'process_dict') self.outputConsole = output_console # Global process status # 0 empty / 1 video_loaded / 2 image_processed / 3 object_detected / 4 motion_model / 5 velocity_field self.process_status = 0 self.prop = {} self.masks = {} self.stab_dict = {} self.process_dict = {} self.cap = cv2.VideoCapture() self.reset() self.test = 0 def reset(self): self.reset_image_processing(False) self.update_process_status(0) # Video properties self.filepath = '' self.cap.release() self.current_cap = None self.prop['current_frame'] = -1 self.prop['video_end_frame'] = 0 self.prop['window_frame'] = (0, 0) self.prop['frame_step'] = 1 self.prop['fps'] = 0 self.prop['duration'] = 0 self.prop['original_resolution'] = (0, 0) self.masks['original'] = np.zeros((0, 0), dtype='uint8') # Stabilisation self.stab_dict['transforms'] = np.zeros((0, 0)) self.stab_dict['window_frame'] = (-1, -1) # Projection self.process_dict['proj_table'] = np.empty((4, 4)) self.process_dict['EOR'] = [] self.process_dict['interior_orient'] = P_funcs.camera_interior() self.manual_objs = 0 def reset_image_processing(self, update_process): if update_process: self.update_process_status(1) # Pre-processing (projection) properties self.process_dict['Ht'], self.process_dict['Ht_ROI'], self.projective_matrix = [np.identity(3) for _ in range(3)] self.process_dict['originXY'] = [0, 0, 0, 'A', 0, 0] self.process_dict['ROI_min']= (0, 0) # Processed images properties self.imgs = [] self.masks['background'] = np.zeros((0, 0)) self.current_img = -1 self.process_dict['resolution_transform_ROI'] = (0, 0) self.masks['processed'] = np.zeros((0, 0), dtype='uint8') self.process_dict['resolution'], self.process_dict['corner'] = [(0, 0) for _ in range(2)] def update_process_status(self, value): self.process_status = value self.process_status_changed.emit(True)
[docs] def read_file(self, filepath, param, image_input): """read video and default associated files as projection, intrinsic camera parameters. Define parameters of the selected video (resolution, fps, etc...) :param filepath: path of the selected video :param param: dictionary to implement :param image_input: flag to indicate image reading :return ROI: pixel of the transformed (or original) ROI :rtype: int :return window_frame: temporal windows for analysis (in frame number) :rtype: list (first frame, end frame) :return original_resolution: resolution of original frame :rtype: list (height,width) :return fps: temporal resolution , frame per second :rtype: float :return duration: duration of the video :rtype: float :return proj_table: table of the ground reference point if exist (file name must be projection.txt in the video folder) :rtype: array :return interior_orient: intrinsic camera parameters (file name must be interiorGeometry.txt in the video folder) :rtype: class object (defined in PhotogrammetryFunctions.py) """ self.filepath = filepath self.outputConsole.appendPlainText("Open " + self.filepath) dir_path = os.path.dirname(str(self.filepath)) if image_input: # Image list w_img = RenameImageWindow(os.path.splitext(self.filepath)[0]) w_img.exec() if w_img.result(): filepattern = w_img.get_pattern() flist, extension = copy_and_rename_image(self.filepath, filepattern) self.cap = cv2.VideoCapture(os.path.join(dir_path, filepattern + '_%04d' + extension)) w_fps = ImageFpsWindow(self) w_fps.exec() else: return else: self.cap = cv2.VideoCapture(self.filepath) self.prop['fps'] = self.cap.get(cv2.CAP_PROP_FPS) self.update_process_status(1) self.imgs.clear() # clear old preprocessed self.current_img = 0 self.prop['current_frame'] = 0 self.prop['video_end_frame'] = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT)) - 1 self.prop['duration'] = self.prop['video_end_frame'] / self.prop['fps'] self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.prop['current_frame']) # Rewind image = self.cap.read()[1] self.prop['original_resolution'] = image.shape self.prop['window_frame'] = [0, self.prop['video_end_frame']] param.dict['IP']['ROIxmax'] = self.prop['original_resolution'][1] - 1 param.dict['IP']['ROIymax'] = self.prop['original_resolution'][0] - 1 param.dict['IP']['window_frame'] = self.prop['window_frame'] if os.path.isfile(os.path.join(dir_path, 'projection.txt')): self.outputConsole.appendPlainText( 'Reading projection file : ' + str(os.path.join(dir_path, 'projection.txt'))) self.process_dict['proj_table'] = np.loadtxt(os.path.join(dir_path, 'projection.txt')) self.outputConsole.appendPlainText('Projection table : ' + str(self.process_dict['proj_table'])) else: self.outputConsole.appendPlainText('No projection file') if os.path.isfile(os.path.join(dir_path, 'interiorGeometry.txt')): self.outputConsole.appendPlainText('Reading interior orientation file : ' + str(os.path.join(dir_path, 'interiorGeometry.txt\n'))) self.process_dict['interior_orient'] = P_funcs.read_aicon_ior( os.path.join(dir_path, 'interiorGeometry.txt')) # read interior orientation from file (aicon) else: self.outputConsole.appendPlainText('No interior orientation file\n') pixel_size = 0.01 # mm self.process_dict['interior_orient'].xh = 0 self.process_dict['interior_orient'].yh = 0 self.process_dict['interior_orient'].ck = -self.process_dict['interior_orient'].xh * pixel_size self.process_dict['interior_orient'].A1 = 0 self.process_dict['interior_orient'].A2 = 0 self.process_dict['interior_orient'].A3 = 0 self.process_dict['interior_orient'].B1 = 0 self.process_dict['interior_orient'].B2 = 0 self.process_dict['interior_orient'].C1 = 0 self.process_dict['interior_orient'].C2 = 0 self.process_dict['interior_orient'].resolution_x = self.prop['original_resolution'][1] self.process_dict['interior_orient'].resolution_y = self.prop['original_resolution'][0] self.process_dict['interior_orient'].sensor_size_x = self.prop['original_resolution'][1] * pixel_size self.process_dict['interior_orient'].sensor_size_y = self.prop['original_resolution'][0] * pixel_size # display video information self.outputConsole.appendPlainText('Resolution = ' + str(self.prop['original_resolution'][1]) + ' x ' + str(self.prop['original_resolution'][0])) self.outputConsole.appendPlainText('Number of frames = ' + str(self.prop['video_end_frame'] + 1)) self.outputConsole.appendPlainText('Fps = ' + str(self.prop['fps']) + ' img/s') self.outputConsole.appendPlainText('Duration = ' + str(self.prop['duration']) + ' s\n')
def display(self, scene, video_choose, color_choice): scene.clear() if video_choose[0:8] == 'Original': if self.prop['current_frame'] > -1: self.cap.set(cv2.CAP_PROP_POS_FRAMES, self.prop['current_frame']) ret, self.current_cap = self.cap.read() if ret: if video_choose[9:] == 'with_mask': frame = cv2.bitwise_and(self.current_cap, self.current_cap, mask=self.masks['original']) image = QImage(frame, frame.shape[1], frame.shape[0], frame.shape[1] * 3, QImage.Format_BGR888) else: image = QImage(self.current_cap, self.current_cap.shape[1], self.current_cap.shape[0], self.current_cap.shape[1] * 3, QImage.Format_BGR888) pix = QPixmap(image) scene.addPixmap(pix) elif video_choose[0:9] == 'Processed': if self.current_img > -1: frame = self.imgs[self.current_img] if color_choice == 1: image = QImage(frame, frame.shape[1], frame.shape[0], frame.shape[1], QImage.Format_Grayscale8) else: image = QImage(frame, frame.shape[1], frame.shape[0], frame.shape[1] * 3, QImage.Format_BGR888) pix = QPixmap(image) scene.addPixmap(pix) elif video_choose == 'Background': frame = self.masks['background'] image = QImage(frame, frame.shape[1], frame.shape[0], frame.shape[1], QImage.Format_Grayscale8) pix = QPixmap(image) scene.addPixmap(pix)
[docs] class ImageFpsWindow(QDialog, Ui_ImageFps): def __init__(self, video): super().__init__() self.video = video self.video.prop['fps'] = 1 self.setupUi(self)
[docs] def accept(self): self.video.prop['fps'] = int(self.lineEdit_fps.text()) self.close()
[docs] class RenameImageWindow(QDialog, Ui_RenameImageWindow): def __init__(self, filepattern): super().__init__() self.setupUi(self) self.lineEdit_imagePrefix.setText(filepattern) def get_pattern(self): return self.lineEdit_imagePrefix.text()