megadetector 5.0.28__py3-none-any.whl → 5.0.29__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/api/batch_processing/api_core/batch_service/score.py +4 -5
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +1 -1
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +1 -1
- megadetector/api/batch_processing/integration/digiKam/xmp_integration.py +2 -2
- megadetector/api/batch_processing/integration/eMammal/test_scripts/push_annotations_to_emammal.py +1 -1
- megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +1 -1
- megadetector/api/synchronous/api_core/tests/load_test.py +2 -3
- megadetector/classification/aggregate_classifier_probs.py +3 -3
- megadetector/classification/analyze_failed_images.py +5 -5
- megadetector/classification/cache_batchapi_outputs.py +5 -5
- megadetector/classification/create_classification_dataset.py +11 -12
- megadetector/classification/crop_detections.py +10 -10
- megadetector/classification/csv_to_json.py +8 -8
- megadetector/classification/detect_and_crop.py +13 -15
- megadetector/classification/evaluate_model.py +7 -7
- megadetector/classification/identify_mislabeled_candidates.py +6 -6
- megadetector/classification/json_to_azcopy_list.py +1 -1
- megadetector/classification/json_validator.py +29 -32
- megadetector/classification/map_classification_categories.py +9 -9
- megadetector/classification/merge_classification_detection_output.py +12 -9
- megadetector/classification/prepare_classification_script.py +19 -19
- megadetector/classification/prepare_classification_script_mc.py +23 -23
- megadetector/classification/run_classifier.py +4 -4
- megadetector/classification/save_mislabeled.py +6 -6
- megadetector/classification/train_classifier.py +1 -1
- megadetector/classification/train_classifier_tf.py +9 -9
- megadetector/classification/train_utils.py +10 -10
- megadetector/data_management/annotations/annotation_constants.py +1 -1
- megadetector/data_management/camtrap_dp_to_coco.py +45 -45
- megadetector/data_management/cct_json_utils.py +101 -101
- megadetector/data_management/cct_to_md.py +49 -49
- megadetector/data_management/cct_to_wi.py +33 -33
- megadetector/data_management/coco_to_labelme.py +75 -75
- megadetector/data_management/coco_to_yolo.py +189 -189
- megadetector/data_management/databases/add_width_and_height_to_db.py +3 -2
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +38 -38
- megadetector/data_management/databases/integrity_check_json_db.py +202 -188
- megadetector/data_management/databases/subset_json_db.py +33 -33
- megadetector/data_management/generate_crops_from_cct.py +38 -38
- megadetector/data_management/get_image_sizes.py +54 -49
- megadetector/data_management/labelme_to_coco.py +130 -124
- megadetector/data_management/labelme_to_yolo.py +78 -72
- megadetector/data_management/lila/create_lila_blank_set.py +81 -83
- megadetector/data_management/lila/create_lila_test_set.py +32 -31
- megadetector/data_management/lila/create_links_to_md_results_files.py +18 -18
- megadetector/data_management/lila/download_lila_subset.py +21 -24
- megadetector/data_management/lila/generate_lila_per_image_labels.py +91 -91
- megadetector/data_management/lila/get_lila_annotation_counts.py +30 -30
- megadetector/data_management/lila/get_lila_image_counts.py +22 -22
- megadetector/data_management/lila/lila_common.py +70 -70
- megadetector/data_management/lila/test_lila_metadata_urls.py +13 -14
- megadetector/data_management/mewc_to_md.py +339 -340
- megadetector/data_management/ocr_tools.py +258 -252
- megadetector/data_management/read_exif.py +231 -224
- megadetector/data_management/remap_coco_categories.py +26 -26
- megadetector/data_management/remove_exif.py +31 -20
- megadetector/data_management/rename_images.py +187 -187
- megadetector/data_management/resize_coco_dataset.py +41 -41
- megadetector/data_management/speciesnet_to_md.py +41 -41
- megadetector/data_management/wi_download_csv_to_coco.py +55 -55
- megadetector/data_management/yolo_output_to_md_output.py +117 -120
- megadetector/data_management/yolo_to_coco.py +195 -188
- megadetector/detection/change_detection.py +831 -0
- megadetector/detection/process_video.py +340 -337
- megadetector/detection/pytorch_detector.py +304 -262
- megadetector/detection/run_detector.py +177 -164
- megadetector/detection/run_detector_batch.py +364 -363
- megadetector/detection/run_inference_with_yolov5_val.py +328 -325
- megadetector/detection/run_tiled_inference.py +256 -249
- megadetector/detection/tf_detector.py +24 -24
- megadetector/detection/video_utils.py +290 -282
- megadetector/postprocessing/add_max_conf.py +15 -11
- megadetector/postprocessing/categorize_detections_by_size.py +44 -44
- megadetector/postprocessing/classification_postprocessing.py +415 -415
- megadetector/postprocessing/combine_batch_outputs.py +20 -21
- megadetector/postprocessing/compare_batch_results.py +528 -517
- megadetector/postprocessing/convert_output_format.py +97 -97
- megadetector/postprocessing/create_crop_folder.py +219 -146
- megadetector/postprocessing/detector_calibration.py +173 -168
- megadetector/postprocessing/generate_csv_report.py +508 -499
- megadetector/postprocessing/load_api_results.py +23 -20
- megadetector/postprocessing/md_to_coco.py +129 -98
- megadetector/postprocessing/md_to_labelme.py +89 -83
- megadetector/postprocessing/md_to_wi.py +40 -40
- megadetector/postprocessing/merge_detections.py +87 -114
- megadetector/postprocessing/postprocess_batch_results.py +313 -298
- megadetector/postprocessing/remap_detection_categories.py +36 -36
- megadetector/postprocessing/render_detection_confusion_matrix.py +205 -199
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +57 -57
- megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +27 -28
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +702 -677
- megadetector/postprocessing/separate_detections_into_folders.py +226 -211
- megadetector/postprocessing/subset_json_detector_output.py +265 -262
- megadetector/postprocessing/top_folders_to_bottom.py +45 -45
- megadetector/postprocessing/validate_batch_results.py +70 -70
- megadetector/taxonomy_mapping/map_lila_taxonomy_to_wi_taxonomy.py +52 -52
- megadetector/taxonomy_mapping/map_new_lila_datasets.py +15 -15
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +14 -14
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +66 -66
- megadetector/taxonomy_mapping/retrieve_sample_image.py +16 -16
- megadetector/taxonomy_mapping/simple_image_download.py +8 -8
- megadetector/taxonomy_mapping/species_lookup.py +33 -33
- megadetector/taxonomy_mapping/taxonomy_csv_checker.py +14 -14
- megadetector/taxonomy_mapping/taxonomy_graph.py +10 -10
- megadetector/taxonomy_mapping/validate_lila_category_mappings.py +13 -13
- megadetector/utils/azure_utils.py +22 -22
- megadetector/utils/ct_utils.py +1018 -200
- megadetector/utils/directory_listing.py +21 -77
- megadetector/utils/gpu_test.py +22 -22
- megadetector/utils/md_tests.py +541 -518
- megadetector/utils/path_utils.py +1457 -398
- megadetector/utils/process_utils.py +41 -41
- megadetector/utils/sas_blob_utils.py +53 -49
- megadetector/utils/split_locations_into_train_val.py +61 -61
- megadetector/utils/string_utils.py +147 -26
- megadetector/utils/url_utils.py +463 -173
- megadetector/utils/wi_utils.py +2629 -2526
- megadetector/utils/write_html_image_list.py +137 -137
- megadetector/visualization/plot_utils.py +21 -21
- megadetector/visualization/render_images_with_thumbnails.py +37 -73
- megadetector/visualization/visualization_utils.py +401 -397
- megadetector/visualization/visualize_db.py +197 -190
- megadetector/visualization/visualize_detector_output.py +79 -73
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/METADATA +135 -132
- megadetector-5.0.29.dist-info/RECORD +163 -0
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/WHEEL +1 -1
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/licenses/LICENSE +0 -0
- {megadetector-5.0.28.dist-info → megadetector-5.0.29.dist-info}/top_level.txt +0 -0
- megadetector/data_management/importers/add_nacti_sizes.py +0 -52
- megadetector/data_management/importers/add_timestamps_to_icct.py +0 -79
- megadetector/data_management/importers/animl_results_to_md_results.py +0 -158
- megadetector/data_management/importers/auckland_doc_test_to_json.py +0 -373
- megadetector/data_management/importers/auckland_doc_to_json.py +0 -201
- megadetector/data_management/importers/awc_to_json.py +0 -191
- megadetector/data_management/importers/bellevue_to_json.py +0 -272
- megadetector/data_management/importers/cacophony-thermal-importer.py +0 -793
- megadetector/data_management/importers/carrizo_shrubfree_2018.py +0 -269
- megadetector/data_management/importers/carrizo_trail_cam_2017.py +0 -289
- megadetector/data_management/importers/cct_field_adjustments.py +0 -58
- megadetector/data_management/importers/channel_islands_to_cct.py +0 -913
- megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +0 -180
- megadetector/data_management/importers/eMammal/eMammal_helpers.py +0 -249
- megadetector/data_management/importers/eMammal/make_eMammal_json.py +0 -223
- megadetector/data_management/importers/ena24_to_json.py +0 -276
- megadetector/data_management/importers/filenames_to_json.py +0 -386
- megadetector/data_management/importers/helena_to_cct.py +0 -283
- megadetector/data_management/importers/idaho-camera-traps.py +0 -1407
- megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +0 -294
- megadetector/data_management/importers/import_desert_lion_conservation_camera_traps.py +0 -387
- megadetector/data_management/importers/jb_csv_to_json.py +0 -150
- megadetector/data_management/importers/mcgill_to_json.py +0 -250
- megadetector/data_management/importers/missouri_to_json.py +0 -490
- megadetector/data_management/importers/nacti_fieldname_adjustments.py +0 -79
- megadetector/data_management/importers/noaa_seals_2019.py +0 -181
- megadetector/data_management/importers/osu-small-animals-to-json.py +0 -364
- megadetector/data_management/importers/pc_to_json.py +0 -365
- megadetector/data_management/importers/plot_wni_giraffes.py +0 -123
- megadetector/data_management/importers/prepare_zsl_imerit.py +0 -131
- megadetector/data_management/importers/raic_csv_to_md_results.py +0 -416
- megadetector/data_management/importers/rspb_to_json.py +0 -356
- megadetector/data_management/importers/save_the_elephants_survey_A.py +0 -320
- megadetector/data_management/importers/save_the_elephants_survey_B.py +0 -329
- megadetector/data_management/importers/snapshot_safari_importer.py +0 -758
- megadetector/data_management/importers/snapshot_serengeti_lila.py +0 -1067
- megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +0 -150
- megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +0 -153
- megadetector/data_management/importers/sulross_get_exif.py +0 -65
- megadetector/data_management/importers/timelapse_csv_set_to_json.py +0 -490
- megadetector/data_management/importers/ubc_to_json.py +0 -399
- megadetector/data_management/importers/umn_to_json.py +0 -507
- megadetector/data_management/importers/wellington_to_json.py +0 -263
- megadetector/data_management/importers/wi_to_json.py +0 -442
- megadetector/data_management/importers/zamba_results_to_md_results.py +0 -180
- megadetector/data_management/lila/add_locations_to_island_camera_traps.py +0 -101
- megadetector/data_management/lila/add_locations_to_nacti.py +0 -151
- megadetector-5.0.28.dist-info/RECORD +0 -209
|
@@ -48,140 +48,140 @@ class ProcessVideoOptions:
|
|
|
48
48
|
"""
|
|
49
49
|
Options controlling the behavior of process_video()
|
|
50
50
|
"""
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
def __init__(self):
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
#: Can be a model filename (.pt or .pb) or a model name (e.g. "MDV5A")
|
|
55
|
-
#:
|
|
55
|
+
#:
|
|
56
56
|
#: Use the string "no_detection" to indicate that you only want to extract frames,
|
|
57
|
-
#: not run a model. If you do this, you almost definitely want to set
|
|
57
|
+
#: not run a model. If you do this, you almost definitely want to set
|
|
58
58
|
#: keep_extracted_frames to "True", otherwise everything in this module is a no-op.
|
|
59
59
|
#: I.e., there's no reason to extract frames, do nothing with them, then delete them.
|
|
60
60
|
self.model_file = 'MDV5A'
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
#: Video (of folder of videos) to process
|
|
63
63
|
self.input_video_file = ''
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
#: .json file to which we should write results
|
|
66
66
|
self.output_json_file = None
|
|
67
|
-
|
|
68
|
-
#: File to which we should write a video with boxes, only relevant if
|
|
67
|
+
|
|
68
|
+
#: File to which we should write a video with boxes, only relevant if
|
|
69
69
|
#: render_output_video is True
|
|
70
70
|
self.output_video_file = None
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
#: Folder to use for extracted frames; will use a folder in system temp space
|
|
73
73
|
#: if this is None
|
|
74
74
|
self.frame_folder = None
|
|
75
|
-
|
|
76
|
-
#: Folder to use for rendered frames (if rendering output video); will use a folder
|
|
75
|
+
|
|
76
|
+
#: Folder to use for rendered frames (if rendering output video); will use a folder
|
|
77
77
|
#: in system temp space if this is None
|
|
78
78
|
self.frame_rendering_folder = None
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
#: Should we render a video with detection boxes?
|
|
81
81
|
#:
|
|
82
82
|
#: If processing a folder, this renders each input video to a separate
|
|
83
83
|
#: video with detection boxes.
|
|
84
84
|
self.render_output_video = False
|
|
85
|
-
|
|
85
|
+
|
|
86
86
|
#: If we are rendering boxes to a new video, should we keep the temporary
|
|
87
87
|
#: rendered frames?
|
|
88
88
|
self.keep_rendered_frames = False
|
|
89
|
-
|
|
89
|
+
|
|
90
90
|
#: Should we keep the extracted frames?
|
|
91
91
|
self.keep_extracted_frames = False
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
#: Should we delete the entire folder the extracted frames are written to?
|
|
94
94
|
#:
|
|
95
|
-
#: By default, we delete the frame files but leave the (probably-empty) folder in place,
|
|
95
|
+
#: By default, we delete the frame files but leave the (probably-empty) folder in place,
|
|
96
96
|
#: for no reason other than being paranoid about deleting folders.
|
|
97
97
|
self.force_extracted_frame_folder_deletion = False
|
|
98
|
-
|
|
98
|
+
|
|
99
99
|
#: Should we delete the entire folder the rendered frames are written to?
|
|
100
100
|
#:
|
|
101
101
|
#: By default, we delete the frame files but leave the (probably-empty) folder in place,
|
|
102
102
|
#: for no reason other than being paranoid about deleting folders.
|
|
103
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
|
|
104
|
+
|
|
105
|
+
#: If we've already run MegaDetector on this video or folder of videos, i.e. if we
|
|
106
106
|
#: find a corresponding MD results file, should we re-use it? Defaults to reprocessing.
|
|
107
107
|
self.reuse_results_if_available = False
|
|
108
|
-
|
|
109
|
-
#: If we've already split this video or folder of videos into frames, should we
|
|
108
|
+
|
|
109
|
+
#: If we've already split this video or folder of videos into frames, should we
|
|
110
110
|
#: we re-use those extracted frames? Defaults to reprocessing.
|
|
111
111
|
self.reuse_frames_if_available = False
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
#: If [input_video_file] is a folder, should we search for videos recursively?
|
|
114
|
-
self.recursive = False
|
|
115
|
-
|
|
114
|
+
self.recursive = False
|
|
115
|
+
|
|
116
116
|
#: Enable additional debug console output
|
|
117
117
|
self.verbose = False
|
|
118
|
-
|
|
118
|
+
|
|
119
119
|
#: fourcc code to use for writing videos; only relevant if render_output_video is True
|
|
120
120
|
self.fourcc = None
|
|
121
|
-
|
|
122
|
-
#: force a specific frame rate for output videos; only relevant if render_output_video
|
|
121
|
+
|
|
122
|
+
#: force a specific frame rate for output videos; only relevant if render_output_video
|
|
123
123
|
#: is True
|
|
124
124
|
self.rendering_fs = None
|
|
125
|
-
|
|
125
|
+
|
|
126
126
|
#: Confidence threshold to use for writing videos with boxes, only relevant if
|
|
127
127
|
#: if render_output_video is True. Defaults to choosing a reasonable threshold
|
|
128
128
|
#: based on the model version.
|
|
129
129
|
self.rendering_confidence_threshold = None
|
|
130
|
-
|
|
130
|
+
|
|
131
131
|
#: Detections below this threshold will not be included in the output file.
|
|
132
132
|
self.json_confidence_threshold = 0.005
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
#: Sample every Nth frame; set to None (default) or 1 to sample every frame. Typically
|
|
135
|
-
#: we sample down to around 3 fps, so for typical 30 fps videos, frame_sample=10 is a
|
|
135
|
+
#: we sample down to around 3 fps, so for typical 30 fps videos, frame_sample=10 is a
|
|
136
136
|
#: typical value. Mutually exclusive with [frames_to_extract] and [time_sample].
|
|
137
137
|
self.frame_sample = None
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
#: Extract a specific set of frames (list of ints, or a single int). Mutually exclusive with
|
|
140
140
|
#: [frame_sample] and [time_sample].
|
|
141
141
|
self.frames_to_extract = None
|
|
142
|
-
|
|
142
|
+
|
|
143
143
|
# Sample frames every N seconds. Mutually exclusive with [frame_sample] and [frames_to_extract].
|
|
144
144
|
self.time_sample = None
|
|
145
|
-
|
|
145
|
+
|
|
146
146
|
#: Number of workers to use for parallelization; set to <= 1 to disable parallelization
|
|
147
147
|
self.n_cores = 1
|
|
148
|
-
|
|
148
|
+
|
|
149
149
|
#: For debugging only, stop processing after a certain number of frames.
|
|
150
150
|
self.debug_max_frames = -1
|
|
151
|
-
|
|
151
|
+
|
|
152
152
|
#: For debugging only, force on-disk frame extraction, even if it wouldn't otherwise be
|
|
153
153
|
#: necessary
|
|
154
154
|
self.force_on_disk_frame_extraction = False
|
|
155
|
-
|
|
155
|
+
|
|
156
156
|
#: File containing non-standard categories, typically only used if you're running a non-MD
|
|
157
157
|
#: detector.
|
|
158
158
|
self.class_mapping_filename = None
|
|
159
|
-
|
|
159
|
+
|
|
160
160
|
#: JPEG quality for frame output, from 0-100. Use None or -1 to let opencv decide.
|
|
161
161
|
self.quality = 90
|
|
162
|
-
|
|
162
|
+
|
|
163
163
|
#: Resize frames so they're at most this wide
|
|
164
164
|
self.max_width = None
|
|
165
|
-
|
|
165
|
+
|
|
166
166
|
#: Run the model at this image size (don't mess with this unless you know what you're
|
|
167
|
-
#: getting into)... if you just want to pass smaller frames to MD, use max_width
|
|
167
|
+
#: getting into)... if you just want to pass smaller frames to MD, use max_width
|
|
168
168
|
self.image_size = None
|
|
169
|
-
|
|
169
|
+
|
|
170
170
|
#: Enable image augmentation
|
|
171
171
|
self.augment = False
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
#: By default, a video with no frames (or no frames retrievable with the current parameters)
|
|
174
174
|
#: is an error, this makes it a warning. This would apply if you request, e.g., the 100th
|
|
175
175
|
#: frame from each video, but a video only has 50 frames.
|
|
176
176
|
self.allow_empty_videos = False
|
|
177
|
-
|
|
178
|
-
#: When processing a folder of videos, should we include just a single representative
|
|
177
|
+
|
|
178
|
+
#: When processing a folder of videos, should we include just a single representative
|
|
179
179
|
#: frame result for each video (default), or every frame that was processed?
|
|
180
180
|
self.include_all_processed_frames = False
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
#: Detector-specific options
|
|
183
183
|
self.detector_options = None
|
|
184
|
-
|
|
184
|
+
|
|
185
185
|
# ...class ProcessVideoOptions
|
|
186
186
|
|
|
187
187
|
|
|
@@ -191,7 +191,7 @@ def _validate_video_options(options):
|
|
|
191
191
|
"""
|
|
192
192
|
Consistency checking for ProcessVideoOptions objects.
|
|
193
193
|
"""
|
|
194
|
-
|
|
194
|
+
|
|
195
195
|
n_sampling_options_configured = 0
|
|
196
196
|
if options.frame_sample is not None:
|
|
197
197
|
n_sampling_options_configured += 1
|
|
@@ -199,45 +199,45 @@ def _validate_video_options(options):
|
|
|
199
199
|
n_sampling_options_configured += 1
|
|
200
200
|
if options.frames_to_extract is not None:
|
|
201
201
|
n_sampling_options_configured += 1
|
|
202
|
-
|
|
202
|
+
|
|
203
203
|
if n_sampling_options_configured > 1:
|
|
204
204
|
raise ValueError('frame_sample, time_sample, and frames_to_extract are mutually exclusive')
|
|
205
|
-
|
|
205
|
+
|
|
206
206
|
return True
|
|
207
|
-
|
|
208
|
-
|
|
207
|
+
|
|
208
|
+
|
|
209
209
|
def _select_temporary_output_folders(options):
|
|
210
210
|
"""
|
|
211
211
|
Choose folders in system temp space for writing temporary frames. Does not create folders,
|
|
212
212
|
just defines them.
|
|
213
213
|
"""
|
|
214
|
-
|
|
214
|
+
|
|
215
215
|
tempdir = os.path.join(tempfile.gettempdir(), 'process_camera_trap_video')
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
# If we create a folder like "process_camera_trap_video" in the system temp dir, it may
|
|
218
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.
|
|
219
|
+
# If we create a uuid-named folder in the system temp dir, we make a mess.
|
|
220
220
|
#
|
|
221
221
|
# Compromise with "process_camera_trap_video-[user]".
|
|
222
222
|
user_tempdir = tempdir + '-' + getpass.getuser()
|
|
223
|
-
|
|
223
|
+
|
|
224
224
|
# I don't know whether it's possible for a username to contain characters that are
|
|
225
225
|
# not valid filename characters, but just to be sure...
|
|
226
226
|
user_tempdir = clean_path(user_tempdir)
|
|
227
|
-
|
|
227
|
+
|
|
228
228
|
frame_output_folder = os.path.join(
|
|
229
229
|
user_tempdir, os.path.basename(options.input_video_file) + '_frames_' + str(uuid1()))
|
|
230
|
-
|
|
230
|
+
|
|
231
231
|
rendering_output_folder = os.path.join(
|
|
232
232
|
tempdir, os.path.basename(options.input_video_file) + '_detections_' + str(uuid1()))
|
|
233
|
-
|
|
233
|
+
|
|
234
234
|
temporary_folder_info = \
|
|
235
235
|
{
|
|
236
236
|
'temp_folder_base':user_tempdir,
|
|
237
237
|
'frame_output_folder':frame_output_folder,
|
|
238
238
|
'rendering_output_folder':rendering_output_folder
|
|
239
239
|
}
|
|
240
|
-
|
|
240
|
+
|
|
241
241
|
return temporary_folder_info
|
|
242
242
|
|
|
243
243
|
# ...def _create_frame_output_folders(...)
|
|
@@ -247,50 +247,50 @@ def _clean_up_rendered_frames(options,rendering_output_folder,detected_frame_fil
|
|
|
247
247
|
"""
|
|
248
248
|
If necessary, delete rendered frames and/or the entire rendering output folder.
|
|
249
249
|
"""
|
|
250
|
-
|
|
250
|
+
|
|
251
251
|
if rendering_output_folder is None:
|
|
252
252
|
return
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
|
|
255
|
-
|
|
255
|
+
|
|
256
256
|
# (Optionally) delete the temporary directory we used for rendered detection images
|
|
257
257
|
if not options.keep_rendered_frames:
|
|
258
|
-
|
|
258
|
+
|
|
259
259
|
try:
|
|
260
|
-
|
|
260
|
+
|
|
261
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
|
|
262
|
+
# matter where it is and (b) we created it in temp space, delete the
|
|
263
263
|
# whole tree
|
|
264
264
|
if options.force_rendered_frame_folder_deletion and \
|
|
265
265
|
(not caller_provided_rendering_output_folder):
|
|
266
|
-
|
|
266
|
+
|
|
267
267
|
if options.verbose:
|
|
268
268
|
print('Recursively deleting rendered frame folder {}'.format(
|
|
269
269
|
rendering_output_folder))
|
|
270
|
-
|
|
270
|
+
|
|
271
271
|
shutil.rmtree(rendering_output_folder)
|
|
272
|
-
|
|
272
|
+
|
|
273
273
|
# ...otherwise just delete the frames, but leave the folder in place
|
|
274
274
|
else:
|
|
275
|
-
|
|
275
|
+
|
|
276
276
|
if options.force_rendered_frame_folder_deletion:
|
|
277
277
|
assert caller_provided_rendering_output_folder
|
|
278
278
|
print('Warning: force_rendered_frame_folder_deletion supplied with a ' + \
|
|
279
279
|
'user-provided folder, only removing frames')
|
|
280
|
-
|
|
280
|
+
|
|
281
281
|
for rendered_frame_fn in detected_frame_files:
|
|
282
282
|
os.remove(rendered_frame_fn)
|
|
283
|
-
|
|
283
|
+
|
|
284
284
|
except Exception as e:
|
|
285
285
|
print('Warning: error deleting rendered frames from folder {}:\n{}'.format(
|
|
286
286
|
rendering_output_folder,str(e)))
|
|
287
287
|
pass
|
|
288
288
|
|
|
289
289
|
elif options.force_rendered_frame_folder_deletion:
|
|
290
|
-
|
|
290
|
+
|
|
291
291
|
print('Warning: keep_rendered_frames and force_rendered_frame_folder_deletion both ' + \
|
|
292
292
|
'specified, not deleting')
|
|
293
|
-
|
|
293
|
+
|
|
294
294
|
# ...def _clean_up_rendered_frames(...)
|
|
295
295
|
|
|
296
296
|
|
|
@@ -298,48 +298,48 @@ def _clean_up_extracted_frames(options,frame_output_folder,frame_filenames):
|
|
|
298
298
|
"""
|
|
299
299
|
If necessary, delete extracted frames and/or the entire temporary frame folder.
|
|
300
300
|
"""
|
|
301
|
-
|
|
301
|
+
|
|
302
302
|
if frame_output_folder is None:
|
|
303
303
|
return
|
|
304
|
-
|
|
304
|
+
|
|
305
305
|
caller_provided_frame_output_folder = (options.frame_folder is not None)
|
|
306
|
-
|
|
306
|
+
|
|
307
307
|
if not options.keep_extracted_frames:
|
|
308
|
-
|
|
308
|
+
|
|
309
309
|
try:
|
|
310
|
-
|
|
310
|
+
|
|
311
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
|
|
312
|
+
# matter where it is and (b) we created it in temp space, delete the
|
|
313
313
|
# whole tree.
|
|
314
314
|
if options.force_extracted_frame_folder_deletion and \
|
|
315
315
|
(not caller_provided_frame_output_folder):
|
|
316
|
-
|
|
316
|
+
|
|
317
317
|
if options.verbose:
|
|
318
318
|
print('Recursively deleting frame output folder {}'.format(frame_output_folder))
|
|
319
|
-
|
|
319
|
+
|
|
320
320
|
shutil.rmtree(frame_output_folder)
|
|
321
|
-
|
|
321
|
+
|
|
322
322
|
# ...otherwise just delete the frames, but leave the folder in place
|
|
323
323
|
else:
|
|
324
|
-
|
|
324
|
+
|
|
325
325
|
if frame_filenames is None:
|
|
326
326
|
return
|
|
327
|
-
|
|
327
|
+
|
|
328
328
|
if options.force_extracted_frame_folder_deletion:
|
|
329
329
|
assert caller_provided_frame_output_folder
|
|
330
330
|
print('Warning: force_extracted_frame_folder_deletion supplied with a ' + \
|
|
331
331
|
'user-provided folder, only removing frames')
|
|
332
|
-
|
|
332
|
+
|
|
333
333
|
for extracted_frame_fn in frame_filenames:
|
|
334
334
|
os.remove(extracted_frame_fn)
|
|
335
|
-
|
|
335
|
+
|
|
336
336
|
except Exception as e:
|
|
337
337
|
print('Warning: error removing extracted frames from folder {}:\n{}'.format(
|
|
338
338
|
frame_output_folder,str(e)))
|
|
339
339
|
pass
|
|
340
|
-
|
|
340
|
+
|
|
341
341
|
elif options.force_extracted_frame_folder_deletion:
|
|
342
|
-
|
|
342
|
+
|
|
343
343
|
print('Warning: keep_extracted_frames and force_extracted_frame_folder_deletion both ' + \
|
|
344
344
|
'specified, not deleting')
|
|
345
345
|
|
|
@@ -350,18 +350,18 @@ def process_video(options):
|
|
|
350
350
|
"""
|
|
351
351
|
Process a single video through MD, optionally writing a new video with boxes.
|
|
352
352
|
Can also be used just to split a video into frames, without running a model.
|
|
353
|
-
|
|
354
|
-
Args:
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
355
|
options (ProcessVideoOptions): all the parameters used to control this process,
|
|
356
356
|
including filenames; see ProcessVideoOptions for details
|
|
357
|
-
|
|
357
|
+
|
|
358
358
|
Returns:
|
|
359
359
|
dict: frame-level MegaDetector results, identical to what's in the output .json file
|
|
360
360
|
"""
|
|
361
361
|
|
|
362
362
|
# Check for incompatible options
|
|
363
363
|
_validate_video_options(options)
|
|
364
|
-
|
|
364
|
+
|
|
365
365
|
if options.output_json_file is None:
|
|
366
366
|
options.output_json_file = options.input_video_file + '.json'
|
|
367
367
|
|
|
@@ -371,59 +371,59 @@ def process_video(options):
|
|
|
371
371
|
if options.time_sample is not None:
|
|
372
372
|
raise ValueError('Time-based sampling is not supported when processing a single video; ' + \
|
|
373
373
|
'consider processing a folder, or using frame_sample')
|
|
374
|
-
|
|
374
|
+
|
|
375
375
|
if options.model_file == 'no_detection' and not options.keep_extracted_frames:
|
|
376
376
|
print('Warning: you asked for no detection, but did not specify keep_extracted_frames, this is a no-op')
|
|
377
377
|
return
|
|
378
|
-
|
|
378
|
+
|
|
379
379
|
# Track whether frame and rendering folders were created by this script
|
|
380
380
|
caller_provided_frame_output_folder = (options.frame_folder is not None)
|
|
381
381
|
caller_provided_rendering_output_folder = (options.frame_rendering_folder is not None)
|
|
382
|
-
|
|
382
|
+
|
|
383
383
|
frame_output_folder = None
|
|
384
384
|
frame_filenames = None
|
|
385
|
-
|
|
385
|
+
|
|
386
386
|
# If we should re-use existing results, and the output file exists, don't bother running MD
|
|
387
387
|
if (options.reuse_results_if_available and os.path.isfile(options.output_json_file)):
|
|
388
|
-
|
|
388
|
+
|
|
389
389
|
print('Loading results from {}'.format(options.output_json_file))
|
|
390
390
|
with open(options.output_json_file,'r') as f:
|
|
391
391
|
results = json.load(f)
|
|
392
|
-
|
|
392
|
+
|
|
393
393
|
# Run MD in memory if we don't need to generate frames
|
|
394
394
|
#
|
|
395
395
|
# Currently if we're generating an output video, we need to generate frames on disk first.
|
|
396
396
|
elif (not options.keep_extracted_frames and \
|
|
397
397
|
not options.render_output_video and \
|
|
398
398
|
not options.force_on_disk_frame_extraction):
|
|
399
|
-
|
|
399
|
+
|
|
400
400
|
# Run MegaDetector in memory
|
|
401
|
-
|
|
401
|
+
|
|
402
402
|
if options.verbose:
|
|
403
403
|
print('Running MegaDetector in memory for {}'.format(options.input_video_file))
|
|
404
|
-
|
|
404
|
+
|
|
405
405
|
if options.frame_folder is not None:
|
|
406
406
|
print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
|
|
407
407
|
'not; no raw frames will be written')
|
|
408
|
-
|
|
408
|
+
|
|
409
409
|
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
410
|
-
|
|
410
|
+
|
|
411
411
|
def frame_callback(image_np,image_id):
|
|
412
412
|
return detector.generate_detections_one_image(image_np,
|
|
413
413
|
image_id,
|
|
414
414
|
detection_threshold=options.json_confidence_threshold,
|
|
415
415
|
augment=options.augment)
|
|
416
|
-
|
|
417
|
-
frame_results = run_callback_on_frames(options.input_video_file,
|
|
416
|
+
|
|
417
|
+
frame_results = run_callback_on_frames(options.input_video_file,
|
|
418
418
|
frame_callback,
|
|
419
|
-
every_n_frames=options.frame_sample,
|
|
420
|
-
verbose=options.verbose,
|
|
419
|
+
every_n_frames=options.frame_sample,
|
|
420
|
+
verbose=options.verbose,
|
|
421
421
|
frames_to_process=options.frames_to_extract)
|
|
422
|
-
|
|
422
|
+
|
|
423
423
|
frame_results['results'] = _add_frame_numbers_to_results(frame_results['results'])
|
|
424
|
-
|
|
424
|
+
|
|
425
425
|
run_detector_batch.write_results_to_file(
|
|
426
|
-
frame_results['results'],
|
|
426
|
+
frame_results['results'],
|
|
427
427
|
options.output_json_file,
|
|
428
428
|
relative_path_base=None,
|
|
429
429
|
detector_file=options.model_file,
|
|
@@ -431,48 +431,48 @@ def process_video(options):
|
|
|
431
431
|
|
|
432
432
|
# Extract frames and optionally run MegaDetector on those frames
|
|
433
433
|
else:
|
|
434
|
-
|
|
434
|
+
|
|
435
435
|
if options.verbose:
|
|
436
436
|
print('Extracting frames for {}'.format(options.input_video_file))
|
|
437
|
-
|
|
438
|
-
# This does not create any folders, just defines temporary folder names in
|
|
437
|
+
|
|
438
|
+
# This does not create any folders, just defines temporary folder names in
|
|
439
439
|
# case we need them.
|
|
440
440
|
temporary_folder_info = _select_temporary_output_folders(options)
|
|
441
|
-
|
|
441
|
+
|
|
442
442
|
if (caller_provided_frame_output_folder):
|
|
443
443
|
frame_output_folder = options.frame_folder
|
|
444
444
|
else:
|
|
445
445
|
frame_output_folder = temporary_folder_info['frame_output_folder']
|
|
446
|
-
|
|
446
|
+
|
|
447
447
|
os.makedirs(frame_output_folder, exist_ok=True)
|
|
448
|
-
|
|
449
|
-
|
|
448
|
+
|
|
449
|
+
|
|
450
450
|
## Extract frames
|
|
451
|
-
|
|
452
|
-
frame_filenames,
|
|
453
|
-
options.input_video_file,
|
|
454
|
-
frame_output_folder,
|
|
455
|
-
every_n_frames=options.frame_sample,
|
|
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
456
|
overwrite=(not options.reuse_frames_if_available),
|
|
457
|
-
quality=options.quality,
|
|
458
|
-
max_width=options.max_width,
|
|
457
|
+
quality=options.quality,
|
|
458
|
+
max_width=options.max_width,
|
|
459
459
|
verbose=options.verbose,
|
|
460
460
|
frames_to_extract=options.frames_to_extract,
|
|
461
461
|
allow_empty_videos=options.allow_empty_videos)
|
|
462
|
-
|
|
462
|
+
|
|
463
463
|
image_file_names = frame_filenames
|
|
464
464
|
if options.debug_max_frames > 0:
|
|
465
465
|
image_file_names = image_file_names[0:options.debug_max_frames]
|
|
466
|
-
|
|
466
|
+
|
|
467
467
|
## Run MegaDetector on those frames
|
|
468
|
-
|
|
468
|
+
|
|
469
469
|
if options.model_file != 'no_detection':
|
|
470
|
-
|
|
470
|
+
|
|
471
471
|
if options.verbose:
|
|
472
472
|
print('Running MD for {}'.format(options.input_video_file))
|
|
473
|
-
|
|
473
|
+
|
|
474
474
|
results = run_detector_batch.load_and_run_detector_batch(
|
|
475
|
-
options.model_file,
|
|
475
|
+
options.model_file,
|
|
476
476
|
image_file_names,
|
|
477
477
|
confidence_threshold=options.json_confidence_threshold,
|
|
478
478
|
n_cores=options.n_cores,
|
|
@@ -481,73 +481,73 @@ def process_video(options):
|
|
|
481
481
|
augment=options.augment,
|
|
482
482
|
image_size=options.image_size,
|
|
483
483
|
detector_options=options.detector_options)
|
|
484
|
-
|
|
484
|
+
|
|
485
485
|
results = _add_frame_numbers_to_results(results)
|
|
486
|
-
|
|
486
|
+
|
|
487
487
|
run_detector_batch.write_results_to_file(
|
|
488
|
-
results,
|
|
488
|
+
results,
|
|
489
489
|
options.output_json_file,
|
|
490
490
|
relative_path_base=frame_output_folder,
|
|
491
491
|
detector_file=options.model_file,
|
|
492
|
-
custom_metadata={'video_frame_rate':
|
|
493
|
-
|
|
492
|
+
custom_metadata={'video_frame_rate':fs})
|
|
493
|
+
|
|
494
494
|
# ...if we are/aren't keeping raw frames on disk
|
|
495
|
-
|
|
496
|
-
|
|
495
|
+
|
|
496
|
+
|
|
497
497
|
## (Optionally) render output video
|
|
498
|
-
|
|
498
|
+
|
|
499
499
|
if options.render_output_video:
|
|
500
|
-
|
|
500
|
+
|
|
501
501
|
## Render detections to images
|
|
502
|
-
|
|
502
|
+
|
|
503
503
|
if (caller_provided_rendering_output_folder):
|
|
504
504
|
rendering_output_dir = options.frame_rendering_folder
|
|
505
505
|
else:
|
|
506
506
|
rendering_output_dir = temporary_folder_info['rendering_output_folder']
|
|
507
|
-
|
|
507
|
+
|
|
508
508
|
os.makedirs(rendering_output_dir,exist_ok=True)
|
|
509
|
-
|
|
509
|
+
|
|
510
510
|
detected_frame_files = visualize_detector_output.visualize_detector_output(
|
|
511
511
|
detector_output_path=options.output_json_file,
|
|
512
512
|
out_dir=rendering_output_dir,
|
|
513
513
|
images_dir=frame_output_folder,
|
|
514
514
|
confidence_threshold=options.rendering_confidence_threshold)
|
|
515
515
|
|
|
516
|
-
|
|
516
|
+
|
|
517
517
|
## Choose the frame rate at which we should render the output video
|
|
518
|
-
|
|
518
|
+
|
|
519
519
|
if options.rendering_fs is not None:
|
|
520
520
|
rendering_fs = options.rendering_fs
|
|
521
521
|
elif options.frame_sample is None and options.time_sample is None:
|
|
522
|
-
rendering_fs =
|
|
522
|
+
rendering_fs = fs
|
|
523
523
|
elif options.frame_sample is not None:
|
|
524
524
|
assert options.time_sample is None
|
|
525
|
-
# If the original video was 30fps and we sampled every 10th frame,
|
|
525
|
+
# If the original video was 30fps and we sampled every 10th frame,
|
|
526
526
|
# render at 3fps
|
|
527
|
-
rendering_fs =
|
|
527
|
+
rendering_fs = fs / options.frame_sample
|
|
528
528
|
elif options.time_sample is not None:
|
|
529
529
|
rendering_fs = options.time_sample
|
|
530
|
-
|
|
531
|
-
|
|
530
|
+
|
|
531
|
+
|
|
532
532
|
## Render the output video
|
|
533
|
-
|
|
533
|
+
|
|
534
534
|
print('Rendering {} frames to {} at {} fps (original video {} fps)'.format(
|
|
535
|
-
len(detected_frame_files), options.output_video_file,rendering_fs,
|
|
536
|
-
frames_to_video(detected_frame_files,
|
|
537
|
-
rendering_fs,
|
|
538
|
-
options.output_video_file,
|
|
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
539
|
codec_spec=options.fourcc)
|
|
540
|
-
|
|
540
|
+
|
|
541
541
|
# Possibly clean up rendered frames
|
|
542
542
|
_clean_up_rendered_frames(options,rendering_output_dir,detected_frame_files)
|
|
543
|
-
|
|
543
|
+
|
|
544
544
|
# ...if we're rendering video
|
|
545
|
-
|
|
546
|
-
|
|
545
|
+
|
|
546
|
+
|
|
547
547
|
## (Optionally) delete the extracted frames
|
|
548
|
-
|
|
548
|
+
|
|
549
549
|
_clean_up_extracted_frames(options, frame_output_folder, frame_filenames)
|
|
550
|
-
|
|
550
|
+
|
|
551
551
|
# ...process_video()
|
|
552
552
|
|
|
553
553
|
|
|
@@ -555,119 +555,119 @@ def process_video_folder(options):
|
|
|
555
555
|
"""
|
|
556
556
|
Process a folder of videos through MD. Can also be used just to split a folder of
|
|
557
557
|
videos into frames, without running a model.
|
|
558
|
-
|
|
559
|
-
When this function is used to run MD, two .json files will get written, one with
|
|
558
|
+
|
|
559
|
+
When this function is used to run MD, two .json files will get written, one with
|
|
560
560
|
an entry for each *frame* (identical to what's created by process_video()), and
|
|
561
561
|
one with an entry for each *video* (which is more suitable for, e.g., reading into
|
|
562
562
|
Timelapse).
|
|
563
|
-
|
|
564
|
-
Args:
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
565
|
options (ProcessVideoOptions): all the parameters used to control this process,
|
|
566
|
-
including filenames; see ProcessVideoOptions for details
|
|
566
|
+
including filenames; see ProcessVideoOptions for details
|
|
567
567
|
"""
|
|
568
|
-
|
|
568
|
+
|
|
569
569
|
## Validate options
|
|
570
570
|
|
|
571
571
|
# Check for incompatible options
|
|
572
572
|
_validate_video_options(options)
|
|
573
|
-
|
|
573
|
+
|
|
574
574
|
assert os.path.isdir(options.input_video_file), \
|
|
575
575
|
'{} is not a folder'.format(options.input_video_file)
|
|
576
|
-
|
|
576
|
+
|
|
577
577
|
if options.model_file == 'no_detection' and not options.keep_extracted_frames:
|
|
578
578
|
print('Warning: you asked for no detection, but did not specify keep_extracted_frames, this is a no-op')
|
|
579
579
|
return
|
|
580
|
-
|
|
580
|
+
|
|
581
581
|
if options.model_file != 'no_detection':
|
|
582
582
|
assert options.output_json_file is not None, \
|
|
583
|
-
'When processing a folder, you must specify an output .json file'
|
|
583
|
+
'When processing a folder, you must specify an output .json file'
|
|
584
584
|
assert options.output_json_file.endswith('.json')
|
|
585
585
|
video_json = options.output_json_file
|
|
586
586
|
frames_json = options.output_json_file.replace('.json','.frames.json')
|
|
587
587
|
os.makedirs(os.path.dirname(video_json),exist_ok=True)
|
|
588
|
-
|
|
588
|
+
|
|
589
589
|
# Track whether frame and rendering folders were created by this script
|
|
590
590
|
caller_provided_frame_output_folder = (options.frame_folder is not None)
|
|
591
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
|
|
592
|
+
|
|
593
|
+
# This does not create any folders, just defines temporary folder names in
|
|
594
594
|
# case we need them.
|
|
595
595
|
temporary_folder_info = _select_temporary_output_folders(options)
|
|
596
|
-
|
|
596
|
+
|
|
597
597
|
frame_output_folder = None
|
|
598
598
|
image_file_names = None
|
|
599
599
|
video_filename_to_fs = {}
|
|
600
|
-
|
|
600
|
+
|
|
601
601
|
if options.time_sample is not None:
|
|
602
602
|
every_n_frames_param = -1 * options.time_sample
|
|
603
603
|
else:
|
|
604
604
|
every_n_frames_param = options.frame_sample
|
|
605
|
-
|
|
605
|
+
|
|
606
606
|
# Run MD in memory if we don't need to generate frames
|
|
607
607
|
#
|
|
608
608
|
# Currently if we're generating an output video, we need to generate frames on disk first.
|
|
609
609
|
if (not options.keep_extracted_frames and \
|
|
610
610
|
not options.render_output_video and \
|
|
611
611
|
not options.force_on_disk_frame_extraction):
|
|
612
|
-
|
|
612
|
+
|
|
613
613
|
if options.verbose:
|
|
614
614
|
print('Running MegaDetector in memory for folder {}'.format(options.input_video_file))
|
|
615
|
-
|
|
615
|
+
|
|
616
616
|
if options.frame_folder is not None:
|
|
617
617
|
print('Warning: frame_folder specified, but keep_extracted_frames is ' + \
|
|
618
618
|
'not; no raw frames will be written')
|
|
619
|
-
|
|
619
|
+
|
|
620
620
|
detector = load_detector(options.model_file,detector_options=options.detector_options)
|
|
621
|
-
|
|
621
|
+
|
|
622
622
|
def frame_callback(image_np,image_id):
|
|
623
623
|
return detector.generate_detections_one_image(image_np,
|
|
624
624
|
image_id,
|
|
625
625
|
detection_threshold=options.json_confidence_threshold,
|
|
626
626
|
augment=options.augment)
|
|
627
|
-
|
|
628
|
-
md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
|
|
627
|
+
|
|
628
|
+
md_results = run_callback_on_frames_for_folder(input_video_folder=options.input_video_file,
|
|
629
629
|
frame_callback=frame_callback,
|
|
630
630
|
every_n_frames=every_n_frames_param,
|
|
631
631
|
verbose=options.verbose)
|
|
632
|
-
|
|
632
|
+
|
|
633
633
|
video_results = md_results['results']
|
|
634
|
-
|
|
634
|
+
|
|
635
635
|
for i_video,video_filename in enumerate(md_results['video_filenames']):
|
|
636
636
|
video_filename = video_filename.replace('\\','/')
|
|
637
637
|
assert video_filename not in video_filename_to_fs
|
|
638
638
|
video_filename_to_fs[video_filename] = md_results['frame_rates'][i_video]
|
|
639
|
-
|
|
639
|
+
|
|
640
640
|
all_frame_results = []
|
|
641
|
-
|
|
641
|
+
|
|
642
642
|
# r = video_results[0]
|
|
643
643
|
for frame_results in video_results:
|
|
644
644
|
_add_frame_numbers_to_results(frame_results)
|
|
645
645
|
all_frame_results.extend(frame_results)
|
|
646
|
-
|
|
646
|
+
|
|
647
647
|
run_detector_batch.write_results_to_file(
|
|
648
|
-
all_frame_results,
|
|
648
|
+
all_frame_results,
|
|
649
649
|
frames_json,
|
|
650
650
|
relative_path_base=None,
|
|
651
651
|
detector_file=options.model_file)
|
|
652
|
-
|
|
652
|
+
|
|
653
653
|
else:
|
|
654
|
-
|
|
654
|
+
|
|
655
655
|
## Split every video into frames
|
|
656
|
-
|
|
656
|
+
|
|
657
657
|
if options.verbose:
|
|
658
658
|
print('Extracting frames for folder {}'.format(options.input_video_file))
|
|
659
|
-
|
|
659
|
+
|
|
660
660
|
if caller_provided_frame_output_folder:
|
|
661
661
|
frame_output_folder = options.frame_folder
|
|
662
662
|
else:
|
|
663
663
|
frame_output_folder = temporary_folder_info['frame_output_folder']
|
|
664
|
-
|
|
664
|
+
|
|
665
665
|
os.makedirs(frame_output_folder, exist_ok=True)
|
|
666
|
-
|
|
667
|
-
frame_filenames,
|
|
666
|
+
|
|
667
|
+
frame_filenames, fs, video_filenames = \
|
|
668
668
|
video_folder_to_frames(input_folder=options.input_video_file,
|
|
669
|
-
output_folder_base=frame_output_folder,
|
|
670
|
-
recursive=options.recursive,
|
|
669
|
+
output_folder_base=frame_output_folder,
|
|
670
|
+
recursive=options.recursive,
|
|
671
671
|
overwrite=(not options.reuse_frames_if_available),
|
|
672
672
|
n_threads=options.n_cores,
|
|
673
673
|
every_n_frames=every_n_frames_param,
|
|
@@ -676,16 +676,16 @@ def process_video_folder(options):
|
|
|
676
676
|
max_width=options.max_width,
|
|
677
677
|
frames_to_extract=options.frames_to_extract,
|
|
678
678
|
allow_empty_videos=options.allow_empty_videos)
|
|
679
|
-
|
|
679
|
+
|
|
680
680
|
for i_video,video_filename_abs in enumerate(video_filenames):
|
|
681
681
|
video_filename_relative = os.path.relpath(video_filename_abs,options.input_video_file)
|
|
682
682
|
video_filename_relative = video_filename_relative.replace('\\','/')
|
|
683
683
|
assert video_filename_relative not in video_filename_to_fs
|
|
684
|
-
video_filename_to_fs[video_filename_relative] =
|
|
685
|
-
|
|
684
|
+
video_filename_to_fs[video_filename_relative] = fs[i_video]
|
|
685
|
+
|
|
686
686
|
print('Extracted frames for {} videos'.format(len(set(video_filenames))))
|
|
687
687
|
image_file_names = list(itertools.chain.from_iterable(frame_filenames))
|
|
688
|
-
|
|
688
|
+
|
|
689
689
|
if len(image_file_names) == 0:
|
|
690
690
|
if len(video_filenames) == 0:
|
|
691
691
|
print('No videos found in folder {}'.format(options.input_video_file))
|
|
@@ -693,31 +693,31 @@ def process_video_folder(options):
|
|
|
693
693
|
print('No frames extracted from folder {}, this may be due to an '\
|
|
694
694
|
'unsupported video codec'.format(options.input_video_file))
|
|
695
695
|
return
|
|
696
|
-
|
|
696
|
+
|
|
697
697
|
if options.debug_max_frames is not None and options.debug_max_frames > 0:
|
|
698
698
|
image_file_names = image_file_names[0:options.debug_max_frames]
|
|
699
|
-
|
|
699
|
+
|
|
700
700
|
if options.model_file == 'no_detection':
|
|
701
701
|
assert options.keep_extracted_frames, \
|
|
702
702
|
'Internal error: keep_extracted_frames not set, but no model specified'
|
|
703
703
|
return
|
|
704
|
-
|
|
705
|
-
|
|
704
|
+
|
|
705
|
+
|
|
706
706
|
## Run MegaDetector on the extracted frames
|
|
707
|
-
|
|
707
|
+
|
|
708
708
|
if options.reuse_results_if_available and \
|
|
709
709
|
os.path.isfile(frames_json):
|
|
710
|
-
|
|
710
|
+
|
|
711
711
|
print('Bypassing inference, loading results from {}'.format(frames_json))
|
|
712
712
|
with open(frames_json,'r') as f:
|
|
713
713
|
results = json.load(f)
|
|
714
|
-
|
|
714
|
+
|
|
715
715
|
else:
|
|
716
|
-
|
|
716
|
+
|
|
717
717
|
print('Running MegaDetector')
|
|
718
|
-
|
|
718
|
+
|
|
719
719
|
results = run_detector_batch.load_and_run_detector_batch(
|
|
720
|
-
options.model_file,
|
|
720
|
+
options.model_file,
|
|
721
721
|
image_file_names,
|
|
722
722
|
confidence_threshold=options.json_confidence_threshold,
|
|
723
723
|
n_cores=options.n_cores,
|
|
@@ -726,24 +726,24 @@ def process_video_folder(options):
|
|
|
726
726
|
augment=options.augment,
|
|
727
727
|
image_size=options.image_size,
|
|
728
728
|
detector_options=options.detector_options)
|
|
729
|
-
|
|
729
|
+
|
|
730
730
|
_add_frame_numbers_to_results(results)
|
|
731
|
-
|
|
731
|
+
|
|
732
732
|
run_detector_batch.write_results_to_file(
|
|
733
|
-
results,
|
|
733
|
+
results,
|
|
734
734
|
frames_json,
|
|
735
735
|
relative_path_base=frame_output_folder,
|
|
736
736
|
detector_file=options.model_file)
|
|
737
|
-
|
|
737
|
+
|
|
738
738
|
# ...if we're re-using existing results / running MD
|
|
739
|
-
|
|
739
|
+
|
|
740
740
|
# ...if we're running MD on in-memory frames vs. extracting frames to disk
|
|
741
|
-
|
|
741
|
+
|
|
742
742
|
## Convert frame-level results to video-level results
|
|
743
743
|
|
|
744
744
|
frame_to_video_options = FrameToVideoOptions()
|
|
745
745
|
frame_to_video_options.include_all_processed_frames = options.include_all_processed_frames
|
|
746
|
-
|
|
746
|
+
|
|
747
747
|
print('Converting frame-level results to video-level results')
|
|
748
748
|
frame_results_to_video_results(frames_json,
|
|
749
749
|
video_json,
|
|
@@ -752,17 +752,17 @@ def process_video_folder(options):
|
|
|
752
752
|
|
|
753
753
|
|
|
754
754
|
## (Optionally) render output videos
|
|
755
|
-
|
|
755
|
+
|
|
756
756
|
if options.render_output_video:
|
|
757
|
-
|
|
757
|
+
|
|
758
758
|
# Render detections to images
|
|
759
759
|
if (caller_provided_rendering_output_folder):
|
|
760
760
|
rendering_output_dir = options.frame_rendering_folder
|
|
761
761
|
else:
|
|
762
762
|
rendering_output_dir = temporary_folder_info['rendering_output_folder']
|
|
763
|
-
|
|
763
|
+
|
|
764
764
|
os.makedirs(rendering_output_dir,exist_ok=True)
|
|
765
|
-
|
|
765
|
+
|
|
766
766
|
detected_frame_files = visualize_detector_output.visualize_detector_output(
|
|
767
767
|
detector_output_path=frames_json,
|
|
768
768
|
out_dir=rendering_output_dir,
|
|
@@ -771,7 +771,7 @@ def process_video_folder(options):
|
|
|
771
771
|
preserve_path_structure=True,
|
|
772
772
|
output_image_width=-1)
|
|
773
773
|
detected_frame_files = [s.replace('\\','/') for s in detected_frame_files]
|
|
774
|
-
|
|
774
|
+
|
|
775
775
|
# Choose an output folder
|
|
776
776
|
output_folder_is_input_folder = False
|
|
777
777
|
if options.output_video_file is not None:
|
|
@@ -786,62 +786,62 @@ def process_video_folder(options):
|
|
|
786
786
|
else:
|
|
787
787
|
output_folder_is_input_folder = True
|
|
788
788
|
output_video_folder = options.input_video_file
|
|
789
|
-
|
|
789
|
+
|
|
790
790
|
# For each video
|
|
791
791
|
#
|
|
792
792
|
# TODO: parallelize this loop
|
|
793
793
|
#
|
|
794
794
|
# i_video=0; input_video_file_abs = video_filenames[i_video]
|
|
795
795
|
for i_video,input_video_file_abs in enumerate(video_filenames):
|
|
796
|
-
|
|
797
|
-
video_fs =
|
|
798
|
-
|
|
796
|
+
|
|
797
|
+
video_fs = fs[i_video]
|
|
798
|
+
|
|
799
799
|
if options.rendering_fs is not None:
|
|
800
800
|
rendering_fs = options.rendering_fs
|
|
801
|
-
elif options.frame_sample is None:
|
|
801
|
+
elif options.frame_sample is None:
|
|
802
802
|
rendering_fs = video_fs
|
|
803
803
|
else:
|
|
804
|
-
# If the original video was 30fps and we sampled every 10th frame,
|
|
805
|
-
# render at 3fps
|
|
804
|
+
# If the original video was 30fps and we sampled every 10th frame,
|
|
805
|
+
# render at 3fps
|
|
806
806
|
rendering_fs = video_fs / options.frame_sample
|
|
807
|
-
|
|
807
|
+
|
|
808
808
|
input_video_file_relative = os.path.relpath(input_video_file_abs,options.input_video_file)
|
|
809
809
|
video_frame_output_folder = os.path.join(rendering_output_dir,input_video_file_relative)
|
|
810
|
-
|
|
810
|
+
|
|
811
811
|
video_frame_output_folder = video_frame_output_folder.replace('\\','/')
|
|
812
812
|
assert os.path.isdir(video_frame_output_folder), \
|
|
813
813
|
'Could not find frame folder for video {}'.format(input_video_file_relative)
|
|
814
|
-
|
|
814
|
+
|
|
815
815
|
# Find the corresponding rendered frame folder
|
|
816
816
|
video_frame_files = [fn for fn in detected_frame_files if \
|
|
817
817
|
fn.startswith(video_frame_output_folder)]
|
|
818
818
|
assert len(video_frame_files) > 0, 'Could not find rendered frames for video {}'.format(
|
|
819
819
|
input_video_file_relative)
|
|
820
|
-
|
|
820
|
+
|
|
821
821
|
# Select the output filename for the rendered video
|
|
822
822
|
if output_folder_is_input_folder:
|
|
823
823
|
video_output_file = insert_before_extension(input_video_file_abs,'annotated','_')
|
|
824
824
|
else:
|
|
825
825
|
video_output_file = os.path.join(output_video_folder,input_video_file_relative)
|
|
826
|
-
|
|
826
|
+
|
|
827
827
|
os.makedirs(os.path.dirname(video_output_file),exist_ok=True)
|
|
828
|
-
|
|
829
|
-
# Create the output video
|
|
828
|
+
|
|
829
|
+
# Create the output video
|
|
830
830
|
print('Rendering detections for video {} to {} at {} fps (original video {} fps)'.format(
|
|
831
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,
|
|
832
|
+
frames_to_video(video_frame_files,
|
|
833
|
+
rendering_fs,
|
|
834
|
+
video_output_file,
|
|
835
835
|
codec_spec=options.fourcc)
|
|
836
|
-
|
|
836
|
+
|
|
837
837
|
# ...for each video
|
|
838
|
-
|
|
838
|
+
|
|
839
839
|
# Possibly clean up rendered frames
|
|
840
|
-
_clean_up_rendered_frames(options,rendering_output_dir,detected_frame_files)
|
|
841
|
-
|
|
840
|
+
_clean_up_rendered_frames(options,rendering_output_dir,detected_frame_files)
|
|
841
|
+
|
|
842
842
|
# ...if we're rendering video
|
|
843
|
-
|
|
844
|
-
|
|
843
|
+
|
|
844
|
+
|
|
845
845
|
## (Optionally) delete the extracted frames
|
|
846
846
|
_clean_up_extracted_frames(options, frame_output_folder, image_file_names)
|
|
847
847
|
|
|
@@ -851,25 +851,25 @@ def process_video_folder(options):
|
|
|
851
851
|
def options_to_command(options):
|
|
852
852
|
"""
|
|
853
853
|
Convert a ProcessVideoOptions object to a corresponding command line.
|
|
854
|
-
|
|
854
|
+
|
|
855
855
|
Args:
|
|
856
856
|
options (ProcessVideoOptions): the options set to render as a command line
|
|
857
|
-
|
|
857
|
+
|
|
858
858
|
Returns:
|
|
859
859
|
str: the command line corresponding to [options]
|
|
860
|
-
|
|
860
|
+
|
|
861
861
|
:meta private:
|
|
862
862
|
"""
|
|
863
863
|
cmd = 'python process_video.py'
|
|
864
864
|
cmd += ' "' + options.model_file + '"'
|
|
865
865
|
cmd += ' "' + options.input_video_file + '"'
|
|
866
|
-
|
|
866
|
+
|
|
867
867
|
if options.recursive:
|
|
868
868
|
cmd += ' --recursive'
|
|
869
869
|
if options.frame_folder is not None:
|
|
870
870
|
cmd += ' --frame_folder' + ' "' + options.frame_folder + '"'
|
|
871
871
|
if options.frame_rendering_folder is not None:
|
|
872
|
-
cmd += ' --frame_rendering_folder' + ' "' + options.frame_rendering_folder + '"'
|
|
872
|
+
cmd += ' --frame_rendering_folder' + ' "' + options.frame_rendering_folder + '"'
|
|
873
873
|
if options.output_json_file is not None:
|
|
874
874
|
cmd += ' --output_json_file' + ' "' + options.output_json_file + '"'
|
|
875
875
|
if options.output_video_file is not None:
|
|
@@ -877,13 +877,13 @@ def options_to_command(options):
|
|
|
877
877
|
if options.keep_extracted_frames:
|
|
878
878
|
cmd += ' --keep_extracted_frames'
|
|
879
879
|
if options.reuse_results_if_available:
|
|
880
|
-
cmd += ' --reuse_results_if_available'
|
|
880
|
+
cmd += ' --reuse_results_if_available'
|
|
881
881
|
if options.reuse_frames_if_available:
|
|
882
882
|
cmd += ' --reuse_frames_if_available'
|
|
883
883
|
if options.render_output_video:
|
|
884
884
|
cmd += ' --render_output_video'
|
|
885
885
|
if options.keep_rendered_frames:
|
|
886
|
-
cmd += ' --keep_rendered_frames'
|
|
886
|
+
cmd += ' --keep_rendered_frames'
|
|
887
887
|
if options.rendering_confidence_threshold is not None:
|
|
888
888
|
cmd += ' --rendering_confidence_threshold ' + str(options.rendering_confidence_threshold)
|
|
889
889
|
if options.json_confidence_threshold is not None:
|
|
@@ -917,37 +917,37 @@ def options_to_command(options):
|
|
|
917
917
|
if options.force_rendered_frame_folder_deletion:
|
|
918
918
|
cmd += ' --force_rendered_frame_folder_deletion'
|
|
919
919
|
if options.detector_options is not None and len(options.detector_options) > 0:
|
|
920
|
-
cmd += '--detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
920
|
+
cmd += '--detector_options {}'.format(dict_to_kvp_list(options.detector_options))
|
|
921
921
|
|
|
922
922
|
return cmd
|
|
923
923
|
|
|
924
|
-
|
|
924
|
+
|
|
925
925
|
#%% Interactive driver
|
|
926
926
|
|
|
927
|
-
if False:
|
|
928
|
-
|
|
927
|
+
if False:
|
|
928
|
+
|
|
929
929
|
pass
|
|
930
930
|
|
|
931
931
|
#%% Process a folder of videos
|
|
932
|
-
|
|
932
|
+
|
|
933
933
|
model_file = 'MDV5A'
|
|
934
934
|
# input_dir = r'g:\temp\test-videos'
|
|
935
935
|
# input_dir = r'G:\temp\md-test-package\md-test-images\video-samples'
|
|
936
936
|
input_dir = os.path.expanduser('~/AppData/Local/Temp/md-tests/md-test-images/video-samples')
|
|
937
937
|
assert os.path.isdir(input_dir)
|
|
938
|
-
|
|
938
|
+
|
|
939
939
|
output_base = r'g:\temp\video_test'
|
|
940
940
|
os.makedirs(output_base,exist_ok=True)
|
|
941
|
-
|
|
941
|
+
|
|
942
942
|
frame_folder = os.path.join(output_base,'frames')
|
|
943
943
|
rendering_folder = os.path.join(output_base,'rendered-frames')
|
|
944
944
|
output_json_file = os.path.join(output_base,'video-test.json')
|
|
945
945
|
output_video_folder = os.path.join(output_base,'output_videos')
|
|
946
|
-
|
|
947
|
-
|
|
946
|
+
|
|
947
|
+
|
|
948
948
|
print('Processing folder {}'.format(input_dir))
|
|
949
|
-
|
|
950
|
-
options = ProcessVideoOptions()
|
|
949
|
+
|
|
950
|
+
options = ProcessVideoOptions()
|
|
951
951
|
options.model_file = model_file
|
|
952
952
|
options.input_video_file = input_dir
|
|
953
953
|
options.output_video_file = output_video_folder
|
|
@@ -960,9 +960,9 @@ if False:
|
|
|
960
960
|
options.max_width = None # 1280
|
|
961
961
|
options.n_cores = 4
|
|
962
962
|
options.verbose = True
|
|
963
|
-
options.render_output_video = False
|
|
963
|
+
options.render_output_video = False
|
|
964
964
|
options.frame_folder = frame_folder
|
|
965
|
-
options.frame_rendering_folder = rendering_folder
|
|
965
|
+
options.frame_rendering_folder = rendering_folder
|
|
966
966
|
options.keep_extracted_frames = False
|
|
967
967
|
options.keep_rendered_frames = False
|
|
968
968
|
options.force_extracted_frame_folder_deletion = False
|
|
@@ -970,113 +970,113 @@ if False:
|
|
|
970
970
|
options.fourcc = 'mp4v'
|
|
971
971
|
options.force_on_disk_frame_extraction = False
|
|
972
972
|
# options.rendering_confidence_threshold = 0.15
|
|
973
|
-
|
|
973
|
+
|
|
974
974
|
cmd = options_to_command(options); print(cmd)
|
|
975
|
-
|
|
975
|
+
|
|
976
976
|
# import clipboard; clipboard.copy(cmd)
|
|
977
977
|
process_video_folder(options)
|
|
978
|
-
|
|
979
|
-
|
|
978
|
+
|
|
979
|
+
|
|
980
980
|
#%% Process a single video
|
|
981
981
|
|
|
982
982
|
fn = r'g:\temp\test-videos\person_and_dog\DSCF0056.AVI'
|
|
983
983
|
assert os.path.isfile(fn)
|
|
984
984
|
model_file = 'MDV5A'
|
|
985
985
|
input_video_file = fn
|
|
986
|
-
|
|
986
|
+
|
|
987
987
|
output_base = r'g:\temp\video_test'
|
|
988
988
|
frame_folder = os.path.join(output_base,'frames')
|
|
989
989
|
rendering_folder = os.path.join(output_base,'rendered-frames')
|
|
990
990
|
output_json_file = os.path.join(output_base,'video-test.json')
|
|
991
991
|
output_video_file = os.path.join(output_base,'output_video.mp4')
|
|
992
|
-
|
|
992
|
+
|
|
993
993
|
options = ProcessVideoOptions()
|
|
994
994
|
options.model_file = model_file
|
|
995
995
|
options.input_video_file = input_video_file
|
|
996
996
|
options.render_output_video = True
|
|
997
997
|
options.output_video_file = output_video_file
|
|
998
|
-
options.output_json_file = output_json_file
|
|
999
|
-
options.verbose = True
|
|
998
|
+
options.output_json_file = output_json_file
|
|
999
|
+
options.verbose = True
|
|
1000
1000
|
options.quality = 75
|
|
1001
1001
|
options.frame_sample = 10
|
|
1002
|
-
options.max_width = 1600
|
|
1002
|
+
options.max_width = 1600
|
|
1003
1003
|
options.frame_folder = frame_folder
|
|
1004
|
-
options.frame_rendering_folder = rendering_folder
|
|
1004
|
+
options.frame_rendering_folder = rendering_folder
|
|
1005
1005
|
options.keep_extracted_frames = False
|
|
1006
1006
|
options.keep_rendered_frames = False
|
|
1007
1007
|
options.force_extracted_frame_folder_deletion = True
|
|
1008
|
-
options.force_rendered_frame_folder_deletion = True
|
|
1008
|
+
options.force_rendered_frame_folder_deletion = True
|
|
1009
1009
|
options.fourcc = 'mp4v'
|
|
1010
1010
|
# options.rendering_confidence_threshold = 0.15
|
|
1011
|
-
|
|
1011
|
+
|
|
1012
1012
|
cmd = options_to_command(options); print(cmd)
|
|
1013
|
-
|
|
1014
|
-
# import clipboard; clipboard.copy(cmd)
|
|
1013
|
+
|
|
1014
|
+
# import clipboard; clipboard.copy(cmd)
|
|
1015
1015
|
process_video(options)
|
|
1016
|
-
|
|
1017
|
-
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
1018
|
#%% Extract specific frames from a single video, no detection
|
|
1019
1019
|
|
|
1020
1020
|
fn = r'g:\temp\test-videos\person_and_dog\DSCF0064.AVI'
|
|
1021
1021
|
assert os.path.isfile(fn)
|
|
1022
1022
|
model_file = 'no_detection'
|
|
1023
1023
|
input_video_file = fn
|
|
1024
|
-
|
|
1024
|
+
|
|
1025
1025
|
output_base = r'g:\temp\video_test'
|
|
1026
1026
|
frame_folder = os.path.join(output_base,'frames')
|
|
1027
1027
|
output_video_file = os.path.join(output_base,'output_videos.mp4')
|
|
1028
|
-
|
|
1028
|
+
|
|
1029
1029
|
options = ProcessVideoOptions()
|
|
1030
1030
|
options.model_file = model_file
|
|
1031
|
-
options.input_video_file = input_video_file
|
|
1032
|
-
options.verbose = True
|
|
1031
|
+
options.input_video_file = input_video_file
|
|
1032
|
+
options.verbose = True
|
|
1033
1033
|
options.quality = 90
|
|
1034
1034
|
options.frame_sample = None
|
|
1035
1035
|
options.frames_to_extract = [0,100]
|
|
1036
|
-
options.max_width = None
|
|
1036
|
+
options.max_width = None
|
|
1037
1037
|
options.frame_folder = frame_folder
|
|
1038
1038
|
options.keep_extracted_frames = True
|
|
1039
|
-
|
|
1039
|
+
|
|
1040
1040
|
cmd = options_to_command(options); print(cmd)
|
|
1041
|
-
|
|
1041
|
+
|
|
1042
1042
|
# import clipboard; clipboard.copy(cmd)
|
|
1043
|
-
process_video(options)
|
|
1044
|
-
|
|
1045
|
-
|
|
1043
|
+
process_video(options)
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
1046
|
#%% Extract specific frames from a folder, no detection
|
|
1047
1047
|
|
|
1048
1048
|
fn = r'g:\temp\test-videos\person_and_dog'
|
|
1049
1049
|
assert os.path.isdir(fn)
|
|
1050
1050
|
model_file = 'no_detection'
|
|
1051
1051
|
input_video_file = fn
|
|
1052
|
-
|
|
1052
|
+
|
|
1053
1053
|
output_base = r'g:\temp\video_test'
|
|
1054
1054
|
frame_folder = os.path.join(output_base,'frames')
|
|
1055
1055
|
output_video_file = os.path.join(output_base,'output_videos.mp4')
|
|
1056
|
-
|
|
1056
|
+
|
|
1057
1057
|
options = ProcessVideoOptions()
|
|
1058
1058
|
options.model_file = model_file
|
|
1059
|
-
options.input_video_file = input_video_file
|
|
1060
|
-
options.verbose = True
|
|
1059
|
+
options.input_video_file = input_video_file
|
|
1060
|
+
options.verbose = True
|
|
1061
1061
|
options.quality = 90
|
|
1062
1062
|
options.frame_sample = None
|
|
1063
1063
|
options.frames_to_extract = [0,100]
|
|
1064
|
-
options.max_width = None
|
|
1064
|
+
options.max_width = None
|
|
1065
1065
|
options.frame_folder = frame_folder
|
|
1066
1066
|
options.keep_extracted_frames = True
|
|
1067
|
-
|
|
1067
|
+
|
|
1068
1068
|
cmd = options_to_command(options); print(cmd)
|
|
1069
|
-
|
|
1069
|
+
|
|
1070
1070
|
# import clipboard; clipboard.copy(cmd)
|
|
1071
|
-
process_video(options)
|
|
1071
|
+
process_video(options)
|
|
1072
|
+
|
|
1072
1073
|
|
|
1073
|
-
|
|
1074
1074
|
#%% Command-line driver
|
|
1075
1075
|
|
|
1076
|
-
def main():
|
|
1076
|
+
def main(): # noqa
|
|
1077
1077
|
|
|
1078
1078
|
default_options = ProcessVideoOptions()
|
|
1079
|
-
|
|
1079
|
+
|
|
1080
1080
|
parser = argparse.ArgumentParser(description=(
|
|
1081
1081
|
'Run MegaDetector on each frame (or every Nth frame) in a video (or folder of videos), optionally '\
|
|
1082
1082
|
'producing a new video with detections annotated'))
|
|
@@ -1091,15 +1091,15 @@ def main():
|
|
|
1091
1091
|
parser.add_argument('--recursive', action='store_true',
|
|
1092
1092
|
help='recurse into [input_video_file]; only meaningful if a folder '\
|
|
1093
1093
|
'is specified as input')
|
|
1094
|
-
|
|
1094
|
+
|
|
1095
1095
|
parser.add_argument('--frame_folder', type=str, default=None,
|
|
1096
1096
|
help='folder to use for intermediate frame storage, defaults to a folder '\
|
|
1097
1097
|
'in the system temporary folder')
|
|
1098
|
-
|
|
1098
|
+
|
|
1099
1099
|
parser.add_argument('--frame_rendering_folder', type=str, default=None,
|
|
1100
1100
|
help='folder to use for rendered frame storage, defaults to a folder in '\
|
|
1101
1101
|
'the system temporary folder')
|
|
1102
|
-
|
|
1102
|
+
|
|
1103
1103
|
parser.add_argument('--output_json_file', type=str,
|
|
1104
1104
|
default=None, help='.json output file, defaults to [video file].json')
|
|
1105
1105
|
|
|
@@ -1109,21 +1109,23 @@ def main():
|
|
|
1109
1109
|
|
|
1110
1110
|
parser.add_argument('--keep_extracted_frames',
|
|
1111
1111
|
action='store_true', help='Disable the deletion of extracted frames')
|
|
1112
|
-
|
|
1112
|
+
|
|
1113
1113
|
parser.add_argument('--reuse_frames_if_available',
|
|
1114
|
-
action='store_true',
|
|
1115
|
-
|
|
1114
|
+
action='store_true',
|
|
1115
|
+
help="Don't extract frames that are already available in the frame extraction folder")
|
|
1116
|
+
|
|
1116
1117
|
parser.add_argument('--reuse_results_if_available',
|
|
1117
|
-
action='store_true',
|
|
1118
|
+
action='store_true',
|
|
1119
|
+
help='If the output .json files exists, and this flag is set,'\
|
|
1118
1120
|
'we\'ll skip running MegaDetector')
|
|
1119
|
-
|
|
1121
|
+
|
|
1120
1122
|
parser.add_argument('--render_output_video', action='store_true',
|
|
1121
1123
|
help='enable video output rendering (not rendered by default)')
|
|
1122
1124
|
|
|
1123
1125
|
parser.add_argument('--fourcc', default=default_fourcc,
|
|
1124
|
-
help='fourcc code to use for video encoding (default {}),
|
|
1125
|
-
|
|
1126
|
-
|
|
1126
|
+
help=f'fourcc code to use for video encoding (default {default_fourcc}), ' + \
|
|
1127
|
+
'only used if render_output_video is True')
|
|
1128
|
+
|
|
1127
1129
|
parser.add_argument('--keep_rendered_frames',
|
|
1128
1130
|
action='store_true', help='Disable the deletion of rendered (w/boxes) frames')
|
|
1129
1131
|
|
|
@@ -1132,24 +1134,25 @@ def main():
|
|
|
1132
1134
|
'delete the frames, but leave the (probably-empty) folder in place. This option '\
|
|
1133
1135
|
'forces deletion of the folder as well. Use at your own risk; does not check '\
|
|
1134
1136
|
'whether other files were present in the folder.')
|
|
1135
|
-
|
|
1137
|
+
|
|
1136
1138
|
parser.add_argument('--force_rendered_frame_folder_deletion',
|
|
1137
1139
|
action='store_true', help='By default, when keep_rendered_frames is False, we '\
|
|
1138
1140
|
'delete the frames, but leave the (probably-empty) folder in place. This option '\
|
|
1139
1141
|
'forces deletion of the folder as well. Use at your own risk; does not check '\
|
|
1140
1142
|
'whether other files were present in the folder.')
|
|
1141
|
-
|
|
1143
|
+
|
|
1142
1144
|
parser.add_argument('--rendering_confidence_threshold', type=float,
|
|
1143
|
-
default=None,
|
|
1144
|
-
help="don't render boxes with confidence below this threshold
|
|
1145
|
+
default=None,
|
|
1146
|
+
help="don't render boxes with confidence below this threshold " + \
|
|
1147
|
+
"(defaults to choosing based on the MD version)")
|
|
1145
1148
|
|
|
1146
1149
|
parser.add_argument('--rendering_fs', type=float,
|
|
1147
|
-
default=None,
|
|
1150
|
+
default=None,
|
|
1148
1151
|
help='force a specific frame rate for output videos (only relevant when using '\
|
|
1149
1152
|
'--render_output_video) (defaults to the original frame rate)')
|
|
1150
1153
|
|
|
1151
1154
|
parser.add_argument('--json_confidence_threshold', type=float,
|
|
1152
|
-
default=default_options.json_confidence_threshold,
|
|
1155
|
+
default=default_options.json_confidence_threshold,
|
|
1153
1156
|
help="don't include boxes in the .json file with confidence "\
|
|
1154
1157
|
'below this threshold (default {})'.format(
|
|
1155
1158
|
default_options.json_confidence_threshold))
|
|
@@ -1168,26 +1171,26 @@ def main():
|
|
|
1168
1171
|
parser.add_argument('--frames_to_extract', nargs='+', type=int,
|
|
1169
1172
|
default=None, help='extract specific frames (one or more ints), mutually exclusive '\
|
|
1170
1173
|
'with --frame_sample and --time_sample.')
|
|
1171
|
-
|
|
1174
|
+
|
|
1172
1175
|
parser.add_argument('--time_sample', type=float,
|
|
1173
1176
|
default=None, help='process frames every N seconds; this is converted to a '\
|
|
1174
1177
|
'frame sampling rate, so it may not be exactly the requested interval in seconds. '\
|
|
1175
1178
|
'mutually exclusive with --frame_sample and --frames_to_extract.')
|
|
1176
1179
|
|
|
1177
1180
|
parser.add_argument('--quality', type=int,
|
|
1178
|
-
default=default_options.quality,
|
|
1179
|
-
help='JPEG quality for extracted frames (defaults to {}),
|
|
1180
|
-
|
|
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')
|
|
1181
1184
|
|
|
1182
1185
|
parser.add_argument('--max_width', type=int,
|
|
1183
|
-
default=default_options.max_width,
|
|
1186
|
+
default=default_options.max_width,
|
|
1184
1187
|
help='Resize frames larger than this before writing (defaults to {})'.format(
|
|
1185
1188
|
default_options.max_width))
|
|
1186
1189
|
|
|
1187
1190
|
parser.add_argument('--debug_max_frames', type=int,
|
|
1188
1191
|
default=-1, help='Trim to N frames for debugging (impacts model execution, '\
|
|
1189
1192
|
'not frame rendering)')
|
|
1190
|
-
|
|
1193
|
+
|
|
1191
1194
|
parser.add_argument('--class_mapping_filename',
|
|
1192
1195
|
type=str,
|
|
1193
1196
|
default=None, help='Use a non-default class mapping, supplied in a .json file '\
|
|
@@ -1197,42 +1200,42 @@ def main():
|
|
|
1197
1200
|
|
|
1198
1201
|
parser.add_argument('--verbose', action='store_true',
|
|
1199
1202
|
help='Enable additional debug output')
|
|
1200
|
-
|
|
1203
|
+
|
|
1201
1204
|
parser.add_argument('--image_size',
|
|
1202
1205
|
type=int,
|
|
1203
1206
|
default=None,
|
|
1204
1207
|
help=('Force image resizing to a specific integer size on the long '\
|
|
1205
|
-
'axis (not recommended to change this)'))
|
|
1206
|
-
|
|
1208
|
+
'axis (not recommended to change this)'))
|
|
1209
|
+
|
|
1207
1210
|
parser.add_argument('--augment',
|
|
1208
1211
|
action='store_true',
|
|
1209
1212
|
help='Enable image augmentation')
|
|
1210
|
-
|
|
1213
|
+
|
|
1211
1214
|
parser.add_argument('--include_all_processed_frames',
|
|
1212
1215
|
action='store_true',
|
|
1213
1216
|
help='When processing a folder of videos, this flag indicates that the output '\
|
|
1214
1217
|
'should include results for every frame that was processed, rather than just '\
|
|
1215
1218
|
'one representative frame for each detection category per video.')
|
|
1216
|
-
|
|
1219
|
+
|
|
1217
1220
|
parser.add_argument('--allow_empty_videos',
|
|
1218
1221
|
action='store_true',
|
|
1219
1222
|
help='By default, videos with no retrievable frames cause an error, this makes it a warning')
|
|
1220
|
-
|
|
1223
|
+
|
|
1221
1224
|
parser.add_argument(
|
|
1222
1225
|
'--detector_options',
|
|
1223
1226
|
nargs='*',
|
|
1224
1227
|
metavar='KEY=VALUE',
|
|
1225
1228
|
default='',
|
|
1226
1229
|
help='Detector-specific options, as a space-separated list of key-value pairs')
|
|
1227
|
-
|
|
1230
|
+
|
|
1228
1231
|
if len(sys.argv[1:]) == 0:
|
|
1229
1232
|
parser.print_help()
|
|
1230
1233
|
parser.exit()
|
|
1231
|
-
|
|
1234
|
+
|
|
1232
1235
|
args = parser.parse_args()
|
|
1233
|
-
options = ProcessVideoOptions()
|
|
1236
|
+
options = ProcessVideoOptions()
|
|
1234
1237
|
args_to_object(args,options)
|
|
1235
|
-
|
|
1238
|
+
|
|
1236
1239
|
options.detector_options = parse_kvp_list(args.detector_options)
|
|
1237
1240
|
|
|
1238
1241
|
if os.path.isdir(options.input_video_file):
|