Skip to content

Commit 323aee3

Browse files
committed
Walk back unnecessary complexity
1 parent 4934acd commit 323aee3

File tree

7 files changed

+65
-151
lines changed

7 files changed

+65
-151
lines changed
File renamed without changes.

headbang/headbang.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
from .percussive_transients import ihpss
1+
from .transients import ihpss
22
from .onset import OnsetDetector, ODF
3-
from .beattrack import ConsensusBeatTracker
3+
from .consensus import ConsensusBeatTracker
44
from .params import DEFAULTS
55
import numpy
66
import madmom
7-
from librosa.beat import beat_track
8-
from essentia.standard import TempoTapMaxAgreement
97

108

119
def align_beats_onsets(beats, onsets, thresh):
@@ -85,8 +83,6 @@ def __init__(
8583
self.power_memory_ms = power_memory_ms
8684
self.filter_order = filter_order
8785

88-
self.ttap = TempoTapMaxAgreement()
89-
9086
def beats(self, x):
9187
self.beat_consensus = self.cbt.beats(x)
9288
if self.disable_onsets:

headbang/hud_tool.py

+20-45
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from tempfile import gettempdir
1515
from headbang.motion import OpenposeDetector, bpm_from_beats
1616
from headbang.params import DEFAULTS
17+
from essentia.standard import TempoTapMaxAgreement
1718

1819
from headbang import HeadbangBeatTracker
1920

@@ -34,36 +35,12 @@ def main():
3435
default=DEFAULTS["bpm_frame_history"],
3536
help="History of frames (in seconds) to be included in the window of current bpm computation (default=%(default)s)",
3637
)
37-
parser.add_argument(
38-
"--adaptive-prominence-ratio",
39-
type=float,
40-
default=DEFAULTS["adaptive_prominence_ratio"],
41-
help="Peak prominence will be this*(max_ycoord-min_ycoord) (default=%(default)s)",
42-
)
43-
parser.add_argument(
44-
"--openpose-confidence-threshold",
45-
type=float,
46-
default=DEFAULTS["openpose_confidence_thresh"],
47-
help="Openpose keypoints above this threshold will be preserved (default=%(default)s)",
48-
)
49-
parser.add_argument(
50-
"--object-limit",
51-
type=int,
52-
default=DEFAULTS["detected_object_limit"],
53-
help="Number of objects to track, sorted by their net displacement (default=%(default)s)",
54-
)
5538
parser.add_argument(
5639
"--event-threshold-frames",
5740
type=int,
5841
default=DEFAULTS["event_thresh_frames"],
5942
help="Threshold in number of frames by which an event is considered to be the same (default=%(default)s)",
6043
)
61-
parser.add_argument(
62-
"--peak-width",
63-
type=int,
64-
default=DEFAULTS["peak_width"],
65-
help="Peak width (in frames), don't want headbangs too close together (default=%(default)s)",
66-
)
6744
parser.add_argument(
6845
"--debug-motion",
6946
action="store_true",
@@ -98,10 +75,6 @@ def main():
9875
pose_tracker = OpenposeDetector(
9976
total_frames,
10077
keypoints=args.keypoints,
101-
obj_limit=args.object_limit,
102-
adaptive_prominence_ratio=args.adaptive_prominence_ratio,
103-
openpose_confidence_threshold=args.openpose_confidence_threshold,
104-
peak_width=args.peak_width,
10578
)
10679

10780
fps = cap.get(cv2.CAP_PROP_FPS)
@@ -176,23 +149,32 @@ def process_first_pass(*args, **kwargs):
176149
all_time = numpy.linspace(0, frame_duration * total_frames, int(total_frames))
177150

178151
# take top peaks only
179-
peaks = pose_tracker.find_peaks()[0][1]
152+
print("Getting peaks of y motion")
153+
peaks = pose_tracker.find_peaks()
180154
bop_locations = all_time[peaks]
181155

156+
if args.debug_motion:
157+
print("Displaying debug y coordinate plot")
158+
pose_tracker.plot_ycoords()
159+
else:
160+
ttap = TempoTapMaxAgreement()
161+
# choose best aligning peaks
162+
best_peaks = None
163+
for i, pks in enumerate(all_peaks):
164+
_, peaks = pks
165+
peak_times = all_times[peaks]
166+
beat_consensus, _ = ttap([all_beat_locations, peak_times])
167+
print("FOR PEAKS: {0}, consensus: {1}".format(i, len(beat_consensus)))
168+
182169
event_thresh = args.event_threshold_frames * frame_duration
183170

184171
print("Marking beat and head bop positions on output frames")
185172

186-
all_beats_bpm = 0
187-
strong_beats_bpm = 0
188-
bop_bpm = 0
189-
190173
print("run a gc, just in case...")
191174
gc.collect()
192175

193176
# define a function to filter the first video to add more stuff
194177
def process_second_pass(get_frame_fn, frame_time):
195-
nonlocal all_beats_bpm, bop_bpm, strong_beats_bpm
196178
frame = get_frame_fn(frame_time)
197179

198180
frame_max = frame_time
@@ -216,18 +198,14 @@ def process_second_pass(get_frame_fn, frame_time):
216198
numpy.where((bop_locations >= frame_min) & (bop_locations <= frame_max))
217199
]
218200

219-
all_beats_bpm_tmp = bpm_from_beats(all_beat_history)
220-
bop_bpm_tmp = bpm_from_beats(bop_history)
221-
222-
if not numpy.isnan(all_beats_bpm_tmp):
223-
all_beats_bpm = all_beats_bpm_tmp
224-
225-
if not numpy.isnan(bop_bpm_tmp):
226-
bop_bpm = bop_bpm_tmp
201+
all_beats_bpm = bpm_from_beats(all_beat_history)
202+
bop_bpm = bpm_from_beats(bop_history)
227203

228204
is_strong_beat = False
229205
is_beat = False
230206
is_bop = False
207+
is_bop_debug2 = False
208+
is_bop_debug3 = False
231209
if any(
232210
[b for b in all_beat_locations if numpy.abs(b - frame_time) <= event_thresh]
233211
):
@@ -333,6 +311,3 @@ def process_second_pass(get_frame_fn, frame_time):
333311

334312
print("cleaning up tmp mp4")
335313
os.remove(tmp_mp4)
336-
337-
if args.debug_motion:
338-
pose_tracker.plot_ycoords()

headbang/motion.py

+42-94
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
import sys
33
import os
44
import scipy
5-
from scipy.signal import find_peaks, peak_prominences
5+
from scipy.signal import find_peaks_cwt
66
import matplotlib.pyplot as plt
7-
from defaultlist import defaultlist
87
from headbang.params import DEFAULTS
98

109
openpose_install_path = "/home/sevagh/thirdparty-repos/openpose"
@@ -17,30 +16,23 @@
1716

1817
class OpenposeDetector:
1918
undef_coord_default = numpy.nan
19+
object_limit = 3
20+
min_confidence = 0.5
2021

2122
def __init__(
2223
self,
2324
n_frames,
2425
keypoints=DEFAULTS["pose_keypoints"],
25-
obj_limit=DEFAULTS["detected_object_limit"],
26-
adaptive_prominence_ratio=DEFAULTS["adaptive_prominence_ratio"],
27-
openpose_confidence_threshold=DEFAULTS["openpose_confidence_thresh"],
28-
peak_width=DEFAULTS["peak_width"],
2926
):
3027
config = {}
31-
# config["dir"] = openpose_install_path
3228
config["logging_level"] = 3
33-
config["net_resolution"] = "320x320" # 320x176
34-
# config["output_resolution"] = "-1x768" # 320x176
29+
config["net_resolution"] = "320x320"
3530
config["model_pose"] = "BODY_25"
3631
config["alpha_pose"] = 0.6
3732
config["scale_gap"] = 0.3
3833
config["scale_number"] = 1
39-
# config["keypoint_scale"] = 4 # scale to -1,1
4034
config["render_threshold"] = 0.05
41-
config[
42-
"num_gpu_start"
43-
] = 0 # If GPU version is built, and multiple GPUs are available, set the ID here
35+
config["num_gpu_start"] = 0
4436
config["disable_blending"] = False
4537

4638
config["model_folder"] = openpose_dir + "/models/"
@@ -51,13 +43,8 @@ def __init__(
5143
self.keypoints = [int(i) for i in keypoints.split(",")]
5244

5345
self.n_frames = int(n_frames)
54-
self.all_y_coords = [[OpenposeDetector.undef_coord_default] * self.n_frames]
46+
self.all_y_coords = [OpenposeDetector.undef_coord_default] * self.n_frames
5547
self.frame_idx = 0
56-
self.obj_limit = obj_limit
57-
58-
self.confidence_threshold = openpose_confidence_threshold
59-
self.adaptive_prominence_ratio = adaptive_prominence_ratio
60-
self.peak_width = peak_width
6148

6249
def detect_pose(self, image):
6350
datum = op.Datum()
@@ -68,76 +55,40 @@ def detect_pose(self, image):
6855
return datum.poseKeypoints, datum.cvOutputData
6956

7057
def process_frame(self, frame):
71-
tracked_objects = None
7258
multiple_detected_poses, outframe = self.detect_pose(frame)
7359

74-
median_x = None
75-
median_y = None
76-
7760
if multiple_detected_poses is not None:
78-
# array of (x, y) coordinates of the head/neck
79-
multiple_poses_of_interest = [
80-
[
81-
(d[0], d[1])
82-
for i, d in enumerate(single_detected_poses)
83-
if i in self.keypoints and d[2] > self.confidence_threshold
84-
]
85-
for single_detected_poses in multiple_detected_poses
86-
]
87-
88-
if multiple_poses_of_interest:
89-
for i, poses_of_interest in enumerate(multiple_poses_of_interest):
90-
poses_of_interest = numpy.asarray(poses_of_interest)
91-
median_coords = numpy.median(poses_of_interest, axis=0)
92-
if not numpy.any(numpy.isnan(median_coords)):
93-
median_y = median_coords[1]
94-
y_norm = median_y / frame.shape[0]
95-
try:
96-
self.all_y_coords[i][self.frame_idx] = y_norm
97-
except IndexError:
98-
self.all_y_coords.append(
99-
[OpenposeDetector.undef_coord_default] * self.n_frames
100-
)
101-
self.all_y_coords[i][self.frame_idx] = y_norm
61+
poses_of_interest = []
62+
63+
# collect (x, y) coordinates of the head, median across the first object_limit objects
64+
for detected_poses in multiple_detected_poses[
65+
: OpenposeDetector.object_limit
66+
]:
67+
for keypoint, d in enumerate(detected_poses):
68+
if (
69+
keypoint in self.keypoints
70+
and d[2] > OpenposeDetector.min_confidence
71+
):
72+
poses_of_interest.append((d[0], d[1]))
73+
74+
poses_of_interest = numpy.asarray(poses_of_interest)
75+
median_coords = numpy.median(poses_of_interest, axis=0)
76+
77+
if not numpy.any(numpy.isnan(median_coords)):
78+
median_y = median_coords[1]
79+
y_norm = median_y / frame.shape[0]
80+
self.all_y_coords[self.frame_idx] = y_norm
10281

10382
self.frame_idx += 1
10483
return outframe
10584

10685
def find_peaks(self):
107-
peaks = [None] * len(self.all_y_coords)
108-
prominences = [None] * len(self.all_y_coords)
109-
adjusted_y_coords = [None] * len(self.all_y_coords)
110-
111-
for i, y_coords in enumerate(self.all_y_coords):
112-
min_coord = numpy.nanmin(y_coords)
113-
max_coord = numpy.nanmax(y_coords)
114-
115-
# adaptive peak prominence - X% of max displacement
116-
adaptive_prominence = self.adaptive_prominence_ratio * (
117-
max_coord - min_coord
118-
)
119-
120-
adjusted_y_coords[i] = numpy.nan_to_num(y_coords, nan=min_coord)
86+
min_coord = numpy.nanmin(self.all_y_coords)
87+
adjusted_y_coords = numpy.nan_to_num(self.all_y_coords, nan=min_coord)
12188

122-
peaks[i], _ = find_peaks(
123-
adjusted_y_coords[i],
124-
prominence=adaptive_prominence,
125-
wlen=self.peak_width,
126-
)
127-
128-
prominences[i], _, _ = peak_prominences(adjusted_y_coords[i], peaks[i])
129-
130-
top_ycoords_and_peaks = [
131-
(ycrds, pks)
132-
for _, pks, ycrds in sorted(
133-
zip(prominences, peaks, adjusted_y_coords),
134-
key=lambda triplet: sum(triplet[0]),
135-
reverse=True,
136-
)
137-
]
138-
139-
# only track up to obj_limit objects
140-
return top_ycoords_and_peaks[: self.obj_limit]
89+
# wavelets are good for peaks
90+
# https://www.ncbi.nlm.nih.gov/pmc/articles/PMC2631518/
91+
return find_peaks_cwt(adjusted_y_coords, numpy.arange(5, 10))
14192

14293
def plot_ycoords(self):
14394
plt.figure(1)
@@ -147,20 +98,17 @@ def plot_ycoords(self):
14798
plt.ylabel("y coord")
14899

149100
frames = numpy.arange(self.n_frames)
150-
best_coords_and_peaks = self.find_peaks()
151-
152-
for i, coordspeaks in enumerate(best_coords_and_peaks):
153-
y_coords, peaks = coordspeaks
154-
y_coords = numpy.asarray(y_coords)
155-
plt.plot(
156-
frames,
157-
y_coords,
158-
"-D",
159-
label="obj {0}".format(i),
160-
markevery=peaks,
161-
mec="black",
162-
mfc="black",
163-
)
101+
peaks = self.find_peaks()
102+
103+
y_coords = numpy.asarray(self.all_y_coords)
104+
105+
plt.plot(
106+
frames,
107+
y_coords,
108+
"-D",
109+
markevery=peaks,
110+
mec="black",
111+
)
164112

165113
plt.legend()
166114
plt.show()

headbang/params.py

+1-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@
1313
"release_ms": 20,
1414
"power_memory_ms": 1,
1515
"filter_order": 3,
16-
"bpm_frame_history": 3.0,
16+
"bpm_frame_history": 2.0,
1717
"pose_keypoints": "0,15,16,17,18",
1818
"event_thresh_frames": 2,
19-
"detected_object_limit": 1,
20-
"adaptive_prominence_ratio": 0.5,
21-
"openpose_confidence_thresh": 0.5,
22-
"peak_width": 3,
2319
}
File renamed without changes.

requirements.txt

-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ moviepy==1.0.3
1111
essentia==2.1b6.dev374
1212
madmom==0.16.1
1313
scipy==1.6.0
14-
defaultlist

0 commit comments

Comments
 (0)