megadetector 5.0.11__py3-none-any.whl → 5.0.12__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/__init__.py +0 -0
- megadetector/api/batch_processing/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/__init__.py +0 -0
- megadetector/api/batch_processing/api_core/batch_service/score.py +439 -0
- megadetector/api/batch_processing/api_core/server.py +294 -0
- megadetector/api/batch_processing/api_core/server_api_config.py +98 -0
- megadetector/api/batch_processing/api_core/server_app_config.py +55 -0
- megadetector/api/batch_processing/api_core/server_batch_job_manager.py +220 -0
- megadetector/api/batch_processing/api_core/server_job_status_table.py +152 -0
- megadetector/api/batch_processing/api_core/server_orchestration.py +360 -0
- megadetector/api/batch_processing/api_core/server_utils.py +92 -0
- megadetector/api/batch_processing/api_core_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_core_support/aggregate_results_manually.py +46 -0
- megadetector/api/batch_processing/api_support/__init__.py +0 -0
- megadetector/api/batch_processing/api_support/summarize_daily_activity.py +152 -0
- megadetector/api/batch_processing/data_preparation/__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 +126 -0
- megadetector/api/batch_processing/integration/eMammal/test_scripts/select_images_for_testing.py +55 -0
- megadetector/api/synchronous/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/__init__.py +0 -0
- megadetector/api/synchronous/api_core/animal_detection_api/api_backend.py +152 -0
- megadetector/api/synchronous/api_core/animal_detection_api/api_frontend.py +266 -0
- megadetector/api/synchronous/api_core/animal_detection_api/config.py +35 -0
- megadetector/api/synchronous/api_core/tests/__init__.py +0 -0
- megadetector/api/synchronous/api_core/tests/load_test.py +110 -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 +627 -0
- megadetector/classification/crop_detections.py +516 -0
- megadetector/classification/csv_to_json.py +226 -0
- megadetector/classification/detect_and_crop.py +855 -0
- megadetector/classification/efficientnet/__init__.py +9 -0
- megadetector/classification/efficientnet/model.py +415 -0
- megadetector/classification/efficientnet/utils.py +610 -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 +699 -0
- megadetector/classification/map_classification_categories.py +276 -0
- megadetector/classification/merge_classification_detection_output.py +506 -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/annotations/__init__.py +0 -0
- megadetector/data_management/annotations/annotation_constants.py +34 -0
- megadetector/data_management/camtrap_dp_to_coco.py +239 -0
- megadetector/data_management/cct_json_utils.py +395 -0
- megadetector/data_management/cct_to_md.py +176 -0
- megadetector/data_management/cct_to_wi.py +289 -0
- megadetector/data_management/coco_to_labelme.py +272 -0
- megadetector/data_management/coco_to_yolo.py +662 -0
- megadetector/data_management/databases/__init__.py +0 -0
- megadetector/data_management/databases/add_width_and_height_to_db.py +33 -0
- megadetector/data_management/databases/combine_coco_camera_traps_files.py +206 -0
- megadetector/data_management/databases/integrity_check_json_db.py +477 -0
- megadetector/data_management/databases/subset_json_db.py +115 -0
- megadetector/data_management/generate_crops_from_cct.py +149 -0
- megadetector/data_management/get_image_sizes.py +189 -0
- megadetector/data_management/importers/add_nacti_sizes.py +52 -0
- megadetector/data_management/importers/add_timestamps_to_icct.py +79 -0
- megadetector/data_management/importers/animl_results_to_md_results.py +158 -0
- megadetector/data_management/importers/auckland_doc_test_to_json.py +373 -0
- megadetector/data_management/importers/auckland_doc_to_json.py +201 -0
- megadetector/data_management/importers/awc_to_json.py +191 -0
- megadetector/data_management/importers/bellevue_to_json.py +273 -0
- megadetector/data_management/importers/cacophony-thermal-importer.py +796 -0
- megadetector/data_management/importers/carrizo_shrubfree_2018.py +269 -0
- megadetector/data_management/importers/carrizo_trail_cam_2017.py +289 -0
- megadetector/data_management/importers/cct_field_adjustments.py +58 -0
- megadetector/data_management/importers/channel_islands_to_cct.py +913 -0
- megadetector/data_management/importers/eMammal/copy_and_unzip_emammal.py +180 -0
- megadetector/data_management/importers/eMammal/eMammal_helpers.py +249 -0
- megadetector/data_management/importers/eMammal/make_eMammal_json.py +223 -0
- megadetector/data_management/importers/ena24_to_json.py +276 -0
- megadetector/data_management/importers/filenames_to_json.py +386 -0
- megadetector/data_management/importers/helena_to_cct.py +283 -0
- megadetector/data_management/importers/idaho-camera-traps.py +1407 -0
- megadetector/data_management/importers/idfg_iwildcam_lila_prep.py +294 -0
- megadetector/data_management/importers/jb_csv_to_json.py +150 -0
- megadetector/data_management/importers/mcgill_to_json.py +250 -0
- megadetector/data_management/importers/missouri_to_json.py +490 -0
- megadetector/data_management/importers/nacti_fieldname_adjustments.py +79 -0
- megadetector/data_management/importers/noaa_seals_2019.py +181 -0
- megadetector/data_management/importers/pc_to_json.py +365 -0
- megadetector/data_management/importers/plot_wni_giraffes.py +123 -0
- megadetector/data_management/importers/prepare-noaa-fish-data-for-lila.py +359 -0
- megadetector/data_management/importers/prepare_zsl_imerit.py +131 -0
- megadetector/data_management/importers/rspb_to_json.py +356 -0
- megadetector/data_management/importers/save_the_elephants_survey_A.py +320 -0
- megadetector/data_management/importers/save_the_elephants_survey_B.py +329 -0
- megadetector/data_management/importers/snapshot_safari_importer.py +758 -0
- megadetector/data_management/importers/snapshot_safari_importer_reprise.py +665 -0
- megadetector/data_management/importers/snapshot_serengeti_lila.py +1067 -0
- megadetector/data_management/importers/snapshotserengeti/make_full_SS_json.py +150 -0
- megadetector/data_management/importers/snapshotserengeti/make_per_season_SS_json.py +153 -0
- megadetector/data_management/importers/sulross_get_exif.py +65 -0
- megadetector/data_management/importers/timelapse_csv_set_to_json.py +490 -0
- megadetector/data_management/importers/ubc_to_json.py +399 -0
- megadetector/data_management/importers/umn_to_json.py +507 -0
- megadetector/data_management/importers/wellington_to_json.py +263 -0
- megadetector/data_management/importers/wi_to_json.py +442 -0
- megadetector/data_management/importers/zamba_results_to_md_results.py +181 -0
- megadetector/data_management/labelme_to_coco.py +547 -0
- megadetector/data_management/labelme_to_yolo.py +272 -0
- megadetector/data_management/lila/__init__.py +0 -0
- megadetector/data_management/lila/add_locations_to_island_camera_traps.py +97 -0
- megadetector/data_management/lila/add_locations_to_nacti.py +147 -0
- megadetector/data_management/lila/create_lila_blank_set.py +558 -0
- megadetector/data_management/lila/create_lila_test_set.py +152 -0
- megadetector/data_management/lila/create_links_to_md_results_files.py +106 -0
- megadetector/data_management/lila/download_lila_subset.py +178 -0
- megadetector/data_management/lila/generate_lila_per_image_labels.py +516 -0
- megadetector/data_management/lila/get_lila_annotation_counts.py +170 -0
- megadetector/data_management/lila/get_lila_image_counts.py +112 -0
- megadetector/data_management/lila/lila_common.py +300 -0
- megadetector/data_management/lila/test_lila_metadata_urls.py +132 -0
- megadetector/data_management/ocr_tools.py +874 -0
- megadetector/data_management/read_exif.py +681 -0
- megadetector/data_management/remap_coco_categories.py +84 -0
- megadetector/data_management/remove_exif.py +66 -0
- megadetector/data_management/resize_coco_dataset.py +189 -0
- megadetector/data_management/wi_download_csv_to_coco.py +246 -0
- megadetector/data_management/yolo_output_to_md_output.py +441 -0
- megadetector/data_management/yolo_to_coco.py +676 -0
- megadetector/detection/__init__.py +0 -0
- megadetector/detection/detector_training/__init__.py +0 -0
- megadetector/detection/detector_training/model_main_tf2.py +114 -0
- megadetector/detection/process_video.py +702 -0
- megadetector/detection/pytorch_detector.py +341 -0
- megadetector/detection/run_detector.py +779 -0
- megadetector/detection/run_detector_batch.py +1219 -0
- megadetector/detection/run_inference_with_yolov5_val.py +917 -0
- megadetector/detection/run_tiled_inference.py +934 -0
- megadetector/detection/tf_detector.py +189 -0
- megadetector/detection/video_utils.py +606 -0
- megadetector/postprocessing/__init__.py +0 -0
- megadetector/postprocessing/add_max_conf.py +64 -0
- megadetector/postprocessing/categorize_detections_by_size.py +163 -0
- megadetector/postprocessing/combine_api_outputs.py +249 -0
- megadetector/postprocessing/compare_batch_results.py +958 -0
- megadetector/postprocessing/convert_output_format.py +396 -0
- megadetector/postprocessing/load_api_results.py +195 -0
- megadetector/postprocessing/md_to_coco.py +310 -0
- megadetector/postprocessing/md_to_labelme.py +330 -0
- megadetector/postprocessing/merge_detections.py +401 -0
- megadetector/postprocessing/postprocess_batch_results.py +1902 -0
- megadetector/postprocessing/remap_detection_categories.py +170 -0
- megadetector/postprocessing/render_detection_confusion_matrix.py +660 -0
- megadetector/postprocessing/repeat_detection_elimination/find_repeat_detections.py +211 -0
- megadetector/postprocessing/repeat_detection_elimination/remove_repeat_detections.py +83 -0
- megadetector/postprocessing/repeat_detection_elimination/repeat_detections_core.py +1631 -0
- megadetector/postprocessing/separate_detections_into_folders.py +730 -0
- megadetector/postprocessing/subset_json_detector_output.py +696 -0
- megadetector/postprocessing/top_folders_to_bottom.py +223 -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 +150 -0
- megadetector/taxonomy_mapping/prepare_lila_taxonomy_release.py +142 -0
- megadetector/taxonomy_mapping/preview_lila_taxonomy.py +590 -0
- megadetector/taxonomy_mapping/retrieve_sample_image.py +71 -0
- megadetector/taxonomy_mapping/simple_image_download.py +219 -0
- megadetector/taxonomy_mapping/species_lookup.py +834 -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/utils/__init__.py +0 -0
- megadetector/utils/azure_utils.py +178 -0
- megadetector/utils/ct_utils.py +612 -0
- megadetector/utils/directory_listing.py +246 -0
- megadetector/utils/md_tests.py +968 -0
- megadetector/utils/path_utils.py +1044 -0
- megadetector/utils/process_utils.py +157 -0
- megadetector/utils/sas_blob_utils.py +509 -0
- megadetector/utils/split_locations_into_train_val.py +228 -0
- megadetector/utils/string_utils.py +92 -0
- megadetector/utils/url_utils.py +323 -0
- megadetector/utils/write_html_image_list.py +225 -0
- megadetector/visualization/__init__.py +0 -0
- megadetector/visualization/plot_utils.py +293 -0
- megadetector/visualization/render_images_with_thumbnails.py +275 -0
- megadetector/visualization/visualization_utils.py +1536 -0
- megadetector/visualization/visualize_db.py +550 -0
- megadetector/visualization/visualize_detector_output.py +405 -0
- {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/METADATA +1 -1
- megadetector-5.0.12.dist-info/RECORD +199 -0
- megadetector-5.0.12.dist-info/top_level.txt +1 -0
- megadetector-5.0.11.dist-info/RECORD +0 -5
- megadetector-5.0.11.dist-info/top_level.txt +0 -1
- {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/LICENSE +0 -0
- {megadetector-5.0.11.dist-info → megadetector-5.0.12.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,702 @@
|
|
|
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
|
+
Operates by separating the video into frames, typically sampling every Nth frame, and writing
|
|
9
|
+
those frames to disk, before running MD. This approach clearly has a downside: it requires
|
|
10
|
+
a bunch more disk space, compared to extracting frames and running MD on them without ever
|
|
11
|
+
writing them to disk. The upside, though, is that this approach allows you to run repeat
|
|
12
|
+
detection elimination after running MegaDetector, and it allows allows more efficient re-use
|
|
13
|
+
of frames if you end up running MD more than once, or running multiple versions of MD.
|
|
14
|
+
|
|
15
|
+
TODO: optionally skip writing frames to disk, and process frames in memory.
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
#%% Imports
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import sys
|
|
23
|
+
import tempfile
|
|
24
|
+
import argparse
|
|
25
|
+
import itertools
|
|
26
|
+
import json
|
|
27
|
+
import shutil
|
|
28
|
+
|
|
29
|
+
from uuid import uuid1
|
|
30
|
+
|
|
31
|
+
from megadetector.detection import run_detector_batch
|
|
32
|
+
from megadetector.visualization import visualize_detector_output
|
|
33
|
+
from megadetector.utils.ct_utils import args_to_object
|
|
34
|
+
from megadetector.utils.path_utils import insert_before_extension
|
|
35
|
+
from megadetector.detection.video_utils import video_to_frames
|
|
36
|
+
from megadetector.detection.video_utils import frames_to_video
|
|
37
|
+
from megadetector.detection.video_utils import frame_results_to_video_results
|
|
38
|
+
from megadetector.detection.video_utils import video_folder_to_frames
|
|
39
|
+
from megadetector.detection.video_utils import default_fourcc
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
#%% Classes
|
|
43
|
+
|
|
44
|
+
class ProcessVideoOptions:
|
|
45
|
+
"""
|
|
46
|
+
Options controlling the behavior of process_video()
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
#: Can be a model filename (.pt or .pb) or a model name (e.g. "MDV5A")
|
|
50
|
+
model_file = 'MDV5A'
|
|
51
|
+
|
|
52
|
+
#: Video (of folder of videos) to process
|
|
53
|
+
input_video_file = ''
|
|
54
|
+
|
|
55
|
+
#: .json file to which we should write results
|
|
56
|
+
output_json_file = None
|
|
57
|
+
|
|
58
|
+
#: File to which we should write a video with boxes, only relevant if
|
|
59
|
+
#: render_output_video is True
|
|
60
|
+
output_video_file = None
|
|
61
|
+
|
|
62
|
+
#: Folder to use for extracted frames; will use a folder in system temp space
|
|
63
|
+
#: if this is None
|
|
64
|
+
frame_folder = None
|
|
65
|
+
|
|
66
|
+
# Folder to use for rendered frames (if rendering output video); will use a folder
|
|
67
|
+
#: in system temp space if this is None
|
|
68
|
+
frame_rendering_folder = None
|
|
69
|
+
|
|
70
|
+
#: Should we render a video with detection boxes?
|
|
71
|
+
#:
|
|
72
|
+
#: Only supported when processing a single video, not a folder.
|
|
73
|
+
render_output_video = False
|
|
74
|
+
|
|
75
|
+
#: If we are rendering boxes to a new video, should we keep the temporary
|
|
76
|
+
#: rendered frames?
|
|
77
|
+
keep_rendered_frames = False
|
|
78
|
+
|
|
79
|
+
#: Should we keep the extracted frames?
|
|
80
|
+
keep_extracted_frames = False
|
|
81
|
+
|
|
82
|
+
#: Should we delete the entire folder the extracted frames are written to?
|
|
83
|
+
#:
|
|
84
|
+
#: By default, we delete the frame files but leave the (probably-empty) folder in place,
|
|
85
|
+
#: for no reason other than being paranoid about deleting folders.
|
|
86
|
+
force_extracted_frame_folder_deletion = False
|
|
87
|
+
|
|
88
|
+
#: Should we delete the entire folder the rendered frames are written to?
|
|
89
|
+
#:
|
|
90
|
+
#: By default, we delete the frame files but leave the (probably-empty) folder in place,
|
|
91
|
+
#: for no reason other than being paranoid about deleting folders.
|
|
92
|
+
force_rendered_frame_folder_deletion = False
|
|
93
|
+
|
|
94
|
+
#: If we've already run MegaDetector on this video or folder of videos, i.e. if we
|
|
95
|
+
#: find a corresponding MD results file, should we re-use it? Defaults to reprocessing.
|
|
96
|
+
reuse_results_if_available = False
|
|
97
|
+
|
|
98
|
+
#: If we've already split this video or folder of videos into frames, should we
|
|
99
|
+
#: we re-use those extracted frames? Defaults to reprocessing.
|
|
100
|
+
reuse_frames_if_available = False
|
|
101
|
+
|
|
102
|
+
#: If [input_video_file] is a folder, should we search for videos recursively?
|
|
103
|
+
recursive = False
|
|
104
|
+
|
|
105
|
+
#: Enable additional debug console output
|
|
106
|
+
verbose = False
|
|
107
|
+
|
|
108
|
+
#: fourcc code to use for writing videos; only relevant if render_output_video is True
|
|
109
|
+
fourcc = None
|
|
110
|
+
|
|
111
|
+
#: Confidence threshold to use for writing videos with boxes, only relevant if
|
|
112
|
+
#: if render_output_video is True. Defaults to choosing a reasonable threshold
|
|
113
|
+
#: based on the model version.
|
|
114
|
+
rendering_confidence_threshold = None
|
|
115
|
+
|
|
116
|
+
#: Detections below this threshold will not be included in the output file.
|
|
117
|
+
json_confidence_threshold = 0.005
|
|
118
|
+
|
|
119
|
+
#: Sample every Nth frame; set to None (default) or 1 to sample every frame. Typically
|
|
120
|
+
#: we sample down to around 3 fps, so for typical 30 fps videos, frame_sample=10 is a
|
|
121
|
+
#: typical value.
|
|
122
|
+
frame_sample = None
|
|
123
|
+
|
|
124
|
+
#: Number of workers to use for parallelization; set to <= 1 to disable parallelization
|
|
125
|
+
n_cores = 1
|
|
126
|
+
|
|
127
|
+
#: For debugging only, stop processing after a certain number of frames.
|
|
128
|
+
debug_max_frames = -1
|
|
129
|
+
|
|
130
|
+
#: File containing non-standard categories, typically only used if you're running a non-MD
|
|
131
|
+
#: detector.
|
|
132
|
+
class_mapping_filename = None
|
|
133
|
+
|
|
134
|
+
# ...class ProcessVideoOptions
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
#%% Functions
|
|
138
|
+
|
|
139
|
+
def process_video(options):
|
|
140
|
+
"""
|
|
141
|
+
Process a single video through MD, optionally writing a new video with boxes
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
options (ProcessVideoOptions): all the parameters used to control this process,
|
|
145
|
+
including filenames; see ProcessVideoOptions for details
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
dict: frame-level MegaDetector results, identical to what's in the output .json file
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
if options.output_json_file is None:
|
|
152
|
+
options.output_json_file = options.input_video_file + '.json'
|
|
153
|
+
|
|
154
|
+
if options.render_output_video and (options.output_video_file is None):
|
|
155
|
+
options.output_video_file = options.input_video_file + '.detections.mp4'
|
|
156
|
+
|
|
157
|
+
tempdir = os.path.join(tempfile.gettempdir(), 'process_camera_trap_video')
|
|
158
|
+
os.makedirs(tempdir,exist_ok=True)
|
|
159
|
+
|
|
160
|
+
# TODO:
|
|
161
|
+
#
|
|
162
|
+
# This is a lazy fix to an issue... if multiple users run this script, the
|
|
163
|
+
# "process_camera_trap_video" folder is owned by the first person who creates it, and others
|
|
164
|
+
# can't write to it. I could create uniquely-named folders, but I philosophically prefer
|
|
165
|
+
# to put all the individual UUID-named folders within a larger folder, so as to be a
|
|
166
|
+
# good tempdir citizen. So, the lazy fix is to make this world-writable.
|
|
167
|
+
try:
|
|
168
|
+
os.chmod(tempdir,0o777)
|
|
169
|
+
except Exception:
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
if options.frame_folder is not None:
|
|
173
|
+
frame_output_folder = options.frame_folder
|
|
174
|
+
else:
|
|
175
|
+
frame_output_folder = os.path.join(
|
|
176
|
+
tempdir, os.path.basename(options.input_video_file) + '_frames_' + str(uuid1()))
|
|
177
|
+
|
|
178
|
+
# TODO: keep track of whether we created this folder, delete if we're deleting the extracted
|
|
179
|
+
# frames and we created the folder, and the output files aren't in the same folder. For now,
|
|
180
|
+
# we're just deleting the extracted frames and leaving the empty folder around in this case.
|
|
181
|
+
os.makedirs(frame_output_folder, exist_ok=True)
|
|
182
|
+
|
|
183
|
+
frame_filenames, Fs = video_to_frames(
|
|
184
|
+
options.input_video_file, frame_output_folder,
|
|
185
|
+
every_n_frames=options.frame_sample, overwrite=(not options.reuse_frames_if_available))
|
|
186
|
+
|
|
187
|
+
image_file_names = frame_filenames
|
|
188
|
+
if options.debug_max_frames > 0:
|
|
189
|
+
image_file_names = image_file_names[0:options.debug_max_frames]
|
|
190
|
+
|
|
191
|
+
if options.reuse_results_if_available and \
|
|
192
|
+
os.path.isfile(options.output_json_file):
|
|
193
|
+
print('Loading results from {}'.format(options.output_json_file))
|
|
194
|
+
with open(options.output_json_file,'r') as f:
|
|
195
|
+
results = json.load(f)
|
|
196
|
+
else:
|
|
197
|
+
results = run_detector_batch.load_and_run_detector_batch(
|
|
198
|
+
options.model_file, image_file_names,
|
|
199
|
+
confidence_threshold=options.json_confidence_threshold,
|
|
200
|
+
n_cores=options.n_cores,
|
|
201
|
+
quiet=(not options.verbose),
|
|
202
|
+
class_mapping_filename=options.class_mapping_filename)
|
|
203
|
+
|
|
204
|
+
run_detector_batch.write_results_to_file(
|
|
205
|
+
results, options.output_json_file,
|
|
206
|
+
relative_path_base=frame_output_folder,
|
|
207
|
+
detector_file=options.model_file,
|
|
208
|
+
custom_metadata={'video_frame_rate':Fs})
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
## (Optionally) render output video
|
|
212
|
+
|
|
213
|
+
if options.render_output_video:
|
|
214
|
+
|
|
215
|
+
# Render detections to images
|
|
216
|
+
if options.frame_rendering_folder is not None:
|
|
217
|
+
rendering_output_dir = options.frame_rendering_folder
|
|
218
|
+
else:
|
|
219
|
+
rendering_output_dir = os.path.join(
|
|
220
|
+
tempdir, os.path.basename(options.input_video_file) + '_detections')
|
|
221
|
+
|
|
222
|
+
# TODO: keep track of whether we created this folder, delete if we're deleting the rendered
|
|
223
|
+
# frames and we created the folder, and the output files aren't in the same folder. For now,
|
|
224
|
+
# we're just deleting the rendered frames and leaving the empty folder around in this case.
|
|
225
|
+
os.makedirs(rendering_output_dir,exist_ok=True)
|
|
226
|
+
|
|
227
|
+
detected_frame_files = visualize_detector_output.visualize_detector_output(
|
|
228
|
+
detector_output_path=options.output_json_file,
|
|
229
|
+
out_dir=rendering_output_dir,
|
|
230
|
+
images_dir=frame_output_folder,
|
|
231
|
+
confidence_threshold=options.rendering_confidence_threshold)
|
|
232
|
+
|
|
233
|
+
# Combine into a video
|
|
234
|
+
if options.frame_sample is None:
|
|
235
|
+
rendering_fs = Fs
|
|
236
|
+
else:
|
|
237
|
+
rendering_fs = Fs / options.frame_sample
|
|
238
|
+
|
|
239
|
+
print('Rendering video to {} at {} fps (original video {} fps)'.format(
|
|
240
|
+
options.output_video_file,rendering_fs,Fs))
|
|
241
|
+
frames_to_video(detected_frame_files, rendering_fs, options.output_video_file, codec_spec=options.fourcc)
|
|
242
|
+
|
|
243
|
+
# Delete the temporary directory we used for detection images
|
|
244
|
+
if not options.keep_rendered_frames:
|
|
245
|
+
try:
|
|
246
|
+
if options.force_rendered_frame_folder_deletion:
|
|
247
|
+
shutil.rmtree(rendering_output_dir)
|
|
248
|
+
else:
|
|
249
|
+
for rendered_frame_fn in detected_frame_files:
|
|
250
|
+
os.remove(rendered_frame_fn)
|
|
251
|
+
except Exception as e:
|
|
252
|
+
print('Warning: error deleting rendered frames from folder {}:\n{}'.format(
|
|
253
|
+
rendering_output_dir,str(e)))
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
# ...if we're rendering video
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
## (Optionally) delete the extracted frames
|
|
260
|
+
|
|
261
|
+
if not options.keep_extracted_frames:
|
|
262
|
+
|
|
263
|
+
try:
|
|
264
|
+
if options.force_extracted_frame_folder_deletion:
|
|
265
|
+
print('Recursively deleting frame output folder {}'.format(frame_output_folder))
|
|
266
|
+
shutil.rmtree(frame_output_folder)
|
|
267
|
+
else:
|
|
268
|
+
for extracted_frame_fn in frame_filenames:
|
|
269
|
+
os.remove(extracted_frame_fn)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
print('Warning: error removing extracted frames from folder {}:\n{}'.format(
|
|
272
|
+
frame_output_folder,str(e)))
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
return results
|
|
276
|
+
|
|
277
|
+
# ...process_video()
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def process_video_folder(options):
|
|
281
|
+
"""
|
|
282
|
+
Process a folder of videos through MD
|
|
283
|
+
|
|
284
|
+
Args:
|
|
285
|
+
options (ProcessVideoOptions): all the parameters used to control this process,
|
|
286
|
+
including filenames; see ProcessVideoOptions for details
|
|
287
|
+
"""
|
|
288
|
+
|
|
289
|
+
## Validate options
|
|
290
|
+
|
|
291
|
+
assert os.path.isdir(options.input_video_file), \
|
|
292
|
+
'{} is not a folder'.format(options.input_video_file)
|
|
293
|
+
|
|
294
|
+
assert options.output_json_file is not None, \
|
|
295
|
+
'When processing a folder, you must specify an output .json file'
|
|
296
|
+
|
|
297
|
+
assert options.output_json_file.endswith('.json')
|
|
298
|
+
video_json = options.output_json_file
|
|
299
|
+
frames_json = options.output_json_file.replace('.json','.frames.json')
|
|
300
|
+
os.makedirs(os.path.dirname(video_json),exist_ok=True)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
## Split every video into frames
|
|
304
|
+
|
|
305
|
+
if options.frame_folder is not None:
|
|
306
|
+
frame_output_folder = options.frame_folder
|
|
307
|
+
else:
|
|
308
|
+
tempdir = os.path.join(tempfile.gettempdir(), 'process_camera_trap_video')
|
|
309
|
+
os.makedirs(tempdir,exist_ok=True)
|
|
310
|
+
|
|
311
|
+
# TODO: see above; this is a lazy fix to a permissions issue
|
|
312
|
+
try:
|
|
313
|
+
os.chmod(tempdir,0o777)
|
|
314
|
+
except Exception:
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
frame_output_folder = os.path.join(
|
|
318
|
+
tempdir, os.path.basename(options.input_video_file) + '_frames_' + str(uuid1()))
|
|
319
|
+
|
|
320
|
+
os.makedirs(frame_output_folder, exist_ok=True)
|
|
321
|
+
|
|
322
|
+
print('Extracting frames')
|
|
323
|
+
frame_filenames, Fs, video_filenames = \
|
|
324
|
+
video_folder_to_frames(input_folder=options.input_video_file,
|
|
325
|
+
output_folder_base=frame_output_folder,
|
|
326
|
+
recursive=options.recursive,
|
|
327
|
+
overwrite=(not options.reuse_frames_if_available),
|
|
328
|
+
n_threads=options.n_cores,every_n_frames=options.frame_sample,
|
|
329
|
+
verbose=options.verbose)
|
|
330
|
+
|
|
331
|
+
image_file_names = list(itertools.chain.from_iterable(frame_filenames))
|
|
332
|
+
|
|
333
|
+
if len(image_file_names) == 0:
|
|
334
|
+
if len(video_filenames) == 0:
|
|
335
|
+
print('No videos found in folder {}'.format(options.input_video_file))
|
|
336
|
+
else:
|
|
337
|
+
print('No frames extracted from folder {}, this may be due to an '\
|
|
338
|
+
'unsupported video codec'.format(options.input_video_file))
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
if options.debug_max_frames is not None and options.debug_max_frames > 0:
|
|
342
|
+
image_file_names = image_file_names[0:options.debug_max_frames]
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
## Run MegaDetector on the extracted frames
|
|
346
|
+
|
|
347
|
+
if options.reuse_results_if_available and \
|
|
348
|
+
os.path.isfile(frames_json):
|
|
349
|
+
print('Loading results from {}'.format(frames_json))
|
|
350
|
+
results = None
|
|
351
|
+
else:
|
|
352
|
+
print('Running MegaDetector')
|
|
353
|
+
results = run_detector_batch.load_and_run_detector_batch(
|
|
354
|
+
options.model_file, image_file_names,
|
|
355
|
+
confidence_threshold=options.json_confidence_threshold,
|
|
356
|
+
n_cores=options.n_cores,
|
|
357
|
+
quiet=(not options.verbose),
|
|
358
|
+
class_mapping_filename=options.class_mapping_filename)
|
|
359
|
+
|
|
360
|
+
run_detector_batch.write_results_to_file(
|
|
361
|
+
results, frames_json,
|
|
362
|
+
relative_path_base=frame_output_folder,
|
|
363
|
+
detector_file=options.model_file,
|
|
364
|
+
custom_metadata={'video_frame_rate':Fs})
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
## Convert frame-level results to video-level results
|
|
368
|
+
|
|
369
|
+
print('Converting frame-level results to video-level results')
|
|
370
|
+
frame_results_to_video_results(frames_json,video_json)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
## (Optionally) render output videos
|
|
374
|
+
|
|
375
|
+
if options.render_output_video:
|
|
376
|
+
|
|
377
|
+
# Render detections to images
|
|
378
|
+
if options.frame_rendering_folder is not None:
|
|
379
|
+
frame_rendering_output_dir = options.frame_rendering_folder
|
|
380
|
+
else:
|
|
381
|
+
frame_rendering_output_dir = os.path.join(
|
|
382
|
+
tempdir, os.path.basename(options.input_video_file) + '_detections')
|
|
383
|
+
|
|
384
|
+
# TODO: keep track of whether we created this folder, delete if we're deleting the rendered
|
|
385
|
+
# frames and we created the folder, and the output files aren't in the same folder. For now,
|
|
386
|
+
# we're just deleting the rendered frames and leaving the empty folder around in this case.
|
|
387
|
+
os.makedirs(frame_rendering_output_dir,exist_ok=True)
|
|
388
|
+
|
|
389
|
+
detected_frame_files = visualize_detector_output.visualize_detector_output(
|
|
390
|
+
detector_output_path=frames_json,
|
|
391
|
+
out_dir=frame_rendering_output_dir,
|
|
392
|
+
images_dir=frame_output_folder,
|
|
393
|
+
confidence_threshold=options.rendering_confidence_threshold,
|
|
394
|
+
preserve_path_structure=True,
|
|
395
|
+
output_image_width=-1)
|
|
396
|
+
|
|
397
|
+
# Choose an output folder
|
|
398
|
+
output_folder_is_input_folder = False
|
|
399
|
+
if options.output_video_file is not None:
|
|
400
|
+
if os.path.isfile(options.output_video_file):
|
|
401
|
+
raise ValueError('Rendering videos for a folder, but an existing file was specified as output')
|
|
402
|
+
elif options.output_video_file == options.input_video_file:
|
|
403
|
+
output_folder_is_input_folder = True
|
|
404
|
+
output_video_folder = options.input_video_file
|
|
405
|
+
else:
|
|
406
|
+
os.makedirs(options.output_video_file,exist_ok=True)
|
|
407
|
+
output_video_folder = options.output_video_file
|
|
408
|
+
else:
|
|
409
|
+
output_folder_is_input_folder = True
|
|
410
|
+
output_video_folder = options.input_video_file
|
|
411
|
+
|
|
412
|
+
# For each video
|
|
413
|
+
#
|
|
414
|
+
# TODO: parallelize this loop
|
|
415
|
+
#
|
|
416
|
+
# i_video=0; input_video_file_abs = video_filenames[i_video]
|
|
417
|
+
for i_video,input_video_file_abs in enumerate(video_filenames):
|
|
418
|
+
|
|
419
|
+
video_fs = Fs[i_video]
|
|
420
|
+
|
|
421
|
+
if options.frame_sample is None:
|
|
422
|
+
rendering_fs = video_fs
|
|
423
|
+
else:
|
|
424
|
+
rendering_fs = video_fs / options.frame_sample
|
|
425
|
+
|
|
426
|
+
input_video_file_relative = os.path.relpath(input_video_file_abs,options.input_video_file)
|
|
427
|
+
video_frame_output_folder = os.path.join(frame_rendering_output_dir,input_video_file_relative)
|
|
428
|
+
assert os.path.isdir(video_frame_output_folder), \
|
|
429
|
+
'Could not find frame folder for video {}'.format(input_video_file_relative)
|
|
430
|
+
|
|
431
|
+
# Find the corresponding rendered frame folder
|
|
432
|
+
video_frame_files = [fn for fn in detected_frame_files if \
|
|
433
|
+
fn.startswith(video_frame_output_folder)]
|
|
434
|
+
assert len(video_frame_files) > 0, 'Could not find rendered frames for video {}'.format(
|
|
435
|
+
input_video_file_relative)
|
|
436
|
+
|
|
437
|
+
# Select the output filename for the rendered video
|
|
438
|
+
if output_folder_is_input_folder:
|
|
439
|
+
video_output_file = insert_before_extension(input_video_file_abs,'annotated','_')
|
|
440
|
+
else:
|
|
441
|
+
video_output_file = os.path.join(output_video_folder,input_video_file_relative)
|
|
442
|
+
|
|
443
|
+
os.makedirs(os.path.dirname(video_output_file),exist_ok=True)
|
|
444
|
+
|
|
445
|
+
# Create the output video
|
|
446
|
+
print('Rendering detections for video {} to {} at {} fps (original video {} fps)'.format(
|
|
447
|
+
input_video_file_relative,video_output_file,rendering_fs,video_fs))
|
|
448
|
+
frames_to_video(video_frame_files, rendering_fs, video_output_file, codec_spec=options.fourcc)
|
|
449
|
+
|
|
450
|
+
# ...for each video
|
|
451
|
+
|
|
452
|
+
# Possibly clean up rendered frames
|
|
453
|
+
if not options.keep_rendered_frames:
|
|
454
|
+
try:
|
|
455
|
+
if options.force_rendered_frame_folder_deletion:
|
|
456
|
+
shutil.rmtree(frame_rendering_output_dir)
|
|
457
|
+
else:
|
|
458
|
+
for rendered_frame_fn in detected_frame_files:
|
|
459
|
+
os.remove(rendered_frame_fn)
|
|
460
|
+
except Exception as e:
|
|
461
|
+
print('Warning: error deleting rendered frames from folder {}:\n{}'.format(
|
|
462
|
+
frame_rendering_output_dir,str(e)))
|
|
463
|
+
pass
|
|
464
|
+
|
|
465
|
+
# ...if we're rendering video
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
## (Optionally) delete the extracted frames
|
|
469
|
+
|
|
470
|
+
if not options.keep_extracted_frames:
|
|
471
|
+
try:
|
|
472
|
+
print('Deleting frame cache')
|
|
473
|
+
if options.force_extracted_frame_folder_deletion:
|
|
474
|
+
print('Recursively deleting frame output folder {}'.format(frame_output_folder))
|
|
475
|
+
shutil.rmtree(frame_output_folder)
|
|
476
|
+
else:
|
|
477
|
+
for frame_fn in image_file_names:
|
|
478
|
+
os.remove(frame_fn)
|
|
479
|
+
except Exception as e:
|
|
480
|
+
print('Warning: error deleting frames from folder {}:\n{}'.format(
|
|
481
|
+
frame_output_folder,str(e)))
|
|
482
|
+
pass
|
|
483
|
+
|
|
484
|
+
# ...process_video_folder()
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def options_to_command(options):
|
|
488
|
+
|
|
489
|
+
cmd = 'python process_video.py'
|
|
490
|
+
cmd += ' "' + options.model_file + '"'
|
|
491
|
+
cmd += ' "' + options.input_video_file + '"'
|
|
492
|
+
|
|
493
|
+
if options.recursive:
|
|
494
|
+
cmd += ' --recursive'
|
|
495
|
+
if options.frame_folder is not None:
|
|
496
|
+
cmd += ' --frame_folder' + ' "' + options.frame_folder + '"'
|
|
497
|
+
if options.frame_rendering_folder is not None:
|
|
498
|
+
cmd += ' --frame_rendering_folder' + ' "' + options.frame_rendering_folder + '"'
|
|
499
|
+
if options.output_json_file is not None:
|
|
500
|
+
cmd += ' --output_json_file' + ' "' + options.output_json_file + '"'
|
|
501
|
+
if options.output_video_file is not None:
|
|
502
|
+
cmd += ' --output_video_file' + ' "' + options.output_video_file + '"'
|
|
503
|
+
if options.keep_extracted_frames:
|
|
504
|
+
cmd += ' --keep_extracted_frames'
|
|
505
|
+
if options.reuse_results_if_available:
|
|
506
|
+
cmd += ' --reuse_results_if_available'
|
|
507
|
+
if options.reuse_frames_if_available:
|
|
508
|
+
cmd += ' --reuse_frames_if_available'
|
|
509
|
+
if options.render_output_video:
|
|
510
|
+
cmd += ' --render_output_video'
|
|
511
|
+
if options.keep_rendered_frames:
|
|
512
|
+
cmd += ' --keep_rendered_frames'
|
|
513
|
+
if options.rendering_confidence_threshold is not None:
|
|
514
|
+
cmd += ' --rendering_confidence_threshold ' + str(options.rendering_confidence_threshold)
|
|
515
|
+
if options.json_confidence_threshold is not None:
|
|
516
|
+
cmd += ' --json_confidence_threshold ' + str(options.json_confidence_threshold)
|
|
517
|
+
if options.n_cores is not None:
|
|
518
|
+
cmd += ' --n_cores ' + str(options.n_cores)
|
|
519
|
+
if options.frame_sample is not None:
|
|
520
|
+
cmd += ' --frame_sample ' + str(options.frame_sample)
|
|
521
|
+
if options.debug_max_frames is not None:
|
|
522
|
+
cmd += ' --debug_max_frames ' + str(options.debug_max_frames)
|
|
523
|
+
if options.class_mapping_filename is not None:
|
|
524
|
+
cmd += ' --class_mapping_filename ' + str(options.class_mapping_filename)
|
|
525
|
+
if options.fourcc is not None:
|
|
526
|
+
cmd += ' --fourcc ' + options.fourcc
|
|
527
|
+
|
|
528
|
+
return cmd
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
#%% Interactive driver
|
|
532
|
+
|
|
533
|
+
if False:
|
|
534
|
+
|
|
535
|
+
#%% Process a folder of videos
|
|
536
|
+
|
|
537
|
+
model_file = 'MDV5A'
|
|
538
|
+
input_dir = r'c:\git\MegaDetector\test_images\test_images'
|
|
539
|
+
frame_folder = r'g:\temp\video_test\frames'
|
|
540
|
+
rendering_folder = r'g:\temp\video_test\rendered-frames'
|
|
541
|
+
output_json_file = r'g:\temp\video_test\video-test.json'
|
|
542
|
+
output_video_folder = r'g:\temp\video_test\output_videos'
|
|
543
|
+
|
|
544
|
+
print('Processing folder {}'.format(input_dir))
|
|
545
|
+
|
|
546
|
+
options = ProcessVideoOptions()
|
|
547
|
+
options.model_file = model_file
|
|
548
|
+
options.input_video_file = input_dir
|
|
549
|
+
options.output_video_file = output_video_folder
|
|
550
|
+
options.frame_folder = frame_folder
|
|
551
|
+
options.output_json_file = output_json_file
|
|
552
|
+
options.frame_rendering_folder = rendering_folder
|
|
553
|
+
options.render_output_video = True
|
|
554
|
+
options.keep_extracted_frames = True
|
|
555
|
+
options.keep_rendered_frames = True
|
|
556
|
+
options.recursive = True
|
|
557
|
+
options.reuse_frames_if_available = True
|
|
558
|
+
options.reuse_results_if_available = True
|
|
559
|
+
# options.confidence_threshold = 0.15
|
|
560
|
+
# options.fourcc = 'mp4v'
|
|
561
|
+
|
|
562
|
+
cmd = options_to_command(options)
|
|
563
|
+
print(cmd)
|
|
564
|
+
# import clipboard; clipboard.copy(cmd)
|
|
565
|
+
|
|
566
|
+
if False:
|
|
567
|
+
process_video_folder(options)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
#%% Process a single video
|
|
571
|
+
|
|
572
|
+
fn = os.path.expanduser('~/tmp/video-test/test-video.mp4')
|
|
573
|
+
model_file = 'MDV5A'
|
|
574
|
+
input_video_file = fn
|
|
575
|
+
frame_folder = os.path.expanduser('~/tmp/video-test/frames')
|
|
576
|
+
rendering_folder = os.path.expanduser('~/tmp/video-test/rendered-frames')
|
|
577
|
+
|
|
578
|
+
options = ProcessVideoOptions()
|
|
579
|
+
options.model_file = model_file
|
|
580
|
+
options.input_video_file = input_video_file
|
|
581
|
+
options.frame_folder = frame_folder
|
|
582
|
+
options.frame_rendering_folder = rendering_folder
|
|
583
|
+
options.render_output_video = True
|
|
584
|
+
options.output_video_file = os.path.expanduser('~/tmp/video-test/detections.mp4')
|
|
585
|
+
|
|
586
|
+
cmd = options_to_command(options)
|
|
587
|
+
print(cmd)
|
|
588
|
+
# import clipboard; clipboard.copy(cmd)
|
|
589
|
+
|
|
590
|
+
if False:
|
|
591
|
+
process_video(options)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
#%% Command-line driver
|
|
595
|
+
|
|
596
|
+
def main():
|
|
597
|
+
|
|
598
|
+
default_options = ProcessVideoOptions()
|
|
599
|
+
|
|
600
|
+
parser = argparse.ArgumentParser(description=(
|
|
601
|
+
'Run MegaDetector on each frame (or every Nth frame) in a video (or folder of videos), optionally '\
|
|
602
|
+
'producing a new video with detections annotated'))
|
|
603
|
+
|
|
604
|
+
parser.add_argument('model_file', type=str,
|
|
605
|
+
help='MegaDetector model file (.pt or .pb) or model name (e.g. "MDV5A")')
|
|
606
|
+
|
|
607
|
+
parser.add_argument('input_video_file', type=str,
|
|
608
|
+
help='video file (or folder) to process')
|
|
609
|
+
|
|
610
|
+
parser.add_argument('--recursive', action='store_true',
|
|
611
|
+
help='recurse into [input_video_file]; only meaningful if a folder '\
|
|
612
|
+
'is specified as input')
|
|
613
|
+
|
|
614
|
+
parser.add_argument('--frame_folder', type=str, default=None,
|
|
615
|
+
help='folder to use for intermediate frame storage, defaults to a folder '\
|
|
616
|
+
'in the system temporary folder')
|
|
617
|
+
|
|
618
|
+
parser.add_argument('--frame_rendering_folder', type=str, default=None,
|
|
619
|
+
help='folder to use for rendered frame storage, defaults to a folder in '\
|
|
620
|
+
'the system temporary folder')
|
|
621
|
+
|
|
622
|
+
parser.add_argument('--output_json_file', type=str,
|
|
623
|
+
default=None, help='.json output file, defaults to [video file].json')
|
|
624
|
+
|
|
625
|
+
parser.add_argument('--output_video_file', type=str,
|
|
626
|
+
default=None, help='video output file (or folder), defaults to '\
|
|
627
|
+
'[video file].mp4 for files, or [video file]_annotated for folders')
|
|
628
|
+
|
|
629
|
+
parser.add_argument('--keep_extracted_frames',
|
|
630
|
+
action='store_true', help='Disable the deletion of extracted frames')
|
|
631
|
+
|
|
632
|
+
parser.add_argument('--reuse_frames_if_available',
|
|
633
|
+
action='store_true', help="Don't extract frames that are already available in the frame extraction folder")
|
|
634
|
+
|
|
635
|
+
parser.add_argument('--reuse_results_if_available',
|
|
636
|
+
action='store_true', help='If the output .json files exists, and this flag is set,'\
|
|
637
|
+
'we\'ll skip running MegaDetector')
|
|
638
|
+
|
|
639
|
+
parser.add_argument('--render_output_video', action='store_true',
|
|
640
|
+
help='enable video output rendering (not rendered by default)')
|
|
641
|
+
|
|
642
|
+
parser.add_argument('--fourcc', default=default_fourcc,
|
|
643
|
+
help='fourcc code to use for video encoding (default {}), only used if render_output_video is True'.format(default_fourcc))
|
|
644
|
+
|
|
645
|
+
parser.add_argument('--keep_rendered_frames',
|
|
646
|
+
action='store_true', help='Disable the deletion of rendered (w/boxes) frames')
|
|
647
|
+
|
|
648
|
+
parser.add_argument('--force_extracted_frame_folder_deletion',
|
|
649
|
+
action='store_true', help='By default, when keep_extracted_frames is False, we '\
|
|
650
|
+
'delete the frames, but leave the (probably-empty) folder in place. This option '\
|
|
651
|
+
'forces deletion of the folder as well. Use at your own risk; does not check '\
|
|
652
|
+
'whether other files were present in the folder.')
|
|
653
|
+
|
|
654
|
+
parser.add_argument('--force_rendered_frame_folder_deletion',
|
|
655
|
+
action='store_true', help='By default, when keep_rendered_frames is False, we '\
|
|
656
|
+
'delete the frames, but leave the (probably-empty) folder in place. This option '\
|
|
657
|
+
'forces deletion of the folder as well. Use at your own risk; does not check '\
|
|
658
|
+
'whether other files were present in the folder.')
|
|
659
|
+
|
|
660
|
+
parser.add_argument('--rendering_confidence_threshold', type=float,
|
|
661
|
+
default=None, help="don't render boxes with confidence below this threshold (defaults to choosing based on the MD version)")
|
|
662
|
+
|
|
663
|
+
parser.add_argument('--json_confidence_threshold', type=float,
|
|
664
|
+
default=0.0, help="don't include boxes in the .json file with confidence "\
|
|
665
|
+
'below this threshold (default {})'.format(
|
|
666
|
+
default_options.json_confidence_threshold))
|
|
667
|
+
|
|
668
|
+
parser.add_argument('--n_cores', type=int,
|
|
669
|
+
default=1, help='number of cores to use for frame separation and detection. '\
|
|
670
|
+
'If using a GPU, this option will be respected for frame separation but '\
|
|
671
|
+
'ignored for detection. Only relevant to frame separation when processing '\
|
|
672
|
+
'a folder.')
|
|
673
|
+
|
|
674
|
+
parser.add_argument('--frame_sample', type=int,
|
|
675
|
+
default=None, help='process every Nth frame (defaults to every frame)')
|
|
676
|
+
|
|
677
|
+
parser.add_argument('--debug_max_frames', type=int,
|
|
678
|
+
default=-1, help='trim to N frames for debugging (impacts model execution, '\
|
|
679
|
+
'not frame rendering)')
|
|
680
|
+
|
|
681
|
+
parser.add_argument('--class_mapping_filename',
|
|
682
|
+
type=str,
|
|
683
|
+
default=None, help='Use a non-default class mapping, supplied in a .json file '\
|
|
684
|
+
'with a dictionary mapping int-strings to strings. This will also disable '\
|
|
685
|
+
'the addition of "1" to all category IDs, so your class mapping should start '\
|
|
686
|
+
'at zero.')
|
|
687
|
+
|
|
688
|
+
if len(sys.argv[1:]) == 0:
|
|
689
|
+
parser.print_help()
|
|
690
|
+
parser.exit()
|
|
691
|
+
|
|
692
|
+
args = parser.parse_args()
|
|
693
|
+
options = ProcessVideoOptions()
|
|
694
|
+
args_to_object(args,options)
|
|
695
|
+
|
|
696
|
+
if os.path.isdir(options.input_video_file):
|
|
697
|
+
process_video_folder(options)
|
|
698
|
+
else:
|
|
699
|
+
process_video(options)
|
|
700
|
+
|
|
701
|
+
if __name__ == '__main__':
|
|
702
|
+
main()
|