Skip to content

Commit 1e1fd76

Browse files
committed
WIP
1 parent ae2f46f commit 1e1fd76

12 files changed

+165
-148
lines changed

README.md

Lines changed: 38 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -26,117 +26,44 @@ The objective of this project is to help Police and higher authorities to track
2626
In such cases, the ideal approach is to go through CCTV footages and evidences. Again, this can be very time consuming and given the number of people that go missing everyday, it can be a challanage to keep up with it.<br>
2727

2828
## Solution (Project's Implementation)
29-
### 1. Registering New Cases
30-
The first step is to register a new case. The GUI application is built using <b>PyQT5</b> that allows you to collect all relevant information and store it in database <b>Postgres</b>.
31-
> Please ignore the SRK's image. It is just for the sake of project :)
32-
33-
![New Case Window](resources/new_case.PNG)
34-
35-
### 2. Waiting for Users to submit images
36-
So far we have only talked about 'how new cases will be registered', the next thing we have to do is to match these registered cases but who do we match it with? This is where ours Users come in. These users are common people like you and me who wants to make a change in the society.<br>
37-
The common people will use an application on their mobile to submit photos of people who they think have lost or found begging while keeping them their identity anonymous. The anonymous part is very important because they fear of local <i>Gundas</i> that might create trouble for them.<br>
38-
> Mobile Application
39-
![Mobile Application](resources/mobile_application.PNG)
40-
41-
> An android Application can also be build and used but I have very little experience in it.
42-
### 3. Matching Cases
43-
The next step is to match the case images and user submitted images. To match <b>KNN Algorithm </b> is used.
44-
![Main Application](resources/app_window.PNG)
45-
46-
## How to run
47-
#### 1. With Docker (Easy)
48-
Prerequisites
49-
```
50-
Docker (docker-compose as well)
51-
```
52-
```
53-
$ git clone https://github.com/gaganmanku96/Finding-missing-person-using-AI
54-
$ cd Finding-missing-person-using-AI
55-
$ docker-compose up --build
56-
$ cd app
57-
$ pip install -r requirements.txt --no-cache-dir
58-
$ python login_window.py
59-
```
60-
At this point you'll see a window like this
61-
![Login Window](resources/login_screen.PNG)
62-
63-
> Default username: admin
64-
> Default password: admin
65-
>
66-
> Login window makes sure that only authenticated can view the registered cases. Each user can only view the cases submitted by him.
67-
> NOTE: There is no concpet of superuser.
68-
69-
After logging in you'll see the main screen through which you'll be able to submit cases.
70-
71-
##### To run the mobile application:
72-
```
73-
$ cd mobile_app
74-
```
75-
or (if you are inside app dir)
76-
```
77-
$ ../mobile_app
78-
```
79-
```
80-
$ python ui.py
81-
```
82-
After that you'll see a window like this<br>
83-
![mobile application](resources/mobile_application.PNG)
84-
85-
You can this to submit user images or you can create your own mobile app.
86-
87-
Once done you'll have to <b>Click on Refresh</b> button on train KNN Model and then on <b>Match</b> to start Matching Images.
88-
89-
#### 2. Without Docker (Intermediate)
90-
Here are the step you have to do
91-
1. Install Postgres Database and replace the username and password in config/.env.file
92-
2. The next step is to run database and make sure it is working.
93-
```
94-
$ cd database
95-
$ pip install -r requirements.txt
96-
$ uvicorn main:app --port 8002
97-
```
98-
3. Next, the face encoding api
99-
```
100-
$ cd face_encoding
101-
$ pip install -r requirements.txt
102-
$ uvicorn main:app --port 8000
103-
```
104-
> If you are using non-conda environment like venv then it might give error while installing dlib library.
105-
4. Running the application
106-
```
107-
$ cd app
108-
$ pip install -r requirements.txt
109-
$ python login.py
110-
```
111-
At this point you'll see a window like this
112-
![Login Window](resources/login_screen.PNG)
113-
114-
> Default username: admin
115-
> Default password: admin
116-
>
117-
> Login window makes sure that only authenticated can view the registered cases. Each user can only view the cases submitted by him.
118-
> NOTE: There is no concpet of superuser.
119-
120-
After logging in you'll see the main screen through which you'll be able to submit cases.
121-
122-
##### To run the mobile application:
123-
```
124-
$ cd mobile_app
125-
```
126-
or (if you are inside app dir)
127-
```
128-
$ ../mobile_app
129-
```
130-
```
131-
$ python ui.py
132-
```
133-
After that you'll see a window like this<br>
134-
![mobile application](resources/mobile_application.PNG)
135-
136-
You can this to submit user images or you can create your own mobile app.
137-
138-
Once done you'll have to <b>Click on Refresh</b> button on train KNN Model and then on <b>Match</b> to start Matching Images.
13929

30+
### 2025 Major Update: Migration to MediaPipe Face Mesh
31+
- The project has migrated from dlib-based facial landmark/encoding to **MediaPipe Face Mesh** for all facial feature extraction and matching.
32+
- All code and database logic now use MediaPipe landmarks, making the app easier to run and maintain (no dlib dependency).
33+
- Registration and matching flows are updated to use face mesh data (stored as JSON in the database).
34+
- All legacy dlib/face_encoding code and dependencies have been removed.
35+
- The database schema now expects a `face_mesh` column (string/JSON) for facial features.
36+
- The app is compatible with Python 3.12+ and the latest Streamlit.
37+
38+
### Features
39+
- User authentication (with hashed passwords, Streamlit Authenticator)
40+
- Register new missing person cases (with image upload and face mesh extraction)
41+
- Public/mobile submission of potential matches
42+
- Case matching using face mesh features
43+
- Admin/user dashboard for case management
44+
45+
46+
## How to Run
47+
48+
1. Clone the repository and install dependencies:
49+
```bash
50+
git clone https://github.com/gaganmanku96/Finding-missing-person-using-AI.git
51+
cd Finding-missing-person-using-AI
52+
pip install -r requirements.txt
53+
```
54+
2. Run the main web app:
55+
```bash
56+
streamlit run Home.py
57+
```
58+
3. To use the mobile/public submission app:
59+
```bash
60+
streamlit run mobile_app.py
61+
```
62+
63+
- The database will auto-create on first run (SQLite, file: `sqlite_database.db`).
64+
- Images are stored in the `resources/` folder.
65+
66+
> **Note:** If you previously used the dlib version, delete any old database files and images, as the schema and features have changed.
14067
14168
## What is left?
14269
- [x] Login (Authentication)
@@ -155,3 +82,4 @@ Once done you'll have to <b>Click on Refresh</b> button on train KNN Model and t
15582
## Vote of Thanks
15683
- Thanks to [Davis King](https://github.com/davisking) for creating dlib and for providing the trained facial feature
15784
detection and face encoding models used in this project.
85+
- Thanks to the [MediaPipe](https://mediapipe.dev/) team for their open-source face mesh solution, now powering this project!

classifier.pkl

11.3 KB
Binary file not shown.

pages/4_Match Cases.py

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,53 @@
11
import streamlit as st
2+
import uuid
23
from pages.helper import db_queries, match_algo, train_model
34
from pages.helper.streamlit_helpers import require_login
45

56

67
def case_viewer(registered_case_id, public_case_id):
7-
case_details = db_queries.get_registered_case_detail(registered_case_id)[0]
8-
data_col, image_col = st.columns(2)
9-
for text, value in zip(
10-
["Name", "Mobile", "Age", "Last Seen", "Birth marks"], case_details
11-
):
12-
data_col.write(f"{text}: {value}")
138
try:
9+
# Convert string IDs to UUID objects
10+
try:
11+
# Try to convert to UUID - this handles both string and UUID inputs
12+
registered_uuid = uuid.UUID(str(registered_case_id))
13+
public_uuid = uuid.UUID(str(public_case_id))
14+
15+
# Convert back to string in the correct format for the database
16+
registered_case_id = str(registered_uuid)
17+
public_case_id = str(public_uuid)
18+
except ValueError as e:
19+
st.error(f"Invalid UUID format: {str(e)}")
20+
return
21+
22+
# Get case details using the properly formatted UUID
23+
case_details = db_queries.get_registered_case_detail(registered_case_id)[0]
24+
data_col, image_col = st.columns(2)
25+
for text, value in zip(
26+
["Name", "Mobile", "Age", "Last Seen", "Birth marks"], case_details
27+
):
28+
data_col.write(f"{text}: {value}")
29+
30+
# Update status with properly formatted UUIDs
1431
db_queries.update_found_status(registered_case_id, public_case_id)
1532
st.success(
1633
"Status Changed. Next time it will be only visible in confirmed cases page"
1734
)
18-
image_col.image(
19-
"./resources/" + str(registered_case_id) + ".jpg",
20-
width=80,
21-
use_container_width=False,
22-
)
35+
36+
# Display image
37+
try:
38+
image_col.image(
39+
"./resources/" + registered_case_id + ".jpg",
40+
width=80,
41+
use_container_width=False,
42+
)
43+
except Exception as img_err:
44+
st.warning(f"Could not load image: {str(img_err)}")
45+
2346
except Exception as e:
2447
import traceback
2548

2649
traceback.print_exc()
27-
st.error("Something went wrong. Please check logs")
50+
st.error(f"Something went wrong: {str(e)}. Please check logs")
2851

2952

3053
if "login_status" not in st.session_state:
@@ -47,9 +70,12 @@ def case_viewer(registered_case_id, public_case_id):
4770
matched_ids = match_algo.match()
4871

4972
if matched_ids["status"]:
50-
for matched_id, submitted_case_id in matched_ids["result"].items():
51-
case_viewer(matched_id, submitted_case_id[0])
52-
st.write("---")
73+
if not matched_ids["result"]:
74+
st.info("No match found")
75+
else:
76+
for matched_id, submitted_case_id in matched_ids["result"].items():
77+
case_viewer(matched_id, submitted_case_id[0])
78+
st.write("---")
5379
else:
5480
st.info("No match found")
5581

34 Bytes
Binary file not shown.
3.19 KB
Binary file not shown.

pages/helper/db_queries.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ def get_public_case_detail(case_id: str):
108108

109109

110110
def get_registered_case_detail(case_id: str):
111+
print(case_id)
111112
with Session(engine) as session:
112113
result = session.exec(
113114
select(

pages/helper/match_algo.py

Lines changed: 85 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77

88
import pandas as pd
99
import numpy as np
10-
from sklearn.exceptions import DataConversionWarning
1110

12-
warnings.filterwarnings(action="ignore", category=DataConversionWarning)
11+
12+
warnings.filterwarnings(action="ignore")
1313

1414

1515
from pages.helper import db_queries
@@ -24,38 +24,100 @@ def get_public_cases_data(status="NF"):
2424
columns=lambda x: "fm_{}".format(x + 1)
2525
)
2626
df = d1.join(d2)
27+
# Ensure all columns except label are float
28+
for col in df.columns:
29+
if col != "label":
30+
df[col] = pd.to_numeric(df[col], errors="coerce")
2731
return df
2832

2933
except Exception as e:
3034
traceback.print_exc()
3135
return None
3236

3337

34-
def match(threshold=0.1):
35-
from scipy.spatial.distance import euclidean
38+
def get_registered_cases_data(status="NF"):
39+
try:
40+
from pages.helper.db_queries import engine, RegisteredCases
41+
import pandas as pd
42+
import json
43+
from sqlmodel import Session, select
44+
45+
with Session(engine) as session:
46+
result = session.exec(
47+
select(
48+
RegisteredCases.id,
49+
RegisteredCases.face_mesh,
50+
RegisteredCases.status,
51+
)
52+
).all()
53+
d1 = pd.DataFrame(result, columns=["label", "face_mesh", "status"])
54+
if status:
55+
d1 = d1[d1["status"] == status]
56+
d1["face_mesh"] = d1["face_mesh"].apply(lambda x: json.loads(x))
57+
d2 = pd.DataFrame(
58+
d1.pop("face_mesh").values.tolist(), index=d1.index
59+
).rename(columns=lambda x: "fm_{}".format(x + 1))
60+
df = d1.join(d2)
61+
# Ensure all columns except label and status are float
62+
for col in df.columns:
63+
if col not in ["label", "status"]:
64+
df[col] = pd.to_numeric(df[col], errors="coerce")
65+
return df
66+
except Exception as e:
67+
traceback.print_exc()
68+
return None
3669

70+
71+
from sklearn.neighbors import KNeighborsClassifier
72+
from sklearn.preprocessing import LabelEncoder
73+
74+
75+
def match(distance_threshold=3):
3776
matched_images = defaultdict(list)
38-
user_submissions_df = get_public_cases_data()
77+
public_cases_df = get_public_cases_data()
78+
registered_cases_df = get_registered_cases_data()
3979

40-
if user_submissions_df is None:
80+
if public_cases_df is None or registered_cases_df is None:
4181
return {"status": False, "message": "Couldn't connect to database"}
42-
43-
if len(user_submissions_df) == 0:
44-
return {"status": False, "message": "No submissions found"}
45-
46-
# Compare each pair (brute force, can optimize)
47-
for i, row1 in user_submissions_df.iterrows():
48-
label1 = row1[0]
49-
mesh1 = np.array(row1[1:]).astype(float)
50-
for j, row2 in user_submissions_df.iterrows():
51-
if i >= j:
52-
continue
53-
label2 = row2[0]
54-
mesh2 = np.array(row2[1:]).astype(float)
55-
dist = euclidean(mesh1, mesh2)
56-
if dist < threshold:
57-
matched_images[label1].append(label2)
58-
matched_images[label2].append(label1)
82+
if len(public_cases_df) == 0 or len(registered_cases_df) == 0:
83+
return {"status": False, "message": "No public or registered cases found"}
84+
85+
# Store original labels before encoding
86+
original_reg_labels = registered_cases_df.iloc[:, 0].tolist()
87+
original_pub_labels = public_cases_df.iloc[:, 0].tolist()
88+
89+
# Prepare training data - use index positions as labels for the classifier
90+
reg_features = registered_cases_df.iloc[:, 2:].values.astype(float)
91+
92+
# Create simple numeric labels for KNN (0, 1, 2, ...)
93+
numeric_labels = list(range(len(reg_features)))
94+
95+
# Train KNN classifier with numeric labels
96+
knn = KNeighborsClassifier(n_neighbors=1, algorithm="ball_tree", weights="distance")
97+
knn.fit(reg_features, numeric_labels)
98+
99+
# For each public submission, find the closest registered case
100+
for i, row in public_cases_df.iterrows():
101+
pub_label = original_pub_labels[i] # Original public case ID
102+
face_encoding = np.array(row[1:]).astype(float)
103+
104+
try:
105+
# Get distances to nearest neighbors
106+
closest_distances = knn.kneighbors([face_encoding])[0][0]
107+
closest_distance = np.min(closest_distances)
108+
print(f"Distance for case {pub_label}: {closest_distance}")
109+
110+
# Check if distance meets threshold criteria
111+
if closest_distance >= distance_threshold: # Lower distance = better match
112+
# Get the index of the predicted registered case
113+
predicted_idx = knn.predict([face_encoding])[0]
114+
# Get the original UUID of the registered case
115+
reg_label = original_reg_labels[predicted_idx]
116+
# Store the match
117+
matched_images[reg_label].append(pub_label)
118+
except Exception as e:
119+
print(f"Error processing public case {pub_label}: {str(e)}")
120+
continue
59121

60122
return {"status": True, "result": matched_images}
61123

72.8 KB
Loading
145 KB
Loading
145 KB
Loading

0 commit comments

Comments
 (0)