Noise floor¶
A motion gate measured from the recording instead of guessed at.
Every motion measure has a floor. Frame differencing sees sensor noise wherever the picture
is bright enough; motion vectors carry the encoder's rate decisions, 46 per cent of them
exactly zero and the median non-zero one 0.79 px of quarter-pel noise; optical flow sits at
0.009 px per pixel per frame across most of the picture. A threshold is what keeps that
out of a result—and a threshold in absolute units cannot serve two recordings whose
floors differ.
Measured on a corpus of six dance recordings, one fixed setting lit 0.52 times the area the dancers covered in one recording and 1.90 times it in another. Too tight and too loose at the same number.
So the floor is taken from the material. The room plate says which pixels have nobody in front of them; whatever their frame-to-frame difference shows, nothing there moved. The gate is a quantile of that distribution, which makes the parameter a false-positive rate rather than a magnitude.
import musicalgestures as mg
floor = mg.frame_difference_floor("session.mp4")
if floor["refused"]:
print(floor["reason"])
else:
video = mg.MgVideo("session.mp4")
video.motion(threshold=floor["threshold"] / 255) # the gate is in grey levels
It can refuse, and that is the point¶
Otsu will split pure noise and report a threshold with no sign of distress: on one recording it proposed an 82-minute "section" from a microphone that had never heard a conversation, and the answer looked no different from a real one. An estimator that always answers has the same fault.
This one declines when there are too few samples to estimate from, and when the gate it
would propose keeps almost none of the moving part—which is what a camera move, a
lighting change or an empty recording looks like from the inside. A refusal carries
threshold: None, so there is nothing to reach for by accident.
What it buys, and what it does not¶
Equalising the false-positive rate makes spatial maps comparable across recordings. It does not make magnitudes comparable: each recording ends at its own operating point, so a quantity of motion gated this way is harder to defend across sessions than one gated at a fixed number, not easier.
Both are therefore kept, and the choice belongs to the analysis:
| you are comparing | use |
|---|---|
| magnitudes across recordings | a fixed threshold, the same in every one |
| pictures, maps, or where motion happened | a measured floor, per recording |
Note that H.264 codes to quarter-pel, so a motion-vector gate at or below 0.25 px cannot remove anything: 0.25 is the smallest non-zero displacement the format can express.
A motion gate measured from the recording instead of guessed at.
Every motion measure has a floor. Frame differencing sees sensor noise everywhere the picture is bright enough; motion vectors carry the encoder's rate decisions, 46 per cent of them exactly zero and the median non-zero one 0.79 px of quarter-pel noise; optical flow sits at 0.009 px per pixel per frame across most of the picture. A threshold is what keeps that out of a result, and a threshold in absolute units cannot serve two recordings whose floors differ.
Measured on a dance corpus of six recordings, one fixed setting lit 0.52 times the area the dancers covered in one recording and 1.90 times it in another --- too tight and too loose at the same number.
So the floor is taken from the material: the distribution of motion magnitudes where there is nothing to move, which the room plate can point at directly. The gate is a quantile of that, which makes the parameter a false-positive rate rather than a magnitude.
What this does and does not buy. Equalising the false-positive rate makes spatial maps comparable across recordings. It does not make magnitudes comparable: each recording ends at its own operating point, so a quantity of motion gated this way is harder to compare across sessions than one gated at a fixed number, not easier. Both are therefore kept. Use a fixed threshold when magnitudes must be compared, and a measured one when pictures must be.
And it can refuse. Otsu will split pure noise and report a threshold with no sign of distress. An estimator that always answers has the same fault, so when the background and the moving parts do not separate --- a camera move, a light change, an empty recording --- this returns no number at all rather than a plausible one.
BoundedSample ¶
BoundedSample(cap=2000000, seed=0)
A uniform sample of a stream, bounded in memory whatever the stream's length.
The floor of a long recording implies hundreds of millions of magnitudes, and
keeping them all once cost 8 GB and took a measurement service with it. This
keeps at most about twice cap values: every arriving batch is kept with the
current probability, and when the store exceeds twice the cap it is uniformly
halved and the probability halves with it. Every value ever offered thus has the
same chance of being in the final sample, so a quantile of the sample estimates
the stream's --- which is all noise_floor asks of it.
Source code in musicalgestures/_noisefloor.py
51 52 53 54 55 56 | |
noise_floor ¶
noise_floor(background, foreground=None, quantile=0.99, min_samples=1000, min_foreground_kept=0.1)
The gate implied by a sample of magnitudes taken where nothing moves.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
background
|
Motion magnitudes from places nothing should be moving --- pixel differences where the plate says nobody is, or vector lengths in unoccupied cells. One dimension; shape is not otherwise used. |
required | |
foreground
|
optional
|
Magnitudes from places something does move. Only used to report, and refuse on, what the gate would cost. Without it the gate is returned unchecked. |
None
|
quantile
|
float
|
Where in the background's tail the gate sits. Defaults to 0.99, meaning one background sample in a hundred survives the gate. |
0.99
|
min_samples
|
int
|
Below this many background samples the estimate is refused. Defaults to 1000. |
1000
|
min_foreground_kept
|
float
|
If the gate would keep less than this fraction of
|
0.1
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
|
dict
|
|
|
dict
|
foreground sample). |
Source code in musicalgestures/_noisefloor.py
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
frame_difference_floor ¶
frame_difference_floor(video, plate=None, quantile=0.99, n_samples=200, width=320, tolerance=12.0, **kwargs)
The frame-differencing gate this recording implies, in grey levels.
The room plate says which pixels have nobody in front of them. Their frame-to-frame differences are the floor by construction --- whatever they show, nothing there moved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
video
|
Path to the video. |
required | |
plate
|
optional
|
The room, from |
None
|
quantile
|
float
|
Where in the background's tail the gate sits. |
0.99
|
n_samples
|
int
|
Frame pairs to sample. |
200
|
width
|
int
|
Working width, which the plate must match. |
320
|
tolerance
|
float
|
Difference from the plate counting as occupied. |
12.0
|
**kwargs
|
Passed to |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
As |
dict
|
by 255 for the |
Source code in musicalgestures/_noisefloor.py
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
motion_vector_floor ¶
motion_vector_floor(video, plate=None, quantile=0.99, width=320, tolerance=12.0, deterministic=False, **kwargs)
The motion-vector gate this encode implies, in pixels of displacement.
The same principle in the units a displacement has. Vectors landing in cells with nobody in front of them are the encoder spending bits on rate rather than on motion.
Note that H.264 codes to quarter-pel, so a gate at or below 0.25 px cannot remove anything: 0.25 is the smallest non-zero displacement the format can express.
Two decodes, walked in step. Occupancy has to be read frame by frame, and the vector reader skips the IDCT --- which is what makes it fast and its picture unusable. Averaging occupancy over the recording instead was tried and is wrong for the same reason a swept map is not an instantaneous one: it marks the dancer's whole path occupied at every moment, so most of the "foreground" is cells the dancer is not in, and the estimate refuses footage it should accept. Neither stream is held in memory beyond the frame in hand.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
video
|
Path to the video. |
required | |
plate
|
optional
|
The room, from |
None
|
quantile
|
float
|
Where in the background's tail the gate sits. |
0.99
|
width
|
int
|
Working width for the plate. |
320
|
tolerance
|
float
|
Difference from the plate counting as occupied. |
12.0
|
deterministic
|
bool
|
Decode single-threaded, so the answer repeats exactly. |
False
|
**kwargs
|
Passed to |
{}
|
Returns:
| Name | Type | Description |
|---|---|---|
dict |
dict
|
As |
Source code in musicalgestures/_noisefloor.py
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 | |