Skip to content

Commit c32d6da

Browse files
authored
Improve sync-imu-video.py presentation (#25)
1 parent 7ec4ab6 commit c32d6da

File tree

1 file changed

+54
-42
lines changed

1 file changed

+54
-42
lines changed

sync-imu-video.py

Lines changed: 54 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
#!/usr/bin/env python3
12
"""
2-
Plot video and gyroscope speed to help synchronizing them
3+
Computes timeshift needed to match gyroscope and video (optical flow) rotations.
4+
Plots the data before and after the synchronization.
35
"""
46

57
import json
@@ -113,14 +115,6 @@ def slurpJson(dataJsonl):
113115
return data
114116

115117

116-
def normalize_array(array):
117-
np_array = np.array(array)
118-
min_value = np.min(np_array)
119-
max_value = np.max(np_array)
120-
normalized_array = (np_array - min_value) / (max_value - min_value)
121-
return normalized_array
122-
123-
124118
def meanError(times, values, fn, fn_range, offset):
125119
newTimes = times - offset
126120
startIdx = None
@@ -150,26 +144,7 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
150144
smallestIdx = np.nanargmin(errors)
151145
return offsets, errors, offsets[smallestIdx], errors[smallestIdx]
152146

153-
154-
if __name__ == '__main__':
155-
import argparse
156-
p = argparse.ArgumentParser(__doc__)
157-
p.add_argument('video', help="path to video file")
158-
p.add_argument('data', help="path to data.jsonl file")
159-
p.add_argument('--flow_winsize', type=int, default=15)
160-
p.add_argument('--max_frames', type=int, default=0)
161-
p.add_argument('-skip', '--skip_first_n_frames', type=int, default=0, help='Skip first N frames')
162-
p.add_argument('--preview', action='store_true')
163-
p.add_argument('--noPlot', action='store_true')
164-
p.add_argument('--sameStart', action='store_true', help="Assume video and gyro start roughly at same time")
165-
p.add_argument('--sameEnd', action='store_true', help="Assume video and gyro end roughly at same time")
166-
p.add_argument('--frameTimeOffsetSeconds', type=float)
167-
p.add_argument('--resize_width', type=int, default=200)
168-
p.add_argument('--output', help="data.jsonl with frame timestamp shifted to match gyroscope timestamps")
169-
p.add_argument('--maxOffset', help="Maximum offset between gyro and frame times in seconds", type=float, default=5.0)
170-
171-
args = p.parse_args()
172-
147+
def main(args):
173148
if not pathlib.Path(args.data).exists():
174149
raise Exception("Data file `{}` does not exist.".format(args.data))
175150
if not pathlib.Path(args.video).exists():
@@ -222,11 +197,17 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
222197
frameSpeed.append(avg_speed)
223198
leader_flow.show_preview('leader')
224199

225-
gyroSpeed = normalize_array(gyroSpeed)
226-
frameSpeed = normalize_array(frameSpeed)
200+
if len(gyroSpeed) == 0:
201+
print("No gyroscope data to plot.")
202+
return
203+
if len(frameSpeed) == 0:
204+
print("No frame data to plot.")
205+
return
227206

228-
# gyroSpeed = scipy.signal.medfilt(gyroSpeed, kernel_size=21)
229-
# frameSpeed = scipy.signal.medfilt(frameSpeed, kernel_size=3)
207+
gyroSpeed = np.array(gyroSpeed)
208+
frameSpeed = np.array(frameSpeed)
209+
gyroSpeed /= np.mean(gyroSpeed)
210+
frameSpeed /= np.mean(frameSpeed)
230211

231212
testedOffsets = []
232213
estimatedErrors = []
@@ -248,18 +229,32 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
248229
testedOffsets.append(offsets)
249230
timeOffset = offset
250231

232+
totalOffset = initialOffset + timeOffset
233+
print("Estimated time offset: {:.4f}s".format(totalOffset))
251234

252-
if not args.noPlot:
235+
if args.rawPlotOnly:
236+
plt.plot(gyroTimes, gyroSpeed, label='Gyroscope speed')
237+
plt.plot(frameTimes, frameSpeed, label='Optical flow speed')
238+
plt.title("Original data")
239+
plt.legend()
240+
plt.xlabel('t (s)')
241+
plt.ylabel('normalized speed')
242+
plt.show()
243+
elif not args.noPlot:
253244
_, subplots = plt.subplots(2 + len(estimatedErrors))
254-
subplots[0].plot(gyroTimes, gyroSpeed, label='Gyro speed')
245+
subplots[0].plot(gyroTimes, gyroSpeed, label='Gyroscope speed')
255246
subplots[0].plot(frameTimes, frameSpeed, label='Optical flow speed')
256-
subplots[0].title.set_text("Normalized gyro speed vs. optical flow speed")
247+
subplots[0].title.set_text("Original data")
248+
subplots[0].set_xlabel('t (s)')
249+
subplots[0].set_ylabel('normalized speed')
257250
subplots[0].legend()
258251

259252
frameTimes = np.array(frameTimes) + timeOffset
260-
subplots[1].plot(gyroTimes, gyroSpeed, label='Gyro speed')
253+
subplots[1].plot(gyroTimes, gyroSpeed, label='Gyroscope speed')
261254
subplots[1].plot(frameTimes, frameSpeed, label=f'Optical flow speed')
262-
subplots[1].title.set_text(f"Normalized gyro speed vs. optical flow speed with {timeOffset} seconds added to frame timetamps")
255+
subplots[1].title.set_text(f"After correction by {timeOffset:.4f}s added to frame timetamps")
256+
subplots[1].set_xlabel('t (s)')
257+
subplots[1].set_ylabel('normalized speed')
263258
subplots[1].legend()
264259

265260
for i in range(len(estimatedErrors)):
@@ -270,16 +265,33 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
270265
plt.legend()
271266
plt.show()
272267

273-
timeOffset += initialOffset
274-
275-
print("Estimated time offset: {:.4f}s".format(timeOffset))
276268
if args.output:
277269
print(f"All frame timestamps corrected in {args.output}")
278270
for entry in data:
279271
if "frames" in entry:
280-
entry["time"] += timeOffset
272+
entry["time"] += totalOffset
281273
data_sorted = sorted(data, key=lambda x: x.get("time", 0.0))
282274
with open(args.output, "w") as f:
283275
for entry in data_sorted:
284276
f.write(json.dumps(entry) + "\n")
285277

278+
if __name__ == '__main__':
279+
import argparse
280+
p = argparse.ArgumentParser(__doc__)
281+
p.add_argument('video', help="path to video file")
282+
p.add_argument('data', help="path to data.jsonl file")
283+
p.add_argument('--flow_winsize', type=int, default=15)
284+
p.add_argument('--max_frames', type=int, default=0)
285+
p.add_argument('-skip', '--skip_first_n_frames', type=int, default=0, help='Skip first N frames')
286+
p.add_argument('--preview', action='store_true')
287+
p.add_argument('--noPlot', action='store_true')
288+
p.add_argument('--rawPlotOnly', action='store_true')
289+
p.add_argument('--sameStart', action='store_true', help="Assume video and gyroscope start roughly at same time")
290+
p.add_argument('--sameEnd', action='store_true', help="Assume video and gyroscope end roughly at same time")
291+
p.add_argument('--frameTimeOffsetSeconds', type=float)
292+
p.add_argument('--resize_width', type=int, default=200)
293+
p.add_argument('--output', help="data.jsonl with frame timestamp shifted to match gyroscope timestamps")
294+
p.add_argument('--maxOffset', help="Maximum offset between gyroscope and frame times in seconds", type=float, default=5.0)
295+
296+
args = p.parse_args()
297+
main(args)

0 commit comments

Comments
 (0)