From de0e20991ee7d677e4a86dfba6977a271d4024a6 Mon Sep 17 00:00:00 2001 From: Zhenyu Liang Date: Wed, 10 Apr 2019 16:14:11 +0800 Subject: [PATCH] Set frame rate of video extracting This value means how many image files generated per second. To reduce the number of image files, the value must be less than the video FPS. --- .../dashboard/static/dashboard/js/dashboard.js | 18 ++++++++++++++++++ .../templates/dashboard/dashboard.html | 9 +++++++++ cvat/apps/engine/task.py | 15 ++++++++++----- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/cvat/apps/dashboard/static/dashboard/js/dashboard.js b/cvat/apps/dashboard/static/dashboard/js/dashboard.js index d12668c0d03c..3cac38716c0b 100644 --- a/cvat/apps/dashboard/static/dashboard/js/dashboard.js +++ b/cvat/apps/dashboard/static/dashboard/js/dashboard.js @@ -331,6 +331,8 @@ function setupTaskCreator() { let customOverlapSize = $("#dashboardCustomOverlap"); let imageQualityInput = $("#dashboardImageQuality"); let customCompressQuality = $("#dashboardCustomQuality"); + let frameRateInput = $("#dashboardFrameRate"); + let customFrameRate = $("#dashboardCustomFrameRate"); let taskMessage = $("#dashboardCreateTaskMessage"); let submitCreate = $("#dashboardSubmitTask"); @@ -345,6 +347,7 @@ function setupTaskCreator() { let segmentSize = 5000; let overlapSize = 0; let compressQuality = 50; + let frameRate = 0; let files = []; dashboardCreateTaskButton.on("click", function() { @@ -417,6 +420,7 @@ function setupTaskCreator() { customSegmentSize.on("change", (e) => segmentSizeInput.prop("disabled", !e.target.checked)); customOverlapSize.on("change", (e) => overlapSizeInput.prop("disabled", !e.target.checked)); customCompressQuality.on("change", (e) => imageQualityInput.prop("disabled", !e.target.checked)); + customFrameRate.on("change", (e) => frameRateInput.prop("disabled", !e.target.checked)); segmentSizeInput.on("change", function() { let value = Math.clamp( @@ -451,6 +455,17 @@ function setupTaskCreator() { compressQuality = value; }); + frameRateInput.on("change", function() { + let value = Math.clamp( + +frameRateInput.prop("value"), + +frameRateInput.prop("min"), + +frameRateInput.prop("max") + ); + + frameRateInput.prop("value", value); + frameRate = value; + }); + submitCreate.on("click", function() { if (!validateName(name)) { taskMessage.css("color", "red"); @@ -515,6 +530,9 @@ function setupTaskCreator() { if (customCompressQuality.prop("checked")) { taskData.append("compress_quality", compressQuality); } + if (customFrameRate.prop("checked")) { + taskData.append("frame_rate", frameRate); + } for (let file of files) { taskData.append("data", file); diff --git a/cvat/apps/dashboard/templates/dashboard/dashboard.html b/cvat/apps/dashboard/templates/dashboard/dashboard.html index 18ddd0882f93..b2c082804c16 100644 --- a/cvat/apps/dashboard/templates/dashboard/dashboard.html +++ b/cvat/apps/dashboard/templates/dashboard/dashboard.html @@ -156,6 +156,15 @@ + + + + + + + + + diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py index 4d2a75f2679d..ebb39005c8c1 100644 --- a/cvat/apps/engine/task.py +++ b/cvat/apps/engine/task.py @@ -272,13 +272,17 @@ def rq_handler(job, exc_type, exc_value, traceback): ############################# Internal implementation for server API class _FrameExtractor: - def __init__(self, source_path, compress_quality, flip_flag=False): + def __init__(self, source_path, compress_quality, flip_flag=False, frame_rate=0): # translate inversed range 1:95 to 2:32 translated_quality = 96 - compress_quality translated_quality = round((((translated_quality - 1) * (31 - 2)) / (95 - 1)) + 2) self.output = tempfile.mkdtemp(prefix='cvat-', suffix='.data') target_path = os.path.join(self.output, '%d.jpg') - output_opts = '-start_number 0 -b:v 10000k -vsync 0 -an -y -q:v ' + str(translated_quality) + output_opts = '-start_number 0 -b:v 10000k -an -y -q:v ' + str(translated_quality) + if frame_rate > 0: + output_opts += ' -r ' + str(frame_rate) + else: + output_opts += ' -vsync 0' if flip_flag: output_opts += ' -vf "transpose=2,transpose=2"' ff = FFmpeg( @@ -536,7 +540,7 @@ def _find_and_unpack_archive(upload_dir): ''' Search a video in upload dir and split it by frames. Copy frames to target dirs ''' -def _find_and_extract_video(upload_dir, output_dir, db_task, compress_quality, flip_flag, job): +def _find_and_extract_video(upload_dir, output_dir, db_task, compress_quality, flip_flag, frame_rate, job): video = None for root, _, files in os.walk(upload_dir): fullnames = map(lambda f: os.path.join(root, f), files) @@ -548,7 +552,7 @@ def _find_and_extract_video(upload_dir, output_dir, db_task, compress_quality, f if video: job.meta['status'] = 'Video is being extracted..' job.save_meta() - extractor = _FrameExtractor(video, compress_quality, flip_flag) + extractor = _FrameExtractor(video, compress_quality, flip_flag, frame_rate) for frame, image_orig_path in enumerate(extractor): image_dest_path = _get_frame_path(frame, output_dir) db_task.size += 1 @@ -696,6 +700,7 @@ def _create_thread(tid, params): 'compress': int(params.get('compress_quality', 50)), 'segment': int(params.get('segment_size', sys.maxsize)), 'labels': params['labels'], + 'frame_rate': int(params.get('frame_rate', 0)), } task_params['overlap'] = int(params.get('overlap_size', 5 if task_params['mode'] == 'interpolation' else 0)) task_params['overlap'] = min(task_params['overlap'], task_params['segment'] - 1) @@ -703,7 +708,7 @@ def _create_thread(tid, params): if task_params['mode'] == 'interpolation': video = _find_and_extract_video(upload_dir, output_dir, db_task, - task_params['compress'], task_params['flip'], job) + task_params['compress'], task_params['flip'], task_params['frame_rate'], job) task_params['data'] = os.path.relpath(video, upload_dir) else: files =_find_and_compress_images(upload_dir, output_dir, db_task,