Simple “Face ID” for your PC

German Gensetskiy
Go Wombat
Published in
4 min readNov 19, 2018

At our office, locking screen is a habit that you need to develop quickly. Because if you will leave your computer unlocked, someone will have fun and change your wallpapers or alias you `sudo` to something ;)

And one day, i started to think, why can’t i automate it? And here i come to the Face Recognition python library. It’s really easy to set up and use.

But first things first. We need to check if it is possible to lock screen from python and how to do it.

Locking Screen

I use Linux Mint with Cinnamon desktop environment. And fortunately, in Cinnamon case it’s pretty easy to lock or unlock the screen using screensaver command.

cinnamon-screensaver-command --activate  # to lock the screen
cinnamon-screensaver-command --deactivate # to unlock the screen

And it’s not a big deal to run terminal command from python:

from subprocess import callLOCK_ARGS = {
True: '--activate',
False: '--deactivate',
}
def lock_screen(lock):
call(('cinnamon-screensaver-command', LOCK_ARGS[lock]))
lock_screen(True) # will lock the screen
lock_screen(False) # will unlock the screen

Setting up face_recognition

The next step is to recognize your lovely face. We will use face-recognition library. You can find many good examples in the repository, I believe that one is useful for us.

It uses OpenCV to capture stream from camera. Also I decided to use constitutional neural network to locate faces in the frame. To have better accuracy.

from threading import Timerimport cv2
import face_recognition
def load_user_encoding():
user_image = face_recognition.load_image_file(os.path.join(BASE_DIR, 'user.jpg'))
user_image_face_encoding = face_recognition.face_encodings(user_image, num_jitters=10)[0]

return user_image_face_encoding
def find_user_in_frame(frame, user_encoding):
face_locations = face_recognition.face_locations(frame, model='cnn')
face_encodings = face_recognition.face_encodings(frame, face_locations, num_jitters=2)

for face_encoding in face_encodings:
matches = face_recognition.compare_faces((user_encoding, ), face_encoding, tolerance=0.9)

return any(matches)
if __name__ == '__main__':
user_encoding = load_user_encoding()
video_capture = cv2.VideoCapture(0) # get a reference to webcam #0 (the default one)

lock_timer = None
process_this_frame = True

while True
:
ret, frame = video_capture.read()
small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
rgb_small_frame = small_frame[:, :, ::-1]

if process_this_frame:
user_found = find_user_in_frame(rgb_small_frame, user_encoding)

if user_found:
print('user found')
lock_screen(False)

if lock_timer is not None: # cancel lock timer if it exists
lock_timer.cancel()
lock_timer = None
else
:
print('user not found')

if lock_timer is None: # start timer if it's not started already
lock_timer = Timer(5, lock_screen, (True,))
lock_timer.start()

process_this_frame = not process_this_frame

As you can see I used threading.Timer to lock screen after 5 seconds in case the user is not found. I recommend to wait a bit before locking the screen because sometimes it can not recognize your face on some frames. Or you can just turn away for a moment.

Optimizing

With that solution it has a nasty delay for reading a frame and bad frameskip. So i decided to optimize it and move the recognition process to separate process using multiprocessing

First of all we need to rewrite our function for finding user so it will be able to called as Process with Pipe instead of return:

def find_user_in_frame(conn, frame, user_encoding):
face_locations = face_recognition.face_locations(frame, model='cnn')
face_encodings = face_recognition.face_encodings(frame, face_locations, num_jitters=2)

found_user = False
for
face_encoding in face_encodings:
matches = face_recognition.compare_faces((user_encoding, ), face_encoding, tolerance=0.9)

found_user = any(matches)
if found_user:
break

conn.send(found_user)

And after that we need to call that function using multiprocessing.Process in main loop:

if __name__ == '__main__':
user_encoding = load_user_encoding()
video_capture = cv2.VideoCapture(0) # get a reference to webcam #0 (the default one)

lock_timer = None

parent_conn, child_conn = Pipe()
find_user_process = None
while True
:
ret, frame = video_capture.read()

small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)

rgb_small_frame = small_frame[:, :, ::-1]

# if process of finding user is not working - start new one
if find_user_process is None:
find_user_process = Process(target=find_user_in_frame, args=(child_conn, rgb_small_frame, user_encoding))
find_user_process.start()
# if process of finding user is already working - check is it done
elif find_user_process is not None and not find_user_process.is_alive():
user_found = parent_conn.recv()
find_user_process = None

if
user_found:
print('user found')
lock_screen(False)
if lock_timer is not None:
lock_timer.cancel()
lock_timer = None
else
:
print('user not found')
if lock_timer is None:
lock_timer = Timer(LOCK_TIMEOUT, lock_screen, (True,))
lock_timer.start()

Now it works more smooth and the delay is minimal.

Thanks for reading! Hope it was interesting and you enjoyed it.

Source code can be found on github:
https://github.com/Ignisor/face-screenlock

Ignisor under support of Go Wombat Team

--

--