class BaseModule(BaseWidget): """Application form""" def __init__(self): global conf conf += 'pythonvideoannotator.resources' # Resources can only be loaded after pyqt is running super(BaseModule, self).__init__('Video annotation editor') self._project = Project(parent=self) Dialog.project = self._project self._player = ControlPlayer("Player") self._time = ControlEventTimeline('Time') self._dock = ControlDockWidget("Timeline", side='bottom', order=1, margin=5) self.formset = ['_player'] self._dock.value = self._time self._player.process_frame_event = self.process_frame_event self._player.click_event = self.on_player_click_event self._time.key_release_event = self.__timeline_key_release_event self.load_order = [] self.mainmenu.insert( 0, { 'File': [{ 'Open': self.__open_project_event, 'icon': conf.ANNOTATOR_ICON_OPEN }, '-', { 'Save': self.__save_project_event, 'icon': conf.ANNOTATOR_ICON_SAVE }, { 'Save as': self.__save_project_as_event, 'icon': conf.ANNOTATOR_ICON_SAVE }, '-', { 'Exit': QApplication.closeAllWindows, 'icon': conf.ANNOTATOR_ICON_EXIT }] }) self.mainmenu.insert(1, {'Modules': []}) self.mainmenu.insert(2, {'Windows': []}) ###################################################################################### #### FUNCTIONS ####################################################################### ###################################################################################### def init_form(self): super(BaseModule, self).init_form() if conf.CHART_FILE_PATH: self._time.import_chart(*conf.CHART_FILE_PATH) if conf.PROJECT_PATH: self.load_project(conf.PROJECT_PATH) ###################################################################################### #### IO FUNCTIONS #################################################################### ###################################################################################### def save(self, data, project_path=None): self._project.save(data, project_path) return data def load(self, data, project_path=None): self._project.load(data, project_path) def save_project(self, project_path=None): try: if project_path is None: project_path = QFileDialog.getExistingDirectory( self, "Select the project directory") if project_path is not None and str(project_path) != '': project_path = str(project_path) self.save({}, project_path) except Exception as e: QMessageBox.critical(self, "Error", str(e)) def load_project(self, project_path=None): if project_path is None: project_path = QFileDialog.getExistingDirectory( self, "Select the project directory") if project_path is not None and str(project_path) != '': self.load({}, str(project_path)) ###################################################################################### #### EVENTS ########################################################################## ###################################################################################### def on_player_click_event(self, event, x, y): """ Code to select a blob with the mouse """ super(VideoAnnotationEditor, self).on_player_click_event(event, x, y) self._player.refresh() def process_frame_event(self, frame): """ Function called before render each frame """ return frame def __open_project_event(self): self.load_project() def __save_project_event(self): self.save_project(self._project.directory) def __save_project_as_event(self): self.save_project() def __timeline_key_release_event(self, event): """ Control video playback using the space bar to Play/Pause """ if event.key() == QtCore.Qt.Key_Space: self._player.stop( ) if self._player.is_playing else _player._video.play() ###################################################################################### #### PROPERTIES ###################################################################### ###################################################################################### @property def timeline(self): return self._time @property def player(self): return self._player @property def video(self): return self._player.value @video.setter def video(self, value): self._player.value = value self._player.enabled = value is not None if value: self._time.max = self._player.max @property def project(self): return self._project
class VideoPlayer(BaseWidget): def __init__(self): super(VideoPlayer, self).__init__('Video Player') #Defnition of the form fields # self._dirname = ControlText('Directory', 'Default Value') self._button_play = ControlButton("Play") self._button_play.value = self.__play_all self._button_pause = ControlButton("Pause") self._button_pause.value = self.__pause_all self._button_refresh = ControlButton("Reload") self._button_refresh.value = self.__refresh_all self._button_print = ControlButton("Print") self._button_print.value = self.__print_value # self.formset = [{'Tab1':['_dirname'],},'_button'] # self._directory = ControlDir('Choose a directory') self._file1 = ControlFile('File 1') self._file2 = ControlFile('File 2') # print(self._file.value) self._filetree = ControlFilesTree('Choose a file') # self._filetree.value = 'C:\\Users\\Ashwini Naik\\Videos\\Captures' # self._checkbox = ControlCheckBox('Choose a directory') # self._checkboxList = ControlCheckBoxList('Choose a file') self._player = ControlPlayer('Choose a file') self._player1 = ControlPlayer('Choose a file') # self._slider = ControlSlider('Slider') self._player.value = '_file.value' # self._player.refresh() self.formset = [('_button_play', '_button_pause', '_button_refresh'), ('_file1', '_file2'), ('_player', '_player1')] # self._control.changed_event = self.__print_value def __print_value(self): print("Player 1" + self._player.value) print("Player 2" + self._player1.value) print("File 1" + self._file1.value) print("File 2" + self._file2.value) def __play_all(self): self._player.show() self._player.value = self._file1.value self._player1.show() self._player1.value = self._file2.value self._player.play() self._player1.play() def __pause_all(self): self._player.stop() self._player1.stop() def __refresh_all(self): self._player.value = self._file1.value self._player1.value = self._file2.value
class QuadView(BaseWidget): def __init__(self, *args, **kwargs): super().__init__('4-Cam') self.set_margin(10) # Definition of the forms fields self._player1 = ControlPlayer('Player1') self._player2 = ControlPlayer('Player2') self._player3 = ControlPlayer('Player3') self._player4 = ControlPlayer('Player4') self._runbutton = ControlButton('Stop') self._screenshot = ControlButton('Screenshot') self._outputfile = ControlDir('Screenshots Ausgabe Ordner') self._cams = ControlCheckBoxList('Kameras') # Define the event that will be called when the run button is processed self._runbutton.value = self.__stopEvent self._screenshot.value = self._saveImages self.__check_all_avaliable_cameras() self.formset = [{ '0-Kameras': [('_runbutton'), ('_player1', '_player2'), ('_player3', '_player4')], '1-Einstellungen': [('_outputfile'), ('_cams')] }] self._player1.value = self.__assign_capture(0) self._player2.value = self.__assign_capture(1) self._player3.value = self.__assign_capture(2) self._player4.value = self.__assign_capture(3) self._outputfile.value = os.getcwd() self.__runEvent() def __assign_capture(self, player_id): checked_cam_ids = self._cams.checked_indexes if checked_cam_ids.__len__() > player_id: return cv2.VideoCapture(checked_cam_ids[player_id]) return cv2.VideoCapture() def __runEvent(self): """ After setting the best parameters run the full algorithm """ self._player1.update_frame() self._player1.play() self._player2.update_frame() self._player2.play() self._player3.update_frame() self._player3.play() self._player4.update_frame() self._player4.play() pass def __stopEvent(self): self._player1.stop() self._player2.stop() self._player3.stop() self._player4.stop() def _saveImages(self): """ Saves the Images of all cams to a file :return: """ currentTime = str(strftime("%Y-%m-%d_%H:%M:%S", gmtime())) cv2.imwrite( os.path.join(self._outputfile.value, currentTime + '_cam_1.png'), self._player1.value['frame']) cv2.imwrite( os.path.join(self._outputfile.value, currentTime + '_cam_2.png'), self._player2.value['frame']) cv2.imwrite( os.path.join(self._outputfile.value, currentTime + '_cam_3.png'), self._player3.value['frame']) cv2.imwrite( os.path.join(self._outputfile.value, currentTime + '_cam_4.png'), self._player4.value['frame']) print("Written screenshots to disk") pass def accessible_device(self, source): cap = cv2.VideoCapture(source) if cap is None or not cap.isOpened(): return False cap.release() return True def __check_all_avaliable_cameras(self): for x in range(0, 10): isCam = self.accessible_device(x) if isCam: self._cams += ('Kamera ' + str(x), True)
class BaseModule(BaseWidget): """Application form""" def __init__(self): global conf conf += 'pythonvideoannotator.resources' # Resources can only be loaded after pyqt is running super(BaseModule, self).__init__('Video annotation editor') self._project = Project(parent=self) Dialog.project = self._project self._player = ControlPlayer("Player") self._time = ControlEventTimeline('Time') self._dock = ControlDockWidget("Timeline", side='bottom', order=1, margin=5) self.formset = ['_player'] self._dock.value = self._time self._player.process_frame_event = self.process_frame_event self._player.click_event = self.on_player_click_event self._time.key_release_event = self.__timeline_key_release_event self.load_order = [] self.mainmenu.insert( 0, { 'File': [{ 'Open': self.__open_project_event, 'icon': conf.ANNOTATOR_ICON_OPEN }, '-', { 'Save': self.__save_project_event, 'icon': conf.ANNOTATOR_ICON_SAVE }, { 'Save as': self.__save_project_as_event, 'icon': conf.ANNOTATOR_ICON_SAVE }, '-', { 'Exit': QApplication.closeAllWindows, 'icon': conf.ANNOTATOR_ICON_EXIT }] }) self.mainmenu.insert(1, {'Modules': []}) self.mainmenu.insert(2, {'Windows': []}) track_user_stats() ######################################################################## ###### CHECK NEW VERSIONS RELEASES ##################################### ######################################################################## try: versions = pypi_xmlrpc.package_releases('Python-video-annotator') if versions is not None: new_version = versions[0] new_version_numbers = [int(x) for x in new_version.split('.')] version_numbers = [int(x) for x in __version__.split('.')] for new_n, n in zip(new_version_numbers, version_numbers): if new_n > n: response = self.question( "<h2>New version <b>[{0}]</b> available</h2>" "<p>Do you wish to update the software?</p>" "<p>The software can be updated later by running the next command in the terminal:</p>" "<i>pip install python-video-annotator --force-reinstall</i>" .format(new_version), 'New version [{0}]'.format(new_version)) if response == 'yes': subprocess.call([ sys.executable, "-m", "pip", "install", 'python-video-annotator', '--force-reinstall' ]) self.message( 'The software was updated and this session will be closed. Please execute the software again.', 'Restart required') exit() break else: print('Enabled to check new versions') except Exception as e: print('Enabled to check new versions:') ###################################################################################### #### FUNCTIONS ####################################################################### ###################################################################################### def init_form(self): super(BaseModule, self).init_form() if conf.CHART_FILE_PATH: self._time.import_chart(*conf.CHART_FILE_PATH) if conf.PROJECT_PATH: self.load_project(conf.PROJECT_PATH) ###################################################################################### #### IO FUNCTIONS #################################################################### ###################################################################################### def save(self, data, project_path=None): self._project.save(data, project_path) return data def load(self, data, project_path=None): try: self._project.load(data, project_path) except FileNotFoundError as e: QMessageBox.critical(self, "Error", str(e)) def save_project(self, project_path=None): try: if project_path is None: project_path = QFileDialog.getExistingDirectory( self, "Select the project directory") if project_path is not None and str(project_path) != '': project_path = str(project_path) self.save({}, project_path) except Exception as e: QMessageBox.critical(self, "Error", str(e)) def load_project(self, project_path=None): if project_path is None: project_path = QFileDialog.getExistingDirectory( self, "Select the project directory") if project_path is not None and str(project_path) != '': self.load({}, str(project_path)) ###################################################################################### #### EVENTS ########################################################################## ###################################################################################### def on_player_click_event(self, event, x, y): """ Code to select a blob with the mouse """ super(VideoAnnotationEditor, self).on_player_click_event(event, x, y) self._player.refresh() def process_frame_event(self, frame): """ Function called before render each frame """ return frame def add_dataset_event(self, dataset): pass def removed_dataset_event(self, dataset): pass def removed_object_event(self, obj): pass def __open_project_event(self): self.load_project() def __save_project_event(self): print('Project saved') self.save_project(self._project.directory) def __save_project_as_event(self): self.save_project() def __timeline_key_release_event(self, event): """ Control video playback using the space bar to Play/Pause """ if event.key() == QtCore.Qt.Key_Space: self._player.stop( ) if self._player.is_playing else _player._video.play() ###################################################################################### #### PROPERTIES ###################################################################### ###################################################################################### @property def timeline(self): return self._time @property def player(self): return self._player @property def video(self): return self._player.value @video.setter def video(self, value): self._player.value = value self._player.enabled = value is not None if value: self._time.max = self._player.max @property def project(self): return self._project
class MotionCounter(BaseWidget): def __init__(self, parent=None): BaseWidget.__init__(self, 'Motion counter', parent_win=parent) self.set_margin(5) self.setMinimumHeight(300) self.setMinimumWidth(500) self._player = ControlPlayer('Player') self._datasets = ControlEmptyWidget('Paths', default=DatasetsDialog()) self._backgrounds = ControlEmptyWidget('Backgrounds', default=ObjectsDialog()) self._show_diff = ControlCheckBox('Show diffs boxes') self._threshold = ControlSlider('Threshold', default=5, minimum=1, maximum=255) self._radius = ControlSlider('Radius', default=30, minimum=1, maximum=200) self._apply = ControlButton('Apply', checkable=True) self._compare = ControlCombo('Compare with') self._progress = ControlProgress('Progress') self._formset = [ '_datasets', '=', '_compare', '_backgrounds', ('_threshold', '_radius', '_show_diff'), '_player', '_apply', '_progress' ] self._compare.add_item('Last frame', 1) self._compare.add_item('First frame', 2) self._compare.add_item('Background image', 3) self.load_order = ['_threshold', '_radius', '_show_diff'] self._backgrounds.value.datasets_filter = lambda x: isinstance( x, Image) self._datasets.value.datasets_filter = lambda x: isinstance( x, (Contours, Path)) self._player.process_frame_event = self.__process_frame_event self._datasets.value.video_selection_changed_event = self.__video_selection_changed_event self._compare.changed_event = self.__compare_changed_event self._apply.value = self.__apply_btn_event self._apply.icon = conf.ANNOTATOR_ICON_MOTION self._progress.hide() self._backgrounds.hide() ########################################################################### ### EVENTS ################################################################ ########################################################################### def __compare_changed_event(self): if self._compare.value == 1: self._backgrounds.hide() elif self._compare.value == 2: self._backgrounds.hide() elif self._compare.value == 3: self._backgrounds.show() self._lastframe = None def __video_selection_changed_event(self): video = self._datasets.value.selected_video if video is not None: self._player.value = video.video_capture def __process_frame_event(self, frame): index = self._player.video_index - 1 selected_video = self._datasets.value.selected_video radius = self._radius.value threshold = self._threshold.value show_diff = self._show_diff.value compare_with = self._compare.value if compare_with == 3 and len(self._backgrounds.value.objects): background_img = self._backgrounds.value.objects[0].image background_img = cv2.cvtColor(background_img, cv2.COLOR_BGR2GRAY) else: background_img = None for video, (begin, end), datasets in self._datasets.value.selected_data: if video != selected_video: continue for dataset in datasets: pos = dataset.get_position(index) if pos is None: continue if show_diff: # calculate the cut x, y = pos cutx = int(round(x - radius)) cuty = int(round(y - radius)) cutxx = int(round(x + radius)) cutyy = int(round(y + radius)) if cutx < 0: cutx = 0 if cutxx > frame.shape[1]: cutxx = frame.shape[1] if cuty < 0: cuty = 0 if cutyy > frame.shape[0]: cutyy = frame.shape[0] gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) small = gray[cuty:cutyy, cutx:cutxx].copy() circular_mask = np.zeros((cutyy - cuty, cutxx - cutx), dtype=np.uint8) cv2.circle(circular_mask, (cutyy - cuty, cutxx - cutx), radius, 255, -1) small_masked = cv2.bitwise_and(circular_mask, small) if not hasattr(self, '_lastframe') or type( self._lastframe) is not np.ndarray: if self._compare.value == 1 or self._compare.value == 2: self._lastframe = gray elif self._compare.value == 3: self._lastframe = background_img if type(self._lastframe) is np.ndarray: last_masked = cv2.bitwise_and( circular_mask, self._lastframe[cuty:cutyy, cutx:cutxx]) diff = cv2.absdiff(small_masked, last_masked) cv2.circle(frame, pos, radius, (0, 0, 0), -1) frame[cuty:cutyy, cutx:cutxx] += cv2.merge( (diff, diff, diff)) if self._compare.value == 1: self._lastframe = gray else: cv2.circle(frame, pos, radius, (0, 0, 255), 2) return frame def __apply_btn_event(self): if self._apply.checked: self._datasets.enabled = False self._show_diff.enabled = False self._threshold.enabled = False self._player.enabled = False self._radius.enabled = False self._player.stop() self._apply.label = 'Cancel' total_2_analyse = 0 for video, (begin, end), datasets in self._datasets.value.selected_data: total_2_analyse += end - begin + 1 self._progress.min = 0 self._progress.max = total_2_analyse self._progress.show() radius = self._radius.value threshold = self._threshold.value count = 0 for video, (begin, end), datasets in self._datasets.value.selected_data: begin = int(begin) end = int(end) + 1 capture = cv2.VideoCapture(video.filepath) capture.set(cv2.CAP_PROP_POS_FRAMES, begin) last_image = [None for x in datasets] compare_with = self._compare.value if compare_with == 3: if len(self._backgrounds.value.objects): background_img = self._backgrounds.value.objects[ 0].image background_img = cv2.cvtColor(background_img, cv2.COLOR_BGR2GRAY) else: self.critical('No background selected') break else: background_img = None for index in range(begin, end): res, frame = capture.read() if not res: break if not self._apply.checked: break for dataset_index, dataset in enumerate(datasets): pos = dataset.get_position(index) if pos is None: continue x, y = pos cutx = int(round(x - radius)) cuty = int(round(y - radius)) cutxx = int(round(x + radius)) cutyy = int(round(y + radius)) if cutx < 0: cutx = 0 if cutxx > frame.shape[1]: cutxx = frame.shape[1] if cuty < 0: cuty = 0 if cutyy > frame.shape[0]: cutyy = frame.shape[0] gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) small = gray[cuty:cutyy, cutx:cutxx].copy() circular_mask = np.zeros((cutyy - cuty, cutxx - cutx), dtype=np.uint8) cv2.circle(circular_mask, (cutyy - cuty, cutxx - cutx), radius, 255, -1) small_masked = cv2.bitwise_and(small, small, circular_mask) if type(last_image[dataset_index]) is not np.ndarray: last_image[dataset_index] = small_masked if compare_with == 1 or compare_with == 2: last_image[dataset_index] = gray elif compare_with == 3: last_image[dataset_index] = background_img segment = last_image[dataset_index] last_masked = cv2.bitwise_and( circular_mask, segment[cuty:cutyy, cutx:cutxx]) diff = cv2.absdiff(small_masked, last_masked) diff[diff < threshold] = 0 diff[diff >= threshold] = 1 motion = np.sum(diff) dataset.set_motion(index, motion) if compare_with == 1: last_image[dataset_index] = gray self._progress.value = count count += 1 self._datasets.enabled = True self._show_diff.enabled = True self._threshold.enabled = True self._player.enabled = True self._radius.enabled = True self._apply.label = 'Apply' self._apply.checked = False self._progress.hide()
class VideoWindow(BaseWidget): def __init__(self, *args, **kwargs): super().__init__('Hazard Labelling') self._args = { "filepath": FILEPATH, "folder": FOLDER, "dest": DEST } self.set_margin(10) #Definition of the forms fields self._videofile = ControlFile('Video') self._hazardbutton = ControlButton('Hazard') self._next = ControlButton('Next Video') self._save_data = ControlButton('Save labels') self._player = ControlPlayer('Player') self._timeline = ControlEventTimeline('Timeline') self._panel = ControlDockWidget(label='Timeline', side='bottom', margin=10) self._status = ControlText('Status') self._file_list = [] if self._args["folder"] is None: if self._args["filepath"] is not None: self._file_list = self._args["filepath"] elif self._args["folder"] is not None: if os.path.isdir(self._args["folder"]): self.__updateStatus("Source folder found at: {}".format(self._args["folder"])) print("Scanning folder and all subfolders... {}".format(self._args["folder"])) count = 0 for (dirpath, dirnames, filenames) in os.walk(self._args["folder"]): path = [] for f in filenames: if f.rsplit('.')[-1] in ACCEPTABLE_EXT: count += 1 path.append(dirpath + "/" + f) self._file_list.extend(path) if count % 100 == 0: print("Found {} files...".format(count)) print("Scan complete, found {} acceptable files".format(count)) self._video_count = len(self._file_list) self._progress = ControlProgress(label="Video %p of " + str(self._video_count), defaultValue=1, min=1, max=self._video_count) self._hazard_counter = 0 self._current_video = 0 #Define function calls on button presses self._videofile.changed_event = self.__videoFileSelectionEvent self._hazardbutton.value = self.__labelHazard self._next.value = self.__nextVideo self._save_data.value = self.__saveData self._panel.value = self._timeline self._progress.value = self._current_video + 1 #Define events self._player.process_frame_event = self.__processFrame self._player.click_event = self.__clickEvent self._player.key_release_event = self.__tagEvent #Define the organization of the Form Controls self._formset = [ '_player', # '_hazardbutton', '_panel', ('_videofile', '_next'), ('_status', '_save_data'), '_progress' ] self._video_loaded = False try: self.__videoFileSelect(self._file_list[self._current_video]) except Exception as e: self.__updateStatus("Select video...") self._hazard_default_duration = 0 def __videoFileSelect(self, filepath): try: self._videofile.value = str(filepath) self._player.value = self._videofile.value self._player.refresh() self._player.update_frame() # Hazard set to occur for 60 frames upon flagging self._hazard_default_duration = int(self._player.fps * 2) self._video_loaded = True except Exception as e: self.__updateStatus("Unable to select video") def __videoFileSelectionEvent(self): """ When the videofile is selected instantiate the video in the player """ try: self._player.value = self._videofile.value # Hazard set to occur for 60 frames upon flagging self._hazard_default_duration = int(self._player.fps * 2) self._video_loaded = True except Exception as e: self.__updateStatus("No video selected") def __nextVideo(self): if self._current_video >= (self._video_count - 1): self.__updateStatus("No more videos") else: self._current_video += 1 self.__reset() self.__videoFileSelect(self._file_list[self._current_video]) self._progress.value = self._current_video + 1 def __reset(self): self._player.stop() self.__saveData() print("saving on reset") self._timeline.clean() self.__updateStatus("") def __processFrame(self, frame): """ Do some processing to the frame and return the result frame """ return frame def __clickEvent(self, click_event, x, y): self.__labelHazard() def __tagEvent(self, event): """ Label hazard using Enter key """ key = event.key() # QtCore.Qt.Key_Enter gives wrong value (at least on test PC) for Enter key # Desired == 16777221, actual == 16777220 key_id = 16777220 if event.key() == key_id: self.__labelHazard() def __addFlag(self, value): self._timeline.add_period(value) def __labelHazard(self): if self._video_loaded: if self._player.video_index > (self._player.max - self._hazard_default_duration): flag_duration = round(self._player.max - self._player.video_index) else: flag_duration = self._hazard_default_duration try: self._hazard_counter += 1 self.__updateStatus("Hazard flagged! | Frame: {} Timestamp: {}".format(self._player.video_index, round(self._player.video_index/self._player.fps, 3))) self.__addFlag((self._player.video_index, self._player.video_index + flag_duration, str(self._hazard_counter))) except Exception as e: try: self._player.refresh() self.__updateStatus("Hazard flagged! | Frame: {} Timestamp: {}".format(self._player.video_index, round(self._player.video_index/self._player.fps, 3))) except Exception as e: self.__updateStatus("Unable to label, exiting...") sys.exit(0) def __saveData(self): self._timeline.export_csv_file(self._videofile.value + "_hazard.csv") self.__updateStatus("Saving {} to {}".format(self._videofile.value + "_hazard.csv", self._args["dest"])) def __updateStatus(self, msg): self._status.value = str(msg)