megadetector 10.0.2__py3-none-any.whl → 10.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of megadetector might be problematic. Click here for more details.
- megadetector/detection/process_video.py +120 -913
- megadetector/detection/pytorch_detector.py +572 -263
- megadetector/detection/run_detector_batch.py +525 -143
- megadetector/detection/run_md_and_speciesnet.py +1301 -0
- megadetector/detection/video_utils.py +240 -105
- megadetector/postprocessing/classification_postprocessing.py +12 -1
- megadetector/postprocessing/compare_batch_results.py +21 -2
- megadetector/postprocessing/merge_detections.py +16 -12
- megadetector/postprocessing/validate_batch_results.py +25 -2
- megadetector/tests/__init__.py +0 -0
- megadetector/tests/test_nms_synthetic.py +335 -0
- megadetector/utils/ct_utils.py +16 -5
- megadetector/utils/extract_frames_from_video.py +303 -0
- megadetector/utils/md_tests.py +578 -520
- megadetector/utils/wi_utils.py +20 -4
- megadetector/visualization/visualize_db.py +8 -22
- megadetector/visualization/visualize_detector_output.py +1 -1
- megadetector/visualization/visualize_video_output.py +607 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.3.dist-info}/METADATA +134 -135
- {megadetector-10.0.2.dist-info → megadetector-10.0.3.dist-info}/RECORD +23 -18
- {megadetector-10.0.2.dist-info → megadetector-10.0.3.dist-info}/licenses/LICENSE +0 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.3.dist-info}/top_level.txt +0 -0
- {megadetector-10.0.2.dist-info → megadetector-10.0.3.dist-info}/WHEEL +0 -0
|
@@ -16,30 +16,16 @@ repeat detection elimination).
|
|
|
16
16
|
|
|
17
17
|
import os
|
|
18
18
|
import sys
|
|
19
|
-
import tempfile
|
|
20
19
|
import argparse
|
|
21
|
-
import itertools
|
|
22
|
-
import json
|
|
23
|
-
import shutil
|
|
24
|
-
import getpass
|
|
25
|
-
|
|
26
|
-
from uuid import uuid1
|
|
27
20
|
|
|
28
21
|
from megadetector.detection import run_detector_batch
|
|
29
|
-
from megadetector.visualization import visualize_detector_output
|
|
30
22
|
from megadetector.utils.ct_utils import args_to_object
|
|
31
23
|
from megadetector.utils.ct_utils import dict_to_kvp_list, parse_kvp_list
|
|
32
|
-
from megadetector.
|
|
33
|
-
from megadetector.detection.video_utils import video_to_frames
|
|
34
|
-
from megadetector.detection.video_utils import run_callback_on_frames
|
|
24
|
+
from megadetector.detection.video_utils import _filename_to_frame_number
|
|
35
25
|
from megadetector.detection.video_utils import run_callback_on_frames_for_folder
|
|
36
|
-
from megadetector.detection.video_utils import frames_to_video
|
|
37
|
-
from megadetector.detection.video_utils import frame_results_to_video_results
|
|
38
|
-
from megadetector.detection.video_utils import FrameToVideoOptions
|
|
39
|
-
from megadetector.detection.video_utils import _add_frame_numbers_to_results
|
|
40
|
-
from megadetector.detection.video_utils import video_folder_to_frames
|
|
41
|
-
from megadetector.detection.video_utils import default_fourcc
|
|
42
26
|
from megadetector.detection.run_detector import load_detector
|
|
27
|
+
from megadetector.postprocessing.validate_batch_results import \
|
|
28
|
+
ValidateBatchResultsOptions, validate_batch_results
|
|
43
29
|
|
|
44
30
|
|
|
45
31
|
#%% Classes
|
|
@@ -65,82 +51,21 @@ class ProcessVideoOptions:
|
|
|
65
51
|
#: .json file to which we should write results
|
|
66
52
|
self.output_json_file = None
|
|
67
53
|
|
|
68
|
-
#: File to which we should write a video with boxes, only relevant if
|
|
69
|
-
#: render_output_video is True
|
|
70
|
-
self.output_video_file = None
|
|
71
|
-
|
|
72
|
-
#: Folder to use for extracted frames; will use a folder in system temp space
|
|
73
|
-
#: if this is None
|
|
74
|
-
self.frame_folder = None
|
|
75
|
-
|
|
76
|
-
#: Folder to use for rendered frames (if rendering output video); will use a folder
|
|
77
|
-
#: in system temp space if this is None
|
|
78
|
-
self.frame_rendering_folder = None
|
|
79
|
-
|
|
80
|
-
#: Should we render a video with detection boxes?
|
|
81
|
-
#:
|
|
82
|
-
#: If processing a folder, this renders each input video to a separate
|
|
83
|
-
#: video with detection boxes.
|
|
84
|
-
self.render_output_video = False
|
|
85
|
-
|
|
86
|
-
#: If we are rendering boxes to a new video, should we keep the temporary
|
|
87
|
-
#: rendered frames?
|
|
88
|
-
self.keep_rendered_frames = False
|
|
89
|
-
|
|
90
|
-
#: Should we keep the extracted frames?
|
|
91
|
-
self.keep_extracted_frames = False
|
|
92
|
-
|
|
93
|
-
#: Should we delete the entire folder the extracted frames are written to?
|
|
94
|
-
#:
|
|
95
|
-
#: By default, we delete the frame files but leave the (probably-empty) folder in place,
|
|
96
|
-
#: for no reason other than being paranoid about deleting folders.
|
|
97
|
-
self.force_extracted_frame_folder_deletion = False
|
|
98
|
-
|
|
99
|
-
#: Should we delete the entire folder the rendered frames are written to?
|
|
100
|
-
#:
|
|
101
|
-
#: By default, we delete the frame files but leave the (probably-empty) folder in place,
|
|
102
|
-
#: for no reason other than being paranoid about deleting folders.
|
|
103
|
-
self.force_rendered_frame_folder_deletion = False
|
|
104
|
-
|
|
105
|
-
#: If we've already run MegaDetector on this video or folder of videos, i.e. if we
|
|
106
|
-
#: find a corresponding MD results file, should we re-use it? Defaults to reprocessing.
|
|
107
|
-
self.reuse_results_if_available = False
|
|
108
|
-
|
|
109
|
-
#: If we've already split this video or folder of videos into frames, should we
|
|
110
|
-
#: we re-use those extracted frames? Defaults to reprocessing.
|
|
111
|
-
self.reuse_frames_if_available = False
|
|
112
|
-
|
|
113
54
|
#: If [input_video_file] is a folder, should we search for videos recursively?
|
|
114
55
|
self.recursive = False
|
|
115
56
|
|
|
116
57
|
#: Enable additional debug console output
|
|
117
58
|
self.verbose = False
|
|
118
59
|
|
|
119
|
-
#: fourcc code to use for writing videos; only relevant if render_output_video is True
|
|
120
|
-
self.fourcc = None
|
|
121
|
-
|
|
122
|
-
#: force a specific frame rate for output videos; only relevant if render_output_video
|
|
123
|
-
#: is True
|
|
124
|
-
self.rendering_fs = None
|
|
125
|
-
|
|
126
|
-
#: Confidence threshold to use for writing videos with boxes, only relevant if
|
|
127
|
-
#: if render_output_video is True. Defaults to choosing a reasonable threshold
|
|
128
|
-
#: based on the model version.
|
|
129
|
-
self.rendering_confidence_threshold = None
|
|
130
|
-
|
|
131
60
|
#: Detections below this threshold will not be included in the output file.
|
|
132
61
|
self.json_confidence_threshold = 0.005
|
|
133
62
|
|
|
134
63
|
#: Sample every Nth frame; set to None (default) or 1 to sample every frame. Typically
|
|
135
64
|
#: we sample down to around 3 fps, so for typical 30 fps videos, frame_sample=10 is a
|
|
136
|
-
#: typical value. Mutually exclusive with [
|
|
65
|
+
#: typical value. Mutually exclusive with [time_sample].
|
|
137
66
|
self.frame_sample = None
|
|
138
67
|
|
|
139
|
-
#:
|
|
140
|
-
#: [frame_sample] and [time_sample].
|
|
141
|
-
self.frames_to_extract = None
|
|
142
|
-
|
|
143
|
-
# Sample frames every N seconds. Mutually exclusive with [frame_sample] and [frames_to_extract].
|
|
68
|
+
#: Sample frames every N seconds. Mutually exclusive with [frame_sample]
|
|
144
69
|
self.time_sample = None
|
|
145
70
|
|
|
146
71
|
#: Number of workers to use for parallelization; set to <= 1 to disable parallelization
|
|
@@ -149,20 +74,10 @@ class ProcessVideoOptions:
|
|
|
149
74
|
#: For debugging only, stop processing after a certain number of frames.
|
|
150
75
|
self.debug_max_frames = -1
|
|
151
76
|
|
|
152
|
-
#: For debugging only, force on-disk frame extraction, even if it wouldn't otherwise be
|
|
153
|
-
#: necessary
|
|
154
|
-
self.force_on_disk_frame_extraction = False
|
|
155
|
-
|
|
156
77
|
#: File containing non-standard categories, typically only used if you're running a non-MD
|
|
157
78
|
#: detector.
|
|
158
79
|
self.class_mapping_filename = None
|
|
159
80
|
|
|
160
|
-
#: JPEG quality for frame output, from 0-100. Use None or -1 to let opencv decide.
|
|
161
|
-
self.quality = 90
|
|
162
|
-
|
|
163
|
-
#: Resize frames so they're at most this wide
|
|
164
|
-
self.max_width = None
|
|
165
|
-
|
|
166
81
|
#: Run the model at this image size (don't mess with this unless you know what you're
|
|
167
82
|
#: getting into)... if you just want to pass smaller frames to MD, use max_width
|
|
168
83
|
self.image_size = None
|
|
@@ -171,14 +86,9 @@ class ProcessVideoOptions:
|
|
|
171
86
|
self.augment = False
|
|
172
87
|
|
|
173
88
|
#: By default, a video with no frames (or no frames retrievable with the current parameters)
|
|
174
|
-
#: is
|
|
175
|
-
#: frame from each video, but a video only has 50 frames.
|
|
89
|
+
#: is treated as a failure; this causes it to be treated as a video with no detections.
|
|
176
90
|
self.allow_empty_videos = False
|
|
177
91
|
|
|
178
|
-
#: When processing a folder of videos, should we include just a single representative
|
|
179
|
-
#: frame result for each video (default), or every frame that was processed?
|
|
180
|
-
self.include_all_processed_frames = False
|
|
181
|
-
|
|
182
92
|
#: Detector-specific options
|
|
183
93
|
self.detector_options = None
|
|
184
94
|
|
|
@@ -197,655 +107,150 @@ def _validate_video_options(options):
|
|
|
197
107
|
n_sampling_options_configured += 1
|
|
198
108
|
if options.time_sample is not None:
|
|
199
109
|
n_sampling_options_configured += 1
|
|
200
|
-
if options.frames_to_extract is not None:
|
|
201
|
-
n_sampling_options_configured += 1
|
|
202
110
|
|
|
203
111
|
if n_sampling_options_configured > 1:
|
|
204
|
-
raise ValueError('frame_sample
|
|
112
|
+
raise ValueError('frame_sample and time_sample are mutually exclusive')
|
|
205
113
|
|
|
206
114
|
return True
|
|
207
115
|
|
|
208
116
|
|
|
209
|
-
def
|
|
210
|
-
"""
|
|
211
|
-
Choose folders in system temp space for writing temporary frames. Does not create folders,
|
|
212
|
-
just defines them.
|
|
213
|
-
"""
|
|
214
|
-
|
|
215
|
-
tempdir = os.path.join(tempfile.gettempdir(), 'process_camera_trap_video')
|
|
216
|
-
|
|
217
|
-
# If we create a folder like "process_camera_trap_video" in the system temp dir, it may
|
|
218
|
-
# be the case that no one else can write to it, even to create user-specific subfolders.
|
|
219
|
-
# If we create a uuid-named folder in the system temp dir, we make a mess.
|
|
220
|
-
#
|
|
221
|
-
# Compromise with "process_camera_trap_video-[user]".
|
|
222
|
-
user_tempdir = tempdir + '-' + getpass.getuser()
|
|
223
|
-
|
|
224
|
-
# I don't know whether it's possible for a username to contain characters that are
|
|
225
|
-
# not valid filename characters, but just to be sure...
|
|
226
|
-
user_tempdir = clean_path(user_tempdir)
|
|
227
|
-
|
|
228
|
-
frame_output_folder = os.path.join(
|
|
229
|
-
user_tempdir, os.path.basename(options.input_video_file) + '_frames_' + str(uuid1()))
|
|
230
|
-
|
|
231
|
-
rendering_output_folder = os.path.join(
|
|
232
|
-
tempdir, os.path.basename(options.input_video_file) + '_detections_' + str(uuid1()))
|
|
233
|
-
|
|
234
|
-
temporary_folder_info = \
|
|
235
|
-
{
|
|
236
|
-
'temp_folder_base':user_tempdir,
|
|
237
|
-
'frame_output_folder':frame_output_folder,
|
|
238
|
-
'rendering_output_folder':rendering_output_folder
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
return temporary_folder_info
|
|
242
|
-
|
|
243
|
-
# ...def _create_frame_output_folders(...)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def _clean_up_rendered_frames(options,rendering_output_folder,detected_frame_files):
|
|
247
|
-
"""
|
|
248
|
-
If necessary, delete rendered frames and/or the entire rendering output folder.
|
|
249
|
-
"""
|
|
250
|
-
|
|
251
|
-
if rendering_output_folder is None:
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
|
|
255
|
-
|
|
256
|
-
# (Optionally) delete the temporary directory we used for rendered detection images
|
|
257
|
-
if not options.keep_rendered_frames:
|
|
258
|
-
|
|
259
|
-
try:
|
|
260
|
-
|
|
261
|
-
# If (a) we're supposed to delete the temporary rendering folder no
|
|
262
|
-
# matter where it is and (b) we created it in temp space, delete the
|
|
263
|
-
# whole tree
|
|
264
|
-
if options.force_rendered_frame_folder_deletion and \
|
|
265
|
-
(not caller_provided_rendering_output_folder):
|
|
266
|
-
|
|
267
|
-
if options.verbose:
|
|
268
|
-
print('Recursively deleting rendered frame folder {}'.format(
|
|
269
|
-
rendering_output_folder))
|
|
270
|
-
|
|
271
|
-
shutil.rmtree(rendering_output_folder)
|
|
272
|
-
|
|
273
|
-
# ...otherwise just delete the frames, but leave the folder in place
|
|
274
|
-
else:
|
|
275
|
-
|
|
276
|
-
if options.force_rendered_frame_folder_deletion:
|
|
277
|
-
assert caller_provided_rendering_output_folder
|
|
278
|
-
print('Warning: force_rendered_frame_folder_deletion supplied with a ' + \
|
|
279
|
-
'user-provided folder, only removing frames')
|
|
280
|
-
|
|
281
|
-
for rendered_frame_fn in detected_frame_files:
|
|
282
|
-
os.remove(rendered_frame_fn)
|
|
283
|
-
|
|
284
|
-
except Exception as e:
|
|
285
|
-
print('Warning: error deleting rendered frames from folder {}:\n{}'.format(
|
|
286
|
-
rendering_output_folder,str(e)))
|
|
287
|
-
pass
|
|
288
|
-
|
|
289
|
-
elif options.force_rendered_frame_folder_deletion:
|
|
290
|
-
|
|
291
|
-
print('Warning: keep_rendered_frames and force_rendered_frame_folder_deletion both ' + \
|
|
292
|
-
'specified, not deleting')
|
|
293
|
-
|
|
294
|
-
# ...def _clean_up_rendered_frames(...)
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
def _clean_up_extracted_frames(options,frame_output_folder,frame_filenames):
|
|
298
|
-
"""
|
|
299
|
-
If necessary, delete extracted frames and/or the entire temporary frame folder.
|
|
300
|
-
"""
|
|
301
|
-
|
|
302
|
-
if frame_output_folder is None:
|
|
303
|
-
return
|
|
304
|
-
|
|
305
|
-
caller_provided_frame_output_folder = (options.frame_folder is not None)
|
|
306
|
-
|
|
307
|
-
if not options.keep_extracted_frames:
|
|
308
|
-
|
|
309
|
-
try:
|
|
310
|
-
|
|
311
|
-
# If (a) we're supposed to delete the temporary frame folder no
|
|
312
|
-
# matter where it is and (b) we created it in temp space, delete the
|
|
313
|
-
# whole tree.
|
|
314
|
-
if options.force_extracted_frame_folder_deletion and \
|
|
315
|
-
(not caller_provided_frame_output_folder):
|
|
316
|
-
|
|
317
|
-
if options.verbose:
|
|
318
|
-
print('Recursively deleting frame output folder {}'.format(frame_output_folder))
|
|
319
|
-
|
|
320
|
-
shutil.rmtree(frame_output_folder)
|
|
321
|
-
|
|
322
|
-
# ...otherwise just delete the frames, but leave the folder in place
|
|
323
|
-
else:
|
|
324
|
-
|
|
325
|
-
if frame_filenames is None:
|
|
326
|
-
return
|
|
327
|
-
|
|
328
|
-
if options.force_extracted_frame_folder_deletion:
|
|
329
|
-
assert caller_provided_frame_output_folder
|
|
330
|
-
print('Warning: force_extracted_frame_folder_deletion supplied with a ' + \
|
|
331
|
-
'user-provided folder, only removing frames')
|
|
332
|
-
|
|
333
|
-
for extracted_frame_fn in frame_filenames:
|
|
334
|
-
os.remove(extracted_frame_fn)
|
|
335
|
-
|
|
336
|
-
except Exception as e:
|
|
337
|
-
print('Warning: error removing extracted frames from folder {}:\n{}'.format(
|
|
338
|
-
frame_output_folder,str(e)))
|
|
339
|
-
pass
|
|
340
|
-
|
|
341
|
-
elif options.force_extracted_frame_folder_deletion:
|
|
342
|
-
|
|
343
|
-
print('Warning: keep_extracted_frames and force_extracted_frame_folder_deletion both ' + \
|
|
344
|
-
'specified, not deleting')
|
|
345
|
-
|
|
346
|
-
# ...def _clean_up_extracted_frames
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
def process_video(options):
|
|
117
|
+
def process_videos(options):
|
|
350
118
|
"""
|
|
351
|
-
Process a
|
|
352
|
-
Can also be used just to split a video into frames, without running a model.
|
|
119
|
+
Process a video or folder of videos through MD.
|
|
353
120
|
|
|
354
121
|
Args:
|
|
355
122
|
options (ProcessVideoOptions): all the parameters used to control this process,
|
|
356
123
|
including filenames; see ProcessVideoOptions for details
|
|
357
|
-
|
|
358
|
-
Returns:
|
|
359
|
-
dict: frame-level MegaDetector results, identical to what's in the output .json file
|
|
360
124
|
"""
|
|
361
125
|
|
|
126
|
+
## Validate options
|
|
127
|
+
|
|
362
128
|
# Check for incompatible options
|
|
363
129
|
_validate_video_options(options)
|
|
364
130
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if options.render_output_video and (options.output_video_file is None):
|
|
369
|
-
options.output_video_file = options.input_video_file + '.detections.mp4'
|
|
131
|
+
assert options.output_json_file.endswith('.json'), \
|
|
132
|
+
'Illegal output file {}'.format(options.output_json_file)
|
|
370
133
|
|
|
371
134
|
if options.time_sample is not None:
|
|
372
|
-
|
|
373
|
-
'consider processing a folder, or using frame_sample')
|
|
374
|
-
|
|
375
|
-
if options.model_file == 'no_detection' and not options.keep_extracted_frames:
|
|
376
|
-
print('Warning: you asked for no detection, but did not specify keep_extracted_frames, this is a no-op')
|
|
377
|
-
return
|
|
378
|
-
|
|
379
|
-
# Track whether frame and rendering folders were created by this script
|
|
380
|
-
caller_provided_frame_output_folder = (options.frame_folder is not None)
|
|
381
|
-
caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
|
|
382
|
-
|
|
383
|
-
frame_output_folder = None
|
|
384
|
-
frame_filenames = None
|
|
385
|
-
|
|
386
|
-
# If we should re-use existing results, and the output file exists, don't bother running MD
|
|
387
|
-
if (options.reuse_results_if_available and os.path.isfile(options.output_json_file)):
|
|
388
|
-
|
|
389
|
-
print('Loading results from {}'.format(options.output_json_file))
|
|
390
|
-
with open(options.output_json_file,'r') as f:
|
|
391
|
-
results = json.load(f)
|
|
392
|
-
|
|
393
|
-
# Run MD in memory if we don't need to generate frames
|
|
394
|
-
#
|
|
395
|
-
# Currently if we're generating an output video, we need to generate frames on disk first.
|
|
396
|
-
elif (not options.keep_extracted_frames and \
|
|
397
|
-
not options.render_output_video and \
|
|
398
|
-
not options.force_on_disk_frame_extraction):
|
|
399
|
-
|
|
400
|
-
# Run MegaDetector in memory
|
|
401
|
-
|
|
402
|
-
if options.verbose:
|
|
403
|
-
print('Running MegaDetector in memory for {}'.format(options.input_video_file))
|
|
404
|
-
|
|
405
|
-
if options.frame_folder is not None:
|
|
406
|
-
print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
|
|
407
|
-
'not; no raw frames will be written')
|
|
408
|
-
|
|
409
|
-
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
410
|
-
|
|
411
|
-
def frame_callback(image_np,image_id):
|
|
412
|
-
return detector.generate_detections_one_image(image_np,
|
|
413
|
-
image_id,
|
|
414
|
-
detection_threshold=options.json_confidence_threshold,
|
|
415
|
-
augment=options.augment)
|
|
416
|
-
|
|
417
|
-
frame_results = run_callback_on_frames(options.input_video_file,
|
|
418
|
-
frame_callback,
|
|
419
|
-
every_n_frames=options.frame_sample,
|
|
420
|
-
verbose=options.verbose,
|
|
421
|
-
frames_to_process=options.frames_to_extract)
|
|
422
|
-
|
|
423
|
-
frame_results['results'] = _add_frame_numbers_to_results(frame_results['results'])
|
|
424
|
-
|
|
425
|
-
run_detector_batch.write_results_to_file(
|
|
426
|
-
frame_results['results'],
|
|
427
|
-
options.output_json_file,
|
|
428
|
-
relative_path_base=None,
|
|
429
|
-
detector_file=options.model_file,
|
|
430
|
-
custom_metadata={'video_frame_rate':frame_results['frame_rate']})
|
|
431
|
-
|
|
432
|
-
# Extract frames and optionally run MegaDetector on those frames
|
|
135
|
+
every_n_frames_param = -1 * options.time_sample
|
|
433
136
|
else:
|
|
137
|
+
every_n_frames_param = options.frame_sample
|
|
434
138
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
# This does not create any folders, just defines temporary folder names in
|
|
439
|
-
# case we need them.
|
|
440
|
-
temporary_folder_info = _select_temporary_output_folders(options)
|
|
441
|
-
|
|
442
|
-
if (caller_provided_frame_output_folder):
|
|
443
|
-
frame_output_folder = options.frame_folder
|
|
444
|
-
else:
|
|
445
|
-
frame_output_folder = temporary_folder_info['frame_output_folder']
|
|
446
|
-
|
|
447
|
-
os.makedirs(frame_output_folder, exist_ok=True)
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
## Extract frames
|
|
451
|
-
|
|
452
|
-
frame_filenames, fs = video_to_frames(
|
|
453
|
-
options.input_video_file,
|
|
454
|
-
frame_output_folder,
|
|
455
|
-
every_n_frames=options.frame_sample,
|
|
456
|
-
overwrite=(not options.reuse_frames_if_available),
|
|
457
|
-
quality=options.quality,
|
|
458
|
-
max_width=options.max_width,
|
|
459
|
-
verbose=options.verbose,
|
|
460
|
-
frames_to_extract=options.frames_to_extract,
|
|
461
|
-
allow_empty_videos=options.allow_empty_videos)
|
|
462
|
-
|
|
463
|
-
image_file_names = frame_filenames
|
|
464
|
-
if options.debug_max_frames > 0:
|
|
465
|
-
image_file_names = image_file_names[0:options.debug_max_frames]
|
|
466
|
-
|
|
467
|
-
## Run MegaDetector on those frames
|
|
468
|
-
|
|
469
|
-
if options.model_file != 'no_detection':
|
|
470
|
-
|
|
471
|
-
if options.verbose:
|
|
472
|
-
print('Running MD for {}'.format(options.input_video_file))
|
|
473
|
-
|
|
474
|
-
results = run_detector_batch.load_and_run_detector_batch(
|
|
475
|
-
options.model_file,
|
|
476
|
-
image_file_names,
|
|
477
|
-
confidence_threshold=options.json_confidence_threshold,
|
|
478
|
-
n_cores=options.n_cores,
|
|
479
|
-
class_mapping_filename=options.class_mapping_filename,
|
|
480
|
-
quiet=True,
|
|
481
|
-
augment=options.augment,
|
|
482
|
-
image_size=options.image_size,
|
|
483
|
-
detector_options=options.detector_options)
|
|
484
|
-
|
|
485
|
-
results = _add_frame_numbers_to_results(results)
|
|
486
|
-
|
|
487
|
-
run_detector_batch.write_results_to_file(
|
|
488
|
-
results,
|
|
489
|
-
options.output_json_file,
|
|
490
|
-
relative_path_base=frame_output_folder,
|
|
491
|
-
detector_file=options.model_file,
|
|
492
|
-
custom_metadata={'video_frame_rate':fs})
|
|
493
|
-
|
|
494
|
-
# ...if we are/aren't keeping raw frames on disk
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
## (Optionally) render output video
|
|
498
|
-
|
|
499
|
-
if options.render_output_video:
|
|
500
|
-
|
|
501
|
-
## Render detections to images
|
|
502
|
-
|
|
503
|
-
if (caller_provided_rendering_output_folder):
|
|
504
|
-
rendering_output_dir = options.frame_rendering_folder
|
|
505
|
-
else:
|
|
506
|
-
rendering_output_dir = temporary_folder_info['rendering_output_folder']
|
|
507
|
-
|
|
508
|
-
os.makedirs(rendering_output_dir,exist_ok=True)
|
|
509
|
-
|
|
510
|
-
detected_frame_files = visualize_detector_output.visualize_detector_output(
|
|
511
|
-
detector_output_path=options.output_json_file,
|
|
512
|
-
out_dir=rendering_output_dir,
|
|
513
|
-
images_dir=frame_output_folder,
|
|
514
|
-
confidence_threshold=options.rendering_confidence_threshold)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
## Choose the frame rate at which we should render the output video
|
|
518
|
-
|
|
519
|
-
if options.rendering_fs is not None:
|
|
520
|
-
rendering_fs = options.rendering_fs
|
|
521
|
-
elif options.frame_sample is None and options.time_sample is None:
|
|
522
|
-
rendering_fs = fs
|
|
523
|
-
elif options.frame_sample is not None:
|
|
524
|
-
assert options.time_sample is None
|
|
525
|
-
# If the original video was 30fps and we sampled every 10th frame,
|
|
526
|
-
# render at 3fps
|
|
527
|
-
rendering_fs = fs / options.frame_sample
|
|
528
|
-
elif options.time_sample is not None:
|
|
529
|
-
rendering_fs = options.time_sample
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
## Render the output video
|
|
533
|
-
|
|
534
|
-
print('Rendering {} frames to {} at {} fps (original video {} fps)'.format(
|
|
535
|
-
len(detected_frame_files), options.output_video_file,rendering_fs,fs))
|
|
536
|
-
frames_to_video(detected_frame_files,
|
|
537
|
-
rendering_fs,
|
|
538
|
-
options.output_video_file,
|
|
539
|
-
codec_spec=options.fourcc)
|
|
540
|
-
|
|
541
|
-
# Possibly clean up rendered frames
|
|
542
|
-
_clean_up_rendered_frames(options,rendering_output_dir,detected_frame_files)
|
|
543
|
-
|
|
544
|
-
# ...if we're rendering video
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
## (Optionally) delete the extracted frames
|
|
548
|
-
|
|
549
|
-
_clean_up_extracted_frames(options, frame_output_folder, frame_filenames)
|
|
139
|
+
if options.verbose:
|
|
140
|
+
print('Running MegaDetector for folder {}'.format(options.input_video_file))
|
|
550
141
|
|
|
551
|
-
|
|
142
|
+
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
552
143
|
|
|
144
|
+
def frame_callback(image_np,image_id):
|
|
145
|
+
return detector.generate_detections_one_image(image_np,
|
|
146
|
+
image_id,
|
|
147
|
+
detection_threshold=options.json_confidence_threshold,
|
|
148
|
+
augment=options.augment)
|
|
553
149
|
|
|
554
|
-
def process_video_folder(options):
|
|
555
150
|
"""
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
an entry for each *frame* (identical to what's created by process_video()), and
|
|
561
|
-
one with an entry for each *video* (which is more suitable for, e.g., reading into
|
|
562
|
-
Timelapse).
|
|
151
|
+
[md_results] will be dict with keys 'video_filenames' (list of str), 'frame_rates' (list of floats),
|
|
152
|
+
'results' (list of list of dicts). 'video_filenames' will contain *relative* filenames.
|
|
153
|
+
'results' is a list (one element per video) of lists (one element per frame) of whatever the
|
|
154
|
+
callback returns, typically (but not necessarily) dicts in the MD results format.
|
|
563
155
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
including filenames; see ProcessVideoOptions for details
|
|
156
|
+
For failed videos, the frame rate will be represented by -1, and "results"
|
|
157
|
+
will be a dict with at least the key "failure".
|
|
567
158
|
"""
|
|
159
|
+
if os.path.isfile(options.input_video_file):
|
|
568
160
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if options.model_file == 'no_detection' and not options.keep_extracted_frames:
|
|
578
|
-
print('Warning: you asked for no detection, but did not specify keep_extracted_frames, this is a no-op')
|
|
579
|
-
return
|
|
580
|
-
|
|
581
|
-
if options.model_file != 'no_detection':
|
|
582
|
-
assert options.output_json_file is not None, \
|
|
583
|
-
'When processing a folder, you must specify an output .json file'
|
|
584
|
-
assert options.output_json_file.endswith('.json')
|
|
585
|
-
video_json = options.output_json_file
|
|
586
|
-
frames_json = options.output_json_file.replace('.json','.frames.json')
|
|
587
|
-
os.makedirs(os.path.dirname(video_json),exist_ok=True)
|
|
588
|
-
|
|
589
|
-
# Track whether frame and rendering folders were created by this script
|
|
590
|
-
caller_provided_frame_output_folder = (options.frame_folder is not None)
|
|
591
|
-
caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
|
|
592
|
-
|
|
593
|
-
# This does not create any folders, just defines temporary folder names in
|
|
594
|
-
# case we need them.
|
|
595
|
-
temporary_folder_info = _select_temporary_output_folders(options)
|
|
596
|
-
|
|
597
|
-
frame_output_folder = None
|
|
598
|
-
image_file_names = None
|
|
599
|
-
video_filename_to_fs = {}
|
|
161
|
+
video_folder = os.path.dirname(options.input_video_file)
|
|
162
|
+
video_bn = os.path.basename(options.input_video_file)
|
|
163
|
+
md_results = run_callback_on_frames_for_folder(input_video_folder=video_folder,
|
|
164
|
+
frame_callback=frame_callback,
|
|
165
|
+
every_n_frames=every_n_frames_param,
|
|
166
|
+
verbose=options.verbose,
|
|
167
|
+
files_to_process_relative=[video_bn])
|
|
600
168
|
|
|
601
|
-
if options.time_sample is not None:
|
|
602
|
-
every_n_frames_param = -1 * options.time_sample
|
|
603
169
|
else:
|
|
604
|
-
every_n_frames_param = options.frame_sample
|
|
605
|
-
|
|
606
|
-
# Run MD in memory if we don't need to generate frames
|
|
607
|
-
#
|
|
608
|
-
# Currently if we're generating an output video, we need to generate frames on disk first.
|
|
609
|
-
if (not options.keep_extracted_frames and \
|
|
610
|
-
not options.render_output_video and \
|
|
611
|
-
not options.force_on_disk_frame_extraction):
|
|
612
|
-
|
|
613
|
-
if options.verbose:
|
|
614
|
-
print('Running MegaDetector in memory for folder {}'.format(options.input_video_file))
|
|
615
|
-
|
|
616
|
-
if options.frame_folder is not None:
|
|
617
|
-
print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
|
|
618
|
-
'not; no raw frames will be written')
|
|
619
|
-
|
|
620
|
-
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
621
170
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
image_id,
|
|
625
|
-
detection_threshold=options.json_confidence_threshold,
|
|
626
|
-
augment=options.augment)
|
|
171
|
+
assert os.path.isdir(options.input_video_file), \
|
|
172
|
+
'{} is neither a file nor a folder'.format(options.input_video_file)
|
|
627
173
|
|
|
174
|
+
video_folder = options.input_video_file
|
|
628
175
|
md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
|
|
629
176
|
frame_callback=frame_callback,
|
|
630
177
|
every_n_frames=every_n_frames_param,
|
|
631
178
|
verbose=options.verbose)
|
|
632
179
|
|
|
633
|
-
|
|
180
|
+
print('Finished running MD on videos')
|
|
634
181
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
video_filename_to_fs[video_filename] = md_results['frame_rates'][i_video]
|
|
182
|
+
video_results = md_results['results']
|
|
183
|
+
video_filenames = md_results['video_filenames']
|
|
184
|
+
video_frame_rates = md_results['frame_rates']
|
|
639
185
|
|
|
640
|
-
|
|
186
|
+
assert len(video_results) == len(video_filenames)
|
|
187
|
+
assert len(video_results) == len(video_frame_rates)
|
|
641
188
|
|
|
642
|
-
|
|
643
|
-
for frame_results in video_results:
|
|
644
|
-
_add_frame_numbers_to_results(frame_results)
|
|
645
|
-
all_frame_results.extend(frame_results)
|
|
189
|
+
video_list_md_format = []
|
|
646
190
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
frames_json,
|
|
650
|
-
relative_path_base=None,
|
|
651
|
-
detector_file=options.model_file)
|
|
191
|
+
# i_video = 0; results_this_video = video_results[i_video]
|
|
192
|
+
for i_video,results_this_video in enumerate(video_results):
|
|
652
193
|
|
|
653
|
-
|
|
194
|
+
video_fn = video_filenames[i_video]
|
|
654
195
|
|
|
655
|
-
|
|
196
|
+
im = {}
|
|
197
|
+
im['file'] = video_fn
|
|
198
|
+
im['frame_rate'] = video_frame_rates[i_video]
|
|
199
|
+
im['frames_processed'] = []
|
|
656
200
|
|
|
657
|
-
if
|
|
658
|
-
print('Extracting frames for folder {}'.format(options.input_video_file))
|
|
201
|
+
if isinstance(results_this_video,dict):
|
|
659
202
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
frame_output_folder = temporary_folder_info['frame_output_folder']
|
|
664
|
-
|
|
665
|
-
os.makedirs(frame_output_folder, exist_ok=True)
|
|
666
|
-
|
|
667
|
-
frame_filenames, fs, video_filenames = \
|
|
668
|
-
video_folder_to_frames(input_folder=options.input_video_file,
|
|
669
|
-
output_folder_base=frame_output_folder,
|
|
670
|
-
recursive=options.recursive,
|
|
671
|
-
overwrite=(not options.reuse_frames_if_available),
|
|
672
|
-
n_threads=options.n_cores,
|
|
673
|
-
every_n_frames=every_n_frames_param,
|
|
674
|
-
verbose=options.verbose,
|
|
675
|
-
quality=options.quality,
|
|
676
|
-
max_width=options.max_width,
|
|
677
|
-
frames_to_extract=options.frames_to_extract,
|
|
678
|
-
allow_empty_videos=options.allow_empty_videos)
|
|
679
|
-
|
|
680
|
-
for i_video,video_filename_abs in enumerate(video_filenames):
|
|
681
|
-
video_filename_relative = os.path.relpath(video_filename_abs,options.input_video_file)
|
|
682
|
-
video_filename_relative = video_filename_relative.replace('\\','/')
|
|
683
|
-
assert video_filename_relative not in video_filename_to_fs
|
|
684
|
-
video_filename_to_fs[video_filename_relative] = fs[i_video]
|
|
685
|
-
|
|
686
|
-
print('Extracted frames for {} videos'.format(len(set(video_filenames))))
|
|
687
|
-
image_file_names = list(itertools.chain.from_iterable(frame_filenames))
|
|
688
|
-
|
|
689
|
-
if len(image_file_names) == 0:
|
|
690
|
-
if len(video_filenames) == 0:
|
|
691
|
-
print('No videos found in folder {}'.format(options.input_video_file))
|
|
692
|
-
else:
|
|
693
|
-
print('No frames extracted from folder {}, this may be due to an '\
|
|
694
|
-
'unsupported video codec'.format(options.input_video_file))
|
|
695
|
-
return
|
|
696
|
-
|
|
697
|
-
if options.debug_max_frames is not None and options.debug_max_frames > 0:
|
|
698
|
-
image_file_names = image_file_names[0:options.debug_max_frames]
|
|
699
|
-
|
|
700
|
-
if options.model_file == 'no_detection':
|
|
701
|
-
assert options.keep_extracted_frames, \
|
|
702
|
-
'Internal error: keep_extracted_frames not set, but no model specified'
|
|
703
|
-
return
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
## Run MegaDetector on the extracted frames
|
|
707
|
-
|
|
708
|
-
if options.reuse_results_if_available and \
|
|
709
|
-
os.path.isfile(frames_json):
|
|
710
|
-
|
|
711
|
-
print('Bypassing inference, loading results from {}'.format(frames_json))
|
|
712
|
-
with open(frames_json,'r') as f:
|
|
713
|
-
results = json.load(f)
|
|
203
|
+
assert 'failure' in results_this_video
|
|
204
|
+
im['failure'] = results_this_video['failure']
|
|
205
|
+
im['detections'] = None
|
|
714
206
|
|
|
715
207
|
else:
|
|
716
208
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
results = run_detector_batch.load_and_run_detector_batch(
|
|
720
|
-
options.model_file,
|
|
721
|
-
image_file_names,
|
|
722
|
-
confidence_threshold=options.json_confidence_threshold,
|
|
723
|
-
n_cores=options.n_cores,
|
|
724
|
-
class_mapping_filename=options.class_mapping_filename,
|
|
725
|
-
quiet=True,
|
|
726
|
-
augment=options.augment,
|
|
727
|
-
image_size=options.image_size,
|
|
728
|
-
detector_options=options.detector_options)
|
|
729
|
-
|
|
730
|
-
_add_frame_numbers_to_results(results)
|
|
731
|
-
|
|
732
|
-
run_detector_batch.write_results_to_file(
|
|
733
|
-
results,
|
|
734
|
-
frames_json,
|
|
735
|
-
relative_path_base=frame_output_folder,
|
|
736
|
-
detector_file=options.model_file)
|
|
737
|
-
|
|
738
|
-
# ...if we're re-using existing results / running MD
|
|
739
|
-
|
|
740
|
-
# ...if we're running MD on in-memory frames vs. extracting frames to disk
|
|
209
|
+
im['detections'] = []
|
|
741
210
|
|
|
742
|
-
|
|
211
|
+
# results_one_frame = results_this_video[0]
|
|
212
|
+
for results_one_frame in results_this_video:
|
|
743
213
|
|
|
744
|
-
|
|
745
|
-
frame_to_video_options.include_all_processed_frames = options.include_all_processed_frames
|
|
214
|
+
assert results_one_frame['file'].startswith(video_fn)
|
|
746
215
|
|
|
747
|
-
|
|
748
|
-
frame_results_to_video_results(frames_json,
|
|
749
|
-
video_json,
|
|
750
|
-
options=frame_to_video_options,
|
|
751
|
-
video_filename_to_frame_rate=video_filename_to_fs)
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
## (Optionally) render output videos
|
|
755
|
-
|
|
756
|
-
if options.render_output_video:
|
|
757
|
-
|
|
758
|
-
# Render detections to images
|
|
759
|
-
if (caller_provided_rendering_output_folder):
|
|
760
|
-
rendering_output_dir = options.frame_rendering_folder
|
|
761
|
-
else:
|
|
762
|
-
rendering_output_dir = temporary_folder_info['rendering_output_folder']
|
|
763
|
-
|
|
764
|
-
os.makedirs(rendering_output_dir,exist_ok=True)
|
|
765
|
-
|
|
766
|
-
detected_frame_files = visualize_detector_output.visualize_detector_output(
|
|
767
|
-
detector_output_path=frames_json,
|
|
768
|
-
out_dir=rendering_output_dir,
|
|
769
|
-
images_dir=frame_output_folder,
|
|
770
|
-
confidence_threshold=options.rendering_confidence_threshold,
|
|
771
|
-
preserve_path_structure=True,
|
|
772
|
-
output_image_width=-1)
|
|
773
|
-
detected_frame_files = [s.replace('\\','/') for s in detected_frame_files]
|
|
774
|
-
|
|
775
|
-
# Choose an output folder
|
|
776
|
-
output_folder_is_input_folder = False
|
|
777
|
-
if options.output_video_file is not None:
|
|
778
|
-
if os.path.isfile(options.output_video_file):
|
|
779
|
-
raise ValueError('Rendering videos for a folder, but an existing file was specified as output')
|
|
780
|
-
elif options.output_video_file == options.input_video_file:
|
|
781
|
-
output_folder_is_input_folder = True
|
|
782
|
-
output_video_folder = options.input_video_file
|
|
783
|
-
else:
|
|
784
|
-
os.makedirs(options.output_video_file,exist_ok=True)
|
|
785
|
-
output_video_folder = options.output_video_file
|
|
786
|
-
else:
|
|
787
|
-
output_folder_is_input_folder = True
|
|
788
|
-
output_video_folder = options.input_video_file
|
|
216
|
+
frame_number = _filename_to_frame_number(results_one_frame['file'])
|
|
789
217
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
# TODO: parallelize this loop
|
|
793
|
-
#
|
|
794
|
-
# i_video=0; input_video_file_abs = video_filenames[i_video]
|
|
795
|
-
for i_video,input_video_file_abs in enumerate(video_filenames):
|
|
218
|
+
assert frame_number not in im['frames_processed'], \
|
|
219
|
+
'Received the same frame twice for video {}'.format(im['file'])
|
|
796
220
|
|
|
797
|
-
|
|
221
|
+
im['frames_processed'].append(frame_number)
|
|
798
222
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
elif options.frame_sample is None:
|
|
802
|
-
rendering_fs = video_fs
|
|
803
|
-
else:
|
|
804
|
-
# If the original video was 30fps and we sampled every 10th frame,
|
|
805
|
-
# render at 3fps
|
|
806
|
-
rendering_fs = video_fs / options.frame_sample
|
|
223
|
+
for det in results_one_frame['detections']:
|
|
224
|
+
det['frame_number'] = frame_number
|
|
807
225
|
|
|
808
|
-
|
|
809
|
-
|
|
226
|
+
# This is a no-op if there were no above-threshold detections
|
|
227
|
+
# in this frame
|
|
228
|
+
im['detections'].extend(results_one_frame['detections'])
|
|
810
229
|
|
|
811
|
-
|
|
812
|
-
assert os.path.isdir(video_frame_output_folder), \
|
|
813
|
-
'Could not find frame folder for video {}'.format(input_video_file_relative)
|
|
230
|
+
# ...for each frame
|
|
814
231
|
|
|
815
|
-
|
|
816
|
-
video_frame_files = [fn for fn in detected_frame_files if \
|
|
817
|
-
fn.startswith(video_frame_output_folder)]
|
|
818
|
-
assert len(video_frame_files) > 0, 'Could not find rendered frames for video {}'.format(
|
|
819
|
-
input_video_file_relative)
|
|
232
|
+
# ...was this a failed video?
|
|
820
233
|
|
|
821
|
-
|
|
822
|
-
if output_folder_is_input_folder:
|
|
823
|
-
video_output_file = insert_before_extension(input_video_file_abs,'annotated','_')
|
|
824
|
-
else:
|
|
825
|
-
video_output_file = os.path.join(output_video_folder,input_video_file_relative)
|
|
234
|
+
video_list_md_format.append(im)
|
|
826
235
|
|
|
827
|
-
|
|
236
|
+
# ...for each video
|
|
828
237
|
|
|
829
|
-
|
|
830
|
-
print('Rendering detections for video {} to {} at {} fps (original video {} fps)'.format(
|
|
831
|
-
input_video_file_relative,video_output_file,rendering_fs,video_fs))
|
|
832
|
-
frames_to_video(video_frame_files,
|
|
833
|
-
rendering_fs,
|
|
834
|
-
video_output_file,
|
|
835
|
-
codec_spec=options.fourcc)
|
|
238
|
+
im['frames_processed'] = sorted(im['frames_processed'])
|
|
836
239
|
|
|
837
|
-
|
|
240
|
+
run_detector_batch.write_results_to_file(
|
|
241
|
+
video_list_md_format,
|
|
242
|
+
options.output_json_file,
|
|
243
|
+
relative_path_base=None,
|
|
244
|
+
detector_file=options.model_file)
|
|
838
245
|
|
|
839
|
-
|
|
840
|
-
|
|
246
|
+
validation_options = ValidateBatchResultsOptions()
|
|
247
|
+
validation_options.raise_errors = True
|
|
248
|
+
validation_options.check_image_existence = True
|
|
249
|
+
validation_options.return_data = False
|
|
250
|
+
validation_options.relative_path_base = video_folder
|
|
251
|
+
validate_batch_results(options.output_json_file,options=validation_options)
|
|
841
252
|
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
## (Optionally) delete the extracted frames
|
|
846
|
-
_clean_up_extracted_frames(options, frame_output_folder, image_file_names)
|
|
847
|
-
|
|
848
|
-
# ...process_video_folder()
|
|
253
|
+
# ...process_videos()
|
|
849
254
|
|
|
850
255
|
|
|
851
256
|
def options_to_command(options):
|
|
@@ -860,62 +265,27 @@ def options_to_command(options):
|
|
|
860
265
|
|
|
861
266
|
:meta private:
|
|
862
267
|
"""
|
|
268
|
+
|
|
863
269
|
cmd = 'python process_video.py'
|
|
864
270
|
cmd += ' "' + options.model_file + '"'
|
|
865
271
|
cmd += ' "' + options.input_video_file + '"'
|
|
866
272
|
|
|
867
273
|
if options.recursive:
|
|
868
274
|
cmd += ' --recursive'
|
|
869
|
-
if options.frame_folder is not None:
|
|
870
|
-
cmd += ' --frame_folder' + ' "' + options.frame_folder + '"'
|
|
871
|
-
if options.frame_rendering_folder is not None:
|
|
872
|
-
cmd += ' --frame_rendering_folder' + ' "' + options.frame_rendering_folder + '"'
|
|
873
275
|
if options.output_json_file is not None:
|
|
874
276
|
cmd += ' --output_json_file' + ' "' + options.output_json_file + '"'
|
|
875
|
-
if options.output_video_file is not None:
|
|
876
|
-
cmd += ' --output_video_file' + ' "' + options.output_video_file + '"'
|
|
877
|
-
if options.keep_extracted_frames:
|
|
878
|
-
cmd += ' --keep_extracted_frames'
|
|
879
|
-
if options.reuse_results_if_available:
|
|
880
|
-
cmd += ' --reuse_results_if_available'
|
|
881
|
-
if options.reuse_frames_if_available:
|
|
882
|
-
cmd += ' --reuse_frames_if_available'
|
|
883
|
-
if options.render_output_video:
|
|
884
|
-
cmd += ' --render_output_video'
|
|
885
|
-
if options.keep_rendered_frames:
|
|
886
|
-
cmd += ' --keep_rendered_frames'
|
|
887
|
-
if options.rendering_confidence_threshold is not None:
|
|
888
|
-
cmd += ' --rendering_confidence_threshold ' + str(options.rendering_confidence_threshold)
|
|
889
277
|
if options.json_confidence_threshold is not None:
|
|
890
278
|
cmd += ' --json_confidence_threshold ' + str(options.json_confidence_threshold)
|
|
891
279
|
if options.n_cores is not None:
|
|
892
280
|
cmd += ' --n_cores ' + str(options.n_cores)
|
|
893
281
|
if options.frame_sample is not None:
|
|
894
282
|
cmd += ' --frame_sample ' + str(options.frame_sample)
|
|
895
|
-
if options.frames_to_extract is not None:
|
|
896
|
-
cmd += ' --frames_to_extract '
|
|
897
|
-
if isinstance(options.frames_to_extract,int):
|
|
898
|
-
frames_to_extract = [options.frames_to_extract]
|
|
899
|
-
else:
|
|
900
|
-
frames_to_extract = options.frames_to_extract
|
|
901
|
-
for frame_number in frames_to_extract:
|
|
902
|
-
cmd += ' {}'.format(frame_number)
|
|
903
283
|
if options.debug_max_frames is not None:
|
|
904
284
|
cmd += ' --debug_max_frames ' + str(options.debug_max_frames)
|
|
905
285
|
if options.class_mapping_filename is not None:
|
|
906
286
|
cmd += ' --class_mapping_filename ' + str(options.class_mapping_filename)
|
|
907
|
-
if options.fourcc is not None:
|
|
908
|
-
cmd += ' --fourcc ' + options.fourcc
|
|
909
|
-
if options.quality is not None:
|
|
910
|
-
cmd += ' --quality ' + str(options.quality)
|
|
911
|
-
if options.max_width is not None:
|
|
912
|
-
cmd += ' --max_width ' + str(options.max_width)
|
|
913
287
|
if options.verbose:
|
|
914
288
|
cmd += ' --verbose'
|
|
915
|
-
if options.force_extracted_frame_folder_deletion:
|
|
916
|
-
cmd += ' --force_extracted_frame_folder_deletion'
|
|
917
|
-
if options.force_rendered_frame_folder_deletion:
|
|
918
|
-
cmd += ' --force_rendered_frame_folder_deletion'
|
|
919
289
|
if options.detector_options is not None and len(options.detector_options) > 0:
|
|
920
290
|
cmd += '--detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
921
291
|
|
|
@@ -930,145 +300,58 @@ if False:
|
|
|
930
300
|
|
|
931
301
|
#%% Process a folder of videos
|
|
932
302
|
|
|
303
|
+
import os
|
|
304
|
+
from megadetector.detection.process_video import \
|
|
305
|
+
process_videos, ProcessVideoOptions
|
|
306
|
+
|
|
933
307
|
model_file = 'MDV5A'
|
|
934
|
-
|
|
935
|
-
# input_dir = r'G:\temp\md-test-package\md-test-images\video-samples'
|
|
936
|
-
input_dir = os.path.expanduser('~/AppData/Local/Temp/md-tests/md-test-images/video-samples')
|
|
308
|
+
input_dir = r"G:\temp\md-test-images\video-samples"
|
|
937
309
|
assert os.path.isdir(input_dir)
|
|
938
310
|
|
|
939
|
-
|
|
940
|
-
os.makedirs(output_base,exist_ok=True)
|
|
941
|
-
|
|
942
|
-
frame_folder = os.path.join(output_base,'frames')
|
|
943
|
-
rendering_folder = os.path.join(output_base,'rendered-frames')
|
|
944
|
-
output_json_file = os.path.join(output_base,'video-test.json')
|
|
945
|
-
output_video_folder = os.path.join(output_base,'output_videos')
|
|
946
|
-
|
|
311
|
+
output_json_file = os.path.join(input_dir,'mdv5a-video.json')
|
|
947
312
|
|
|
948
313
|
print('Processing folder {}'.format(input_dir))
|
|
949
314
|
|
|
950
315
|
options = ProcessVideoOptions()
|
|
316
|
+
options.json_confidence_threshold = 0.05
|
|
951
317
|
options.model_file = model_file
|
|
952
318
|
options.input_video_file = input_dir
|
|
953
|
-
options.output_video_file = output_video_folder
|
|
954
319
|
options.output_json_file = output_json_file
|
|
955
320
|
options.recursive = True
|
|
956
|
-
options.
|
|
957
|
-
options.
|
|
958
|
-
options.quality = None # 90
|
|
959
|
-
options.frame_sample = 10
|
|
960
|
-
options.max_width = None # 1280
|
|
961
|
-
options.n_cores = 4
|
|
321
|
+
# options.frame_sample = 10
|
|
322
|
+
options.time_sample = 2
|
|
962
323
|
options.verbose = True
|
|
963
|
-
options.render_output_video = False
|
|
964
|
-
options.frame_folder = frame_folder
|
|
965
|
-
options.frame_rendering_folder = rendering_folder
|
|
966
|
-
options.keep_extracted_frames = False
|
|
967
|
-
options.keep_rendered_frames = False
|
|
968
|
-
options.force_extracted_frame_folder_deletion = False
|
|
969
|
-
options.force_rendered_frame_folder_deletion = False
|
|
970
|
-
options.fourcc = 'mp4v'
|
|
971
|
-
options.force_on_disk_frame_extraction = False
|
|
972
|
-
# options.rendering_confidence_threshold = 0.15
|
|
973
|
-
|
|
974
|
-
cmd = options_to_command(options); print(cmd)
|
|
975
324
|
|
|
976
|
-
|
|
977
|
-
process_video_folder(options)
|
|
325
|
+
process_videos(options)
|
|
978
326
|
|
|
979
327
|
|
|
980
328
|
#%% Process a single video
|
|
981
329
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
output_base = r'g:\temp\video_test'
|
|
988
|
-
frame_folder = os.path.join(output_base,'frames')
|
|
989
|
-
rendering_folder = os.path.join(output_base,'rendered-frames')
|
|
990
|
-
output_json_file = os.path.join(output_base,'video-test.json')
|
|
991
|
-
output_video_file = os.path.join(output_base,'output_video.mp4')
|
|
992
|
-
|
|
993
|
-
options = ProcessVideoOptions()
|
|
994
|
-
options.model_file = model_file
|
|
995
|
-
options.input_video_file = input_video_file
|
|
996
|
-
options.render_output_video = True
|
|
997
|
-
options.output_video_file = output_video_file
|
|
998
|
-
options.output_json_file = output_json_file
|
|
999
|
-
options.verbose = True
|
|
1000
|
-
options.quality = 75
|
|
1001
|
-
options.frame_sample = 10
|
|
1002
|
-
options.max_width = 1600
|
|
1003
|
-
options.frame_folder = frame_folder
|
|
1004
|
-
options.frame_rendering_folder = rendering_folder
|
|
1005
|
-
options.keep_extracted_frames = False
|
|
1006
|
-
options.keep_rendered_frames = False
|
|
1007
|
-
options.force_extracted_frame_folder_deletion = True
|
|
1008
|
-
options.force_rendered_frame_folder_deletion = True
|
|
1009
|
-
options.fourcc = 'mp4v'
|
|
1010
|
-
# options.rendering_confidence_threshold = 0.15
|
|
1011
|
-
|
|
1012
|
-
cmd = options_to_command(options); print(cmd)
|
|
1013
|
-
|
|
1014
|
-
# import clipboard; clipboard.copy(cmd)
|
|
1015
|
-
process_video(options)
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
#%% Extract specific frames from a single video, no detection
|
|
1019
|
-
|
|
1020
|
-
fn = r'g:\temp\test-videos\person_and_dog\DSCF0064.AVI'
|
|
1021
|
-
assert os.path.isfile(fn)
|
|
1022
|
-
model_file = 'no_detection'
|
|
1023
|
-
input_video_file = fn
|
|
1024
|
-
|
|
1025
|
-
output_base = r'g:\temp\video_test'
|
|
1026
|
-
frame_folder = os.path.join(output_base,'frames')
|
|
1027
|
-
output_video_file = os.path.join(output_base,'output_videos.mp4')
|
|
1028
|
-
|
|
1029
|
-
options = ProcessVideoOptions()
|
|
1030
|
-
options.model_file = model_file
|
|
1031
|
-
options.input_video_file = input_video_file
|
|
1032
|
-
options.verbose = True
|
|
1033
|
-
options.quality = 90
|
|
1034
|
-
options.frame_sample = None
|
|
1035
|
-
options.frames_to_extract = [0,100]
|
|
1036
|
-
options.max_width = None
|
|
1037
|
-
options.frame_folder = frame_folder
|
|
1038
|
-
options.keep_extracted_frames = True
|
|
1039
|
-
|
|
1040
|
-
cmd = options_to_command(options); print(cmd)
|
|
330
|
+
import os
|
|
331
|
+
from megadetector.detection.process_video import \
|
|
332
|
+
process_videos, ProcessVideoOptions
|
|
333
|
+
from megadetector.detection.video_utils import find_videos
|
|
1041
334
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
#%% Extract specific frames from a folder, no detection
|
|
335
|
+
model_file = 'MDV5A'
|
|
336
|
+
input_dir = r"G:\temp\md-test-images\video-samples"
|
|
337
|
+
assert os.path.isdir(input_dir)
|
|
338
|
+
video_fn_abs = find_videos(input_dir)[0]
|
|
1047
339
|
|
|
1048
|
-
|
|
1049
|
-
assert os.path.isdir(fn)
|
|
1050
|
-
model_file = 'no_detection'
|
|
1051
|
-
input_video_file = fn
|
|
340
|
+
output_json_file = os.path.join(input_dir,'mdv5a-single-video.json')
|
|
1052
341
|
|
|
1053
|
-
|
|
1054
|
-
frame_folder = os.path.join(output_base,'frames')
|
|
1055
|
-
output_video_file = os.path.join(output_base,'output_videos.mp4')
|
|
342
|
+
print('Processing video {}'.format(video_fn_abs))
|
|
1056
343
|
|
|
1057
344
|
options = ProcessVideoOptions()
|
|
345
|
+
options.json_confidence_threshold = 0.05
|
|
1058
346
|
options.model_file = model_file
|
|
1059
|
-
options.input_video_file =
|
|
347
|
+
options.input_video_file = video_fn_abs
|
|
348
|
+
options.output_json_file = output_json_file
|
|
349
|
+
options.recursive = True
|
|
350
|
+
# options.frame_sample = 10
|
|
351
|
+
options.time_sample = 2
|
|
1060
352
|
options.verbose = True
|
|
1061
|
-
options.quality = 90
|
|
1062
|
-
options.frame_sample = None
|
|
1063
|
-
options.frames_to_extract = [0,100]
|
|
1064
|
-
options.max_width = None
|
|
1065
|
-
options.frame_folder = frame_folder
|
|
1066
|
-
options.keep_extracted_frames = True
|
|
1067
|
-
|
|
1068
|
-
cmd = options_to_command(options); print(cmd)
|
|
1069
353
|
|
|
1070
|
-
|
|
1071
|
-
process_video(options)
|
|
354
|
+
process_videos(options)
|
|
1072
355
|
|
|
1073
356
|
|
|
1074
357
|
#%% Command-line driver
|
|
@@ -1092,65 +375,9 @@ def main(): # noqa
|
|
|
1092
375
|
help='recurse into [input_video_file]; only meaningful if a folder '\
|
|
1093
376
|
'is specified as input')
|
|
1094
377
|
|
|
1095
|
-
parser.add_argument('--frame_folder', type=str, default=None,
|
|
1096
|
-
help='folder to use for intermediate frame storage, defaults to a folder '\
|
|
1097
|
-
'in the system temporary folder')
|
|
1098
|
-
|
|
1099
|
-
parser.add_argument('--frame_rendering_folder', type=str, default=None,
|
|
1100
|
-
help='folder to use for rendered frame storage, defaults to a folder in '\
|
|
1101
|
-
'the system temporary folder')
|
|
1102
|
-
|
|
1103
378
|
parser.add_argument('--output_json_file', type=str,
|
|
1104
379
|
default=None, help='.json output file, defaults to [video file].json')
|
|
1105
380
|
|
|
1106
|
-
parser.add_argument('--output_video_file', type=str,
|
|
1107
|
-
default=None, help='video output file (or folder), defaults to '\
|
|
1108
|
-
'[video file].mp4 for files, or [video file]_annotated for folders')
|
|
1109
|
-
|
|
1110
|
-
parser.add_argument('--keep_extracted_frames',
|
|
1111
|
-
action='store_true', help='Disable the deletion of extracted frames')
|
|
1112
|
-
|
|
1113
|
-
parser.add_argument('--reuse_frames_if_available',
|
|
1114
|
-
action='store_true',
|
|
1115
|
-
help="Don't extract frames that are already available in the frame extraction folder")
|
|
1116
|
-
|
|
1117
|
-
parser.add_argument('--reuse_results_if_available',
|
|
1118
|
-
action='store_true',
|
|
1119
|
-
help='If the output .json files exists, and this flag is set,'\
|
|
1120
|
-
'we\'ll skip running MegaDetector')
|
|
1121
|
-
|
|
1122
|
-
parser.add_argument('--render_output_video', action='store_true',
|
|
1123
|
-
help='enable video output rendering (not rendered by default)')
|
|
1124
|
-
|
|
1125
|
-
parser.add_argument('--fourcc', default=default_fourcc,
|
|
1126
|
-
help=f'fourcc code to use for video encoding (default {default_fourcc}), ' + \
|
|
1127
|
-
'only used if render_output_video is True')
|
|
1128
|
-
|
|
1129
|
-
parser.add_argument('--keep_rendered_frames',
|
|
1130
|
-
action='store_true', help='Disable the deletion of rendered (w/boxes) frames')
|
|
1131
|
-
|
|
1132
|
-
parser.add_argument('--force_extracted_frame_folder_deletion',
|
|
1133
|
-
action='store_true', help='By default, when keep_extracted_frames is False, we '\
|
|
1134
|
-
'delete the frames, but leave the (probably-empty) folder in place. This option '\
|
|
1135
|
-
'forces deletion of the folder as well. Use at your own risk; does not check '\
|
|
1136
|
-
'whether other files were present in the folder.')
|
|
1137
|
-
|
|
1138
|
-
parser.add_argument('--force_rendered_frame_folder_deletion',
|
|
1139
|
-
action='store_true', help='By default, when keep_rendered_frames is False, we '\
|
|
1140
|
-
'delete the frames, but leave the (probably-empty) folder in place. This option '\
|
|
1141
|
-
'forces deletion of the folder as well. Use at your own risk; does not check '\
|
|
1142
|
-
'whether other files were present in the folder.')
|
|
1143
|
-
|
|
1144
|
-
parser.add_argument('--rendering_confidence_threshold', type=float,
|
|
1145
|
-
default=None,
|
|
1146
|
-
help="don't render boxes with confidence below this threshold " + \
|
|
1147
|
-
"(defaults to choosing based on the MD version)")
|
|
1148
|
-
|
|
1149
|
-
parser.add_argument('--rendering_fs', type=float,
|
|
1150
|
-
default=None,
|
|
1151
|
-
help='force a specific frame rate for output videos (only relevant when using '\
|
|
1152
|
-
'--render_output_video) (defaults to the original frame rate)')
|
|
1153
|
-
|
|
1154
381
|
parser.add_argument('--json_confidence_threshold', type=float,
|
|
1155
382
|
default=default_options.json_confidence_threshold,
|
|
1156
383
|
help="don't include boxes in the .json file with confidence "\
|
|
@@ -1166,26 +393,12 @@ def main(): # noqa
|
|
|
1166
393
|
|
|
1167
394
|
parser.add_argument('--frame_sample', type=int,
|
|
1168
395
|
default=None, help='process every Nth frame (defaults to every frame), mutually exclusive '\
|
|
1169
|
-
'with --
|
|
1170
|
-
|
|
1171
|
-
parser.add_argument('--frames_to_extract', nargs='+', type=int,
|
|
1172
|
-
default=None, help='extract specific frames (one or more ints), mutually exclusive '\
|
|
1173
|
-
'with --frame_sample and --time_sample.')
|
|
396
|
+
'with --time_sample.')
|
|
1174
397
|
|
|
1175
398
|
parser.add_argument('--time_sample', type=float,
|
|
1176
399
|
default=None, help='process frames every N seconds; this is converted to a '\
|
|
1177
400
|
'frame sampling rate, so it may not be exactly the requested interval in seconds. '\
|
|
1178
|
-
'mutually exclusive with --frame_sample
|
|
1179
|
-
|
|
1180
|
-
parser.add_argument('--quality', type=int,
|
|
1181
|
-
default=default_options.quality,
|
|
1182
|
-
help=f'JPEG quality for extracted frames (defaults to {default_options.quality}), ' + \
|
|
1183
|
-
'use -1 to force no quality setting')
|
|
1184
|
-
|
|
1185
|
-
parser.add_argument('--max_width', type=int,
|
|
1186
|
-
default=default_options.max_width,
|
|
1187
|
-
help='Resize frames larger than this before writing (defaults to {})'.format(
|
|
1188
|
-
default_options.max_width))
|
|
401
|
+
'mutually exclusive with --frame_sample')
|
|
1189
402
|
|
|
1190
403
|
parser.add_argument('--debug_max_frames', type=int,
|
|
1191
404
|
default=-1, help='Trim to N frames for debugging (impacts model execution, '\
|
|
@@ -1211,12 +424,6 @@ def main(): # noqa
|
|
|
1211
424
|
action='store_true',
|
|
1212
425
|
help='Enable image augmentation')
|
|
1213
426
|
|
|
1214
|
-
parser.add_argument('--include_all_processed_frames',
|
|
1215
|
-
action='store_true',
|
|
1216
|
-
help='When processing a folder of videos, this flag indicates that the output '\
|
|
1217
|
-
'should include results for every frame that was processed, rather than just '\
|
|
1218
|
-
'one representative frame for each detection category per video.')
|
|
1219
|
-
|
|
1220
427
|
parser.add_argument('--allow_empty_videos',
|
|
1221
428
|
action='store_true',
|
|
1222
429
|
help='By default, videos with no retrievable frames cause an error, this makes it a warning')
|
|
@@ -1239,13 +446,13 @@ def main(): # noqa
|
|
|
1239
446
|
options.detector_options = parse_kvp_list(args.detector_options)
|
|
1240
447
|
|
|
1241
448
|
if os.path.isdir(options.input_video_file):
|
|
1242
|
-
|
|
449
|
+
process_videos(options)
|
|
1243
450
|
else:
|
|
1244
451
|
assert os.path.isfile(options.input_video_file), \
|
|
1245
452
|
'{} is not a valid file or folder name'.format(options.input_video_file)
|
|
1246
453
|
assert not options.recursive, \
|
|
1247
454
|
'--recursive is only meaningful when processing a folder'
|
|
1248
|
-
|
|
455
|
+
process_videos(options)
|
|
1249
456
|
|
|
1250
457
|
if __name__ == '__main__':
|
|
1251
458
|
main()
|