diff --git a/docs/docs/configuration/index.md b/docs/docs/configuration/index.md index 7cad6fa23..c74098110 100644 --- a/docs/docs/configuration/index.md +++ b/docs/docs/configuration/index.md @@ -194,10 +194,14 @@ motion: # Increasing this value will make motion detection less sensitive and decreasing it will make motion detection more sensitive. # The value should be between 1 and 255. threshold: 25 - # Optional: Minimum size in pixels in the resized motion image that counts as motion (default: ~0.17% of the motion frame area) - # Increasing this value will prevent smaller areas of motion from being detected. Decreasing will make motion detection more sensitive to smaller - # moving objects. - contour_area: 100 + # Optional: Minimum size in pixels in the resized motion image that counts as motion (default: 30) + # Increasing this value will prevent smaller areas of motion from being detected. Decreasing will + # make motion detection more sensitive to smaller moving objects. + # As a rule of thumb: + # - 15 - high sensitivity + # - 30 - medium sensitivity + # - 50 - low sensitivity + contour_area: 30 # Optional: Alpha value passed to cv2.accumulateWeighted when averaging the motion delta across multiple frames (default: shown below) # Higher values mean the current frame impacts the delta a lot, and a single raindrop may register as motion. # Too low and a fast moving person wont be detected as motion. @@ -207,10 +211,10 @@ motion: # Low values will cause things like moving shadows to be detected as motion for longer. # https://www.geeksforgeeks.org/background-subtraction-in-an-image-using-concept-of-running-average/ frame_alpha: 0.2 - # Optional: Height of the resized motion frame (default: 1/6th of the original frame height, but no less than 180) - # This operates as an efficient blur alternative. Higher values will result in more granular motion detection at the expense of higher CPU usage. - # Lower values result in less CPU, but small changes may not register as motion. - frame_height: 180 + # Optional: Height of the resized motion frame (default: 80) + # This operates as an efficient blur alternative. Higher values will result in more granular motion detection at the expense + # of higher CPU usage. Lower values result in less CPU, but small changes may not register as motion. + frame_height: 50 # Optional: motion mask # NOTE: see docs for more detailed info on creating masks mask: 0,900,1080,900,1080,1920,0,1920 diff --git a/frigate/config.py b/frigate/config.py index a5eda7b9c..e911f76cd 100644 --- a/frigate/config.py +++ b/frigate/config.py @@ -103,10 +103,10 @@ class MotionConfig(FrigateBaseModel): ge=1, le=255, ) - contour_area: Optional[int] = Field(title="Contour Area") + contour_area: Optional[int] = Field(default=30, 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") + frame_height: Optional[int] = Field(default=50, title="Frame Height") mask: Union[str, List[str]] = Field( default="", title="Coordinates polygon for the motion mask." ) @@ -119,13 +119,6 @@ class RuntimeMotionConfig(MotionConfig): 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 - - 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.004 - mask = config.get("mask", "") config["raw_mask"] = mask diff --git a/frigate/motion.py b/frigate/motion.py index 84f05b9e1..9d6b9a6ca 100644 --- a/frigate/motion.py +++ b/frigate/motion.py @@ -23,6 +23,7 @@ class MotionDetector: interpolation=cv2.INTER_LINEAR, ) self.mask = np.where(resized_mask == [0]) + self.save_images = False def detect(self, frame): motion_boxes = [] @@ -37,16 +38,13 @@ class MotionDetector: ) # Improve contrast - minval = np.percentile(resized_frame, 5) - maxval = np.percentile(resized_frame, 95) + minval = np.percentile(resized_frame, 4) + maxval = np.percentile(resized_frame, 96) resized_frame = np.clip(resized_frame, minval, maxval) resized_frame = (((resized_frame - minval) / (maxval - minval)) * 255).astype( np.uint8 ) - # convert to grayscale - # resized_frame = cv2.cvtColor(resized_frame, cv2.COLOR_BGR2GRAY) - # mask frame resized_frame[self.mask] = [255] @@ -55,6 +53,8 @@ class MotionDetector: if self.frame_counter < 30: self.frame_counter += 1 else: + if self.save_images: + self.frame_counter += 1 # compare to average frameDelta = cv2.absdiff(resized_frame, cv2.convertScaleAbs(self.avg_frame)) @@ -64,7 +64,6 @@ class MotionDetector: cv2.accumulateWeighted(frameDelta, self.avg_delta, self.config.delta_alpha) # compute the threshold image for the current frame - # TODO: threshold current_thresh = cv2.threshold( frameDelta, self.config.threshold, 255, cv2.THRESH_BINARY )[1] @@ -81,8 +80,10 @@ class MotionDetector: # dilate the thresholded image to fill in holes, then find contours # on thresholded image - thresh = cv2.dilate(thresh, None, iterations=2) - cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + thresh_dilated = cv2.dilate(thresh, None, iterations=2) + cnts = cv2.findContours( + thresh_dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE + ) cnts = imutils.grab_contours(cnts) # loop over the contours @@ -100,6 +101,35 @@ class MotionDetector: ) ) + if self.save_images: + thresh_dilated = cv2.cvtColor(thresh_dilated, cv2.COLOR_GRAY2BGR) + # print("--------") + # print(self.frame_counter) + for c in cnts: + contour_area = cv2.contourArea(c) + # print(contour_area) + if contour_area > self.config.contour_area: + x, y, w, h = cv2.boundingRect(c) + cv2.rectangle( + thresh_dilated, + (x, y), + (x + w, y + h), + (0, 0, 255), + 2, + ) + # print("--------") + image_row_1 = cv2.hconcat( + [ + cv2.cvtColor(frameDelta, cv2.COLOR_GRAY2BGR), + cv2.cvtColor(avg_delta_image, cv2.COLOR_GRAY2BGR), + ] + ) + image_row_2 = cv2.hconcat( + [cv2.cvtColor(thresh, cv2.COLOR_GRAY2BGR), thresh_dilated] + ) + combined_image = cv2.vconcat([image_row_1, image_row_2]) + cv2.imwrite(f"motion/motion-{self.frame_counter}.jpg", combined_image) + if len(motion_boxes) > 0: self.motion_frame_count += 1 if self.motion_frame_count >= 10: