Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow configuration of the "learning rate" parameter of the background subtractor #158

Merged
merged 5 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ dist/
/tutorial-env/*
.DS_Store
.vs_code/
.vscode/
.venv/
8 changes: 8 additions & 0 deletions docs/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,14 @@ The following options control motion detection. A more comprehensive descriptio
```
</span>

* <b><pre>learning-rate</pre></b>
The value between 0 and 1 that indicates how fast the background model is learnt. Negative parameter value makes the algorithm to use some automatically chosen learning rate. 0 means that the background model is not updated at all, 1 means that the background model is completely reinitialized from the last frame.
<span class="dvr-scan-default">
```
learning-rate = -1
```
</span>

* <b><pre>region-of-interest</pre></b>
Region of interest of the form `(x, y), (w, h)`, where `x, y` is the top left corner, and w, h is the width/height in pixels.
<span class="dvr-scan-example">
Expand Down
1 change: 1 addition & 0 deletions dvr_scan/cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ def from_config(config_value: str, default: 'RGBValue') -> 'RGBValue':
'threshold': 0.15,
'kernel-size': KernelSizeValue(),
'downscale-factor': 0,
'learning-rate': float(-1),
# TODO(v1.7): Remove, replaced with region files.
'region-of-interest': RegionValueDeprecated(),
'load-region': '',
Expand Down
1 change: 1 addition & 0 deletions dvr_scan/cli/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ def run_dvr_scan(settings: ProgramSettings) -> ty.List[ty.Tuple[FrameTimecode, F
threshold=settings.get('threshold'),
kernel_size=settings.get('kernel-size'),
downscale_factor=settings.get('downscale-factor'),
learning_rate=settings.get('learning-rate'),
)

scanner.set_event_params(
Expand Down
9 changes: 7 additions & 2 deletions dvr_scan/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,7 @@ def __init__(self,
self._threshold = 0.15 # -t/--threshold
self._kernel_size = None # -k/--kernel-size
self._downscale_factor = 1 # -df/--downscale-factor
self._learningRate = -1 # learning-rate

# TODO(v1.6): Add ability to configure the rejection filter (_max_score_) by adding a
# threshold + amount option (e.g. ignore up to 2 frames in a row that are over score 100).
Expand Down Expand Up @@ -371,6 +372,7 @@ def set_detection_params(
threshold: float = 0.15,
kernel_size: int = -1,
downscale_factor: int = 1,
learning_rate: float = -1,
):
"""Set detection parameters."""
self._threshold = threshold
Expand All @@ -380,6 +382,7 @@ def set_detection_params(
self._downscale_factor = max(downscale_factor, 1)
assert kernel_size == -1 or kernel_size == 0 or kernel_size >= 3
self._kernel_size = kernel_size
self._learning_rate = learning_rate

def set_regions(self,
region_editor: bool = False,
Expand Down Expand Up @@ -566,16 +569,18 @@ def scan(self) -> Optional[DetectionResult]:

# Create background subtractor and motion detector.
detector = MotionDetector(
subtractor=self._subtractor_type.value(kernel_size=kernel_size),
subtractor=self._subtractor_type.value(
kernel_size=kernel_size, learning_rate=self._learning_rate),
frame_size=self._input.resolution,
downscale=self._downscale_factor,
regions=self._regions)

logger.info(
'Using subtractor %s with kernel_size = %s%s',
'Using subtractor %s with kernel_size = %s%s and learning_rate = %s',
self._subtractor_type.name,
str(kernel_size) if kernel_size else 'off',
' (auto)' if self._kernel_size == -1 else '',
str(self._learning_rate) if self._learning_rate != -1 else 'auto',
)

# Correct event length parameters to account frame skip.
Expand Down
10 changes: 8 additions & 2 deletions dvr_scan/subtractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(
history: int = 500,
variance_threshold: int = 16,
detect_shadows: bool = False,
learning_rate: float = -1,
):
if kernel_size < 0 or (kernel_size > 1 and kernel_size % 2 == 0):
raise ValueError("kernel_size must be >= 0")
Expand All @@ -65,10 +66,11 @@ def __init__(
)
# Default shadow value is 127, set to 0 so they are discarded before filtering.
self._subtractor.setShadowValue(0)
self._learning_rate = learning_rate

def apply(self, frame: numpy.ndarray) -> numpy.ndarray:
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame_mask = self._subtractor.apply(frame_gray)
frame_mask = self._subtractor.apply(frame_gray, learningRate=self._learning_rate)
if not self._kernel is None:
frame_filt = cv2.morphologyEx(frame_mask, cv2.MORPH_OPEN, self._kernel)
else:
Expand All @@ -90,6 +92,7 @@ def __init__(
use_history: bool = True,
max_pixel_stability: int = 15 * 60,
is_parallel: bool = True,
learning_rate: float = -1,
):
if kernel_size < 0 or (kernel_size > 1 and kernel_size % 2 == 0):
raise ValueError("kernel_size must be odd integer >= 1 or zero (0)")
Expand All @@ -101,6 +104,7 @@ def __init__(
maxPixelStability=max_pixel_stability,
isParallel=is_parallel,
)
self._learning_rate = learning_rate

@staticmethod
def is_available():
Expand All @@ -116,6 +120,7 @@ def __init__(
history: int = 500,
variance_threshold: int = 16,
detect_shadows: bool = False,
learning_rate: float = -1,
):
if kernel_size < 0 or (kernel_size > 1 and kernel_size % 2 == 0):
raise ValueError("kernel_size must be odd integer >= 1 or zero (0)")
Expand All @@ -129,13 +134,14 @@ def __init__(
)
# Default shadow value is 127, set to 0 so they are discarded before filtering.
self._subtractor.setShadowValue(0)
self._learning_rate = learning_rate

def apply(self, frame: numpy.ndarray) -> numpy.ndarray:
stream = cv2.cuda_Stream()
frame_rgb_dev = cv2.cuda_GpuMat()
frame_rgb_dev.upload(frame, stream=stream)
frame_gray_dev = cv2.cuda.cvtColor(frame_rgb_dev, cv2.COLOR_BGR2GRAY, stream=stream)
frame_mask_dev = self._subtractor.apply(frame_gray_dev, -1, stream=stream)
frame_mask_dev = self._subtractor.apply(frame_gray_dev, self._learning_rate, stream=stream)
if not self._filter is None:
frame_filt_dev = self._filter.apply(frame_mask_dev, stream=stream)
else:
Expand Down