Fixed scaling 🎉
This commit is contained in:
parent
7d942ee456
commit
1d17bb629b
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 \_('_')_/)
|
||||
'''
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue