1
+ #!/usr/bin/env python3
1
2
"""
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.
3
5
"""
4
6
5
7
import json
@@ -113,14 +115,6 @@ def slurpJson(dataJsonl):
113
115
return data
114
116
115
117
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
-
124
118
def meanError (times , values , fn , fn_range , offset ):
125
119
newTimes = times - offset
126
120
startIdx = None
@@ -150,26 +144,7 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
150
144
smallestIdx = np .nanargmin (errors )
151
145
return offsets , errors , offsets [smallestIdx ], errors [smallestIdx ]
152
146
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 ):
173
148
if not pathlib .Path (args .data ).exists ():
174
149
raise Exception ("Data file `{}` does not exist." .format (args .data ))
175
150
if not pathlib .Path (args .video ).exists ():
@@ -222,11 +197,17 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
222
197
frameSpeed .append (avg_speed )
223
198
leader_flow .show_preview ('leader' )
224
199
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
227
206
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 )
230
211
231
212
testedOffsets = []
232
213
estimatedErrors = []
@@ -248,18 +229,32 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
248
229
testedOffsets .append (offsets )
249
230
timeOffset = offset
250
231
232
+ totalOffset = initialOffset + timeOffset
233
+ print ("Estimated time offset: {:.4f}s" .format (totalOffset ))
251
234
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 :
253
244
_ , subplots = plt .subplots (2 + len (estimatedErrors ))
254
- subplots [0 ].plot (gyroTimes , gyroSpeed , label = 'Gyro speed' )
245
+ subplots [0 ].plot (gyroTimes , gyroSpeed , label = 'Gyroscope speed' )
255
246
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' )
257
250
subplots [0 ].legend ()
258
251
259
252
frameTimes = np .array (frameTimes ) + timeOffset
260
- subplots [1 ].plot (gyroTimes , gyroSpeed , label = 'Gyro speed' )
253
+ subplots [1 ].plot (gyroTimes , gyroSpeed , label = 'Gyroscope speed' )
261
254
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' )
263
258
subplots [1 ].legend ()
264
259
265
260
for i in range (len (estimatedErrors )):
@@ -270,16 +265,33 @@ def findMinimumError(gyro_time, gyro_angular_speed, fn, fn_range, offsets):
270
265
plt .legend ()
271
266
plt .show ()
272
267
273
- timeOffset += initialOffset
274
-
275
- print ("Estimated time offset: {:.4f}s" .format (timeOffset ))
276
268
if args .output :
277
269
print (f"All frame timestamps corrected in { args .output } " )
278
270
for entry in data :
279
271
if "frames" in entry :
280
- entry ["time" ] += timeOffset
272
+ entry ["time" ] += totalOffset
281
273
data_sorted = sorted (data , key = lambda x : x .get ("time" , 0.0 ))
282
274
with open (args .output , "w" ) as f :
283
275
for entry in data_sorted :
284
276
f .write (json .dumps (entry ) + "\n " )
285
277
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