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 call LOCK_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 Timer import 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.