├── README.md ├── Tracking.py ├── __pycache__ └── utils.cpython-38.pyc ├── assets ├── adding.png ├── tracking.png └── webcam.gif ├── config.yaml ├── dataset ├── 1_Elon_Musk.jpg ├── 2_Jenna_Ortega.jpg ├── 3_Bill_Gates.jpg └── database.pkl ├── packages.txt ├── pages ├── 1_🔧_Updating.py └── 2_💾_Database.py ├── requirements.txt └── utils.py /README.md: -------------------------------------------------------------------------------- 1 | # Face recognition app using Streamlit 2 | 3 | This is a face recognition application built using Python, [Face-Recognition API](https://github.com/ageitgey/face_recognition) and Streamlit framework. The app allows users to upload an image containing faces and performs face recognition using the face recognition library. 4 | 5 | ## Features 6 | 7 | - Face detection and recognition 8 | - Multi-face recognition 9 | - Option to display recognized faces 10 | - User-friendly interface 11 | 12 | ## Requirements 13 | - Python 3.9 14 | - Streamlit 1.22.0 15 | - face_recognition 16 | 17 | ## Repository structure 18 | ```bash 19 | ├───dataset 20 | │ │───ID_Name.jpg 21 | │ │───... 22 | ├───pages 23 | │ ├───1_🔧_Updating.py 24 | │ └───2_💾_Database 25 | ├───Tracking.py 26 | │───utils.py 27 | ├───config.yaml 28 | ├───requirements.txt 29 | ├───packages.txt 30 | └───README.md 31 | ``` 32 | 33 | ## Description 34 | - **dataset**: contains images of people to be recognized. The file name format is ID_Name.jpg. `For example, 1_Elon_Musk.jpg, 2_Jenna_Ortega.jpg, 3_Bill_Gates.jpg, etc.` It is freely to use jpg, jpeg or png format. 35 | - **pages**: contains the code for each page of the app. If you want to add more pages, you can create a new file which format is `Order_Icon_Pagename` in this folder, or just no-icon page with format `Order_Pagename`. 36 | - **Tracking.py**: home page of the app, using for tracking real-time using webcam and picture. 37 | - **utils.py**: contains the functions utilized by the app. 38 | - **config.yaml**: contains the configuration for the app such as path of dataset dir and prompt messages. 39 | - **requirements.txt**: contains the dependencies for the app. 40 | - **packages.txt**: contains the packages for the app used to deploy on Streamlit Cloud. 41 | 42 | 43 | 44 | ## Installation 45 | 1. Clone the repository 46 | ```bash 47 | git clone https://github.com/datct00/Face-recognition-app-using-Streamlit.git 48 | cd Face-recognition-app-using-Streamlit 49 | ``` 50 | 51 | 2. Install the dependencies 52 | ```bash 53 | pip install -r requirements.txt 54 | ``` 55 | 56 | 3. Run the app 57 | ```bash 58 | streamlit run Tracking.py 59 | ``` 60 | 61 | ## Usage 62 | 1. Tracking real-time using webcam 63 | 2. Tracking using a image file 64 | 3. Updating database (adding, deleting and updating) 65 | 4. Viewing the database 66 | 67 | 68 | ## Demo 69 | 70 | 1. Tracking using camera 71 | ![Tracking using webcam](assets/webcam.gif) 72 | 73 | 2. Tracking using picture 74 | ![Tracking using picture](assets/tracking.png) 75 | 76 | 3. Adding new person to database 77 | ![Adding new person to database](assets/adding.png) 78 | 79 | 4. Deployed app on Streamlit Cloud. [Click here](https://datct00-face-recognition-app-using-streamlit-tracking-sel9ym.streamlit.app/) to watch a demo of the app. 80 | 81 | ## Contact 82 | If you have any questions, feel free to contact me via email: `chungtiendat8102000@gmail.com` 83 | -------------------------------------------------------------------------------- /Tracking.py: -------------------------------------------------------------------------------- 1 | 2 | import streamlit as st 3 | import cv2 4 | import face_recognition as frg 5 | import yaml 6 | from utils import recognize, build_dataset 7 | # Path: code\app.py 8 | 9 | st.set_page_config(layout="wide") 10 | #Config 11 | cfg = yaml.load(open('config.yaml','r'),Loader=yaml.FullLoader) 12 | PICTURE_PROMPT = cfg['INFO']['PICTURE_PROMPT'] 13 | WEBCAM_PROMPT = cfg['INFO']['WEBCAM_PROMPT'] 14 | 15 | 16 | 17 | st.sidebar.title("Settings") 18 | 19 | 20 | 21 | #Create a menu bar 22 | menu = ["Picture","Webcam"] 23 | choice = st.sidebar.selectbox("Input type",menu) 24 | #Put slide to adjust tolerance 25 | TOLERANCE = st.sidebar.slider("Tolerance",0.0,1.0,0.5,0.01) 26 | st.sidebar.info("Tolerance is the threshold for face recognition. The lower the tolerance, the more strict the face recognition. The higher the tolerance, the more loose the face recognition.") 27 | 28 | #Infomation section 29 | st.sidebar.title("Student Information") 30 | name_container = st.sidebar.empty() 31 | id_container = st.sidebar.empty() 32 | name_container.info('Name: Unnknown') 33 | id_container.success('ID: Unknown') 34 | if choice == "Picture": 35 | st.title("Face Recognition App") 36 | st.write(PICTURE_PROMPT) 37 | uploaded_images = st.file_uploader("Upload",type=['jpg','png','jpeg'],accept_multiple_files=True) 38 | if len(uploaded_images) != 0: 39 | #Read uploaded image with face_recognition 40 | for image in uploaded_images: 41 | image = frg.load_image_file(image) 42 | image, name, id = recognize(image,TOLERANCE) 43 | name_container.info(f"Name: {name}") 44 | id_container.success(f"ID: {id}") 45 | st.image(image) 46 | else: 47 | st.info("Please upload an image") 48 | 49 | elif choice == "Webcam": 50 | st.title("Face Recognition App") 51 | st.write(WEBCAM_PROMPT) 52 | #Camera Settings 53 | cam = cv2.VideoCapture(0) 54 | cam.set(cv2.CAP_PROP_FRAME_WIDTH, 640) 55 | cam.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) 56 | FRAME_WINDOW = st.image([]) 57 | 58 | while True: 59 | ret, frame = cam.read() 60 | if not ret: 61 | st.error("Failed to capture frame from camera") 62 | st.info("Please turn off the other app that is using the camera and restart app") 63 | st.stop() 64 | image, name, id = recognize(frame,TOLERANCE) 65 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 66 | #Display name and ID of the person 67 | 68 | name_container.info(f"Name: {name}") 69 | id_container.success(f"ID: {id}") 70 | FRAME_WINDOW.image(image) 71 | 72 | with st.sidebar.form(key='my_form'): 73 | st.title("Developer Section") 74 | submit_button = st.form_submit_button(label='REBUILD DATASET') 75 | if submit_button: 76 | with st.spinner("Rebuilding dataset..."): 77 | build_dataset() 78 | st.success("Dataset has been reset") -------------------------------------------------------------------------------- /__pycache__/utils.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/__pycache__/utils.cpython-38.pyc -------------------------------------------------------------------------------- /assets/adding.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/assets/adding.png -------------------------------------------------------------------------------- /assets/tracking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/assets/tracking.png -------------------------------------------------------------------------------- /assets/webcam.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/assets/webcam.gif -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | 2 | PATH: 3 | DATASET_DIR: 'dataset/' 4 | PKL_PATH: 'dataset/database.pkl' 5 | 6 | INFO: 7 | PICTURE_PROMPT: 'This app recognizes faces in a live video stream. To use it, simply press start and allow access to your webcam.' 8 | WEBCAM_PROMPT: 'This app recognizes faces in a live video stream. To use it, simply press start and allow access to your webcam.' -------------------------------------------------------------------------------- /dataset/1_Elon_Musk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/dataset/1_Elon_Musk.jpg -------------------------------------------------------------------------------- /dataset/2_Jenna_Ortega.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/dataset/2_Jenna_Ortega.jpg -------------------------------------------------------------------------------- /dataset/3_Bill_Gates.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/dataset/3_Bill_Gates.jpg -------------------------------------------------------------------------------- /dataset/database.pkl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datct00/Face-recognition-app-using-Streamlit/f010f51d720ea09ec2ab2dc6ba1178e419928966/dataset/database.pkl -------------------------------------------------------------------------------- /packages.txt: -------------------------------------------------------------------------------- 1 | python3-opencv -------------------------------------------------------------------------------- /pages/1_🔧_Updating.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import cv2 3 | import yaml 4 | import pickle 5 | from utils import submitNew, get_info_from_id, deleteOne 6 | import numpy as np 7 | 8 | st.set_page_config(layout="wide") 9 | st.title("Face Recognition App") 10 | st.write("This app is used to add new faces to the dataset") 11 | 12 | menu = ["Adding","Deleting", "Adjusting"] 13 | choice = st.sidebar.selectbox("Options",menu) 14 | if choice == "Adding": 15 | name = st.text_input("Name",placeholder='Enter name') 16 | id = st.text_input("ID",placeholder='Enter id') 17 | #Create 2 options: Upload image or use webcam 18 | #If upload image is selected, show a file uploader 19 | #If use webcam is selected, show a button to start webcam 20 | upload = st.radio("Upload image or use webcam",("Upload","Webcam")) 21 | if upload == "Upload": 22 | uploaded_image = st.file_uploader("Upload",type=['jpg','png','jpeg']) 23 | if uploaded_image is not None: 24 | st.image(uploaded_image) 25 | submit_btn = st.button("Submit",key="submit_btn") 26 | if submit_btn: 27 | if name == "" or id == "": 28 | st.error("Please enter name and ID") 29 | else: 30 | ret = submitNew(name, id, uploaded_image) 31 | if ret == 1: 32 | st.success("Student Added") 33 | elif ret == 0: 34 | st.error("Student ID already exists") 35 | elif ret == -1: 36 | st.error("There is no face in the picture") 37 | elif upload == "Webcam": 38 | img_file_buffer = st.camera_input("Take a picture") 39 | submit_btn = st.button("Submit",key="submit_btn") 40 | if img_file_buffer is not None: 41 | # To read image file buffer with OpenCV: 42 | bytes_data = img_file_buffer.getvalue() 43 | cv2_img = cv2.imdecode(np.frombuffer(bytes_data, np.uint8), cv2.IMREAD_COLOR) 44 | if submit_btn: 45 | if name == "" or id == "": 46 | st.error("Please enter name and ID") 47 | else: 48 | ret = submitNew(name, id, cv2_img) 49 | if ret == 1: 50 | st.success("Student Added") 51 | elif ret == 0: 52 | st.error("Student ID already exists") 53 | elif ret == -1: 54 | st.error("There is no face in the picture") 55 | elif choice == "Deleting": 56 | def del_btn_callback(id): 57 | deleteOne(id) 58 | st.success("Student deleted") 59 | 60 | id = st.text_input("ID",placeholder='Enter id') 61 | submit_btn = st.button("Submit",key="submit_btn") 62 | if submit_btn: 63 | name, image,_ = get_info_from_id(id) 64 | if name == None and image == None: 65 | st.error("Student ID does not exist") 66 | else: 67 | st.success(f"Name of student with ID {id} is: {name}") 68 | st.warning("Please check the image below to make sure you are deleting the right student") 69 | st.image(image) 70 | del_btn = st.button("Delete",key="del_btn",on_click=del_btn_callback, args=(id,)) 71 | 72 | elif choice == "Adjusting": 73 | def form_callback(old_name, old_id, old_image, old_idx): 74 | new_name = st.session_state['new_name'] 75 | new_id = st.session_state['new_id'] 76 | new_image = st.session_state['new_image'] 77 | 78 | name = old_name 79 | id = old_id 80 | image = old_image 81 | 82 | if new_image is not None: 83 | image = cv2.imdecode(np.frombuffer(new_image.read(), np.uint8), cv2.IMREAD_COLOR) 84 | 85 | if new_name != old_name: 86 | name = new_name 87 | 88 | if new_id != old_id: 89 | id = new_id 90 | 91 | ret = submitNew(name, id, image, old_idx=old_idx) 92 | if ret == 1: 93 | st.success("Student Added") 94 | elif ret == 0: 95 | st.error("Student ID already exists") 96 | elif ret == -1: 97 | st.error("There is no face in the picture") 98 | id = st.text_input("ID",placeholder='Enter id') 99 | submit_btn = st.button("Submit",key="submit_btn") 100 | if submit_btn: 101 | old_name, old_image, old_idx = get_info_from_id(id) 102 | if old_name == None and old_image == None: 103 | st.error("Student ID does not exist") 104 | else: 105 | with st.form(key='my_form'): 106 | st.title("Adjusting student info") 107 | col1, col2 = st.columns(2) 108 | new_name = col1.text_input("Name",key='new_name', value=old_name, placeholder='Enter new name') 109 | new_id = col1.text_input("ID",key='new_id',value=id,placeholder='Enter new id') 110 | new_image = col1.file_uploader("Upload new image",key='new_image',type=['jpg','png','jpeg']) 111 | col2.image(old_image,caption='Current image',width=400) 112 | st.form_submit_button(label='Submit',on_click=form_callback, args=(old_name, id, old_image, old_idx)) 113 | -------------------------------------------------------------------------------- /pages/2_💾_Database.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | import pickle 3 | import yaml 4 | import pandas as pd 5 | cfg = yaml.load(open("config.yaml", "r"), Loader=yaml.FullLoader) 6 | PKL_PATH = cfg['PATH']["PKL_PATH"] 7 | st.set_page_config(layout="wide") 8 | 9 | #Load databse 10 | with open(PKL_PATH, 'rb') as file: 11 | database = pickle.load(file) 12 | 13 | Index, Id, Name, Image = st.columns([0.5,0.5,3,3]) 14 | 15 | for idx, person in database.items(): 16 | with Index: 17 | st.write(idx) 18 | with Id: 19 | st.write(person['id']) 20 | with Name: 21 | st.write(person['name']) 22 | with Image: 23 | st.image(person['image'],width=200) 24 | 25 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | opencv-python 2 | cmake 3 | pyyaml 4 | streamlit==1.22.0 5 | 6 | #Fork of face_recognition using prebuild dlib library instead 7 | face_recognition @ git+https://github.com/thetoby9944/face_recognition 8 | 9 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import face_recognition as frg 2 | import pickle as pkl 3 | import os 4 | import cv2 5 | import numpy as np 6 | import yaml 7 | from collections import defaultdict 8 | 9 | information = defaultdict(dict) 10 | cfg = yaml.load(open('config.yaml','r'),Loader=yaml.FullLoader) 11 | DATASET_DIR = cfg['PATH']['DATASET_DIR'] 12 | PKL_PATH = cfg['PATH']['PKL_PATH'] 13 | 14 | def get_databse(): 15 | with open(PKL_PATH,'rb') as f: 16 | database = pkl.load(f) 17 | return database 18 | def recognize(image,TOLERANCE): 19 | database = get_databse() 20 | known_encoding = [database[id]['encoding'] for id in database.keys()] 21 | name = 'Unknown' 22 | id = 'Unknown' 23 | face_locations = frg.face_locations(image) 24 | face_encodings = frg.face_encodings(image,face_locations) 25 | for (top,right,bottom,left),face_encoding in zip(face_locations,face_encodings): 26 | matches = frg.compare_faces(known_encoding,face_encoding,tolerance=TOLERANCE) 27 | distance = frg.face_distance(known_encoding,face_encoding) 28 | name = 'Unknown' 29 | id = 'Unknown' 30 | if True in matches: 31 | match_index = matches.index(True) 32 | name = database[match_index]['name'] 33 | id = database[match_index]['id'] 34 | distance = round(distance[match_index],2) 35 | cv2.putText(image,str(distance),(left,top-30),cv2.FONT_HERSHEY_SIMPLEX,0.75,(0,255,0),2) 36 | cv2.rectangle(image,(left,top),(right,bottom),(0,255,0),2) 37 | cv2.putText(image,name,(left,top-10),cv2.FONT_HERSHEY_SIMPLEX,0.75,(0,255,0),2) 38 | return image, name, id 39 | def isFaceExists(image): 40 | face_location = frg.face_locations(image) 41 | if len(face_location) == 0: 42 | return False 43 | return True 44 | def submitNew(name, id, image, old_idx=None): 45 | database = get_databse() 46 | #Read image 47 | if type(image) != np.ndarray: 48 | image = cv2.imdecode(np.fromstring(image.read(), np.uint8), 1) 49 | 50 | isFaceInPic = isFaceExists(image) 51 | if not isFaceInPic: 52 | return -1 53 | #Encode image 54 | encoding = frg.face_encodings(image)[0] 55 | #Append to database 56 | #check if id already exists 57 | existing_id = [database[i]['id'] for i in database.keys()] 58 | #Update mode 59 | if old_idx is not None: 60 | new_idx = old_idx 61 | #Add mode 62 | else: 63 | if id in existing_id: 64 | return 0 65 | new_idx = len(database) 66 | image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB) 67 | database[new_idx] = {'image':image, 68 | 'id': id, 69 | 'name':name, 70 | 'encoding':encoding} 71 | with open(PKL_PATH,'wb') as f: 72 | pkl.dump(database,f) 73 | return True 74 | def get_info_from_id(id): 75 | database = get_databse() 76 | for idx, person in database.items(): 77 | if person['id'] == id: 78 | name = person['name'] 79 | image = person['image'] 80 | return name, image, idx 81 | return None, None, None 82 | def deleteOne(id): 83 | database = get_databse() 84 | id = str(id) 85 | for key, person in database.items(): 86 | if person['id'] == id: 87 | del database[key] 88 | break 89 | with open(PKL_PATH,'wb') as f: 90 | pkl.dump(database,f) 91 | return True 92 | def build_dataset(): 93 | counter = 0 94 | for image in os.listdir(DATASET_DIR): 95 | image_path = os.path.join(DATASET_DIR,image) 96 | image_name = image.split('.')[0] 97 | parsed_name = image_name.split('_') 98 | person_id = parsed_name[0] 99 | person_name = ' '.join(parsed_name[1:]) 100 | if not image_path.endswith('.jpg'): 101 | continue 102 | image = frg.load_image_file(image_path) 103 | information[counter]['image'] = image 104 | information[counter]['id'] = person_id 105 | information[counter]['name'] = person_name 106 | information[counter]['encoding'] = frg.face_encodings(image)[0] 107 | counter += 1 108 | 109 | with open(os.path.join(DATASET_DIR,'database.pkl'),'wb') as f: 110 | pkl.dump(information,f) 111 | 112 | if __name__ == "__main__": 113 | deleteOne(4) 114 | 115 | --------------------------------------------------------------------------------