######################################################################
# 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()