forked from Github/frigate
Compare commits
1 Commits
v0.15.0-be
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
727bddac03 |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -66,7 +66,7 @@ jobs:
|
||||
${{ steps.setup.outputs.image-name }}-standard-arm64
|
||||
cache-from: type=registry,ref=${{ steps.setup.outputs.cache-name }}-arm64
|
||||
- name: Build and push RPi build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
push: true
|
||||
targets: rpi
|
||||
@@ -94,7 +94,7 @@ jobs:
|
||||
BASE_IMAGE: timongentzsch/l4t-ubuntu20-opencv:latest
|
||||
SLIM_BASE: timongentzsch/l4t-ubuntu20-opencv:latest
|
||||
TRT_BASE: timongentzsch/l4t-ubuntu20-opencv:latest
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
push: true
|
||||
targets: tensorrt
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
BASE_IMAGE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime
|
||||
SLIM_BASE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime
|
||||
TRT_BASE: nvcr.io/nvidia/l4t-tensorrt:r8.5.2-runtime
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
push: true
|
||||
targets: tensorrt
|
||||
@@ -149,7 +149,7 @@ jobs:
|
||||
- name: Build and push TensorRT (x86 GPU)
|
||||
env:
|
||||
COMPUTE_LEVEL: "50 60 70 80 90"
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
push: true
|
||||
targets: tensorrt
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Rockchip build
|
||||
uses: docker/bake-action@v3
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
push: true
|
||||
targets: rk
|
||||
@@ -199,7 +199,7 @@ jobs:
|
||||
with:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push Hailo-8l build
|
||||
uses: docker/bake-action@v4
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
push: true
|
||||
targets: h8l
|
||||
@@ -212,7 +212,7 @@ jobs:
|
||||
env:
|
||||
AMDGPU: gfx
|
||||
HSA_OVERRIDE: 0
|
||||
uses: docker/bake-action@v3
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
push: true
|
||||
targets: rocm
|
||||
|
||||
@@ -61,7 +61,7 @@ def start(id, num_detections, detection_queue, event):
|
||||
object_detector.cleanup()
|
||||
print(f"{id} - Processed for {duration:.2f} seconds.")
|
||||
print(f"{id} - FPS: {object_detector.fps.eps():.2f}")
|
||||
print(f"{id} - Average frame processing time: {mean(frame_times) * 1000:.2f}ms")
|
||||
print(f"{id} - Average frame processing time: {mean(frame_times)*1000:.2f}ms")
|
||||
|
||||
|
||||
######
|
||||
|
||||
@@ -156,9 +156,7 @@ cameras:
|
||||
|
||||
#### Reolink Doorbell
|
||||
|
||||
The reolink doorbell supports two way audio via go2rtc and other applications. It is important that the http-flv stream is still used for stability, a secondary rtsp stream can be added that will be using for the two way audio only.
|
||||
|
||||
Ensure HTTP is enabled in the camera's advanced network settings. To use two way talk with Frigate, see the [Live view documentation](/configuration/live#two-way-talk).
|
||||
The reolink doorbell supports 2-way audio via go2rtc and other applications. It is important that the http-flv stream is still used for stability, a secondary rtsp stream can be added that will be using for the two way audio only.
|
||||
|
||||
```yaml
|
||||
go2rtc:
|
||||
|
||||
@@ -138,13 +138,3 @@ services:
|
||||
:::
|
||||
|
||||
See [go2rtc WebRTC docs](https://github.com/AlexxIT/go2rtc/tree/v1.8.3#module-webrtc) for more information about this.
|
||||
|
||||
### Two way talk
|
||||
|
||||
For devices that support two way talk, Frigate can be configured to use the feature from the camera's Live view in the Web UI. You should:
|
||||
|
||||
- Set up go2rtc with [WebRTC](#webrtc-extra-configuration).
|
||||
- Ensure you access Frigate via https (may require [opening port 8971](/frigate/installation/#ports)).
|
||||
- For the Home Assistant Frigate card, [follow the docs](https://github.com/dermotduffy/frigate-hass-card?tab=readme-ov-file#using-2-way-audio) for the correct source.
|
||||
|
||||
To use the Reolink Doorbell with two way talk, you should use the [recommended Reolink configuration](/configuration/camera_specific#reolink-doorbell)
|
||||
|
||||
@@ -305,15 +305,8 @@ To install make sure you have the [community app plugin here](https://forums.unr
|
||||
|
||||
## Proxmox
|
||||
|
||||
[According to Proxmox documentation](https://pve.proxmox.com/pve-docs/pve-admin-guide.html#chapter_pct) it is recommended that you run application containers like Frigate inside a Proxmox QEMU VM. This will give you all the advantages of application containerization, while also providing the benefits that VMs offer, such as strong isolation from the host and the ability to live-migrate, which otherwise isn’t possible with containers.
|
||||
It is recommended to run Frigate in LXC, rather than in a VM, for maximum performance. The setup can be complex so be prepared to read the Proxmox and LXC documentation. Suggestions include:
|
||||
|
||||
:::warning
|
||||
|
||||
If you choose to run Frigate via LXC in Proxmox the setup can be complex so be prepared to read the Proxmox and LXC documentation, Frigate does not officially support running inside of an LXC.
|
||||
|
||||
:::
|
||||
|
||||
Suggestions include:
|
||||
- For Intel-based hardware acceleration, to allow access to the `/dev/dri/renderD128` device with major number 226 and minor number 128, add the following lines to the `/etc/pve/lxc/<id>.conf` LXC configuration:
|
||||
- `lxc.cgroup2.devices.allow: c 226:128 rwm`
|
||||
- `lxc.mount.entry: /dev/dri/renderD128 dev/dri/renderD128 none bind,optional,create=file`
|
||||
|
||||
@@ -3,15 +3,7 @@ id: recordings
|
||||
title: Troubleshooting Recordings
|
||||
---
|
||||
|
||||
## I have Frigate configured for motion recording only, but it still seems to be recording even with no motion. Why?
|
||||
|
||||
You'll want to:
|
||||
|
||||
- Make sure your camera's timestamp is masked out with a motion mask. Even if there is no motion occurring in your scene, your motion settings may be sensitive enough to count your timestamp as motion.
|
||||
- If you have audio detection enabled, keep in mind that audio that is heard above `min_volume` is considered motion.
|
||||
- [Tune your motion detection settings](/configuration/motion_detection) either by editing your config file or by using the UI's Motion Tuner.
|
||||
|
||||
## I see the message: WARNING : Unable to keep up with recording segments in cache for camera. Keeping the 5 most recent segments out of 6 and discarding the rest...
|
||||
### WARNING : Unable to keep up with recording segments in cache for camera. Keeping the 5 most recent segments out of 6 and discarding the rest...
|
||||
|
||||
This error can be caused by a number of different issues. The first step in troubleshooting is to enable debug logging for recording. This will enable logging showing how long it takes for recordings to be moved from RAM cache to the disk.
|
||||
|
||||
@@ -48,7 +40,6 @@ On linux, some helpful tools/commands in diagnosing would be:
|
||||
On modern linux kernels, the system will utilize some swap if enabled. Setting vm.swappiness=1 no longer means that the kernel will only swap in order to avoid OOM. To prevent any swapping inside a container, set allocations memory and memory+swap to be the same and disable swapping by setting the following docker/podman run parameters:
|
||||
|
||||
**Compose example**
|
||||
|
||||
```yaml
|
||||
version: "3.9"
|
||||
services:
|
||||
@@ -63,7 +54,6 @@ services:
|
||||
```
|
||||
|
||||
**Run command example**
|
||||
|
||||
```
|
||||
--memory=<MAXRAM> --memory-swap=<MAXSWAP> --memory-swappiness=0
|
||||
```
|
||||
|
||||
@@ -151,7 +151,7 @@ class WebPushClient(Communicator): # type: ignore[misc]
|
||||
camera: str = payload["after"]["camera"]
|
||||
title = f"{', '.join(sorted_objects).replace('_', ' ').title()}{' was' if state == 'end' else ''} detected in {', '.join(payload['after']['data']['zones']).replace('_', ' ').title()}"
|
||||
message = f"Detected on {camera.replace('_', ' ').title()}"
|
||||
image = f"{payload['after']['thumb_path'].replace('/media/frigate', '')}"
|
||||
image = f'{payload["after"]["thumb_path"].replace("/media/frigate", "")}'
|
||||
|
||||
# if event is ongoing open to live view otherwise open to recordings view
|
||||
direct_url = f"/review?id={reviewId}" if state == "end" else f"/#{camera}"
|
||||
|
||||
@@ -85,7 +85,7 @@ class ZoneConfig(BaseModel):
|
||||
if explicit:
|
||||
self.coordinates = ",".join(
|
||||
[
|
||||
f"{round(int(p.split(',')[0]) / frame_shape[1], 3)},{round(int(p.split(',')[1]) / frame_shape[0], 3)}"
|
||||
f'{round(int(p.split(",")[0]) / frame_shape[1], 3)},{round(int(p.split(",")[1]) / frame_shape[0], 3)}'
|
||||
for p in coordinates
|
||||
]
|
||||
)
|
||||
|
||||
@@ -219,19 +219,19 @@ class TensorRtDetector(DetectionApi):
|
||||
]
|
||||
|
||||
def __init__(self, detector_config: TensorRTDetectorConfig):
|
||||
assert TRT_SUPPORT, (
|
||||
f"TensorRT libraries not found, {DETECTOR_KEY} detector not present"
|
||||
)
|
||||
assert (
|
||||
TRT_SUPPORT
|
||||
), f"TensorRT libraries not found, {DETECTOR_KEY} detector not present"
|
||||
|
||||
(cuda_err,) = cuda.cuInit(0)
|
||||
assert cuda_err == cuda.CUresult.CUDA_SUCCESS, (
|
||||
f"Failed to initialize cuda {cuda_err}"
|
||||
)
|
||||
assert (
|
||||
cuda_err == cuda.CUresult.CUDA_SUCCESS
|
||||
), f"Failed to initialize cuda {cuda_err}"
|
||||
err, dev_count = cuda.cuDeviceGetCount()
|
||||
logger.debug(f"Num Available Devices: {dev_count}")
|
||||
assert detector_config.device < dev_count, (
|
||||
f"Invalid TensorRT Device Config. Device {detector_config.device} Invalid."
|
||||
)
|
||||
assert (
|
||||
detector_config.device < dev_count
|
||||
), f"Invalid TensorRT Device Config. Device {detector_config.device} Invalid."
|
||||
err, self.cu_ctx = cuda.cuCtxCreate(
|
||||
cuda.CUctx_flags.CU_CTX_MAP_HOST, detector_config.device
|
||||
)
|
||||
|
||||
@@ -68,13 +68,11 @@ class PlusApi:
|
||||
or self._token_data["expires"] - datetime.datetime.now().timestamp() < 60
|
||||
):
|
||||
if self.key is None:
|
||||
raise Exception(
|
||||
"Plus API key not set. See https://docs.frigate.video/integrations/plus#set-your-api-key"
|
||||
)
|
||||
raise Exception("Plus API not activated")
|
||||
parts = self.key.split(":")
|
||||
r = requests.get(f"{self.host}/v1/auth/token", auth=(parts[0], parts[1]))
|
||||
if not r.ok:
|
||||
raise Exception(f"Unable to refresh API token: {r.text}")
|
||||
raise Exception("Unable to refresh API token")
|
||||
self._token_data = r.json()
|
||||
|
||||
def _get_authorization_header(self) -> dict:
|
||||
@@ -118,6 +116,15 @@ class PlusApi:
|
||||
logger.error(f"Failed to upload original: {r.status_code} {r.text}")
|
||||
raise Exception(r.text)
|
||||
|
||||
# resize and submit annotate
|
||||
files = {"file": get_jpg_bytes(image, 640, 70)}
|
||||
data = presigned_urls["annotate"]["fields"]
|
||||
data["content-type"] = "image/jpeg"
|
||||
r = requests.post(presigned_urls["annotate"]["url"], files=files, data=data)
|
||||
if not r.ok:
|
||||
logger.error(f"Failed to upload annotate: {r.status_code} {r.text}")
|
||||
raise Exception(r.text)
|
||||
|
||||
# resize and submit thumbnail
|
||||
files = {"file": get_jpg_bytes(image, 200, 70)}
|
||||
data = presigned_urls["thumbnail"]["fields"]
|
||||
|
||||
@@ -135,7 +135,7 @@ class PtzMotionEstimator:
|
||||
|
||||
try:
|
||||
logger.debug(
|
||||
f"{camera}: Motion estimator transformation: {self.coord_transformations.rel_to_abs([[0, 0]])}"
|
||||
f"{camera}: Motion estimator transformation: {self.coord_transformations.rel_to_abs([[0,0]])}"
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
@@ -471,7 +471,7 @@ class PtzAutoTracker:
|
||||
self.onvif.get_camera_status(camera)
|
||||
|
||||
logger.info(
|
||||
f"Calibration for {camera} in progress: {round((step / num_steps) * 100)}% complete"
|
||||
f"Calibration for {camera} in progress: {round((step/num_steps)*100)}% complete"
|
||||
)
|
||||
|
||||
self.calibrating[camera] = False
|
||||
@@ -690,7 +690,7 @@ class PtzAutoTracker:
|
||||
f"{camera}: Predicted movement time: {self._predict_movement_time(camera, pan, tilt)}"
|
||||
)
|
||||
logger.debug(
|
||||
f"{camera}: Actual movement time: {self.ptz_metrics[camera].stop_time.value - self.ptz_metrics[camera].start_time.value}"
|
||||
f"{camera}: Actual movement time: {self.ptz_metrics[camera].stop_time.value-self.ptz_metrics[camera].start_time.value}"
|
||||
)
|
||||
|
||||
# save metrics for better estimate calculations
|
||||
@@ -983,10 +983,10 @@ class PtzAutoTracker:
|
||||
logger.debug(f"{camera}: Zoom test: at max zoom: {at_max_zoom}")
|
||||
logger.debug(f"{camera}: Zoom test: at min zoom: {at_min_zoom}")
|
||||
logger.debug(
|
||||
f"{camera}: Zoom test: zoom in hysteresis limit: {zoom_in_hysteresis} value: {AUTOTRACKING_ZOOM_IN_HYSTERESIS} original: {self.tracked_object_metrics[camera]['original_target_box']} max: {self.tracked_object_metrics[camera]['max_target_box']} target: {calculated_target_box if calculated_target_box else self.tracked_object_metrics[camera]['target_box']}"
|
||||
f'{camera}: Zoom test: zoom in hysteresis limit: {zoom_in_hysteresis} value: {AUTOTRACKING_ZOOM_IN_HYSTERESIS} original: {self.tracked_object_metrics[camera]["original_target_box"]} max: {self.tracked_object_metrics[camera]["max_target_box"]} target: {calculated_target_box if calculated_target_box else self.tracked_object_metrics[camera]["target_box"]}'
|
||||
)
|
||||
logger.debug(
|
||||
f"{camera}: Zoom test: zoom out hysteresis limit: {zoom_out_hysteresis} value: {AUTOTRACKING_ZOOM_OUT_HYSTERESIS} original: {self.tracked_object_metrics[camera]['original_target_box']} max: {self.tracked_object_metrics[camera]['max_target_box']} target: {calculated_target_box if calculated_target_box else self.tracked_object_metrics[camera]['target_box']}"
|
||||
f'{camera}: Zoom test: zoom out hysteresis limit: {zoom_out_hysteresis} value: {AUTOTRACKING_ZOOM_OUT_HYSTERESIS} original: {self.tracked_object_metrics[camera]["original_target_box"]} max: {self.tracked_object_metrics[camera]["max_target_box"]} target: {calculated_target_box if calculated_target_box else self.tracked_object_metrics[camera]["target_box"]}'
|
||||
)
|
||||
|
||||
# Zoom in conditions (and)
|
||||
@@ -1069,7 +1069,7 @@ class PtzAutoTracker:
|
||||
pan = ((centroid_x / camera_width) - 0.5) * 2
|
||||
tilt = (0.5 - (centroid_y / camera_height)) * 2
|
||||
|
||||
logger.debug(f"{camera}: Original box: {obj.obj_data['box']}")
|
||||
logger.debug(f'{camera}: Original box: {obj.obj_data["box"]}')
|
||||
logger.debug(f"{camera}: Predicted box: {tuple(predicted_box)}")
|
||||
logger.debug(
|
||||
f"{camera}: Velocity: {tuple(np.round(average_velocity).flatten().astype(int))}"
|
||||
@@ -1179,7 +1179,7 @@ class PtzAutoTracker:
|
||||
)
|
||||
zoom = (ratio - 1) / (ratio + 1)
|
||||
logger.debug(
|
||||
f"{camera}: limit: {self.tracked_object_metrics[camera]['max_target_box']}, ratio: {ratio} zoom calculation: {zoom}"
|
||||
f'{camera}: limit: {self.tracked_object_metrics[camera]["max_target_box"]}, ratio: {ratio} zoom calculation: {zoom}'
|
||||
)
|
||||
if not result:
|
||||
# zoom out with special condition if zooming out because of velocity, edges, etc.
|
||||
|
||||
@@ -449,7 +449,7 @@ class RecordingMaintainer(threading.Thread):
|
||||
return None
|
||||
else:
|
||||
logger.debug(
|
||||
f"Copied {file_path} in {datetime.datetime.now().timestamp() - start_frame} seconds."
|
||||
f"Copied {file_path} in {datetime.datetime.now().timestamp()-start_frame} seconds."
|
||||
)
|
||||
|
||||
try:
|
||||
|
||||
@@ -256,7 +256,7 @@ class ReviewSegmentMaintainer(threading.Thread):
|
||||
elif object["sub_label"][0] in self.config.model.all_attributes:
|
||||
segment.detections[object["id"]] = object["sub_label"][0]
|
||||
else:
|
||||
segment.detections[object["id"]] = f"{object['label']}-verified"
|
||||
segment.detections[object["id"]] = f'{object["label"]}-verified'
|
||||
segment.sub_labels[object["id"]] = object["sub_label"][0]
|
||||
|
||||
# if object is alert label
|
||||
@@ -352,7 +352,7 @@ class ReviewSegmentMaintainer(threading.Thread):
|
||||
elif object["sub_label"][0] in self.config.model.all_attributes:
|
||||
detections[object["id"]] = object["sub_label"][0]
|
||||
else:
|
||||
detections[object["id"]] = f"{object['label']}-verified"
|
||||
detections[object["id"]] = f'{object["label"]}-verified'
|
||||
sub_labels[object["id"]] = object["sub_label"][0]
|
||||
|
||||
# if object is alert label
|
||||
@@ -527,9 +527,7 @@ class ReviewSegmentMaintainer(threading.Thread):
|
||||
|
||||
if event_id in self.indefinite_events[camera]:
|
||||
self.indefinite_events[camera].pop(event_id)
|
||||
|
||||
if len(self.indefinite_events[camera]) == 0:
|
||||
current_segment.last_update = manual_info["end_time"]
|
||||
current_segment.last_update = manual_info["end_time"]
|
||||
else:
|
||||
logger.error(
|
||||
f"Event with ID {event_id} has a set duration and can not be ended manually."
|
||||
|
||||
@@ -72,7 +72,8 @@ class BaseServiceProcess(Service, ABC):
|
||||
running = False
|
||||
except TimeoutError:
|
||||
self.manager.logger.warning(
|
||||
f"{self.name} is still running after {timeout} seconds. Killing."
|
||||
f"{self.name} is still running after "
|
||||
f"{timeout} seconds. Killing."
|
||||
)
|
||||
|
||||
if running:
|
||||
|
||||
@@ -339,7 +339,7 @@ class TrackedObject:
|
||||
box[2],
|
||||
box[3],
|
||||
self.obj_data["label"],
|
||||
f"{int(self.thumbnail_data['score'] * 100)}% {int(self.thumbnail_data['area'])}",
|
||||
f"{int(self.thumbnail_data['score']*100)}% {int(self.thumbnail_data['area'])}",
|
||||
thickness=thickness,
|
||||
color=color,
|
||||
)
|
||||
|
||||
@@ -314,7 +314,7 @@ def get_relative_coordinates(
|
||||
continue
|
||||
|
||||
rel_points.append(
|
||||
f"{round(x / frame_shape[1], 3)},{round(y / frame_shape[0], 3)}"
|
||||
f"{round(x / frame_shape[1], 3)},{round(y / frame_shape[0], 3)}"
|
||||
)
|
||||
|
||||
relative_masks.append(",".join(rel_points))
|
||||
@@ -337,7 +337,7 @@ def get_relative_coordinates(
|
||||
return []
|
||||
|
||||
rel_points.append(
|
||||
f"{round(x / frame_shape[1], 3)},{round(y / frame_shape[0], 3)}"
|
||||
f"{round(x / frame_shape[1], 3)},{round(y / frame_shape[0], 3)}"
|
||||
)
|
||||
|
||||
mask = ",".join(rel_points)
|
||||
|
||||
@@ -390,22 +390,12 @@ def try_get_info(f, h, default="N/A"):
|
||||
|
||||
|
||||
def get_nvidia_gpu_stats() -> dict[int, dict]:
|
||||
names: dict[str, int] = {}
|
||||
results = {}
|
||||
try:
|
||||
nvml.nvmlInit()
|
||||
deviceCount = nvml.nvmlDeviceGetCount()
|
||||
for i in range(deviceCount):
|
||||
handle = nvml.nvmlDeviceGetHandleByIndex(i)
|
||||
gpu_name = nvml.nvmlDeviceGetName(handle)
|
||||
|
||||
# handle case where user has multiple of same GPU
|
||||
if gpu_name in names:
|
||||
names[gpu_name] += 1
|
||||
gpu_name += f" ({names.get(gpu_name)})"
|
||||
else:
|
||||
names[gpu_name] = 1
|
||||
|
||||
meminfo = try_get_info(nvml.nvmlDeviceGetMemoryInfo, handle)
|
||||
util = try_get_info(nvml.nvmlDeviceGetUtilizationRates, handle)
|
||||
enc = try_get_info(nvml.nvmlDeviceGetEncoderUtilization, handle)
|
||||
@@ -433,7 +423,7 @@ def get_nvidia_gpu_stats() -> dict[int, dict]:
|
||||
dec_util = -1
|
||||
|
||||
results[i] = {
|
||||
"name": gpu_name,
|
||||
"name": nvml.nvmlDeviceGetName(handle),
|
||||
"gpu": gpu_util,
|
||||
"mem": gpu_mem_util,
|
||||
"enc": enc_util,
|
||||
|
||||
@@ -208,7 +208,7 @@ class ProcessClip:
|
||||
box[2],
|
||||
box[3],
|
||||
obj["id"],
|
||||
f"{int(obj['score'] * 100)}% {int(obj['area'])}",
|
||||
f"{int(obj['score']*100)}% {int(obj['area'])}",
|
||||
thickness=thickness,
|
||||
color=color,
|
||||
)
|
||||
@@ -227,7 +227,7 @@ class ProcessClip:
|
||||
)
|
||||
|
||||
cv2.imwrite(
|
||||
f"{os.path.join(debug_path, os.path.basename(self.clip_path))}.{int(frame_time * 1000000)}.jpg",
|
||||
f"{os.path.join(debug_path, os.path.basename(self.clip_path))}.{int(frame_time*1000000)}.jpg",
|
||||
current_frame,
|
||||
)
|
||||
|
||||
@@ -290,7 +290,7 @@ def process(path, label, output, debug_path):
|
||||
1 for result in results if result[1]["true_positive_objects"] > 0
|
||||
)
|
||||
print(
|
||||
f"Objects were detected in {positive_count}/{len(results)}({positive_count / len(results) * 100:.2f}%) clip(s)."
|
||||
f"Objects were detected in {positive_count}/{len(results)}({positive_count/len(results)*100:.2f}%) clip(s)."
|
||||
)
|
||||
|
||||
if output:
|
||||
|
||||
Reference in New Issue
Block a user