Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set frame rate of video extracting #382

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cvat/apps/dashboard/static/dashboard/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -345,6 +347,7 @@ function setupTaskCreator() {
let segmentSize = 5000;
let overlapSize = 0;
let compressQuality = 50;
let frameRate = 0;
let files = [];

dashboardCreateTaskButton.on("click", function() {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 9 additions & 0 deletions cvat/apps/dashboard/templates/dashboard/dashboard.html
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@
<input type="checkbox" id="dashboardCustomQuality" title="Custom image quality"/>
</td>
</tr>
<tr>
<td>
<label class="regular h2"> Frame Rate </label>
</td>
<td>
<input type="number" id="dashboardFrameRate" class="regular" style="width: 4.5em;" max="60" min="0" value="0" disabled=true/>
<input type="checkbox" id="dashboardCustomFrameRate" title="Custom frame rate"/>
</td>
</tr>
</table>


Expand Down
15 changes: 10 additions & 5 deletions cvat/apps/engine/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Contributor

@nmanovic nmanovic Apr 10, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main problem with the approach is you will get annotation file which doesn't correspond to the video file. For example, originally you have a video file with 30FPS. If someone annotates the whole video file we will get <frame000000>, <frame000001>, ... inside the annotation file for every frame. Now you say that we can provide "custom" FPS. You will get another <frame000000>, <frame000001>, ... which don't correspond to the original video file. How to match if you don't store extra parameters (e.g. custom frame rate) inside the annotation file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the internals of backend. Is there any difference between upload a video directly with upload all frame images pre-extracted locally?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zliang7, if you still have questions it is better to organize a meeting and discuss.

# 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'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Option -vsync 0 is very important. It allows to generate non-duplicated frames. -r can lead to duplicated frames and in general it is bad

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to keep the vsync options. But if both -vsync and -r options are specified, ffmpeg reports error:
"Using -vsync 0 and -r can produce invalid output files"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zliang7, this is why '-r' option is not good here. See my thoughts below.

if flip_flag:
output_opts += ' -vf "transpose=2,transpose=2"'
ff = FFmpeg(
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -696,14 +700,15 @@ 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)
slogger.glob.info("Task #{} parameters: {}".format(tid, task_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,
Expand Down