Repository: blakeblackshear/frigate

Issue:
Motion detection defaults don't work well for low resolution substreams
**Describe the bug**
A clear and concise description of what your issue is.
The default motion detection configuration appears tuned for a 1080p stream at 5fps. However, my ReoLink RLC-410 provides a 640x480 substream at 7fps. So with 1080p, you'd get the following default configuration:

```json
"fps": 5,
"frame_shape": [
  1080,
  1920
],
"height": 1080,
"motion": {
  "contour_area": 100,
  "delta_alpha": 0.2,
  "frame_alpha": 0.2,
  "frame_height": 180,
  "threshold": 25
},
```

Very reasonable. The contour size tracks a reasonable, small portion of the resized image - the resized frame height is still large enough to not lose thin things like legs. However, configure that substream:

```json
"fps": 7,
"frame_shape": [
  480,
  640
],
"height": 480,
"motion": {
  "contour_area": 100,
  "delta_alpha": 0.2,
  "frame_alpha": 0.2,
  "frame_height": 80,
  "threshold": 25
},
```

We now have a tiny, 80 pixels high image to do motion detection on. But the `contour_area` has not scaled - so we need 6.8 times the area to trigger motion compared to the 1080 stream. Similarly, the `frame_alpha` and `delta_alpha` have not changed - so to the motion detector, it looks like the objects are moving a lot slower.

My proposal:
1. Change `delta_alpha` and `frame_alpha` from constant defaults to 1/fps
2. Change `contour_area` from a constant default to `scaled width * scaled height * 0.00173611111` (or some more reasonable constant value, possibly taking into account masked area)
3. Change `frame_height` from default `height/6` to `max(height/6, 180)`

There are risks; changing the alpha rates might make motion detection worse if the default values were working well already despite being very different from the framerate, or if `contour_area` was configured explicitly to match the automatic scaled value of `frame_height`. And the increased frame height might increase CPU for people with small substreams like I have. But overall, I think this would result in the configuration that seems to have been originally intended, and it certainly works better on my cameras.

**Version of frigate**
0.8.4-5043040




Discussion:
I'm not so sure about adjusting the alpha rates, but the contour area and frame height make sense. 

Resolving patch:
diff --git a/frigate/config.py b/frigate/config.py
--- a/frigate/config.py
+++ b/frigate/config.py
@@ -82,7 +82,7 @@ class MotionConfig(BaseModel):
         ge=1,
         le=255,
     )
-    contour_area: int = Field(default=100, title="Contour Area")
+    contour_area: Optional[int] = Field(title="Contour Area")
     delta_alpha: float = Field(default=0.2, title="Delta Alpha")
     frame_alpha: float = Field(default=0.2, title="Frame Alpha")
     frame_height: Optional[int] = Field(title="Frame Height")
@@ -99,7 +99,11 @@ def __init__(self, **config):
         frame_shape = config.get("frame_shape", (1, 1))
 
         if "frame_height" not in config:
-            config["frame_height"] = frame_shape[0] // 6
+            config["frame_height"] = max(frame_shape[0] // 6, 120)
+
+        if "contour_area" not in config:
+            frame_width = frame_shape[1] * config["frame_height"] / frame_shape[0]
+            config["contour_area"] = config["frame_height"] * frame_width * 0.003912363
 
         mask = config.get("mask", "")
         config["raw_mask"] = mask


Test patch:
diff --git a/frigate/test/test_config.py b/frigate/test/test_config.py
--- a/frigate/test/test_config.py
+++ b/frigate/test/test_config.py
@@ -449,9 +449,60 @@ def test_max_disappeared_default(self):
         assert config == frigate_config.dict(exclude_unset=True)
 
         runtime_config = frigate_config.runtime_config
-        ffmpeg_cmds = runtime_config.cameras["back"].ffmpeg_cmds
         assert runtime_config.cameras["back"].detect.max_disappeared == 5 * 5
 
+    def test_motion_frame_height_wont_go_below_120(self):
+
+        config = {
+            "mqtt": {"host": "mqtt"},
+            "cameras": {
+                "back": {
+                    "ffmpeg": {
+                        "inputs": [
+                            {
+                                "path": "rtsp://10.0.0.1:554/video",
+                                "roles": ["detect"],
+                            },
+                        ]
+                    },
+                    "height": 480,
+                    "width": 640,
+                }
+            },
+        }
+
+        frigate_config = FrigateConfig(**config)
+        assert config == frigate_config.dict(exclude_unset=True)
+
+        runtime_config = frigate_config.runtime_config
+        assert runtime_config.cameras["back"].motion.frame_height >= 120
+
+    def test_motion_contour_area_dynamic(self):
+
+        config = {
+            "mqtt": {"host": "mqtt"},
+            "cameras": {
+                "back": {
+                    "ffmpeg": {
+                        "inputs": [
+                            {
+                                "path": "rtsp://10.0.0.1:554/video",
+                                "roles": ["detect"],
+                            },
+                        ]
+                    },
+                    "height": 1080,
+                    "width": 1920,
+                }
+            },
+        }
+
+        frigate_config = FrigateConfig(**config)
+        assert config == frigate_config.dict(exclude_unset=True)
+
+        runtime_config = frigate_config.runtime_config
+        assert round(runtime_config.cameras["back"].motion.contour_area) == 225
+
 
 if __name__ == "__main__":
     unittest.main(verbosity=2)