def calibrateCamera(): '''Calibrate the camera. Get the true FPS (including processing) from the camera''' print('{} [CLIENT]: Calibrating camera'.format(timestamp())) camera = cv.VideoCapture(0) width = camera.get(cv.CAP_PROP_FRAME_WIDTH) height = camera.get(cv.CAP_PROP_FRAME_HEIGHT) # Adjust frame size if it is excessively large while width * height >= 1000000: width *= 0.75 height *= 0.75 # OpenCV will override input and round to the closest (recognized) resolution. For example, I set res to 1900x1070 and it will override as 1920x1080. camera.set(3, width) camera.set(4, height) if not camera.isOpened(): # Attempt to open capture device once more, after a failure camera.open() if not camera.isOpened(): print('{} [CLIENT]: Issue opening camera'.format(timestamp())) exit() start_time = time.time() count = 0 while int(time.time() - start_time) < 10: ret, frame = camera.read() count += 1 # number of frames return camera, int(count / 10)
def alert(id): '''Notify (via email) that motion has been detected.''' gmail_user = ENV('GMAIL_USER') gmail_password = ENV('GMAIL_APP_PASSWORD') sent_from = gmail_user to = [] # Email recipients here subject = 'ALERT - Security System' body = 'ALERT: Movement has been detected by the security system on {} by camera {}'.format( datetime.datetime.now().strftime("%d/%m/%Y at %H:%M:%S"), id) email_text = """\ From: %s To: %s Subject: %s %s """ % (sent_from, ", ".join(to), subject, body) if len(to) == 0: return try: server = smtplib.SMTP_SSL('smtp.gmail.com', 465) server.ehlo() server.login(gmail_user, gmail_password) server.sendmail(sent_from, to, email_text) server.close() print( '{} [SERVER]: Movement detected. Alerts successfully sent.'.format( helper.timestamp())) except: print('{} [SERVER]: There was an issue sending alerts.'.format( helper.timestamp()))
def main(): '''Client that connects and streams video to server.''' server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.connect((HOST, PORT)) confirmRelationship(server) print('{} [CLIENT]: Established connection with server'.format(timestamp())) # Set and calibrate camera camera, FPS = calibrateCamera() print('{} [CLIENT]: Beginning stream to server'.format(timestamp())) ret, frame1 = camera.read() ret, frame2 = camera.read() connection_failed = False while camera.isOpened(): detected_motion, motion_frame = detectMotion(frame1, frame2) frame = drawTime(motion_frame, int(camera.get(3)), int(camera.get(4))) data = {'FRAME': frame, 'MOTION': detected_motion, 'FPS': FPS, 'WIDTH': int(camera.get(3)), 'HEIGHT': int(camera.get(4)) } pickled_data = pickle.dumps(data) try: server.sendall(struct.pack("P", len(pickled_data))+pickled_data) except socket.error: # Handle connection issues print('{} [CLIENT]: Connection to server disrupted'.format(timestamp())) connected = False server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) while not connected: # Try to reconnect try: server.connect((HOST, PORT)) confirmRelationship(server) connected = True if connection_failed: # Connection was re-established after it recently failed print('{} [CLIENT]: Re-established connection with server'.format(timestamp())) connection_failed = False except socket.error: connected = False connection_failed = True camera.release() time.sleep(2.5) # Wait before trying to reconnect print('{} [CLIENT]: Attempting to re-establish connection with server'.format(timestamp())) camera.open(0) frame1 = frame2 ret, frame2 = camera.read() cv.waitKey(1)
def processFrame(data, address, id, recording, FRAMES): '''Process the frame. Handle recording and alert decisions.''' # Handle recording behavior # If there is motion in this frame or there was recently motion (and it is recording), then act accordingly # Ignore if this is the first frame sent from client if (data['MOTION'] and len(FRAMES[address[1]]) != 1) or recording: if (helper.getStatus() == 'on') and not recording: alert( id ) # Alert when motion (recording) first starts and alerts are enabled recording, output_file = record(FRAMES[address[1]], recording, int(ENV('SECONDS')), id) processed_frame = helper.drawRecording(data['FRAME'], data['WIDTH'], data['HEIGHT']) if not recording: # No longer recording. Throw away all but last few FRAMES temp_frames = FRAMES[address[1]] temp_frames = temp_frames[(len(temp_frames) - (data['FPS'] * int(ENV('SECONDS')))):] if output_file is not None: print('{} [SERVER]: Video recording saved to {}'.format( helper.timestamp(), output_file)) else: temp_frames = None else: processed_frame = data['FRAME'] temp_frames = None return processed_frame, recording, temp_frames
def record(frames, already_recording, SECONDS, id): '''Record video when motion is detected. Includes the few seconds prior to motion detection and few seconds after the motion stops.''' FPS = frames[0]['FPS'] WIDTH = frames[0]['WIDTH'] HEIGHT = frames[0]['HEIGHT'] motion = [] for frame in frames: motion.append(frame['MOTION']) # Check if there has been motion in the last few seconds start = len(motion) - (FPS * SECONDS) # If the start index is less than 0, then default to True. movement_lately = True if (start < 0 or (True in motion[start:])) else False output_file = None if not movement_lately: #stop the recording. Write to a video file print('{} [SERVER]: Saving recording from Client {} '.format( helper.timestamp(), id)) # Ensure that user has a correct video type if ENV('RECORDING_TYPE') == 'mp4': fourcc = cv.VideoWriter_fourcc(*'mp4v') elif ENV('RECORDING_TYPE') == 'avi': fourcc = cv.VideoWriter_fourcc(*'XVID') else: print( '{} [SERVER]: Can not record video of type: {}\nChange the RECORDING_TYPE to one of the acceptable video types (listed under step 2a on README) in your .env and restart the server to enable video recordings to be saved' .format(helper.timestamp(), ENV('RECORDING_TYPE'))) return False, None output_file = "static/recordings/{}CAM{}.{}".format( datetime.datetime.now().strftime("%m-%d-%Y/%H_%M_%S"), id, ENV('RECORDING_TYPE')) os.makedirs(os.path.dirname(output_file), exist_ok=True) open(output_file, 'a+').close() output = cv.VideoWriter(output_file, fourcc, FPS, (WIDTH, HEIGHT), True) for frame in frames: output.write(helper.drawRecording(frame['FRAME'], WIDTH, HEIGHT)) output.release() # Completed writing to output file return movement_lately, output_file
def disconnect(client, address, FRAMES, id): '''Handle client disconnection''' print('{} [SERVER]: Socket {} (client {}) disconnected'.format( helper.timestamp(), address[1], id)) helper.setStandby(id) # Set standby image as frame client.close() # Remove this process from PROCESSES and update client count # TODO: #PROCESSES.pop(id - 1) # camera 3 is index 2 (3rd process) # TODO: see if this works FRAMES.pop(address[1], None) helper.updateClientCount(helper.getClientCount() - 1)
def main(): print('{} [SERVER]: Server running on port {}'.format( helper.timestamp(), PORT)) # Create Server (socket) and bind it to a address/port. server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind((HOST, PORT)) server.listen(MAX_CLIENTS) print('{} [SERVER]: Listening for (client) sockets'.format( helper.timestamp())) while True: # Accept client connection client, address = server.accept() client.settimeout(5.0) if helper.isBlacklisted( address[0]) or helper.getClientCount() == MAX_CLIENTS: # Close connection since they are blacklisted or # there are already the max number of clients connected. print('{} [SERVER]: Blacklisted IP {} attempted to connect'.format( helper.timestamp(), address[0])) client.close() continue # Wait for next client # Only continue with this client if they send a confirmation message. # This is sort of a second handshake before the client establishes a video stream to server. try: confirmation = client.recv( 1024).decode() # Client should be sending confirmation except (socket.timeout, UnicodeDecodeError): # Client did not send decodable confirmation in time. Add them to the blacklist if they are unrecognized. if not helper.isWhitelisted(address[0]): helper.addToBlackList(address[0]) print( '{} [SERVER]: IP {} has been blacklisted for failing to confirm the connection' .format(helper.timestamp(), address[0])) client.close() continue # Wait for next client # Whitelist client IP, since they connected successfully. helper.addToWhiteList(address[0]) # Begin a process for this client's video stream helper.updateClientCount(helper.getClientCount() + 1) print('{} [SERVER]: Socket {} connected as client {}'.format( helper.timestamp(), address, helper.getClientCount())) # Create, save, and start process (camera stream) p = process(target=stream_camera, args=( client, address, helper.getClientCount(), )) PROCESSES.append(p) p.start() # Clear PROCESSES for p in PROCESSES: p.join() # Join all PROCESSES socket.close() # TODO: see if this should be server.close() instead
# Clear PROCESSES for p in PROCESSES: p.join() # Join all PROCESSES socket.close() # TODO: see if this should be server.close() instead if __name__ == '__main__': while True: try: helper.toggleStatus('on') # Set alert status to 'on' by default helper.updateClientCount( 0 ) # No clients can be connected on startup. (Connection will be re-established) # Set each client default frame and lock to 'unlocked' standby = cv.imread('static/standby.jpg', cv.IMREAD_UNCHANGED) for i in range(1, 4): helper.unlock(i) # Unlock all frames for next server instance #with open('data/stream_frames/{}/frame.jpg'.format(i), 'r') as file: cv.imwrite('data/stream_frames/{}/frame.jpg'.format(i), standby) main() except Exception as e: print(e) print('{} [SERVER]: Server crashed'.format(helper.timestamp())) time.sleep(5) print('{} [SERVER]: Restarting server'.format(helper.timestamp()))
def detectMotion(frame1, frame2): '''Detect motion given two frames. Returns boolean and frame with motion area outlined.''' # Helpful Source: Divyanshu Shekhar - https://divyanshushekhar.com/motion-detection-opencv/ difference = cv.absdiff(frame1, frame2) gray_difference = cv.cvtColor(difference, cv.COLOR_BGR2GRAY) blur = cv.GaussianBlur(gray_difference, (5, 5), 0) __, thresh = cv.threshold(blur, 20, 255, cv.THRESH_BINARY) dilated = cv.dilate(thresh, None, iterations=3) contours, __ = cv.findContours(dilated, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE) detected = False frame = frame1.copy() for contour in contours: (x, y, w, h) = cv.boundingRect(contour) # Place date on frame regardless of any movement if cv.contourArea(contour) >= THRESHOLD: detected = True cv.rectangle(frame, (x, y), (x+w, y+h), (255, 255, 255), 1) cv.putText(frame, 'MOTION', (10, 20), cv.FONT_HERSHEY_PLAIN, 1.25, (0, 0, 224), 2, cv.FILLED, False) return detected, frame if __name__ == '__main__': try: main() except Exception as e: print('{} [CLIENT]: Exiting script because of error:\n{}'.format(timestamp(), e))