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 # Set it to the env RUN_SCALE if it isn't blank, otherwise set it to 0.25
default=os.environ["RUN_SCALE"] default=os.environ["RUN_SCALE"]
if "RUN_SCALE" in os.environ and os.environ["RUN_SCALE"] != "" if "RUN_SCALE" in os.environ and os.environ["RUN_SCALE"] != ""
# else 0.25, # else 0.25,
else 1, else 1,
type=float, type=float,
help="The scale to run the detection at, default is 0.25", 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 # Set it to the env VIEW_SCALE if it isn't blank, otherwise set it to 0.75
default=os.environ["VIEW_SCALE"] default=os.environ["VIEW_SCALE"]
if "VIEW_SCALE" in os.environ and os.environ["VIEW_SCALE"] != "" if "VIEW_SCALE" in os.environ and os.environ["VIEW_SCALE"] != ""
# else 0.75, # else 0.75,
else 1, else 1,
type=float, type=float,
help="The scale to view the detection at, default is 0.75", help="The scale to view the detection at, default is 0.75",
) )
@ -88,7 +88,7 @@ def main():
type=str, type=str,
help="The directory to store the faces. Should contain 1 subdirectory of images per person", help="The directory to store the faces. Should contain 1 subdirectory of images per person",
) )
stream_source = argparser.add_mutually_exclusive_group() stream_source = argparser.add_mutually_exclusive_group()
stream_source.add_argument( stream_source.add_argument(
"--url", "--url",
@ -107,7 +107,7 @@ def main():
help="The capture device to use. Can also be a url.", 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 # 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 # 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 # 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. # 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) # 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): if face_details := utils.recognize_face(
plot_boxes.append( face_details ) path_to_directory=Path(args.faces_directory), run_frame=run_frame
objects_and_peoples=notify.thing_detected( ):
plot_boxes.append(face_details)
objects_and_peoples = notify.thing_detected(
thing_name=face_details["label"], thing_name=face_details["label"],
objects_and_peoples=objects_and_peoples, objects_and_peoples=objects_and_peoples,
detection_type="peoples", detection_type="peoples",
@ -209,12 +211,12 @@ def main():
ntfy_url=args.ntfy_url, ntfy_url=args.ntfy_url,
) )
# The following is stuff for objects # The following is stuff for objects
# Setup dictionary of object names # 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(): for name in r.names.values():
objects_and_peoples["objects"][name] = { objects_and_peoples["objects"][name] = {
"last_detection_time": None, "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, thing_name=class_id,
objects_and_peoples=objects_and_peoples, objects_and_peoples=objects_and_peoples,
detection_type="objects", detection_type="objects",
@ -274,8 +276,7 @@ def main():
ntfy_url=args.ntfy_url, 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 debug plotting, use r.plot() to cross reference the bounding boxes drawn by the plot_label() and r.plot()
# To do so, use r.plot() to cross reference the bounding box drawn by the plot_label function and r.plot()
frame_to_show = utils.plot_label( frame_to_show = utils.plot_label(
boxes=plot_boxes, boxes=plot_boxes,
full_frame=frame, full_frame=frame,
@ -297,5 +298,6 @@ def main():
video_capture.release() video_capture.release()
cv2.destroyAllWindows() cv2.destroyAllWindows()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -2,10 +2,9 @@ import httpx
import time import time
''' """
Structure of objects_and_peoples 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. 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": { "objects": {
"object_name": { "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 = {} # objects_and_peoples = {}
def thing_detected( def thing_detected(
thing_name: str, thing_name: str,
objects_and_peoples: dict, objects_and_peoples: dict,
detection_type: str = "objects", detection_type: str = "objects",
detection_window: int = 15, detection_window: int = 15,
detection_duration: int = 2, detection_duration: int = 2,
notification_window: int = 15, notification_window: int = 15,
ntfy_url: str = "https://ntfy.sh/set-detect-notify" ntfy_url: str = "https://ntfy.sh/set-detect-notify",
) -> dict: ) -> dict:
''' """
A function to make sure 2 seconds of detection is detected in 15 seconds, 15 seconds apart. 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 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 # "Alias" the objects and peoples dictionaries so it's easier to work with
respective_type = objects_and_peoples[detection_type] respective_type = objects_and_peoples[detection_type]
@ -93,22 +92,18 @@ def thing_detected(
# (re)send notification # (re)send notification
# Check if detection has been ongoing for 2 seconds or more in the past 15 seconds # Check if detection has been ongoing for 2 seconds or more in the past 15 seconds
if ( if (
respective_type[thing_name]["detection_duration"] respective_type[thing_name]["detection_duration"] >= detection_duration
>= detection_duration
and time.time() - respective_type[thing_name]["last_detection_time"] and time.time() - respective_type[thing_name]["last_detection_time"]
<= detection_window <= detection_window
): ):
# If the last notification was more than 15 seconds ago, then send a notification # If the last notification was more than 15 seconds ago, then send a notification
if ( if (
respective_type[thing_name]["last_notification_time"] is None respective_type[thing_name]["last_notification_time"] is None
or time.time() or time.time() - respective_type[thing_name]["last_notification_time"]
- respective_type[thing_name]["last_notification_time"]
> notification_window > notification_window
): ):
respective_type[thing_name]["last_notification_time"] = time.time() respective_type[thing_name]["last_notification_time"] = time.time()
print( print(f"Detected {thing_name} for {detection_duration} seconds")
f"Detected {thing_name} for {detection_duration} seconds"
)
headers = construct_ntfy_headers( headers = construct_ntfy_headers(
title=f"{thing_name} detected", title=f"{thing_name} detected",
tag="rotating_light", tag="rotating_light",
@ -140,4 +135,3 @@ def send_notification(data: str, headers: dict, url: str):
if url is None or data is None: if url is None or data is None:
raise ValueError("url and data cannot be None") raise ValueError("url and data cannot be None")
httpx.post(url, data=data.encode("utf-8"), headers=headers) httpx.post(url, data=data.encode("utf-8"), headers=headers)

View File

@ -5,6 +5,7 @@ from deepface import DeepFace
first_face_try = True first_face_try = True
def plot_label( def plot_label(
# list of dicts with each dict containing a label, x1, y1, x2, y2 # list of dicts with each dict containing a label, x1, y1, x2, y2
boxes: list = None, boxes: list = None,
@ -18,20 +19,23 @@ def plot_label(
view_scale: float = None, view_scale: float = None,
font: int = cv2.FONT_HERSHEY_SIMPLEX, 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) view_frame = cv2.resize(full_frame, (0, 0), fx=view_scale, fy=view_scale)
for thing in boxes: for thing in boxes:
cv2.rectangle( cv2.rectangle(
# Image # Image
view_frame, view_frame,
# Start point # Top left corner
( (
int(thing["x1"] * (run_scale / view_scale)), int((thing["x1"] / run_scale) * view_scale),
int(thing["y1"] * (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["x2"] / run_scale) * view_scale),
int(thing["y2"] * (run_scale / view_scale)), int((thing["y2"] / run_scale) * view_scale),
), ),
# Color # Color
(0, 255, 0), (0, 255, 0),
@ -45,8 +49,8 @@ def plot_label(
thing["label"], thing["label"],
# Origin # Origin
( (
int(thing["x1"] * (run_scale / view_scale)), int((thing["x1"] / run_scale) * view_scale),
int(thing["y1"] * (run_scale / view_scale)), int((thing["y1"] / run_scale) * view_scale) - 10,
), ),
# Font # Font
font, font,
@ -61,14 +65,14 @@ def plot_label(
def recognize_face( def recognize_face(
path_to_directory: Path = Path("faces"), path_to_directory: Path = Path("faces"),
# opencv image # opencv image
run_frame: np.ndarray = None, run_frame: np.ndarray = None,
) -> np.ndarray: ) -> np.ndarray:
''' """
Accepts a path to a directory of images of faces to be used as a refference 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 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 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 dict contains the following keys: label, x1, y1, x2, y2
The directory should be structured as follows: 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` 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 That name will be used as the label for the face in the frame
''' """
global first_face_try global first_face_try
# If it's the first time the function is being run, remove representations_vgg_face.pkl, if it exists # 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 # face_dataframes is a vanilla list of dataframes
try: 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: 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 return None
# Iteate over the dataframes # Iteate over the dataframes
for df in face_dataframes: for df in face_dataframes:
@ -112,7 +124,7 @@ def recognize_face(
# Get the name of the parent directory # Get the name of the parent directory
label = path_to_image.parent.name label = path_to_image.parent.name
# Return the coordinates of the box in xyxy format, rather than xywh # 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 # Also, xyxy is just the top left and bottom right corners of the box
coordinates = { coordinates = {
"x1": df.iloc[-1]["source_x"], "x1": df.iloc[-1]["source_x"],
@ -120,14 +132,18 @@ def recognize_face(
"x2": df.iloc[-1]["source_x"] + df.iloc[-1]["source_w"], "x2": df.iloc[-1]["source_x"] + df.iloc[-1]["source_w"],
"y2": df.iloc[-1]["source_y"] + df.iloc[-1]["source_h"], "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"] distance = df.iloc[-1]["VGG-Face_cosine"]
# if 0.5 < distance < 0.7: # if 0.5 < distance < 0.7:
# label = "Unknown" # label = "Unknown"
to_return = dict(label=label, **coordinates) 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 return to_return
''' """
Example dataframe, for reference Example dataframe, for reference
identity (path to image) | source_x | source_y | source_w | source_h | VGG-Face_cosine (pretty much the confidence \_('_')_/) identity (path to image) | source_x | source_y | source_w | source_h | VGG-Face_cosine (pretty much the confidence \_('_')_/)
''' """