mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-01-22 20:18:30 +03:00
Compare commits
3 Commits
7b038d832e
...
e07ce5deb9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e07ce5deb9 | ||
|
|
0a8f499640 | ||
|
|
ea39bb3565 |
5
.github/copilot-instructions.md
vendored
5
.github/copilot-instructions.md
vendored
@ -1,2 +1,3 @@
|
||||
Never write strings in the frontend directly, always write to and reference the relevant translations file.
|
||||
Always conform new and refactored code to the existing coding style in the project.
|
||||
- For Frigate NVR, never write strings in the frontend directly. Since the project uses `react-i18next`, use `t()` and write the English string in the relevant translations file in `web/public/locales/en`.
|
||||
- Always conform new and refactored code to the existing coding style in the project.
|
||||
- Always have a way to test your work and confirm your changes. When running backend tests, use `python3 -u -m unittest`.
|
||||
|
||||
@ -66,8 +66,6 @@ Some models are labeled as **hybrid** (capable of both thinking and instruct tas
|
||||
**Recommendation:**
|
||||
Always select the `-instruct` or documented instruct/tagged variant of any model you use in your Frigate configuration. If in doubt, refer to your model provider’s documentation or model library for guidance on the correct model variant to use.
|
||||
|
||||
|
||||
|
||||
### Supported Models
|
||||
|
||||
You must use a vision capable model with Frigate. Current model variants can be found [in their model library](https://ollama.com/search?c=vision). Note that Frigate will not automatically download the model you specify in your config, you must download the model to your local instance of Ollama first i.e. by running `ollama pull qwen3-vl:2b-instruct` on your Ollama server/Docker container. Note that the model specified in Frigate's config must match the downloaded model tag.
|
||||
@ -93,7 +91,7 @@ genai:
|
||||
|
||||
## Google Gemini
|
||||
|
||||
Google Gemini has a free tier allowing [15 queries per minute](https://ai.google.dev/pricing) to the API, which is more than sufficient for standard Frigate usage.
|
||||
Google Gemini has a [free tier](https://ai.google.dev/pricing) for the API, however the limits may not be sufficient for standard Frigate usage. Choose a plan appropriate for your installation.
|
||||
|
||||
### Supported Models
|
||||
|
||||
@ -114,7 +112,7 @@ To start using Gemini, you must first get an API key from [Google AI Studio](htt
|
||||
genai:
|
||||
provider: gemini
|
||||
api_key: "{FRIGATE_GEMINI_API_KEY}"
|
||||
model: gemini-2.0-flash
|
||||
model: gemini-2.5-flash
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
@ -68,8 +68,8 @@ Fine-tune the LPR feature using these optional parameters at the global level of
|
||||
- Default: `1000` pixels. Note: this is intentionally set very low as it is an _area_ measurement (length x width). For reference, 1000 pixels represents a ~32x32 pixel square in your camera image.
|
||||
- Depending on the resolution of your camera's `detect` stream, you can increase this value to ignore small or distant plates.
|
||||
- **`device`**: Device to use to run license plate detection _and_ recognition models.
|
||||
- Default: `CPU`
|
||||
- This can be `CPU`, `GPU`, or the GPU's device number. For users without a model that detects license plates natively, using a GPU may increase performance of the YOLOv9 license plate detector model. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation. However, for users who run a model that detects `license_plate` natively, there is little to no performance gain reported with running LPR on GPU compared to the CPU.
|
||||
- Default: `None`
|
||||
- This is auto-selected by Frigate and can be `CPU`, `GPU`, or the GPU's device number. For users without a model that detects license plates natively, using a GPU may increase performance of the YOLOv9 license plate detector model. See the [Hardware Accelerated Enrichments](/configuration/hardware_acceleration_enrichments.md) documentation. However, for users who run a model that detects `license_plate` natively, there is little to no performance gain reported with running LPR on GPU compared to the CPU.
|
||||
- **`model_size`**: The size of the model used to identify regions of text on plates.
|
||||
- Default: `small`
|
||||
- This can be `small` or `large`.
|
||||
@ -432,6 +432,6 @@ If you are using a model that natively detects `license_plate`, add an _object m
|
||||
|
||||
If you are not using a model that natively detects `license_plate` or you are using dedicated LPR camera mode, only a _motion mask_ over your text is required.
|
||||
|
||||
### I see "Error running ... model" in my logs. How can I fix this?
|
||||
### I see "Error running ... model" in my logs, or my inference time is very high. How can I fix this?
|
||||
|
||||
This usually happens when your GPU is unable to compile or use one of the LPR models. Set your `device` to `CPU` and try again. GPU acceleration only provides a slight performance increase, and the models are lightweight enough to run without issue on most CPUs.
|
||||
|
||||
@ -23,7 +23,12 @@ from markupsafe import escape
|
||||
from peewee import SQL, fn, operator
|
||||
from pydantic import ValidationError
|
||||
|
||||
from frigate.api.auth import allow_any_authenticated, allow_public, require_role
|
||||
from frigate.api.auth import (
|
||||
allow_any_authenticated,
|
||||
allow_public,
|
||||
get_allowed_cameras_for_filter,
|
||||
require_role,
|
||||
)
|
||||
from frigate.api.defs.query.app_query_parameters import AppTimelineHourlyQueryParameters
|
||||
from frigate.api.defs.request.app_body import AppConfigSetBody
|
||||
from frigate.api.defs.tags import Tags
|
||||
@ -687,13 +692,19 @@ def plusModels(request: Request, filterByCurrentModelDetector: bool = False):
|
||||
@router.get(
|
||||
"/recognized_license_plates", dependencies=[Depends(allow_any_authenticated())]
|
||||
)
|
||||
def get_recognized_license_plates(split_joined: Optional[int] = None):
|
||||
def get_recognized_license_plates(
|
||||
split_joined: Optional[int] = None,
|
||||
allowed_cameras: List[str] = Depends(get_allowed_cameras_for_filter),
|
||||
):
|
||||
try:
|
||||
query = (
|
||||
Event.select(
|
||||
SQL("json_extract(data, '$.recognized_license_plate') AS plate")
|
||||
)
|
||||
.where(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
|
||||
.where(
|
||||
(SQL("json_extract(data, '$.recognized_license_plate') IS NOT NULL"))
|
||||
& (Event.camera << allowed_cameras)
|
||||
)
|
||||
.distinct()
|
||||
)
|
||||
recognized_license_plates = [row[0] for row in query.tuples()]
|
||||
|
||||
@ -662,6 +662,13 @@ class FrigateConfig(FrigateBaseModel):
|
||||
# generate zone contours
|
||||
if len(camera_config.zones) > 0:
|
||||
for zone in camera_config.zones.values():
|
||||
if zone.filters:
|
||||
for object_name, filter_config in zone.filters.items():
|
||||
zone.filters[object_name] = RuntimeFilterConfig(
|
||||
frame_shape=camera_config.frame_shape,
|
||||
**filter_config.model_dump(exclude_unset=True),
|
||||
)
|
||||
|
||||
zone.generate_contour(camera_config.frame_shape)
|
||||
|
||||
# Set live view stream if none is set
|
||||
|
||||
@ -24,6 +24,14 @@ class GeminiClient(GenAIClient):
|
||||
http_options_dict = {
|
||||
"api_version": "v1",
|
||||
"timeout": int(self.timeout * 1000), # requires milliseconds
|
||||
"retry_options": types.HttpRetryOptions(
|
||||
attempts=3,
|
||||
initial_delay=1.0,
|
||||
max_delay=60.0,
|
||||
exp_base=2.0,
|
||||
jitter=1.0,
|
||||
http_status_codes=[429, 500, 502, 503, 504],
|
||||
),
|
||||
}
|
||||
|
||||
if isinstance(self.genai_config.provider_options, dict):
|
||||
|
||||
@ -632,6 +632,49 @@ class TestConfig(unittest.TestCase):
|
||||
)
|
||||
assert frigate_config.cameras["back"].zones["test"].color != (0, 0, 0)
|
||||
|
||||
def test_zone_filter_area_percent_converts_to_pixels(self):
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
"record": {
|
||||
"alerts": {
|
||||
"retain": {
|
||||
"days": 20,
|
||||
}
|
||||
}
|
||||
},
|
||||
"cameras": {
|
||||
"back": {
|
||||
"ffmpeg": {
|
||||
"inputs": [
|
||||
{"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]}
|
||||
]
|
||||
},
|
||||
"detect": {
|
||||
"height": 1080,
|
||||
"width": 1920,
|
||||
"fps": 5,
|
||||
},
|
||||
"zones": {
|
||||
"notification": {
|
||||
"coordinates": "0.03,1,0.025,0,0.626,0,0.643,1",
|
||||
"objects": ["person"],
|
||||
"filters": {"person": {"min_area": 0.1}},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
frigate_config = FrigateConfig(**config)
|
||||
expected_min_area = int(1080 * 1920 * 0.1)
|
||||
assert (
|
||||
frigate_config.cameras["back"]
|
||||
.zones["notification"]
|
||||
.filters["person"]
|
||||
.min_area
|
||||
== expected_min_area
|
||||
)
|
||||
|
||||
def test_zone_relative_matches_explicit(self):
|
||||
config = {
|
||||
"mqtt": {"host": "mqtt"},
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
"untilForTime": "Until {{time}}",
|
||||
"untilForRestart": "Until Frigate restarts.",
|
||||
"untilRestart": "Until restart",
|
||||
"never": "Never",
|
||||
"ago": "{{timeAgo}} ago",
|
||||
"justNow": "Just now",
|
||||
"today": "Today",
|
||||
|
||||
@ -268,7 +268,7 @@ export default function CreateTriggerDialog({
|
||||
<FormItem className="flex flex-row items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<FormLabel className="text-base">
|
||||
{t("enabled", { ns: "common" })}
|
||||
{t("button.enabled", { ns: "common" })}
|
||||
</FormLabel>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{t("triggers.dialog.form.enabled.description")}
|
||||
@ -394,7 +394,10 @@ export default function CreateTriggerDialog({
|
||||
</FormLabel>
|
||||
<div className="space-y-2">
|
||||
{availableActions.map((action) => (
|
||||
<div key={action} className="flex items-center space-x-2">
|
||||
<label
|
||||
key={action}
|
||||
className="flex cursor-pointer items-center space-x-2"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={form
|
||||
@ -416,10 +419,10 @@ export default function CreateTriggerDialog({
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm font-normal">
|
||||
<span className="text-sm font-normal">
|
||||
{t(`triggers.actions.${action}`)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<FormDescription>
|
||||
|
||||
@ -142,7 +142,10 @@ export default function Step3ThresholdAndActions({
|
||||
<FormLabel>{t("triggers.dialog.form.actions.title")}</FormLabel>
|
||||
<div className="space-y-2">
|
||||
{availableActions.map((action) => (
|
||||
<div key={action} className="flex items-center space-x-2">
|
||||
<label
|
||||
key={action}
|
||||
className="flex cursor-pointer items-center space-x-2"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={form
|
||||
@ -164,10 +167,10 @@ export default function Step3ThresholdAndActions({
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-sm font-normal">
|
||||
<span className="text-sm font-normal">
|
||||
{t(`triggers.actions.${action}`)}
|
||||
</FormLabel>
|
||||
</div>
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<FormDescription>
|
||||
@ -197,9 +200,7 @@ export default function Step3ThresholdAndActions({
|
||||
{isLoading && <ActivityIndicator className="mr-2 size-5" />}
|
||||
{isLoading
|
||||
? t("button.saving", { ns: "common" })
|
||||
: t("triggers.dialog.form.save", {
|
||||
defaultValue: "Save Trigger",
|
||||
})}
|
||||
: t("button.save", { ns: "common" })}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@ -206,7 +206,7 @@ function Exports() {
|
||||
>
|
||||
{Object.values(exports).map((item) => (
|
||||
<ExportCard
|
||||
key={item.name}
|
||||
key={item.id}
|
||||
className={
|
||||
search == "" || filteredExports.includes(item) ? "" : "hidden"
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { Toaster, toast } from "sonner";
|
||||
import { toast } from "sonner";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import useSWR from "swr";
|
||||
import axios from "axios";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@ -598,7 +599,7 @@ export default function TriggerView({
|
||||
date_style: "medium",
|
||||
},
|
||||
)
|
||||
: "Never"}
|
||||
: t("never", { ns: "common" })}
|
||||
</span>
|
||||
{trigger_status?.triggers[trigger.name]
|
||||
?.triggering_event_id && (
|
||||
@ -663,7 +664,9 @@ export default function TriggerView({
|
||||
<TableHeader className="sticky top-0 bg-muted/50">
|
||||
<TableRow>
|
||||
<TableHead className="w-4"></TableHead>
|
||||
<TableHead>{t("name", { ns: "common" })}</TableHead>
|
||||
<TableHead>
|
||||
{t("name", { ns: "triggers.table.name" })}
|
||||
</TableHead>
|
||||
<TableHead>{t("triggers.table.type")}</TableHead>
|
||||
<TableHead>
|
||||
{t("triggers.table.lastTriggered")}
|
||||
@ -759,7 +762,7 @@ export default function TriggerView({
|
||||
date_style: "medium",
|
||||
},
|
||||
)
|
||||
: "Never"}
|
||||
: t("time.never", { ns: "common" })}
|
||||
</span>
|
||||
{trigger_status?.triggers[trigger.name]
|
||||
?.triggering_event_id && (
|
||||
|
||||
Loading…
Reference in New Issue
Block a user