mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-09-28 19:06:52 +03:00
Enable event snapshot API to honour query params after event ends (#22375)
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
CI / ARM Extra Build (push) Blocked by required conditions
CI / Synaptics Build (push) Blocked by required conditions
CI / AMD64 Build (push) Waiting to run
CI / ARM Build (push) Waiting to run
CI / Jetson Jetpack 6 (push) Waiting to run
CI / AMD64 Extra Build (push) Blocked by required conditions
CI / Assemble and push default build (push) Blocked by required conditions
* Enable event snapshot API to honour query params * fix unused imports * Fixes * Run ruff check --fix * Web changes * Further config and web fixes * Further docs tweak * Fix missing quality default in MediaEventsSnapshotQueryParams * Manual events: don't save annotated jpeg; store frame time * Remove unnecessary grayscale helper * Add caveat to docs on snapshot_frame_time pre-0.18 * JPG snapshot should not be treated as clean * Ensure tracked details uses uncropped, bbox'd snapshot * Ensure all UI pages / menu actions use uncropped, bbox'd * web lint * Add missed config helper text * Expect SnapshotsConfig not Any * docs: Remove pre-0.18 note * Specify timestamp=0 in the UI * Move tests out of http media * Correct missed settings.json wording Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Revert to default None for quality * Correct camera snapshot config wording Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Fix quality=0 handling Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * Fix quality=0 handling #2 Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> * ReRun generate_config_translations --------- Co-authored-by: leccelecce <example@example.com> Co-authored-by: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com>
This commit is contained in:
co-authored by
leccelecce
Josh Hawkins
parent
b6c03c99de
commit
ec7040bed5
@@ -547,7 +547,10 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
"has_clip": self.config.cameras[camera_name].record.enabled
|
||||
and include_recording,
|
||||
"has_snapshot": True,
|
||||
"snapshot_clean": True,
|
||||
"snapshot_frame_time": frame_time,
|
||||
"type": source_type,
|
||||
"draw": draw,
|
||||
},
|
||||
)
|
||||
)
|
||||
@@ -603,6 +606,7 @@ class TrackedObjectProcessor(threading.Thread):
|
||||
"has_clip": self.config.cameras[camera_name].record.enabled
|
||||
and include_recording,
|
||||
"has_snapshot": True,
|
||||
"snapshot_clean": True,
|
||||
"type": "api",
|
||||
"recognized_license_plate": plate,
|
||||
"recognized_license_plate_score": score,
|
||||
|
||||
+35
-138
@@ -13,7 +13,6 @@ import numpy as np
|
||||
from frigate.config import (
|
||||
CameraConfig,
|
||||
FilterConfig,
|
||||
SnapshotsConfig,
|
||||
UIConfig,
|
||||
)
|
||||
from frigate.const import CLIPS_DIR, REPLAY_CAMERA_PREFIX, THUMB_DIR
|
||||
@@ -22,9 +21,7 @@ from frigate.review.types import SeverityEnum
|
||||
from frigate.util.builtin import sanitize_float
|
||||
from frigate.util.image import (
|
||||
area,
|
||||
calculate_region,
|
||||
draw_box_with_label,
|
||||
draw_timestamp,
|
||||
get_snapshot_bytes,
|
||||
is_better_thumbnail,
|
||||
)
|
||||
from frigate.util.object import box_inside
|
||||
@@ -393,6 +390,7 @@ class TrackedObject:
|
||||
"camera": self.camera_config.name,
|
||||
"frame_time": self.obj_data["frame_time"],
|
||||
"snapshot": self.thumbnail_data,
|
||||
"snapshot_clean": True,
|
||||
"label": self.obj_data["label"],
|
||||
"sub_label": self.obj_data.get("sub_label"),
|
||||
"top_score": self.top_score,
|
||||
@@ -449,27 +447,15 @@ class TrackedObject:
|
||||
return img.tobytes()
|
||||
|
||||
def get_clean_webp(self) -> bytes | None:
|
||||
if self.thumbnail_data is None:
|
||||
return None
|
||||
|
||||
try:
|
||||
best_frame = cv2.cvtColor(
|
||||
self.frame_cache[self.thumbnail_data["frame_time"]]["frame"],
|
||||
cv2.COLOR_YUV2BGR_I420,
|
||||
)
|
||||
except KeyError:
|
||||
logger.warning(
|
||||
f"Unable to create clean webp because frame {self.thumbnail_data['frame_time']} is not in the cache"
|
||||
)
|
||||
return None
|
||||
|
||||
ret, webp = cv2.imencode(
|
||||
".webp", best_frame, [int(cv2.IMWRITE_WEBP_QUALITY), 60]
|
||||
webp_bytes, _ = self.get_img_bytes(
|
||||
ext="webp",
|
||||
timestamp=False,
|
||||
bounding_box=False,
|
||||
crop=False,
|
||||
height=None,
|
||||
quality=self.camera_config.snapshots.quality,
|
||||
)
|
||||
if ret:
|
||||
return webp.tobytes()
|
||||
else:
|
||||
return None
|
||||
return webp_bytes
|
||||
|
||||
def get_img_bytes(
|
||||
self,
|
||||
@@ -491,131 +477,42 @@ class TrackedObject:
|
||||
)
|
||||
except KeyError:
|
||||
logger.warning(
|
||||
f"Unable to create jpg because frame {frame_time} is not in the cache"
|
||||
f"Unable to create snapshot because frame {frame_time} is not in the cache"
|
||||
)
|
||||
return None, None
|
||||
|
||||
if bounding_box:
|
||||
thickness = 2
|
||||
color = self.colormap.get(self.obj_data["label"], (255, 255, 255))
|
||||
|
||||
# draw the bounding boxes on the frame
|
||||
box = self.thumbnail_data["box"]
|
||||
draw_box_with_label(
|
||||
best_frame,
|
||||
box[0],
|
||||
box[1],
|
||||
box[2],
|
||||
box[3],
|
||||
self.obj_data["label"],
|
||||
f"{int(self.thumbnail_data['score'] * 100)}% {int(self.thumbnail_data['area'])}"
|
||||
+ (
|
||||
f" {self.thumbnail_data['current_estimated_speed']:.1f}"
|
||||
if self.thumbnail_data["current_estimated_speed"] != 0
|
||||
else ""
|
||||
),
|
||||
thickness=thickness,
|
||||
color=color,
|
||||
)
|
||||
|
||||
# draw any attributes
|
||||
for attribute in self.thumbnail_data["attributes"]:
|
||||
box = attribute["box"]
|
||||
box_area = int((box[2] - box[0]) * (box[3] - box[1]))
|
||||
draw_box_with_label(
|
||||
best_frame,
|
||||
box[0],
|
||||
box[1],
|
||||
box[2],
|
||||
box[3],
|
||||
attribute["label"],
|
||||
f"{attribute['score']:.0%} {str(box_area)}",
|
||||
thickness=thickness,
|
||||
color=color,
|
||||
)
|
||||
|
||||
if crop:
|
||||
box = self.thumbnail_data["box"]
|
||||
box_size = 300
|
||||
region = calculate_region(
|
||||
best_frame.shape,
|
||||
box[0],
|
||||
box[1],
|
||||
box[2],
|
||||
box[3],
|
||||
box_size,
|
||||
multiplier=1.1,
|
||||
)
|
||||
best_frame = best_frame[region[1] : region[3], region[0] : region[2]]
|
||||
|
||||
if height:
|
||||
width = int(height * best_frame.shape[1] / best_frame.shape[0])
|
||||
best_frame = cv2.resize(
|
||||
best_frame, dsize=(width, height), interpolation=cv2.INTER_AREA
|
||||
)
|
||||
if timestamp:
|
||||
colors = self.camera_config.timestamp_style.color
|
||||
draw_timestamp(
|
||||
best_frame,
|
||||
self.thumbnail_data["frame_time"],
|
||||
self.camera_config.timestamp_style.format,
|
||||
font_effect=self.camera_config.timestamp_style.effect,
|
||||
font_thickness=self.camera_config.timestamp_style.thickness,
|
||||
font_color=(colors.blue, colors.green, colors.red),
|
||||
position=self.camera_config.timestamp_style.position,
|
||||
)
|
||||
|
||||
quality_params = []
|
||||
|
||||
if ext == "jpg":
|
||||
quality_params = [int(cv2.IMWRITE_JPEG_QUALITY), quality or 70]
|
||||
elif ext == "webp":
|
||||
quality_params = [int(cv2.IMWRITE_WEBP_QUALITY), quality or 60]
|
||||
|
||||
ret, jpg = cv2.imencode(f".{ext}", best_frame, quality_params)
|
||||
|
||||
if ret:
|
||||
return jpg.tobytes(), frame_time
|
||||
else:
|
||||
return None, None
|
||||
return get_snapshot_bytes(
|
||||
best_frame,
|
||||
frame_time,
|
||||
ext=ext,
|
||||
timestamp=timestamp,
|
||||
bounding_box=bounding_box,
|
||||
crop=crop,
|
||||
height=height,
|
||||
quality=quality,
|
||||
label=self.obj_data["label"],
|
||||
box=self.thumbnail_data["box"],
|
||||
score=self.thumbnail_data["score"],
|
||||
area=self.thumbnail_data["area"],
|
||||
attributes=self.thumbnail_data["attributes"],
|
||||
color=self.colormap.get(self.obj_data["label"], (255, 255, 255)),
|
||||
timestamp_style=self.camera_config.timestamp_style,
|
||||
estimated_speed=self.thumbnail_data["current_estimated_speed"],
|
||||
)
|
||||
|
||||
def write_snapshot_to_disk(self) -> None:
|
||||
snapshot_config: SnapshotsConfig = self.camera_config.snapshots
|
||||
jpg_bytes, _ = self.get_img_bytes(
|
||||
ext="jpg",
|
||||
timestamp=snapshot_config.timestamp,
|
||||
bounding_box=snapshot_config.bounding_box,
|
||||
crop=snapshot_config.crop,
|
||||
height=snapshot_config.height,
|
||||
quality=snapshot_config.quality,
|
||||
)
|
||||
if jpg_bytes is None:
|
||||
webp_bytes = self.get_clean_webp()
|
||||
if webp_bytes is None:
|
||||
logger.warning(f"Unable to save snapshot for {self.obj_data['id']}.")
|
||||
else:
|
||||
with open(
|
||||
os.path.join(
|
||||
CLIPS_DIR, f"{self.camera_config.name}-{self.obj_data['id']}.jpg"
|
||||
CLIPS_DIR,
|
||||
f"{self.camera_config.name}-{self.obj_data['id']}-clean.webp",
|
||||
),
|
||||
"wb",
|
||||
) as j:
|
||||
j.write(jpg_bytes)
|
||||
|
||||
# write clean snapshot if enabled
|
||||
if snapshot_config.clean_copy:
|
||||
webp_bytes = self.get_clean_webp()
|
||||
if webp_bytes is None:
|
||||
logger.warning(
|
||||
f"Unable to save clean snapshot for {self.obj_data['id']}."
|
||||
)
|
||||
else:
|
||||
with open(
|
||||
os.path.join(
|
||||
CLIPS_DIR,
|
||||
f"{self.camera_config.name}-{self.obj_data['id']}-clean.webp",
|
||||
),
|
||||
"wb",
|
||||
) as p:
|
||||
p.write(webp_bytes)
|
||||
) as p:
|
||||
p.write(webp_bytes)
|
||||
|
||||
def write_thumbnail_to_disk(self) -> None:
|
||||
if not self.camera_config.name:
|
||||
|
||||
Reference in New Issue
Block a user