from system.log_support import init_logger import time import cv2 as cv from system.motion_detection import MotionDetector import imutils import config import datetime as dts import numpy as np from system.camera_support import CameraConnectionSupport import os from system.shared import makeAbsoluteAppPath, mkdir_p class MotionDrivenRecorder(CameraConnectionSupport): def __init__(self, camConnectionString, logger): CameraConnectionSupport.__init__(self, camConnectionString, logger) # initializing motion detector self.detector = MotionDetector() self.detector.resizeBeforeDetect = False self.detector.multiFrameDetection = False self.inMotionDetectedState = False self.__camConnectionDts = None self.__canDetectMotion = False self.camFps = None self.preAlarmRecordingSecondsQty = 0 self.__savedFrames = [] self.__isRecording = False # output writer self.outputDirectory = None self.__output = None self.subFolderNameGeneratorFunc = None self.__prevSubFolder = None self.scaleFrameTo = None def _addPreAlarmFrame(self, frame): if self.preAlarmRecordingSecondsQty == 0: return if self.camFps is None: return totalQty = int(self.preAlarmRecordingSecondsQty * self.camFps) if len(self.__savedFrames) < totalQty: self.__savedFrames.append(frame) return if len(self.__savedFrames) > 0: self.__savedFrames.pop(0) self.__savedFrames.append(frame) def canDetectMotion(self): if self.__canDetectMotion: return True if self.__camConnectionDts is None: return False minDts = self.__camConnectionDts + dts.timedelta(seconds=config.INITIAL_WAIT_INTERVAL_BEFORE_MOTION_DETECTION_SECS) if minDts > self.utcNow(): return False self.__canDetectMotion = True return True def setError(self, errorText): self.logger.error(errorText) return CameraConnectionSupport.setError(self, errorText) def _writeOutFrame(self, frame): assert self.__output is not None self.__output.write(frame) def _stopRecording(self): if self.__output is None: return self.__output.release() self.__output = None self.__isRecording = False def _getSubFolderName(self, dts): if self.subFolderNameGeneratorFunc is None: return None return self.subFolderNameGeneratorFunc(dts) def _startRecording(self): if self.outputDirectory is None: return self.setError("output directory is not specified") if None in [self.frameWidth, self.frameHeight]: return self.setError("resolution is't specified") fourcc = cv.VideoWriter_fourcc(*config.FOURCC_CODEC) videoSize = (self.frameWidth, self.frameHeight) # calculation output filename now = dts.datetime.utcnow() fileName = "video_{}{}".format(now.strftime("%Y%m%dT%H%M%S"), config.OUTPUT_FILES_EXTENSION) subFolder = self._getSubFolderName(now) if subFolder is not None: needCreate = ((self.__prevSubFolder is not None) or (subFolder != self.__prevSubFolder)) dirName = os.path.join(self.outputDirectory, subFolder) dirName = os.path.normpath(dirName) if (needCreate) and (not os.path.exists(dirName)): self.logger.info("adding new directory: {}".format(dirName)) if not mkdir_p(dirName): return self.setError("can't create sub-directory: {}".format(dirName)) fileName = os.path.join(dirName, fileName) else: fileName = os.path.join(self.outputDirectory, fileName) self.__output = cv.VideoWriter(fileName, fourcc, config.OUTPUT_FRAME_RATE, videoSize) self.__isRecording = True return True def _flushPreRecordingFrames(self): """ Writes pre-alarm frames to output file :return: """ assert self.__output is not None for frame in self.__savedFrames: self.__output.write(frame) self.__savedFrames = [] return True def loop(self): # noqa """ Main loop for motion detection tester :return: """ self.logger.info("main loop started") emptyFrame = None while True: # initializing connection to camera if self.cap is None: if not self._initCamera(): continue self.__camConnectionDts = self.utcNow() # reading frames from camera ret, current_frame = self.cap.read() # if can't read current frame - going to the next loop if (ret == False) or (current_frame is None): # the connection broke, or the stream came to an end continue if self.scaleFrameTo is not None: current_frame = imutils.resize(current_frame, width=self.scaleFrameTo[0], height=self.scaleFrameTo[1]) # get timestamp of the frame instant = time.time() ############################################################ # calculating width and height of current video stream # ############################################################ frameHeight = np.size(current_frame, 0) frameWidth = np.size(current_frame, 1) if self.camFps is None: self.camFps = self.cap.get(cv.CAP_PROP_FPS) self.logger.info("FPS = {}".format(self.camFps)) ############################################ # adding frame to pre-recording buffer # ############################################ if self.preAlarmRecordingSecondsQty > 0: self._addPreAlarmFrame(current_frame) if emptyFrame is None: emptyFrame = np.zeros((frameHeight, frameWidth, 3), np.uint8) resolutionChanged = False if None in [self.frameWidth, self.frameHeight]: self.frameWidth = frameWidth self.frameHeight = frameHeight self.nb_pixels = self.frameWidth * self.frameHeight self.logger.info("self.width = {}".format(self.frameWidth)) self.logger.info("self.height = {}".format(self.frameHeight)) resolutionChanged = True else: resolutionChanged = ((self.frameWidth != frameWidth) or (self.frameHeight != frameHeight)) if resolutionChanged: # TODO: need process when recording now, or will be exception! self.onFrameSizeUpdate(frameWidth, frameHeight) ######################## # detecting motion # ######################## motionDetected = False # detection motion if can do it now if self.canDetectMotion(): if self.detector.motionDetected(current_frame): self.trigger_time = instant # Update the trigger_time if not self.inMotionDetectedState: self.logger.info("something moved!") motionDetected = True self.inMotionDetectedState = True now = self.utcNow() # prolongating motion for minimal motion duration if (not motionDetected) and (self.detector.motionDetectionDts is not None): minDuration = self.detector.motionDetectionDts + dts.timedelta(seconds=config.MINIMAL_MOTION_DURATION) if minDuration > now: motionDetected = True # clearing motion detection flag when needed if not motionDetected: self.inMotionDetectedState = False if self.__isRecording: self._stopRecording() elif not self.__isRecording: self._startRecording() self._flushPreRecordingFrames() # calculating left seconds for motion (for further use in label) dx = 0 if motionDetected: dx = now - self.detector.motionDetectionDts dx = config.MINIMAL_MOTION_DURATION - dx.seconds # adding label for frame with detected motion if motionDetected: text = "MOTION DETECTED [{}]".format(dx) cv.putText( current_frame, text, (10, 20), cv.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), # b g r 2 ) if self.__isRecording: self._writeOutFrame(current_frame) # show current frame cv.imshow("frame", current_frame) # reading key and breaking loop when Esc or 'q' key pressed key = cv.waitKey(1) if (key & 0xFF == ord("q")) or (key == 27): break # stop recording if now recording if self.__isRecording: self._stopRecording() if self.cap is not None: self.cap.release() cv.destroyAllWindows() self.logger.info("main loop finished") def main(): logger = init_logger() logger.info("app started") ########################################### # creating directory for output files # ########################################### videoPath = config.PATH_FOR_VIDEO videoPath = makeAbsoluteAppPath(videoPath) if not os.path.exists(videoPath): logger.info("making directory for output files: {}".format(videoPath)) if not mkdir_p(videoPath): logger.error("can't create directory for output files") return -1 ############################## # initializing processor # ############################## processor = MotionDrivenRecorder(config.cam, logger) processor.preAlarmRecordingSecondsQty = config.PRE_ALARM_RECORDING_SECONDS processor.outputDirectory = videoPath processor.subFolderNameGeneratorFunc = config.subFolderNameGeneratorFunc processor.scaleFrameTo = config.scaleFrameTo processor.loop() logger.info("app finished") return 0 if __name__ == "__main__": ret = main() exit(ret)