Fixed scaling 🎉

This commit is contained in:
slashtechno 2023-10-14 19:25:27 -05:00
parent 7d942ee456
commit 1d17bb629b
Signed by: slashtechno
GPG Key ID: 8EC1D9D9286C2B17
3 changed files with 71 additions and 59 deletions

View File

@ -46,8 +46,8 @@ def main():
# Set it to the env RUN_SCALE if it isn't blank, otherwise set it to 0.25
default=os.environ["RUN_SCALE"]
if "RUN_SCALE" in os.environ and os.environ["RUN_SCALE"] != ""
# else 0.25,
else 1,
# else 0.25,
else 1,
type=float,
help="The scale to run the detection at, default is 0.25",
)
@ -56,8 +56,8 @@ def main():
# Set it to the env VIEW_SCALE if it isn't blank, otherwise set it to 0.75
default=os.environ["VIEW_SCALE"]
if "VIEW_SCALE" in os.environ and os.environ["VIEW_SCALE"] != ""
# else 0.75,
else 1,
# else 0.75,
else 1,
type=float,
help="The scale to view the detection at, default is 0.75",
)
@ -88,7 +88,7 @@ def main():
type=str,
help="The directory to store the faces. Should contain 1 subdirectory of images per person",
)
stream_source = argparser.add_mutually_exclusive_group()
stream_source.add_argument(
"--url",
@ -107,7 +107,7 @@ def main():
help="The capture device to use. Can also be a url.",
)
# Defaults for the stuff here and down are already set in notify.py.
# Defaults for the stuff here and down are already set in notify.py.
# Setting them here just means that argparse will display the default values as defualt
# TODO: Perhaps just remove the default parameter and just add to the help message that the default is set is x
@ -197,9 +197,11 @@ def main():
# The following is stuff for people
# This is still in the for loop as each result, no matter if anything is detected, will be present.
# Thus, there will always be one result (r)
if face_details := utils.recognize_face(path_to_directory=Path(args.faces_directory), run_frame=run_frame):
plot_boxes.append( face_details )
objects_and_peoples=notify.thing_detected(
if face_details := utils.recognize_face(
path_to_directory=Path(args.faces_directory), run_frame=run_frame
):
plot_boxes.append(face_details)
objects_and_peoples = notify.thing_detected(
thing_name=face_details["label"],
objects_and_peoples=objects_and_peoples,
detection_type="peoples",
@ -209,12 +211,12 @@ def main():
ntfy_url=args.ntfy_url,
)
# The following is stuff for objects
# Setup dictionary of object names
if objects_and_peoples["objects"] == {} or objects_and_peoples["objects"] is None:
if (
objects_and_peoples["objects"] == {}
or objects_and_peoples["objects"] is None
):
for name in r.names.values():
objects_and_peoples["objects"][name] = {
"last_detection_time": None,
@ -264,7 +266,7 @@ def main():
}
)
objects_and_peoples=notify.thing_detected(
objects_and_peoples = notify.thing_detected(
thing_name=class_id,
objects_and_peoples=objects_and_peoples,
detection_type="objects",
@ -274,8 +276,7 @@ def main():
ntfy_url=args.ntfy_url,
)
# TODO: On 10-14-2023, while testing, it seemed the bounding box was too low. Troubleshoot if it's a plotting problem.
# To do so, use r.plot() to cross reference the bounding box drawn by the plot_label function and r.plot()
# To debug plotting, use r.plot() to cross reference the bounding boxes drawn by the plot_label() and r.plot()
frame_to_show = utils.plot_label(
boxes=plot_boxes,
full_frame=frame,
@ -297,5 +298,6 @@ def main():
video_capture.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()

View File

@ -2,10 +2,9 @@ import httpx
import time
'''
"""
Structure of objects_and_peoples
Really, the only reason peoples is a separate dictionary is to prevent duplicates, though it just makes the code more complicated.
TODO: Make a function to check if a person is in the objects dictionary and vice versa
{
"objects": {
"object_name": {
@ -22,24 +21,24 @@ TODO: Make a function to check if a person is in the objects dictionary and vice
},
},
}
'''
"""
# objects_and_peoples = {}
def thing_detected(
thing_name: str,
objects_and_peoples: dict,
detection_type: str = "objects",
detection_window: int = 15,
detection_duration: int = 2,
notification_window: int = 15,
ntfy_url: str = "https://ntfy.sh/set-detect-notify"
) -> dict:
'''
thing_name: str,
objects_and_peoples: dict,
detection_type: str = "objects",
detection_window: int = 15,
detection_duration: int = 2,
notification_window: int = 15,
ntfy_url: str = "https://ntfy.sh/set-detect-notify",
) -> dict:
"""
A function to make sure 2 seconds of detection is detected in 15 seconds, 15 seconds apart.
Takes a dict that will be retured with the updated detection times. MAKE SURE TO SAVE THE RETURNED DICTIONARY
'''
"""
# "Alias" the objects and peoples dictionaries so it's easier to work with
respective_type = objects_and_peoples[detection_type]
@ -93,22 +92,18 @@ def thing_detected(
# (re)send notification
# Check if detection has been ongoing for 2 seconds or more in the past 15 seconds
if (
respective_type[thing_name]["detection_duration"]
>= detection_duration
respective_type[thing_name]["detection_duration"] >= detection_duration
and time.time() - respective_type[thing_name]["last_detection_time"]
<= detection_window
):
# If the last notification was more than 15 seconds ago, then send a notification
if (
respective_type[thing_name]["last_notification_time"] is None
or time.time()
- respective_type[thing_name]["last_notification_time"]
or time.time() - respective_type[thing_name]["last_notification_time"]
> notification_window
):
respective_type[thing_name]["last_notification_time"] = time.time()
print(
f"Detected {thing_name} for {detection_duration} seconds"
)
print(f"Detected {thing_name} for {detection_duration} seconds")
headers = construct_ntfy_headers(
title=f"{thing_name} detected",
tag="rotating_light",
@ -140,4 +135,3 @@ def send_notification(data: str, headers: dict, url: str):
if url is None or data is None:
raise ValueError("url and data cannot be None")
httpx.post(url, data=data.encode("utf-8"), headers=headers)

View File

@ -5,6 +5,7 @@ from deepface import DeepFace
first_face_try = True
def plot_label(
# list of dicts with each dict containing a label, x1, y1, x2, y2
boxes: list = None,
@ -18,20 +19,23 @@ def plot_label(
view_scale: float = None,
font: int = cv2.FONT_HERSHEY_SIMPLEX,
):
# x1 and y1 are the top left corner of the box
# x2 and y2 are the bottom right corner of the box
# Example scaling: full_frame: 1 run_frame: 0.5 view_frame: 0.25
view_frame = cv2.resize(full_frame, (0, 0), fx=view_scale, fy=view_scale)
for thing in boxes:
cv2.rectangle(
# Image
view_frame,
# Start point
# Top left corner
(
int(thing["x1"] * (run_scale / view_scale)),
int(thing["y1"] * (run_scale / view_scale)),
int((thing["x1"] / run_scale) * view_scale),
int((thing["y1"] / run_scale) * view_scale),
),
# End point
# Bottom right corner
(
int(thing["x2"] * (run_scale / view_scale)),
int(thing["y2"] * (run_scale / view_scale)),
int((thing["x2"] / run_scale) * view_scale),
int((thing["y2"] / run_scale) * view_scale),
),
# Color
(0, 255, 0),
@ -45,8 +49,8 @@ def plot_label(
thing["label"],
# Origin
(
int(thing["x1"] * (run_scale / view_scale)),
int(thing["y1"] * (run_scale / view_scale)),
int((thing["x1"] / run_scale) * view_scale),
int((thing["y1"] / run_scale) * view_scale) - 10,
),
# Font
font,
@ -61,14 +65,14 @@ def plot_label(
def recognize_face(
path_to_directory: Path = Path("faces"),
# opencv image
run_frame: np.ndarray = None,
path_to_directory: Path = Path("faces"),
# opencv image
run_frame: np.ndarray = None,
) -> np.ndarray:
'''
"""
Accepts a path to a directory of images of faces to be used as a refference
In addition, accepts an opencv image to be used as the frame to be searched
Returns a single dictonary as currently only 1 face can be detected in each frame
dict contains the following keys: label, x1, y1, x2, y2
The directory should be structured as follows:
@ -85,7 +89,7 @@ def recognize_face(
Point is, `name` is the name of the person in the images in the directory `name`
That name will be used as the label for the face in the frame
'''
"""
global first_face_try
# If it's the first time the function is being run, remove representations_vgg_face.pkl, if it exists
@ -99,9 +103,17 @@ def recognize_face(
# face_dataframes is a vanilla list of dataframes
try:
face_dataframes = DeepFace.find(run_frame, db_path=str(path_to_directory), enforce_detection=True, silent=True)
face_dataframes = DeepFace.find(
run_frame,
db_path=str(path_to_directory),
enforce_detection=True,
silent=True,
)
except ValueError as e:
if str(e) == "Face could not be detected. Please confirm that the picture is a face photo or consider to set enforce_detection param to False.":
if (
str(e)
== "Face could not be detected. Please confirm that the picture is a face photo or consider to set enforce_detection param to False."
):
return None
# Iteate over the dataframes
for df in face_dataframes:
@ -112,7 +124,7 @@ def recognize_face(
# Get the name of the parent directory
label = path_to_image.parent.name
# Return the coordinates of the box in xyxy format, rather than xywh
# This is because YOLO uses xyxy, and that's how plot_label expects
# This is because YOLO uses xyxy, and that's how plot_label expects
# Also, xyxy is just the top left and bottom right corners of the box
coordinates = {
"x1": df.iloc[-1]["source_x"],
@ -120,14 +132,18 @@ def recognize_face(
"x2": df.iloc[-1]["source_x"] + df.iloc[-1]["source_w"],
"y2": df.iloc[-1]["source_y"] + df.iloc[-1]["source_h"],
}
# After some brief testing, it seems positve matches are > 0.3
# I have not seen any false positives, so there is no threashold yet
distance = df.iloc[-1]["VGG-Face_cosine"]
# if 0.5 < distance < 0.7:
# label = "Unknown"
# label = "Unknown"
to_return = dict(label=label, **coordinates)
print(f'Confindence: {distance}, filname: {path_to_image.name}, to_return: {to_return}')
print(
f"Confindence: {distance}, filname: {path_to_image.name}, to_return: {to_return}"
)
return to_return
'''
"""
Example dataframe, for reference
identity (path to image) | source_x | source_y | source_w | source_h | VGG-Face_cosine (pretty much the confidence \_('_')_/)
'''
"""