megadetector 10.0.15__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.
- megadetector/__init__.py +0 -0
- megadetector/api/__init__.py +0 -0
- megadetector/api/batch_processing/integration/digiKam/setup.py +6 -0
- megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +465 -0
- megadetector/api/batch_processing/integration/eMammal/test_scripts/config_template.py +5 -0
- megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +125 -0
- megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
- megadetector/classification/__init__.py +0 -0
- megadetector/classification/aggregate_classifier_probs.py +108 -0
- megadetector/classification/analyze_failed_images.py +227 -0
- megadetector/classification/cache_batchapi_outputs.py +198 -0
- megadetector/classification/create_classification_dataset.py +626 -0
- megadetector/classification/crop_detections.py +516 -0
- megadetector/classification/csv_to_json.py +226 -0
- megadetector/classification/detect_and_crop.py +853 -0
- megadetector/classification/efficientnet/__init__.py +9 -0
- megadetector/classification/efficientnet/model.py +415 -0
- megadetector/classification/efficientnet/utils.py +608 -0
- megadetector/classification/evaluate_model.py +520 -0
- megadetector/classification/identify_mislabeled_candidates.py +152 -0
- megadetector/classification/json_to_azcopy_list.py +63 -0
- megadetector/classification/json_validator.py +696 -0
- megadetector/classification/map_classification_categories.py +276 -0
- megadetector/classification/merge_classification_detection_output.py +509 -0
- megadetector/classification/prepare_classification_script.py +194 -0
- megadetector/classification/prepare_classification_script_mc.py +228 -0
- megadetector/classification/run_classifier.py +287 -0
- megadetector/classification/save_mislabeled.py +110 -0
- megadetector/classification/train_classifier.py +827 -0
- megadetector/classification/train_classifier_tf.py +725 -0
- megadetector/classification/train_utils.py +323 -0
- megadetector/data_management/__init__.py +0 -0
- megadetector/data_management/animl_to_md.py +161 -0
- megadetector/data_management/annotations/__init__.py +0 -0
- megadetector/data_management/annotations/annotation_constants.py +33 -0
- megadetector/data_management/camtrap_dp_to_coco.py +270 -0
- megadetector/data_management/cct_json_utils.py +566 -0
- megadetector/data_management/cct_to_md.py +184 -0
- megadetector/data_management/cct_to_wi.py +293 -0
- megadetector/data_management/coco_to_labelme.py +284 -0
- megadetector/data_management/coco_to_yolo.py +701 -0
- megadetector/data_management/databases/__init__.py +0 -0
- megadetector/data_management/databases/add_width_and_height_to_db.py +107 -0
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +210 -0
- megadetector/data_management/databases/integrity_check_json_db.py +563 -0
- megadetector/data_management/databases/subset_json_db.py +195 -0
- megadetector/data_management/generate_crops_from_cct.py +200 -0
- megadetector/data_management/get_image_sizes.py +164 -0
- megadetector/data_management/labelme_to_coco.py +559 -0
- megadetector/data_management/labelme_to_yolo.py +349 -0
- megadetector/data_management/lila/__init__.py +0 -0
- megadetector/data_management/lila/create_lila_blank_set.py +556 -0
- megadetector/data_management/lila/create_lila_test_set.py +192 -0
- megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
- megadetector/data_management/lila/download_lila_subset.py +182 -0
- megadetector/data_management/lila/generate_lila_per_image_labels.py +777 -0
- megadetector/data_management/lila/get_lila_annotation_counts.py +174 -0
- megadetector/data_management/lila/get_lila_image_counts.py +112 -0
- megadetector/data_management/lila/lila_common.py +319 -0
- megadetector/data_management/lila/test_lila_metadata_urls.py +164 -0
- megadetector/data_management/mewc_to_md.py +344 -0
- megadetector/data_management/ocr_tools.py +873 -0
- megadetector/data_management/read_exif.py +964 -0
- megadetector/data_management/remap_coco_categories.py +195 -0
- megadetector/data_management/remove_exif.py +156 -0
- megadetector/data_management/rename_images.py +194 -0
- megadetector/data_management/resize_coco_dataset.py +665 -0
- megadetector/data_management/speciesnet_to_md.py +41 -0
- megadetector/data_management/wi_download_csv_to_coco.py +247 -0
- megadetector/data_management/yolo_output_to_md_output.py +594 -0
- megadetector/data_management/yolo_to_coco.py +984 -0
- megadetector/data_management/zamba_to_md.py +188 -0
- megadetector/detection/__init__.py +0 -0
- megadetector/detection/change_detection.py +840 -0
- megadetector/detection/process_video.py +479 -0
- megadetector/detection/pytorch_detector.py +1451 -0
- megadetector/detection/run_detector.py +1267 -0
- megadetector/detection/run_detector_batch.py +2172 -0
- megadetector/detection/run_inference_with_yolov5_val.py +1314 -0
- megadetector/detection/run_md_and_speciesnet.py +1604 -0
- megadetector/detection/run_tiled_inference.py +1044 -0
- megadetector/detection/tf_detector.py +209 -0
- megadetector/detection/video_utils.py +1379 -0
- megadetector/postprocessing/__init__.py +0 -0
- megadetector/postprocessing/add_max_conf.py +72 -0
- megadetector/postprocessing/categorize_detections_by_size.py +166 -0
- megadetector/postprocessing/classification_postprocessing.py +1943 -0
- megadetector/postprocessing/combine_batch_outputs.py +249 -0
- megadetector/postprocessing/compare_batch_results.py +2110 -0
- megadetector/postprocessing/convert_output_format.py +403 -0
- megadetector/postprocessing/create_crop_folder.py +629 -0
- megadetector/postprocessing/detector_calibration.py +570 -0
- megadetector/postprocessing/generate_csv_report.py +522 -0
- megadetector/postprocessing/load_api_results.py +223 -0
- megadetector/postprocessing/md_to_coco.py +428 -0
- megadetector/postprocessing/md_to_labelme.py +351 -0
- megadetector/postprocessing/md_to_wi.py +41 -0
- megadetector/postprocessing/merge_detections.py +392 -0
- megadetector/postprocessing/postprocess_batch_results.py +2140 -0
- megadetector/postprocessing/remap_detection_categories.py +226 -0
- megadetector/postprocessing/render_detection_confusion_matrix.py +677 -0
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +206 -0
- megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +82 -0
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1665 -0
- megadetector/postprocessing/separate_detections_into_folders.py +795 -0
- megadetector/postprocessing/subset_json_detector_output.py +964 -0
- megadetector/postprocessing/top_folders_to_bottom.py +238 -0
- megadetector/postprocessing/validate_batch_results.py +332 -0
- megadetector/taxonomy_mapping/__init__.py +0 -0
- megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +491 -0
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +211 -0
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +165 -0
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +543 -0
- megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
- megadetector/taxonomy_mapping/simple_image_download.py +231 -0
- megadetector/taxonomy_mapping/species_lookup.py +1008 -0
- megadetector/taxonomy_mapping/taxonomy_csv_checker.py +159 -0
- megadetector/taxonomy_mapping/taxonomy_graph.py +346 -0
- megadetector/taxonomy_mapping/validate_lila_category_mappings.py +83 -0
- megadetector/tests/__init__.py +0 -0
- megadetector/tests/test_nms_synthetic.py +335 -0
- megadetector/utils/__init__.py +0 -0
- megadetector/utils/ct_utils.py +1857 -0
- megadetector/utils/directory_listing.py +199 -0
- megadetector/utils/extract_frames_from_video.py +307 -0
- megadetector/utils/gpu_test.py +125 -0
- megadetector/utils/md_tests.py +2072 -0
- megadetector/utils/path_utils.py +2872 -0
- megadetector/utils/process_utils.py +172 -0
- megadetector/utils/split_locations_into_train_val.py +237 -0
- megadetector/utils/string_utils.py +234 -0
- megadetector/utils/url_utils.py +825 -0
- megadetector/utils/wi_platform_utils.py +968 -0
- megadetector/utils/wi_taxonomy_utils.py +1766 -0
- megadetector/utils/write_html_image_list.py +239 -0
- megadetector/visualization/__init__.py +0 -0
- megadetector/visualization/plot_utils.py +309 -0
- megadetector/visualization/render_images_with_thumbnails.py +243 -0
- megadetector/visualization/visualization_utils.py +1973 -0
- megadetector/visualization/visualize_db.py +630 -0
- megadetector/visualization/visualize_detector_output.py +498 -0
- megadetector/visualization/visualize_video_output.py +705 -0
- megadetector-10.0.15.dist-info/METADATA +115 -0
- megadetector-10.0.15.dist-info/RECORD +147 -0
- megadetector-10.0.15.dist-info/WHEEL +5 -0
- megadetector-10.0.15.dist-info/licenses/LICENSE +19 -0
- megadetector-10.0.15.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
"""
|
|
2
|
+
|
|
3
|
+
process_video.py
|
|
4
|
+
|
|
5
|
+
Splits a video (or folder of videos) into frames, runs the frames through run_detector_batch.py,
|
|
6
|
+
and optionally stitches together results into a new video with detection boxes.
|
|
7
|
+
|
|
8
|
+
When possible, video processing happens in memory, without writing intermediate frames to disk.
|
|
9
|
+
If the caller requests that frames be saved, frames are written before processing, and the MD
|
|
10
|
+
results correspond to the frames that were written to disk (which simplifies, for example,
|
|
11
|
+
repeat detection elimination).
|
|
12
|
+
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
#%% Imports
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import argparse
|
|
20
|
+
|
|
21
|
+
from megadetector.detection import run_detector_batch
|
|
22
|
+
from megadetector.utils.ct_utils import args_to_object
|
|
23
|
+
from megadetector.utils.ct_utils import dict_to_kvp_list, parse_kvp_list
|
|
24
|
+
from megadetector.detection.video_utils import _filename_to_frame_number
|
|
25
|
+
from megadetector.detection.video_utils import find_videos
|
|
26
|
+
from megadetector.detection.video_utils import run_callback_on_frames_for_folder
|
|
27
|
+
from megadetector.detection.run_detector import load_detector
|
|
28
|
+
from megadetector.postprocessing.validate_batch_results import \
|
|
29
|
+
ValidateBatchResultsOptions, validate_batch_results
|
|
30
|
+
|
|
31
|
+
# Notes to self re: upcoming work on checkpointing
|
|
32
|
+
from megadetector.utils.ct_utils import split_list_into_fixed_size_chunks # noqa
|
|
33
|
+
from megadetector.detection.run_detector_batch import write_checkpoint, load_checkpoint # noqa
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
#%% Classes
|
|
37
|
+
|
|
38
|
+
class ProcessVideoOptions:
|
|
39
|
+
"""
|
|
40
|
+
Options controlling the behavior of process_video()
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(self):
|
|
44
|
+
|
|
45
|
+
#: Can be a model filename (.pt or .pb) or a model name (e.g. "MDV5A")
|
|
46
|
+
#:
|
|
47
|
+
#: Use the string "no_detection" to indicate that you only want to extract frames,
|
|
48
|
+
#: not run a model. If you do this, you almost definitely want to set
|
|
49
|
+
#: keep_extracted_frames to "True", otherwise everything in this module is a no-op.
|
|
50
|
+
#: I.e., there's no reason to extract frames, do nothing with them, then delete them.
|
|
51
|
+
self.model_file = 'MDV5A'
|
|
52
|
+
|
|
53
|
+
#: Video (of folder of videos) to process
|
|
54
|
+
self.input_video_file = ''
|
|
55
|
+
|
|
56
|
+
#: .json file to which we should write results
|
|
57
|
+
self.output_json_file = None
|
|
58
|
+
|
|
59
|
+
#: If [input_video_file] is a folder, should we search for videos recursively?
|
|
60
|
+
self.recursive = False
|
|
61
|
+
|
|
62
|
+
#: Enable additional debug console output
|
|
63
|
+
self.verbose = False
|
|
64
|
+
|
|
65
|
+
#: Detections below this threshold will not be included in the output file.
|
|
66
|
+
self.json_confidence_threshold = 0.005
|
|
67
|
+
|
|
68
|
+
#: Sample every Nth frame; set to None (default) or 1 to sample every frame. Typically
|
|
69
|
+
#: we sample down to around 3 fps, so for typical 30 fps videos, frame_sample=10 is a
|
|
70
|
+
#: typical value. Mutually exclusive with [time_sample].
|
|
71
|
+
self.frame_sample = None
|
|
72
|
+
|
|
73
|
+
#: Sample frames every N seconds. Mutually exclusive with [frame_sample]
|
|
74
|
+
self.time_sample = None
|
|
75
|
+
|
|
76
|
+
#: Run the model at this image size (don't mess with this unless you know what you're
|
|
77
|
+
#: getting into)... if you just want to pass smaller frames to MD, use max_width
|
|
78
|
+
self.image_size = None
|
|
79
|
+
|
|
80
|
+
#: Enable image augmentation
|
|
81
|
+
self.augment = False
|
|
82
|
+
|
|
83
|
+
#: By default, a video with no frames (or no frames retrievable with the current parameters)
|
|
84
|
+
#: is silently stored as a failure; this causes it to halt execution.
|
|
85
|
+
self.exit_on_empty_video = False
|
|
86
|
+
|
|
87
|
+
#: Detector-specific options
|
|
88
|
+
self.detector_options = None
|
|
89
|
+
|
|
90
|
+
#: Write a checkpoint file (to resume processing later) every N videos;
|
|
91
|
+
#: set to -1 (default) to disable checkpointing
|
|
92
|
+
self.checkpoint_frequency = -1
|
|
93
|
+
|
|
94
|
+
#: Path to checkpoint file; None (default) for auto-generation based on output filename
|
|
95
|
+
self.checkpoint_path = None
|
|
96
|
+
|
|
97
|
+
#: Resume from a checkpoint file, or "auto" to use the most recent checkpoint in the
|
|
98
|
+
#: output directory
|
|
99
|
+
self.resume_from_checkpoint = None
|
|
100
|
+
|
|
101
|
+
# ...class ProcessVideoOptions
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
#%% Functions
|
|
105
|
+
|
|
106
|
+
def _validate_video_options(options):
|
|
107
|
+
"""
|
|
108
|
+
Consistency checking for ProcessVideoOptions objects.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
n_sampling_options_configured = 0
|
|
112
|
+
if options.frame_sample is not None:
|
|
113
|
+
n_sampling_options_configured += 1
|
|
114
|
+
if options.time_sample is not None:
|
|
115
|
+
n_sampling_options_configured += 1
|
|
116
|
+
|
|
117
|
+
if n_sampling_options_configured > 1:
|
|
118
|
+
raise ValueError('frame_sample and time_sample are mutually exclusive')
|
|
119
|
+
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def process_videos(options):
|
|
124
|
+
"""
|
|
125
|
+
Process a video or folder of videos through MD.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
options (ProcessVideoOptions): all the parameters used to control this process,
|
|
129
|
+
including filenames; see ProcessVideoOptions for details
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
## Validate options
|
|
133
|
+
|
|
134
|
+
# Check for incompatible options
|
|
135
|
+
_validate_video_options(options)
|
|
136
|
+
|
|
137
|
+
if options.output_json_file is None:
|
|
138
|
+
video_file = options.input_video_file.replace('\\','/')
|
|
139
|
+
if video_file.endswith('/'):
|
|
140
|
+
video_file = video_file[:-1]
|
|
141
|
+
options.output_json_file = video_file + '.json'
|
|
142
|
+
print('Output file not specified, defaulting to {}'.format(
|
|
143
|
+
options.output_json_file))
|
|
144
|
+
|
|
145
|
+
assert options.output_json_file.endswith('.json'), \
|
|
146
|
+
'Illegal output file {}'.format(options.output_json_file)
|
|
147
|
+
|
|
148
|
+
if options.time_sample is not None:
|
|
149
|
+
every_n_frames_param = -1 * options.time_sample
|
|
150
|
+
else:
|
|
151
|
+
every_n_frames_param = options.frame_sample
|
|
152
|
+
|
|
153
|
+
if options.verbose:
|
|
154
|
+
print('Processing videos from input source {}'.format(options.input_video_file))
|
|
155
|
+
|
|
156
|
+
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
157
|
+
|
|
158
|
+
def frame_callback(image_np,image_id):
|
|
159
|
+
return detector.generate_detections_one_image(image_np,
|
|
160
|
+
image_id,
|
|
161
|
+
detection_threshold=options.json_confidence_threshold,
|
|
162
|
+
augment=options.augment,
|
|
163
|
+
image_size=options.image_size,
|
|
164
|
+
verbose=options.verbose)
|
|
165
|
+
|
|
166
|
+
"""
|
|
167
|
+
[md_results] will be dict with keys 'video_filenames' (list of str), 'frame_rates' (list of floats),
|
|
168
|
+
'results' (list of list of dicts). 'video_filenames' will contain *relative* filenames.
|
|
169
|
+
'results' is a list (one element per video) of lists (one element per frame) of whatever the
|
|
170
|
+
callback returns, typically (but not necessarily) dicts in the MD results format.
|
|
171
|
+
|
|
172
|
+
For failed videos, the frame rate will be represented by -1, and "results"
|
|
173
|
+
will be a dict with at least the key "failure".
|
|
174
|
+
"""
|
|
175
|
+
if os.path.isfile(options.input_video_file):
|
|
176
|
+
|
|
177
|
+
video_folder = os.path.dirname(options.input_video_file)
|
|
178
|
+
video_bn = os.path.basename(options.input_video_file)
|
|
179
|
+
md_results = run_callback_on_frames_for_folder(input_video_folder=video_folder,
|
|
180
|
+
frame_callback=frame_callback,
|
|
181
|
+
every_n_frames=every_n_frames_param,
|
|
182
|
+
verbose=options.verbose,
|
|
183
|
+
files_to_process_relative=[video_bn],
|
|
184
|
+
error_on_empty_video=options.exit_on_empty_video)
|
|
185
|
+
|
|
186
|
+
else:
|
|
187
|
+
|
|
188
|
+
assert os.path.isdir(options.input_video_file), \
|
|
189
|
+
'{} is neither a file nor a folder'.format(options.input_video_file)
|
|
190
|
+
|
|
191
|
+
video_folder = options.input_video_file
|
|
192
|
+
|
|
193
|
+
md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
|
|
194
|
+
frame_callback=frame_callback,
|
|
195
|
+
every_n_frames=every_n_frames_param,
|
|
196
|
+
verbose=options.verbose,
|
|
197
|
+
recursive=options.recursive,
|
|
198
|
+
error_on_empty_video=options.exit_on_empty_video)
|
|
199
|
+
|
|
200
|
+
# ...whether we're processing a file or a folder
|
|
201
|
+
|
|
202
|
+
print('Finished running MD on videos')
|
|
203
|
+
|
|
204
|
+
video_results = md_results['results']
|
|
205
|
+
video_filenames = md_results['video_filenames']
|
|
206
|
+
video_frame_rates = md_results['frame_rates']
|
|
207
|
+
|
|
208
|
+
assert len(video_results) == len(video_filenames)
|
|
209
|
+
assert len(video_results) == len(video_frame_rates)
|
|
210
|
+
|
|
211
|
+
video_list_md_format = []
|
|
212
|
+
|
|
213
|
+
# i_video = 0; results_this_video = video_results[i_video]
|
|
214
|
+
for i_video,results_this_video in enumerate(video_results):
|
|
215
|
+
|
|
216
|
+
video_fn = video_filenames[i_video]
|
|
217
|
+
|
|
218
|
+
im = {}
|
|
219
|
+
im['file'] = video_fn
|
|
220
|
+
im['frame_rate'] = video_frame_rates[i_video]
|
|
221
|
+
im['frames_processed'] = []
|
|
222
|
+
|
|
223
|
+
if isinstance(results_this_video,dict):
|
|
224
|
+
|
|
225
|
+
assert 'failure' in results_this_video
|
|
226
|
+
im['failure'] = results_this_video['failure']
|
|
227
|
+
im['detections'] = None
|
|
228
|
+
|
|
229
|
+
else:
|
|
230
|
+
|
|
231
|
+
im['detections'] = []
|
|
232
|
+
|
|
233
|
+
# results_one_frame = results_this_video[0]
|
|
234
|
+
for results_one_frame in results_this_video:
|
|
235
|
+
|
|
236
|
+
assert results_one_frame['file'].startswith(video_fn)
|
|
237
|
+
|
|
238
|
+
frame_number = _filename_to_frame_number(results_one_frame['file'])
|
|
239
|
+
|
|
240
|
+
assert frame_number not in im['frames_processed'], \
|
|
241
|
+
'Received the same frame twice for video {}'.format(im['file'])
|
|
242
|
+
|
|
243
|
+
im['frames_processed'].append(frame_number)
|
|
244
|
+
|
|
245
|
+
for det in results_one_frame['detections']:
|
|
246
|
+
det['frame_number'] = frame_number
|
|
247
|
+
|
|
248
|
+
# This is a no-op if there were no above-threshold detections
|
|
249
|
+
# in this frame
|
|
250
|
+
im['detections'].extend(results_one_frame['detections'])
|
|
251
|
+
|
|
252
|
+
# ...for each frame
|
|
253
|
+
|
|
254
|
+
# ...was this a failed video?
|
|
255
|
+
|
|
256
|
+
im['frames_processed'] = sorted(im['frames_processed'])
|
|
257
|
+
|
|
258
|
+
video_list_md_format.append(im)
|
|
259
|
+
|
|
260
|
+
# ...for each video
|
|
261
|
+
|
|
262
|
+
run_detector_batch.write_results_to_file(
|
|
263
|
+
video_list_md_format,
|
|
264
|
+
options.output_json_file,
|
|
265
|
+
relative_path_base=None,
|
|
266
|
+
detector_file=options.model_file)
|
|
267
|
+
|
|
268
|
+
validation_options = ValidateBatchResultsOptions()
|
|
269
|
+
validation_options.raise_errors = True
|
|
270
|
+
validation_options.check_image_existence = True
|
|
271
|
+
validation_options.return_data = False
|
|
272
|
+
validation_options.relative_path_base = video_folder
|
|
273
|
+
validate_batch_results(options.output_json_file,options=validation_options)
|
|
274
|
+
|
|
275
|
+
# ...process_videos()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def options_to_command(options):
|
|
279
|
+
"""
|
|
280
|
+
Convert a ProcessVideoOptions object to a corresponding command line.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
options (ProcessVideoOptions): the options set to render as a command line
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
str: the command line corresponding to [options]
|
|
287
|
+
|
|
288
|
+
:meta private:
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
cmd = 'python process_video.py'
|
|
292
|
+
cmd += ' "' + options.model_file + '"'
|
|
293
|
+
cmd += ' "' + options.input_video_file + '"'
|
|
294
|
+
|
|
295
|
+
if options.recursive:
|
|
296
|
+
cmd += ' --recursive'
|
|
297
|
+
if options.output_json_file is not None:
|
|
298
|
+
cmd += ' --output_json_file' + ' "' + options.output_json_file + '"'
|
|
299
|
+
if options.json_confidence_threshold is not None:
|
|
300
|
+
cmd += ' --json_confidence_threshold ' + str(options.json_confidence_threshold)
|
|
301
|
+
if options.frame_sample is not None:
|
|
302
|
+
cmd += ' --frame_sample ' + str(options.frame_sample)
|
|
303
|
+
if options.verbose:
|
|
304
|
+
cmd += ' --verbose'
|
|
305
|
+
if options.detector_options is not None and len(options.detector_options) > 0:
|
|
306
|
+
cmd += ' --detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
307
|
+
|
|
308
|
+
return cmd
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
#%% Interactive driver
|
|
312
|
+
|
|
313
|
+
if False:
|
|
314
|
+
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
#%% Process a folder of videos
|
|
318
|
+
|
|
319
|
+
import os
|
|
320
|
+
from megadetector.detection.process_video import \
|
|
321
|
+
process_videos, ProcessVideoOptions
|
|
322
|
+
|
|
323
|
+
model_file = 'MDV5A'
|
|
324
|
+
input_dir = r"G:\temp\md-test-images\video-samples"
|
|
325
|
+
assert os.path.isdir(input_dir)
|
|
326
|
+
|
|
327
|
+
output_json_file = os.path.join(input_dir,'mdv5a-video.json')
|
|
328
|
+
|
|
329
|
+
print('Processing folder {}'.format(input_dir))
|
|
330
|
+
|
|
331
|
+
options = ProcessVideoOptions()
|
|
332
|
+
options.json_confidence_threshold = 0.05
|
|
333
|
+
options.model_file = model_file
|
|
334
|
+
options.input_video_file = input_dir
|
|
335
|
+
options.output_json_file = output_json_file
|
|
336
|
+
options.recursive = True
|
|
337
|
+
# options.frame_sample = 10
|
|
338
|
+
options.time_sample = 2
|
|
339
|
+
options.verbose = True
|
|
340
|
+
|
|
341
|
+
process_videos(options)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
#%% Process a single video
|
|
345
|
+
|
|
346
|
+
import os
|
|
347
|
+
from megadetector.detection.process_video import \
|
|
348
|
+
process_videos, ProcessVideoOptions
|
|
349
|
+
from megadetector.detection.video_utils import find_videos
|
|
350
|
+
|
|
351
|
+
model_file = 'MDV5A'
|
|
352
|
+
input_dir = r"G:\temp\md-test-images\video-samples"
|
|
353
|
+
assert os.path.isdir(input_dir)
|
|
354
|
+
video_fn_abs = find_videos(input_dir)[0]
|
|
355
|
+
|
|
356
|
+
output_json_file = os.path.join(input_dir,'mdv5a-single-video.json')
|
|
357
|
+
|
|
358
|
+
print('Processing video {}'.format(video_fn_abs))
|
|
359
|
+
|
|
360
|
+
options = ProcessVideoOptions()
|
|
361
|
+
options.json_confidence_threshold = 0.05
|
|
362
|
+
options.model_file = model_file
|
|
363
|
+
options.input_video_file = video_fn_abs
|
|
364
|
+
options.output_json_file = output_json_file
|
|
365
|
+
options.recursive = True
|
|
366
|
+
# options.frame_sample = 10
|
|
367
|
+
options.time_sample = 2
|
|
368
|
+
options.verbose = True
|
|
369
|
+
|
|
370
|
+
process_videos(options)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
#%% Command-line driver
|
|
374
|
+
|
|
375
|
+
def main(): # noqa
|
|
376
|
+
|
|
377
|
+
default_options = ProcessVideoOptions()
|
|
378
|
+
|
|
379
|
+
parser = argparse.ArgumentParser(description=(
|
|
380
|
+
'Run MegaDetector on each frame (or every Nth frame) in a video (or folder of videos), optionally '\
|
|
381
|
+
'producing a new video with detections annotated'))
|
|
382
|
+
|
|
383
|
+
parser.add_argument('model_file', type=str,
|
|
384
|
+
help='MegaDetector model file (.pt or .pb) or model name (e.g. "MDV5A"), '\
|
|
385
|
+
'or the string "no_detection" to run just frame extraction')
|
|
386
|
+
|
|
387
|
+
parser.add_argument('input_video_file', type=str,
|
|
388
|
+
help='video file (or folder) to process')
|
|
389
|
+
|
|
390
|
+
parser.add_argument('--recursive', action='store_true',
|
|
391
|
+
help='recurse into [input_video_file]; only meaningful if a folder '\
|
|
392
|
+
'is specified as input')
|
|
393
|
+
|
|
394
|
+
parser.add_argument('--output_json_file', type=str,
|
|
395
|
+
default=None, help='.json output file, defaults to [video file].json')
|
|
396
|
+
|
|
397
|
+
parser.add_argument('--json_confidence_threshold', type=float,
|
|
398
|
+
default=default_options.json_confidence_threshold,
|
|
399
|
+
help="don't include boxes in the .json file with confidence "\
|
|
400
|
+
'below this threshold (default {})'.format(
|
|
401
|
+
default_options.json_confidence_threshold))
|
|
402
|
+
|
|
403
|
+
parser.add_argument('--frame_sample', type=int,
|
|
404
|
+
default=None, help='process every Nth frame (defaults to every frame), mutually exclusive '\
|
|
405
|
+
'with --time_sample.')
|
|
406
|
+
|
|
407
|
+
parser.add_argument('--time_sample', type=float,
|
|
408
|
+
default=None, help='process frames every N seconds; this is converted to a '\
|
|
409
|
+
'frame sampling rate, so it may not be exactly the requested interval in seconds. '\
|
|
410
|
+
'mutually exclusive with --frame_sample')
|
|
411
|
+
|
|
412
|
+
parser.add_argument('--verbose', action='store_true',
|
|
413
|
+
help='Enable additional debug output')
|
|
414
|
+
|
|
415
|
+
parser.add_argument('--image_size',
|
|
416
|
+
type=int,
|
|
417
|
+
default=None,
|
|
418
|
+
help=('Force image resizing to a specific integer size on the long '\
|
|
419
|
+
'axis (not recommended to change this)'))
|
|
420
|
+
|
|
421
|
+
parser.add_argument('--augment',
|
|
422
|
+
action='store_true',
|
|
423
|
+
help='Enable image augmentation')
|
|
424
|
+
|
|
425
|
+
parser.add_argument('--exit_on_empty_video',
|
|
426
|
+
action='store_true',
|
|
427
|
+
help=('By default, videos with no retrievable frames are stored as failures; this' \
|
|
428
|
+
'causes them to halt execution'))
|
|
429
|
+
|
|
430
|
+
parser.add_argument(
|
|
431
|
+
'--detector_options',
|
|
432
|
+
nargs='*',
|
|
433
|
+
metavar='KEY=VALUE',
|
|
434
|
+
default='',
|
|
435
|
+
help='Detector-specific options, as a space-separated list of key-value pairs')
|
|
436
|
+
|
|
437
|
+
parser.add_argument(
|
|
438
|
+
'--checkpoint_frequency',
|
|
439
|
+
type=int,
|
|
440
|
+
default=default_options.checkpoint_frequency,
|
|
441
|
+
help='Write a checkpoint file (to resume processing later) every N videos; ' + \
|
|
442
|
+
'set to -1 to disable checkpointing (default {})'.format(
|
|
443
|
+
default_options.checkpoint_frequency))
|
|
444
|
+
|
|
445
|
+
parser.add_argument(
|
|
446
|
+
'--checkpoint_path',
|
|
447
|
+
type=str,
|
|
448
|
+
default=None,
|
|
449
|
+
help='Path to checkpoint file; defaults to a file in the same directory ' + \
|
|
450
|
+
'as the output file')
|
|
451
|
+
|
|
452
|
+
parser.add_argument(
|
|
453
|
+
'--resume_from_checkpoint',
|
|
454
|
+
type=str,
|
|
455
|
+
default=None,
|
|
456
|
+
help='Resume from a specific checkpoint file, or "auto" to resume from the ' + \
|
|
457
|
+
'most recent checkpoint in the output directory')
|
|
458
|
+
|
|
459
|
+
if len(sys.argv[1:]) == 0:
|
|
460
|
+
parser.print_help()
|
|
461
|
+
parser.exit()
|
|
462
|
+
|
|
463
|
+
args = parser.parse_args()
|
|
464
|
+
options = ProcessVideoOptions()
|
|
465
|
+
args_to_object(args,options)
|
|
466
|
+
|
|
467
|
+
options.detector_options = parse_kvp_list(args.detector_options)
|
|
468
|
+
|
|
469
|
+
if os.path.isdir(options.input_video_file):
|
|
470
|
+
process_videos(options)
|
|
471
|
+
else:
|
|
472
|
+
assert os.path.isfile(options.input_video_file), \
|
|
473
|
+
'{} is not a valid file or folder name'.format(options.input_video_file)
|
|
474
|
+
assert not options.recursive, \
|
|
475
|
+
'--recursive is only meaningful when processing a folder'
|
|
476
|
+
process_videos(options)
|
|
477
|
+
|
|
478
|
+
if __name__ == '__main__':
|
|
479
|
+
main()
|