Skip to content

Filter

filter_frame

filter_frame(motion_frame, filtertype, threshold, kernel_size)

Applies a threshold filter and then a median filter (of kernel_sizexkernel_size) to an image or videoframe.

Parameters:

Name Type Description Default
motion_frame array(uint8)

Input motion image.

required
filtertype str

'Regular' turns all values below threshold to 0. 'Binary' turns all values below threshold to 0, above threshold to 1. 'Blob' removes individual pixels with erosion method.

required
threshold float

A number in the range of 0 to 1. Eliminates pixel values less than given threshold.

required
kernel_size int

Size of structuring element.

required

Returns:

Type Description

np.array(uint8): The filtered frame.

Source code in musicalgestures/_filter.py
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def filter_frame(motion_frame, filtertype, threshold, kernel_size):
    """
    Applies a threshold filter and then a median filter (of `kernel_size`x`kernel_size`) to an image or videoframe.

    Args:
        motion_frame (np.array(uint8)): Input motion image.
        filtertype (str): 'Regular' turns all values below `threshold` to 0. 'Binary' turns all values below `threshold` to 0, above `threshold` to 1. 'Blob' removes individual pixels with erosion method.
        threshold (float): A number in the range of 0 to 1. Eliminates pixel values less than given threshold.
        kernel_size (int): Size of structuring element.

    Returns:
        np.array(uint8): The filtered frame.
    """

    from scipy.signal import medfilt2d   # lazy import: keeps scipy.signal out of startup
    if filtertype.lower() == 'regular':
        motion_frame = (motion_frame > threshold*255)*motion_frame
        motion_frame = medfilt2d(motion_frame, kernel_size)
    elif filtertype.lower() == 'binary':
        motion_frame = (motion_frame > threshold*255)*255
        motion_frame = medfilt2d(motion_frame.astype(np.uint8), kernel_size)
    elif filtertype.lower() == 'blob':
        motion_frame = cv2.erode(motion_frame, np.ones([kernel_size, kernel_size]), iterations=1)
    return motion_frame

filter_frame_ffmpeg

filter_frame_ffmpeg(filename, cmd, color, blur, filtertype, threshold, kernel_size, use_median, invert=False)

Builds an FFmpeg filter-complex string for frame differencing, thresholding and optional median filtering.

Parameters:

Name Type Description Default
filename str

Path to the input video file (used to derive frame dimensions).

required
cmd list

Base FFmpeg command list to which extra inputs are appended in-place.

required
color bool

If True, use gbrp pixel format; otherwise gray.

required
blur str

'Average' applies a 10×10 box blur before differencing; 'None' skips it.

required
filtertype str

'Regular' thresholds frame differences; 'Binary' binarises them; 'Blob' erodes.

required
threshold float

Pixel-value threshold in the range 0–1.

required
kernel_size int

Radius for the median or erosion filter.

required
use_median bool

If True, apply a median filter after thresholding.

required
invert bool

If True, negate the output. Defaults to False.

False

Returns:

Type Description

tuple[list, str]: Updated cmd list and the assembled filter-complex string.

Source code in musicalgestures/_filter.py
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
def filter_frame_ffmpeg(filename, cmd, color, blur, filtertype, threshold, kernel_size, use_median, invert=False):
    """
    Builds an FFmpeg filter-complex string for frame differencing, thresholding and optional median filtering.

    Args:
        filename (str): Path to the input video file (used to derive frame dimensions).
        cmd (list): Base FFmpeg command list to which extra inputs are appended in-place.
        color (bool): If True, use gbrp pixel format; otherwise gray.
        blur (str): 'Average' applies a 10×10 box blur before differencing; 'None' skips it.
        filtertype (str): 'Regular' thresholds frame differences; 'Binary' binarises them; 'Blob' erodes.
        threshold (float): Pixel-value threshold in the range 0–1.
        kernel_size (int): Radius for the median or erosion filter.
        use_median (bool): If True, apply a median filter after thresholding.
        invert (bool, optional): If True, negate the output. Defaults to False.

    Returns:
        tuple[list, str]: Updated `cmd` list and the assembled filter-complex string.
    """
    cmd_filter = ''

    # set color mode
    if color == True:
        pixformat = 'gbrp'
    else:
        pixformat = 'gray'
    cmd_filter += f'format={pixformat},'

    # set blur
    if blur.lower() == 'average':
        cmd_filter += 'avgblur=sizeX=10:sizeY=10,'

    # set frame difference
    if filtertype.lower() == 'regular':
        cmd_filter += 'tblend=all_mode=difference[diff],'
    else:
        cmd_filter += 'tblend=all_mode=difference,'

    width, height = get_widthheight(filename)

    threshold_color = matplotlib.colors.to_hex([threshold, threshold, threshold])
    threshold_color = '0x' + threshold_color[1:]

    # set threshold
    if filtertype.lower() == 'regular':
        cmd += ['-f', 'lavfi', '-i', f'color={threshold_color},scale={width}:{height}',
                '-f', 'lavfi', '-i', f'color=black,scale={width}:{height}']
        cmd_filter += '[0:v][1][2][diff]threshold,'
    elif filtertype.lower() == 'binary':
        cmd += ['-f', 'lavfi', '-i', f'color={threshold_color},scale={width}:{height}', '-f', 'lavfi', '-i',
                f'color=black,scale={width}:{height}', '-f', 'lavfi', '-i', f'color=white,scale={width}:{height}']
        cmd_filter += 'threshold,'
    elif filtertype.lower() == 'blob':
        # cmd_filter += 'erosion,' # erosion is always 3x3 so we will hack it with a median filter with percentile=0 which will pick minimum values
        cmd_filter += f'median=radius={kernel_size}:percentile=0,'

    # set median
    if use_median and filtertype.lower() != 'blob':  # makes no sense to median-filter the eroded video
        cmd_filter += f'median=radius={kernel_size},'

    if invert:
        cmd_filter += 'negate,'

    return cmd, cmd_filter