From f3a1c1de0a696070f373836458fb94e68a669e1d Mon Sep 17 00:00:00 2001 From: Blake Blackshear Date: Sat, 14 Aug 2021 14:18:35 -0500 Subject: [PATCH] move width/height/fps under detect and make required also resizes the output from ffmpeg to specified size --- docs/docs/configuration/cameras.md | 37 ++-- docs/docs/configuration/index.md | 14 +- frigate/config.py | 37 ++-- frigate/test/test_config.py | 170 +++++++++++++----- web/src/components/CameraImage.jsx | 3 +- .../components/__tests__/CameraImage.test.jsx | 2 +- web/src/routes/CameraMap.jsx | 7 +- 7 files changed, 175 insertions(+), 95 deletions(-) diff --git a/docs/docs/configuration/cameras.md b/docs/docs/configuration/cameras.md index 2f5a4df51..35bd3e77a 100644 --- a/docs/docs/configuration/cameras.md +++ b/docs/docs/configuration/cameras.md @@ -32,9 +32,10 @@ cameras: roles: - clips - record - width: 1280 - height: 720 - fps: 5 + detect: + width: 1280 + height: 720 + fps: 5 ``` ## Masks & Zones @@ -280,14 +281,20 @@ cameras: # Optional: camera specific output args (default: inherit) output_args: - # Required: width of the frame for the input with the detect role - width: 1280 - # Required: height of the frame for the input with the detect role - height: 720 - # Optional: desired fps for your camera for the input with the detect role - # NOTE: Recommended value of 5. Ideally, try and reduce your FPS on the camera. - # Frigate will attempt to autodetect if not specified. - fps: 5 + # Required: Camera level detect settings + detect: + # Required: width of the frame for the input with the detect role + width: 1280 + # Required: height of the frame for the input with the detect role + height: 720 + # Required: desired fps for your camera for the input with the detect role + # NOTE: Recommended value of 5. Ideally, try and reduce your FPS on the camera. + fps: 5 + # Optional: enables detection for the camera (default: True) + # This value can be set via MQTT and will be updated in startup based on retained value + enabled: True + # Optional: Number of frames without a detection before frigate considers an object to be gone. (default: 5x the frame rate) + max_disappeared: 25 # Optional: camera level motion config motion: @@ -319,14 +326,6 @@ cameras: max_area: 100000 threshold: 0.7 - # Optional: Camera level detect settings - detect: - # Optional: enables detection for the camera (default: True) - # This value can be set via MQTT and will be updated in startup based on retained value - enabled: True - # Optional: Number of frames without a detection before frigate considers an object to be gone. (default: 5x the frame rate) - max_disappeared: 25 - # Optional: save clips configuration clips: # Required: enables clips for the camera (default: shown below) diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index 016d633eb..d923ff1c3 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -20,9 +20,10 @@ cameras: roles: - detect - rtmp - width: 1280 - height: 720 - fps: 5 + detect: + width: 1280 + height: 720 + fps: 5 ``` ## Required @@ -76,9 +77,10 @@ cameras: roles: - detect - rtmp - width: 1280 - height: 720 - fps: 5 + detect: + width: 1280 + height: 720 + fps: 5 ``` ## Optional diff --git a/frigate/config.py b/frigate/config.py index 275208bfe..e466abc22 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -151,6 +151,9 @@ class RuntimeMotionConfig(MotionConfig): class DetectConfig(BaseModel): + height: int = Field(title="Height of the stream for the detect role.") + width: int = Field(title="Width of the stream for the detect role.") + fps: int = Field(title="Number of frames per second to process through detection.") enabled: bool = Field(default=True, title="Detection Enabled.") max_disappeared: Optional[int] = Field( title="Maximum number of frames the object can dissapear before detection ends." @@ -435,11 +438,6 @@ class CameraLiveConfig(BaseModel): class CameraConfig(BaseModel): name: Optional[str] = Field(title="Camera name.") ffmpeg: CameraFfmpegConfig = Field(title="FFmpeg configuration for the camera.") - height: int = Field(title="Height of the stream for the detect role.") - width: int = Field(title="Width of the stream for the detect role.") - fps: Optional[int] = Field( - title="Number of frames per second to process through Frigate." - ) best_image_timeout: int = Field( default=60, title="How long to wait for the image with the highest confidence score.", @@ -465,7 +463,7 @@ class CameraConfig(BaseModel): default_factory=ObjectConfig, title="Object configuration." ) motion: Optional[MotionConfig] = Field(title="Motion detection configuration.") - detect: Optional[DetectConfig] = Field(title="Object detection configuration.") + detect: DetectConfig = Field(title="Object detection configuration.") timestamp_style: TimestampStyleConfig = Field( default_factory=TimestampStyleConfig, title="Timestamp style configuration." ) @@ -483,11 +481,11 @@ class CameraConfig(BaseModel): @property def frame_shape(self) -> Tuple[int, int]: - return self.height, self.width + return self.detect.height, self.detect.width @property def frame_shape_yuv(self) -> Tuple[int, int]: - return self.height * 3 // 2, self.width + return self.detect.height * 3 // 2, self.detect.width @property def ffmpeg_cmds(self) -> List[Dict[str, List[str]]]: @@ -508,9 +506,17 @@ class CameraConfig(BaseModel): if isinstance(self.ffmpeg.output_args.detect, list) else self.ffmpeg.output_args.detect.split(" ") ) - ffmpeg_output_args = detect_args + ffmpeg_output_args + ["pipe:"] - if self.fps: - ffmpeg_output_args = ["-r", str(self.fps)] + ffmpeg_output_args + ffmpeg_output_args = ( + [ + "-r", + str(self.detect.fps), + "-s", + f"{self.detect.width}x{self.detect.height}", + ] + + detect_args + + ffmpeg_output_args + + ["pipe:"] + ) if "rtmp" in ffmpeg_input.roles and self.rtmp.enabled: rtmp_args = ( self.ffmpeg.output_args.rtmp @@ -735,12 +741,9 @@ class FrigateConfig(BaseModel): ) # Default detect configuration - max_disappeared = (camera_config.fps or 5) * 5 - if camera_config.detect: - if camera_config.detect.max_disappeared is None: - camera_config.detect.max_disappeared = max_disappeared - else: - camera_config.detect = DetectConfig(max_disappeared=max_disappeared) + max_disappeared = camera_config.detect.fps * 5 + if camera_config.detect.max_disappeared is None: + camera_config.detect.max_disappeared = max_disappeared # Default live configuration if camera_config.live is None: diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py index c3c77a866..9db5d3401 100644 --- a/frigate/test/test_config.py +++ b/frigate/test/test_config.py @@ -18,8 +18,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -42,8 +45,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -60,8 +66,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -82,8 +91,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "objects": {"track": ["cat"]}, } }, @@ -105,8 +117,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -130,8 +145,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -152,8 +170,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "objects": { "track": ["person", "dog"], "filters": {"dog": {"threshold": 0.7}}, @@ -179,8 +200,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "objects": { "mask": "0,0,1,1,0,1", "filters": {"dog": {"mask": "1,1,1,1,1,1"}}, @@ -210,8 +234,11 @@ class TestConfig(unittest.TestCase): }, ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -233,8 +260,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "objects": { "track": ["person", "dog"], "filters": {"dog": {"threshold": 0.7}}, @@ -260,8 +290,11 @@ class TestConfig(unittest.TestCase): ], "input_args": ["-re"], }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "objects": { "track": ["person", "dog"], "filters": {"dog": {"threshold": 0.7}}, @@ -292,8 +325,11 @@ class TestConfig(unittest.TestCase): ], "input_args": "test3", }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "objects": { "track": ["person", "dog"], "filters": {"dog": {"threshold": 0.7}}, @@ -321,8 +357,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -344,8 +383,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video2", "roles": ["detect"]}, ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -362,8 +404,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "zones": {"back": {"coordinates": "1,1,1,1,1,1"}}, } }, @@ -381,8 +426,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "zones": {"test": {"coordinates": "1,1,1,1,1,1"}}, } }, @@ -408,8 +456,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/video", "roles": ["detect"]} ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, "clips": {"enabled": True}, } }, @@ -436,8 +487,11 @@ class TestConfig(unittest.TestCase): {"path": "rtsp://10.0.0.1:554/record", "roles": ["record"]}, ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -463,9 +517,12 @@ class TestConfig(unittest.TestCase): }, ] }, - "height": 1080, - "width": 1920, - "detect": {"enabled": True}, + "detect": { + "enabled": True, + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -490,8 +547,11 @@ class TestConfig(unittest.TestCase): }, ] }, - "height": 480, - "width": 640, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -516,8 +576,11 @@ class TestConfig(unittest.TestCase): }, ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -543,8 +606,11 @@ class TestConfig(unittest.TestCase): }, ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -569,8 +635,11 @@ class TestConfig(unittest.TestCase): }, ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } @@ -596,8 +665,11 @@ class TestConfig(unittest.TestCase): }, ] }, - "height": 1080, - "width": 1920, + "detect": { + "height": 1080, + "width": 1920, + "fps": 5, + }, } }, } diff --git a/web/src/components/CameraImage.jsx b/web/src/components/CameraImage.jsx index 52f537834..a0896e2c8 100644 --- a/web/src/components/CameraImage.jsx +++ b/web/src/components/CameraImage.jsx @@ -12,7 +12,8 @@ export default function CameraImage({ camera, onload, searchParams = '', stretch const canvasRef = useRef(null); const [{ width: availableWidth }] = useResizeObserver(containerRef); - const { name, width, height } = config.cameras[camera]; + const { name } = config.cameras[camera]; + const { width, height } = config.cameras[camera].detect; const aspectRatio = width / height; const scaledHeight = useMemo(() => { diff --git a/web/src/components/__tests__/CameraImage.test.jsx b/web/src/components/__tests__/CameraImage.test.jsx index 7cdcd7ef3..2546ca4db 100644 --- a/web/src/components/__tests__/CameraImage.test.jsx +++ b/web/src/components/__tests__/CameraImage.test.jsx @@ -7,7 +7,7 @@ import { render, screen } from '@testing-library/preact'; describe('CameraImage', () => { beforeEach(() => { jest.spyOn(Api, 'useConfig').mockImplementation(() => { - return { data: { cameras: { front: { name: 'front', width: 1280, height: 720 } } } }; + return { data: { cameras: { front: { name: 'front', detect: { width: 1280, height: 720 } } } } }; }); jest.spyOn(Api, 'useApiHost').mockReturnValue('http://base-url.local:5000'); jest.spyOn(Hooks, 'useResizeObserver').mockImplementation(() => [{ width: 0 }]); diff --git a/web/src/routes/CameraMap.jsx b/web/src/routes/CameraMap.jsx index 1e3d32760..d6837ba8f 100644 --- a/web/src/routes/CameraMap.jsx +++ b/web/src/routes/CameraMap.jsx @@ -15,13 +15,16 @@ export default function CameraMasks({ camera, url }) { const cameraConfig = config.cameras[camera]; const { - width, - height, motion: { mask: motionMask }, objects: { filters: objectFilters }, zones, } = cameraConfig; + const { + width, + height, + } = cameraConfig.detect; + const [{ width: scaledWidth }] = useResizeObserver(imageRef); const imageScale = scaledWidth / width;