mirror of
https://github.com/blakeblackshear/frigate.git
synced 2024-11-21 19:07:46 +01:00
add object masks and move moton mask
This commit is contained in:
parent
9b3ab486de
commit
4164beff1c
@ -11,6 +11,7 @@ import voluptuous as vol
|
|||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
from frigate.const import RECORD_DIR, CLIPS_DIR, CACHE_DIR
|
||||||
|
from frigate.util import create_mask
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -92,6 +93,7 @@ GLOBAL_FFMPEG_SCHEMA = vol.Schema(
|
|||||||
|
|
||||||
MOTION_SCHEMA = vol.Schema(
|
MOTION_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
|
'mask': vol.Any(str, [str]),
|
||||||
'threshold': vol.Range(min=1, max=255),
|
'threshold': vol.Range(min=1, max=255),
|
||||||
'contour_area': int,
|
'contour_area': int,
|
||||||
'delta_alpha': float,
|
'delta_alpha': float,
|
||||||
@ -127,7 +129,13 @@ def filters_for_all_tracked_objects(object_config):
|
|||||||
OBJECTS_SCHEMA = vol.Schema(vol.All(filters_for_all_tracked_objects,
|
OBJECTS_SCHEMA = vol.Schema(vol.All(filters_for_all_tracked_objects,
|
||||||
{
|
{
|
||||||
vol.Optional('track', default=['person']): [str],
|
vol.Optional('track', default=['person']): [str],
|
||||||
vol.Optional('filters', default = {}): FILTER_SCHEMA.extend({ str: {vol.Optional('min_score', default=0.5): float}})
|
vol.Optional('filters', default = {}): FILTER_SCHEMA.extend(
|
||||||
|
{
|
||||||
|
str: {
|
||||||
|
vol.Optional('min_score', default=0.5): float,
|
||||||
|
'mask': vol.Any(str, [str]),
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
@ -170,7 +178,6 @@ CAMERAS_SCHEMA = vol.Schema(vol.All(
|
|||||||
vol.Required('height'): int,
|
vol.Required('height'): int,
|
||||||
vol.Required('width'): int,
|
vol.Required('width'): int,
|
||||||
'fps': int,
|
'fps': int,
|
||||||
'mask': vol.Any(str, [str]),
|
|
||||||
vol.Optional('best_image_timeout', default=60): int,
|
vol.Optional('best_image_timeout', default=60): int,
|
||||||
vol.Optional('zones', default={}): {
|
vol.Optional('zones', default={}): {
|
||||||
str: {
|
str: {
|
||||||
@ -487,11 +494,13 @@ class RecordConfig():
|
|||||||
}
|
}
|
||||||
|
|
||||||
class FilterConfig():
|
class FilterConfig():
|
||||||
def __init__(self, config):
|
def __init__(self, config, frame_shape=None):
|
||||||
self._min_area = config['min_area']
|
self._min_area = config['min_area']
|
||||||
self._max_area = config['max_area']
|
self._max_area = config['max_area']
|
||||||
self._threshold = config['threshold']
|
self._threshold = config['threshold']
|
||||||
self._min_score = config.get('min_score')
|
self._min_score = config.get('min_score')
|
||||||
|
self._raw_mask = config.get('mask')
|
||||||
|
self._mask = create_mask(frame_shape, self._raw_mask) if frame_shape else None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_area(self):
|
def min_area(self):
|
||||||
@ -509,21 +518,26 @@ class FilterConfig():
|
|||||||
def min_score(self):
|
def min_score(self):
|
||||||
return self._min_score
|
return self._min_score
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mask(self):
|
||||||
|
return self._mask
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
'min_area': self.min_area,
|
'min_area': self.min_area,
|
||||||
'max_area': self.max_area,
|
'max_area': self.max_area,
|
||||||
'threshold': self.threshold,
|
'threshold': self.threshold,
|
||||||
'min_score': self.min_score
|
'min_score': self.min_score,
|
||||||
|
'mask': self._raw_mask
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjectConfig():
|
class ObjectConfig():
|
||||||
def __init__(self, global_config, config):
|
def __init__(self, global_config, config, frame_shape):
|
||||||
self._track = config.get('track', global_config['track'])
|
self._track = config.get('track', global_config['track'])
|
||||||
if 'filters' in config:
|
if 'filters' in config:
|
||||||
self._filters = { name: FilterConfig(c) for name, c in config['filters'].items() }
|
self._filters = { name: FilterConfig(c, frame_shape) for name, c in config['filters'].items() }
|
||||||
else:
|
else:
|
||||||
self._filters = { name: FilterConfig(c) for name, c in global_config['filters'].items() }
|
self._filters = { name: FilterConfig(c, frame_shape) for name, c in global_config['filters'].items() }
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def track(self):
|
def track(self):
|
||||||
@ -670,12 +684,18 @@ class CameraRtmpConfig():
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MotionConfig():
|
class MotionConfig():
|
||||||
def __init__(self, global_config, config, camera_height: int):
|
def __init__(self, global_config, config, frame_shape):
|
||||||
|
self._raw_mask = config.get('mask')
|
||||||
|
self._mask = create_mask(frame_shape, self._raw_mask) if self._raw_mask else None
|
||||||
self._threshold = config.get('threshold', global_config.get('threshold', 25))
|
self._threshold = config.get('threshold', global_config.get('threshold', 25))
|
||||||
self._contour_area = config.get('contour_area', global_config.get('contour_area', 100))
|
self._contour_area = config.get('contour_area', global_config.get('contour_area', 100))
|
||||||
self._delta_alpha = config.get('delta_alpha', global_config.get('delta_alpha', 0.2))
|
self._delta_alpha = config.get('delta_alpha', global_config.get('delta_alpha', 0.2))
|
||||||
self._frame_alpha = config.get('frame_alpha', global_config.get('frame_alpha', 0.2))
|
self._frame_alpha = config.get('frame_alpha', global_config.get('frame_alpha', 0.2))
|
||||||
self._frame_height = config.get('frame_height', global_config.get('frame_height', camera_height//6))
|
self._frame_height = config.get('frame_height', global_config.get('frame_height', frame_shape[0]//6))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mask(self):
|
||||||
|
return self._mask
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def threshold(self):
|
def threshold(self):
|
||||||
@ -699,6 +719,7 @@ class MotionConfig():
|
|||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
return {
|
return {
|
||||||
|
'mask': self._raw_mask,
|
||||||
'threshold': self.threshold,
|
'threshold': self.threshold,
|
||||||
'contour_area': self.contour_area,
|
'contour_area': self.contour_area,
|
||||||
'delta_alpha': self.delta_alpha,
|
'delta_alpha': self.delta_alpha,
|
||||||
@ -776,8 +797,6 @@ class CameraConfig():
|
|||||||
self._frame_shape = (self._height, self._width)
|
self._frame_shape = (self._height, self._width)
|
||||||
self._frame_shape_yuv = (self._frame_shape[0]*3//2, self._frame_shape[1])
|
self._frame_shape_yuv = (self._frame_shape[0]*3//2, self._frame_shape[1])
|
||||||
self._fps = config.get('fps')
|
self._fps = config.get('fps')
|
||||||
self._mask = self._create_mask(config.get('mask'))
|
|
||||||
self._raw_mask = config.get('mask')
|
|
||||||
self._best_image_timeout = config['best_image_timeout']
|
self._best_image_timeout = config['best_image_timeout']
|
||||||
self._zones = { name: ZoneConfig(name, z) for name, z in config['zones'].items() }
|
self._zones = { name: ZoneConfig(name, z) for name, z in config['zones'].items() }
|
||||||
self._clips = CameraClipsConfig(global_config, config['clips'])
|
self._clips = CameraClipsConfig(global_config, config['clips'])
|
||||||
@ -785,8 +804,8 @@ class CameraConfig():
|
|||||||
self._rtmp = CameraRtmpConfig(global_config, config['rtmp'])
|
self._rtmp = CameraRtmpConfig(global_config, config['rtmp'])
|
||||||
self._snapshots = CameraSnapshotsConfig(global_config, config['snapshots'])
|
self._snapshots = CameraSnapshotsConfig(global_config, config['snapshots'])
|
||||||
self._mqtt = CameraMqttConfig(config['mqtt'])
|
self._mqtt = CameraMqttConfig(config['mqtt'])
|
||||||
self._objects = ObjectConfig(global_config['objects'], config.get('objects', {}))
|
self._objects = ObjectConfig(global_config['objects'], config.get('objects', {}), self._frame_shape)
|
||||||
self._motion = MotionConfig(global_config['motion'], config['motion'], self._height)
|
self._motion = MotionConfig(global_config['motion'], config['motion'], self._frame_shape)
|
||||||
self._detect = DetectConfig(global_config['detect'], config['detect'], config.get('fps', 5))
|
self._detect = DetectConfig(global_config['detect'], config['detect'], config.get('fps', 5))
|
||||||
|
|
||||||
self._ffmpeg_cmds = []
|
self._ffmpeg_cmds = []
|
||||||
@ -803,31 +822,6 @@ class CameraConfig():
|
|||||||
|
|
||||||
self._set_zone_colors(self._zones)
|
self._set_zone_colors(self._zones)
|
||||||
|
|
||||||
def _create_mask(self, mask):
|
|
||||||
mask_img = np.zeros(self.frame_shape, np.uint8)
|
|
||||||
mask_img[:] = 255
|
|
||||||
|
|
||||||
if isinstance(mask, list):
|
|
||||||
for m in mask:
|
|
||||||
self._add_mask(m, mask_img)
|
|
||||||
|
|
||||||
elif isinstance(mask, str):
|
|
||||||
self._add_mask(mask, mask_img)
|
|
||||||
|
|
||||||
return mask_img
|
|
||||||
|
|
||||||
def _add_mask(self, mask, mask_img):
|
|
||||||
if mask.startswith('poly,'):
|
|
||||||
points = mask.split(',')[1:]
|
|
||||||
contour = np.array([[int(points[i]), int(points[i+1])] for i in range(0, len(points), 2)])
|
|
||||||
cv2.fillPoly(mask_img, pts=[contour], color=(0))
|
|
||||||
else:
|
|
||||||
mask_file = cv2.imread(f"/config/{mask}", cv2.IMREAD_GRAYSCALE)
|
|
||||||
if mask_file is None or mask_file.size == 0:
|
|
||||||
logger.warning(f"Could not read mask file {mask}")
|
|
||||||
else:
|
|
||||||
mask_img[np.where(mask_file==[0])] = [0]
|
|
||||||
|
|
||||||
def _get_ffmpeg_cmd(self, ffmpeg_input):
|
def _get_ffmpeg_cmd(self, ffmpeg_input):
|
||||||
ffmpeg_output_args = []
|
ffmpeg_output_args = []
|
||||||
if 'detect' in ffmpeg_input.roles:
|
if 'detect' in ffmpeg_input.roles:
|
||||||
@ -891,10 +885,6 @@ class CameraConfig():
|
|||||||
def fps(self):
|
def fps(self):
|
||||||
return self._fps
|
return self._fps
|
||||||
|
|
||||||
@property
|
|
||||||
def mask(self):
|
|
||||||
return self._mask
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def best_image_timeout(self):
|
def best_image_timeout(self):
|
||||||
return self._best_image_timeout
|
return self._best_image_timeout
|
||||||
@ -963,7 +953,6 @@ class CameraConfig():
|
|||||||
'objects': self.objects.to_dict(),
|
'objects': self.objects.to_dict(),
|
||||||
'motion': self.motion.to_dict(),
|
'motion': self.motion.to_dict(),
|
||||||
'detect': self.detect.to_dict(),
|
'detect': self.detect.to_dict(),
|
||||||
'mask': self._raw_mask,
|
|
||||||
'frame_shape': self.frame_shape,
|
'frame_shape': self.frame_shape,
|
||||||
'ffmpeg_cmds': [{'roles': c['roles'], 'cmd': ' '.join(c['cmd'])} for c in self.ffmpeg_cmds],
|
'ffmpeg_cmds': [{'roles': c['roles'], 'cmd': ' '.join(c['cmd'])} for c in self.ffmpeg_cmds],
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import collections
|
|||||||
import datetime
|
import datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import signal
|
import signal
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
import threading
|
import threading
|
||||||
@ -15,6 +16,8 @@ import cv2
|
|||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def draw_box_with_label(frame, x_min, y_min, x_max, y_max, label, info, thickness=2, color=None, position='ul'):
|
def draw_box_with_label(frame, x_min, y_min, x_max, y_max, label, info, thickness=2, color=None, position='ul'):
|
||||||
if color is None:
|
if color is None:
|
||||||
@ -288,6 +291,31 @@ def print_stack(sig, frame):
|
|||||||
def listen():
|
def listen():
|
||||||
signal.signal(signal.SIGUSR1, print_stack)
|
signal.signal(signal.SIGUSR1, print_stack)
|
||||||
|
|
||||||
|
def create_mask(frame_shape, mask):
|
||||||
|
mask_img = np.zeros(frame_shape, np.uint8)
|
||||||
|
mask_img[:] = 255
|
||||||
|
|
||||||
|
if isinstance(mask, list):
|
||||||
|
for m in mask:
|
||||||
|
add_mask(m, mask_img)
|
||||||
|
|
||||||
|
elif isinstance(mask, str):
|
||||||
|
add_mask(mask, mask_img)
|
||||||
|
|
||||||
|
return mask_img
|
||||||
|
|
||||||
|
def add_mask(mask, mask_img):
|
||||||
|
if mask.startswith('poly,'):
|
||||||
|
points = mask.split(',')[1:]
|
||||||
|
contour = np.array([[int(points[i]), int(points[i+1])] for i in range(0, len(points), 2)])
|
||||||
|
cv2.fillPoly(mask_img, pts=[contour], color=(0))
|
||||||
|
else:
|
||||||
|
mask_file = cv2.imread(f"/config/{mask}", cv2.IMREAD_GRAYSCALE)
|
||||||
|
if mask_file is None or mask_file.size == 0:
|
||||||
|
logger.warning(f"Could not read mask file {mask}")
|
||||||
|
else:
|
||||||
|
mask_img[np.where(mask_file==[0])] = [0]
|
||||||
|
|
||||||
class FrameManager(ABC):
|
class FrameManager(ABC):
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def create(self, name, size) -> AnyStr:
|
def create(self, name, size) -> AnyStr:
|
||||||
|
Loading…
Reference in New Issue
Block a user