From 51e17420d7a0271a03395cb4e2936e3fd0908e3d Mon Sep 17 00:00:00 2001 From: boltgolt Date: Sun, 21 Jun 2020 21:40:47 +0200 Subject: [PATCH] Added the option to save snapshots --- .gitignore | 3 +++ src/compare.py | 49 +++++++++++++++++++++++++++++++++++----- src/config.ini | 11 ++++++++- src/logo.png | Bin 0 -> 3068 bytes src/snapshot.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 src/logo.png create mode 100644 src/snapshot.py diff --git a/.gitignore b/.gitignore index 58f2dae..0b8e60a 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,9 @@ ENV/ # generated models /src/models +# snapshots +/src/snapshots + # build files debian/howdy.substvars debian/files diff --git a/src/compare.py b/src/compare.py index 8ddd0c7..658f94f 100644 --- a/src/compare.py +++ b/src/compare.py @@ -16,6 +16,8 @@ import json import configparser import cv2 import dlib +import datetime +import snapshot import numpy as np import _thread as thread from recorders.video_capture import VideoCapture @@ -48,6 +50,18 @@ def init_detector(lock): lock.release() +def make_snapshot(type): + """Generate snapshot after detection""" + snapshot.generate(snapframes, [ + type + " LOGIN", + "Date: " + datetime.datetime.utcnow().strftime("%Y/%m/%d %H:%M:%S UTC"), + "Scan time: " + str(round(time.time() - timings["fr"], 2)) + "s", + "Frames: " + str(frames) + " (" + str(round(frames / (time.time() - timings["fr"]), 2)) + "FPS)", + "Hostname: " + os.uname().nodename, + "Best certainty value: " + str(round(lowest_certainty * 10, 1)) + ]) + + # Make sure we were given an username to tast against if len(sys.argv) < 2: sys.exit(12) @@ -67,7 +81,11 @@ black_tries = 0 dark_tries = 0 # Total amount of frames captured frames = 0 -# face recognition/detection instances +# Captured frames for snapshot capture +snapframes = [] +# Tracks the lowest certainty value in the loop +lowest_certainty = 10 +# Face recognition/detection instances face_detector = None pose_predictor = None face_encoder = None @@ -95,6 +113,8 @@ timeout = config.getint("video", "timeout", fallback=5) dark_threshold = config.getfloat("video", "dark_threshold", fallback=50.0) video_certainty = config.getfloat("video", "certainty", fallback=3.5) / 10 end_report = config.getboolean("debug", "end_report", fallback=False) +capture_failed = config.getboolean("snapshots", "capture_failed", fallback=False) +capture_successful = config.getboolean("snapshots", "capture_successful", fallback=False) # Save the time needed to start the script timings["in"] = time.time() - timings["st"] @@ -150,7 +170,11 @@ while True: # Stop if we've exceded the time limit if time.time() - timings["fr"] > timeout: - if (dark_tries == valid_frames): + # Create a timeout snapshot if enabled + if capture_failed: + make_snapshot("FAILED") + + if dark_tries == valid_frames: print("All frames were too dark, please check dark_threshold in config") print("Average darkness: " + str(dark_running_total / valid_frames) + ", Threshold: " + str(dark_threshold)) sys.exit(13) @@ -159,9 +183,14 @@ while True: # Grab a single frame of video frame, gsframe = video_capture.read_frame() - gsframe = clahe.apply(gsframe) + # If snapshots have been turned on + if capture_failed or capture_successful: + # Start capturing frames for the snapshot + if len(snapframes) < 3: + snapframes.append(frame) + # Create a histogram of the image with 8 values hist = cv2.calcHist([gsframe], [0], None, [8], [0, 256]) # All values combined for percentage calculation @@ -210,10 +239,14 @@ while True: match_index = np.argmin(matches) match = matches[match_index] + # Update certainty if we have a new low + if lowest_certainty > match: + lowest_certainty = match + # Check if a match that's confident enough if 0 < match < video_certainty: timings["tt"] = time.time() - timings["st"] - timings["fr"] = time.time() - timings["fr"] + timings["fl"] = time.time() - timings["fr"] # If set to true in the config, print debug text if end_report: @@ -227,7 +260,7 @@ while True: print(" Open cam + load libs: %dms" % (round(max(timings["ll"], timings["ic"]) * 1000, ))) print_timing(" Opening the camera", "ic") print_timing(" Importing recognition libs", "ll") - print_timing("Searching for known face", "fr") + print_timing("Searching for known face", "fl") print_timing("Total time", "tt") print("\nResolution") @@ -238,13 +271,17 @@ while True: print(" Used: %dx%d" % (scale_height, scale_width)) # Show the total number of frames and calculate the FPS by deviding it by the total scan time - print("\nFrames searched: %d (%.2f fps)" % (frames, frames / timings["fr"])) + print("\nFrames searched: %d (%.2f fps)" % (frames, frames / timings["fl"])) print("Black frames ignored: %d " % (black_tries, )) print("Dark frames ignored: %d " % (dark_tries, )) print("Certainty of winning frame: %.3f" % (match * 10, )) print("Winning model: %d (\"%s\")" % (match_index, models[match_index]["label"])) + # Make snapshot if enabled + if capture_successful: + make_snapshot("SUCCESSFUL") + # End peacefully sys.exit(0) diff --git a/src/config.ini b/src/config.ini index a6a0fa3..7ff7695 100644 --- a/src/config.ini +++ b/src/config.ini @@ -30,6 +30,7 @@ use_cnn = false [video] # The certainty of the detected face belonging to the user of the account # On a scale from 1 to 10, values above 5 are not recommended +# Lower is better certainty = 3.5 # The number of seconds to search before timing out @@ -37,7 +38,7 @@ timeout = 4 # The path of the device to capture frames from # Should be set automatically by an installer if your distro has one -device_path = "/dev/video1" +device_path = none # Scale down the video feed to this maximum height # Speeds up face recognition but can make it less precise @@ -73,6 +74,14 @@ force_mjpeg = false # OPENCV only. exposure = -1 +[snapshots] +# Capture snapshots of failed login attempts and save them to disk with metadata +# Snapshots are saved to the "snapshots" folder +capture_failed = true + +# Do the same as the option above but for successful attemtps +capture_successful = true + [debug] # Show a short but detailed diagnostic report in console # Enabling this can cause some UI apps to fail, only enable it to debug diff --git a/src/logo.png b/src/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f9fe9ca74129bfcaa4f40a6cc6068f5d5f60c565 GIT binary patch literal 3068 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 KMNUMnLSTY4lg-Nj literal 0 HcmV?d00001 diff --git a/src/snapshot.py b/src/snapshot.py new file mode 100644 index 0000000..20f1f54 --- /dev/null +++ b/src/snapshot.py @@ -0,0 +1,59 @@ +# Create and save snapshots of auth attemtps + +# Import modules +import cv2 +import os +import datetime +import numpy as np + + +def generate(frames, text_lines): + """Generate a shapshot from given frames""" + + # Don't execute if no frames were given + if len(frames) == 0: + return + + # Get the path to the containing folder + abpath = os.path.dirname(os.path.abspath(__file__)) + # Get frame dimentions + frame_height, frame_width, cc = frames[0].shape + # Spread the given frames out horizontally + snap = np.concatenate(frames, axis=1) + + # Create colors + pad_color = [44, 44, 44] + text_color = [255, 255, 255] + + # Add a gray square at the bottom of the image + snap = cv2.copyMakeBorder(snap, 0, len(text_lines) * 20 + 40, 0, 0, cv2.BORDER_CONSTANT, value=pad_color) + + # Add the Howdy logo if there's space to do so + if len(frames) > 1: + # Load the logo from file + logo = cv2.imread(abpath + "/logo.png") + # Calculate the position of the logo + logo_y = frame_height + 20 + logo_x = frame_width * len(frames) - 210 + + # Overlay the logo on top of the image + snap[logo_y:logo_y+57, logo_x:logo_x+180] = logo + + # Go through each line + line_number = 0 + for line in text_lines: + # Calculate how far the line should be from the top + padding_top = frame_height + 30 + (line_number * 20) + # Print the line onto the image + cv2.putText(snap, line, (30, padding_top), cv2.FONT_HERSHEY_SIMPLEX, .4, text_color, 0, cv2.LINE_AA) + + line_number += 1 + + # Made sure a snapshot folder exist + if not os.path.exists(abpath + "/snapshots"): + os.makedirs(abpath + "/snapshots") + + # Generate a filename based on the current time + filename = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%S.jpg") + # Write the image to that file + cv2.imwrite(abpath + "/snapshots/" + filename, snap)