From: Good Guy Date: Thu, 17 Sep 2020 19:23:08 +0000 (-0600) Subject: apply sge motion plugin mods X-Git-Tag: 2020-09~6 X-Git-Url: https://git.cinelerra-gg.org/git/?a=commitdiff_plain;h=166867a58d74619aa11aeb562a994cc364d62231;p=goodguy%2Fcinelerra.git apply sge motion plugin mods --- diff --git a/cinelerra-5.1/plugins/motion/motion.C b/cinelerra-5.1/plugins/motion/motion.C index b8da2dca..d35d4cc5 100644 --- a/cinelerra-5.1/plugins/motion/motion.C +++ b/cinelerra-5.1/plugins/motion/motion.C @@ -56,6 +56,8 @@ MotionConfig::MotionConfig() global_block_h = 33; //MIN_BLOCK; block_x = 50; block_y = 50; + noise_level = 0; + noise_rotation = 0; global_positions = 256; rotate_positions = 8; // 4; magnitude = 100; @@ -65,6 +67,7 @@ MotionConfig::MotionConfig() action_type = MotionScan::STABILIZE; global = 1; rotate = 1; + twopass = 0; addtrackedframeoffset = 0; strcpy(tracking_file, TRACKING_FILE); tracking_type = MotionScan::SAVE; //MotionScan::NO_CALCULATE; @@ -96,6 +99,7 @@ int MotionConfig::equivalent(MotionConfig &that) rotation_center == that.rotation_center && action_type == that.action_type && global == that.global && rotate == that.rotate && + twopass == that.twopass && addtrackedframeoffset == that.addtrackedframeoffset && draw_vectors == that.draw_vectors && block_count == that.block_count && @@ -103,6 +107,8 @@ int MotionConfig::equivalent(MotionConfig &that) global_block_h == that.global_block_h && EQUIV(block_x, that.block_x) && EQUIV(block_y, that.block_y) && + noise_level == that.noise_level && + noise_rotation == that.noise_rotation && global_positions == that.global_positions && rotate_positions == that.rotate_positions && magnitude == that.magnitude && @@ -125,12 +131,15 @@ void MotionConfig::copy_from(MotionConfig &that) action_type = that.action_type; global = that.global; rotate = that.rotate; + twopass = that.twopass; addtrackedframeoffset = that.addtrackedframeoffset; tracking_type = that.tracking_type; draw_vectors = that.draw_vectors; block_count = that.block_count; block_x = that.block_x; block_y = that.block_y; + noise_level = that.noise_level; + noise_rotation = that.noise_rotation; global_positions = that.global_positions; rotate_positions = that.rotate_positions; global_block_w = that.global_block_w; @@ -178,6 +187,7 @@ MotionMain::MotionMain(PluginServer *server) cache_line[0] = 0; cache_key = active_key = -1; dx_offset = dy_offset = 0; + dt_offset = 0; load_ok = 0; save_dx = load_dx = 0; save_dy = load_dy = 0; @@ -242,6 +252,10 @@ void MotionMain::update_gui() window->block_y->update(config.block_y); window->block_x_text->update((float)config.block_x); window->block_y_text->update((float)config.block_y); + window->noise_level->update(config.noise_level); + window->noise_level_text->update((float)config.noise_level); + window->noise_rotation->update(config.noise_rotation); + window->noise_rotation_text->update((float)config.noise_rotation); window->magnitude->update(config.magnitude); window->return_speed->update(config.return_speed); window->rotate_magnitude->update(config.rotate_magnitude); @@ -255,9 +269,15 @@ void MotionMain::update_gui() window->track_previous->update(config.tracking_object == MotionScan::TRACK_PREVIOUS); window->previous_same->update(config.tracking_object == MotionScan::PREVIOUS_SAME_BLOCK); if( config.tracking_object != MotionScan::TRACK_SINGLE ) + { window->track_frame_number->disable(); + window->frame_current->disable(); + } else + { window->track_frame_number->enable(); + window->frame_current->enable(); + } window->action_type->set_text( ActionType::to_text(config.action_type)); @@ -298,9 +318,12 @@ void MotionMain::save_data(KeyFrame *keyframe) output.tag.set_property("RETURN_SPEED", config.return_speed); output.tag.set_property("ROTATE_MAGNITUDE", config.rotate_magnitude); output.tag.set_property("ROTATE_RETURN_SPEED", config.rotate_return_speed); + output.tag.set_property("NOISE_LEVEL", config.noise_level); + output.tag.set_property("NOISE_ROTATION", config.noise_rotation); output.tag.set_property("ACTION_TYPE", config.action_type); output.tag.set_property("GLOBAL", config.global); output.tag.set_property("ROTATE", config.rotate); + output.tag.set_property("TWOPASS", config.twopass); output.tag.set_property("ADDTRACKEDFRAMEOFFSET", config.addtrackedframeoffset); output.tag.set_property("TRACKING_FILE", config.tracking_file); output.tag.set_property("TRACKING_TYPE", config.tracking_type); @@ -339,9 +362,12 @@ void MotionMain::read_data(KeyFrame *keyframe) config.return_speed = input.tag.get_property("RETURN_SPEED", config.return_speed); config.rotate_magnitude = input.tag.get_property("ROTATE_MAGNITUDE", config.rotate_magnitude); config.rotate_return_speed = input.tag.get_property("ROTATE_RETURN_SPEED", config.rotate_return_speed); + config.noise_level = input.tag.get_property("NOISE_LEVEL", config.noise_level); + config.noise_rotation = input.tag.get_property("NOISE_ROTATION", config.noise_rotation); config.action_type = input.tag.get_property("ACTION_TYPE", config.action_type); config.global = input.tag.get_property("GLOBAL", config.global); config.rotate = input.tag.get_property("ROTATE", config.rotate); + config.twopass = input.tag.get_property("TWOPASS", config.twopass); config.addtrackedframeoffset = input.tag.get_property("ADDTRACKEDFRAMEOFFSET", config.addtrackedframeoffset); input.tag.get_property("TRACKING_FILE", config.tracking_file); config.tracking_type = input.tag.get_property("TRACKING_TYPE", config.tracking_type); @@ -370,10 +396,12 @@ void MotionMain::allocate_temp(int w, int h, int color_model) void MotionMain::process_global() { - if( !engine ) engine = new MotionScan(PluginClient::get_project_smp() + 1, + if( !engine ) engine = new MotionScan(this, + PluginClient::get_project_smp() + 1, PluginClient::get_project_smp() + 1); -// Determine if frames changed +// Determine if frames changed, either single pass or pass 1 +// Attention, prev_global_ref and current_global_ref are interchanged engine->scan_frame(current_global_ref, prev_global_ref, config.global_range_w, config.global_range_h, config.global_block_w, config.global_block_h, @@ -382,14 +410,14 @@ void MotionMain::process_global() config.action_type, config.horizontal_only, config.vertical_only, get_source_position(), config.global_positions, total_dx, total_dy, - 0, 0, load_ok, load_dx, load_dy); + 0, 0, config.twopass, load_ok, load_dx, load_dy); current_dx = (engine->dx_result += dx_offset); current_dy = (engine->dy_result += dy_offset); -// Write results +// Save results if( config.tracking_type == MotionScan::SAVE ) { - save_dx = engine->dx_result; - save_dy = engine->dy_result; + save_dx = current_dx; + save_dy = current_dy; } // Add current motion vector to accumulation vector. @@ -410,11 +438,11 @@ void MotionMain::process_global() // Clamp accumulation vector if( config.magnitude < 100 ) { - int block_x_orig = (int64_t)(config.block_x * current_global_ref->get_w() / 100); - int block_y_orig = (int64_t)(config.block_y * current_global_ref->get_h() / 100); - int max_block_x = (int64_t)(current_global_ref->get_w() - block_x_orig) + int block_x_orig = lrint(config.block_x * prev_global_ref->get_w() / 100); + int block_y_orig = lrint(config.block_y * prev_global_ref->get_h() / 100); + int max_block_x = (int64_t)(prev_global_ref->get_w() - block_x_orig) * OVERSAMPLE * config.magnitude / 100; - int max_block_y = (int64_t)(current_global_ref->get_h() - block_y_orig) + int max_block_y = (int64_t)(prev_global_ref->get_h() - block_y_orig) * OVERSAMPLE * config.magnitude / 100; int min_block_x = (int64_t)-block_x_orig * OVERSAMPLE * config.magnitude / 100; @@ -430,6 +458,186 @@ printf("MotionMain::process_global 2 total_dx=%.02f total_dy=%.02f\n", (float)total_dx / OVERSAMPLE, (float)total_dy / OVERSAMPLE); #endif +// If there will be 2nd pass, target will be transformed then + if(!config.twopass || load_ok) + { + if( config.tracking_object != MotionScan::TRACK_SINGLE && !config.rotate ) { +// Transfer current reference frame to previous reference frame and update +// counter. Must wait for rotate to compare. + prev_global_ref->copy_from(current_global_ref); + previous_frame_number = get_source_position(); + } + +// No 2nd pass, decide here what to do with target based on requested operation + int interpolation = NEAREST_NEIGHBOR; + float dx = 0., dy = 0.; + switch(config.action_type) { + case MotionScan::NOTHING: + global_target_dst->copy_from(global_target_src); + break; + case MotionScan::TRACK_PIXEL: + interpolation = NEAREST_NEIGHBOR; + dx = rint((float)total_dx / OVERSAMPLE); + dy = rint((float)total_dy / OVERSAMPLE); + break; + case MotionScan::STABILIZE_PIXEL: + interpolation = NEAREST_NEIGHBOR; + dx = -rint((float)total_dx / OVERSAMPLE); + dy = -rint((float)total_dy / OVERSAMPLE); + break; + case MotionScan::TRACK: + interpolation = CUBIC_LINEAR; + dx = (float)total_dx / OVERSAMPLE; + dy = (float)total_dy / OVERSAMPLE; + break; + case MotionScan::STABILIZE: + interpolation = CUBIC_LINEAR; + dx = -(float)total_dx / OVERSAMPLE; + dy = -(float)total_dy / OVERSAMPLE; + break; + } + + + if( config.action_type != MotionScan::NOTHING ) { + if( !overlayer ) + overlayer = new OverlayFrame(PluginClient::get_project_smp() + 1); + global_target_dst->clear_frame(); + overlayer->overlay(global_target_dst, global_target_src, + 0, 0, global_target_src->get_w(), global_target_src->get_h(), + dx, dy, + (float)global_target_src->get_w() + dx, + (float)global_target_src->get_h() + dy, + 1, TRANSFER_REPLACE, interpolation); + } + } +} + +void MotionMain::refine_global() +{ + int block_x, block_y; + double tmp_x, tmp_y; + float dx = 0., dy = 0.; + +// Use temp_frame instead of current_global_ref for refined translation search + allocate_temp(w, h, current_global_ref->get_color_model()); + temp_frame->clear_frame(); + + if(config.rotate) + { +// Here we have to rotate current_rotate_ref into temp_frame +// backwards because prev_global_ref and current_global_ref are interchanged + if(!rotate_engine) + rotate_engine = new AffineEngine( + PluginClient::get_project_smp() + 1, + PluginClient::get_project_smp() + 1); + + float angle; + if(config.tracking_object == MotionScan::TRACK_SINGLE) + { + angle = total_angle; + } + else + { + angle = current_angle; + } + +// Pivot need not be very accurate, it is attached to the previous frame +// while current frame is moved and rotated +// Nevertheless compute it in floating point and round to int afterwards + tmp_x = current_rotate_ref->get_w() * config.block_x / 100; + tmp_y = current_rotate_ref->get_h() * config.block_y / 100; + if(config.tracking_object == MotionScan::TRACK_PREVIOUS) + { +// Pivot is moved along the previous frame + tmp_x += (double)(total_dx-current_dx) / OVERSAMPLE; + tmp_y += (double)(total_dy-current_dy) / OVERSAMPLE; + } + block_x = lrint (tmp_x); + block_y = lrint (tmp_y); + rotate_engine->set_in_pivot(block_x, block_y); + rotate_engine->set_out_pivot(block_x, block_y); + + rotate_engine->rotate(temp_frame, current_rotate_ref, -angle); + } + else + { +// Here we have to translate current_global_ref into temp_frame +// backwards because prev_global_ref and current_global_ref are interchanged + if(!overlayer) + overlayer = new OverlayFrame(PluginClient::get_project_smp() + 1); + if(config.tracking_object == MotionScan::TRACK_SINGLE) + { + dx = (float)total_dx / OVERSAMPLE; + dy = (float)total_dy / OVERSAMPLE; + } + else + { + dx = (float)current_dx / OVERSAMPLE; + dy = (float)current_dy / OVERSAMPLE; + } + + overlayer->overlay(temp_frame, + current_global_ref, + 0, + 0, + current_global_ref->get_w(), + current_global_ref->get_h(), + -dx, + -dy, + (float)current_global_ref->get_w() - dx, + (float)current_global_ref->get_h() - dy, + 1, + TRANSFER_REPLACE, + CUBIC_LINEAR); + } + +// Determine additional translation, pass 2 +// Attention, prev_global_ref and current_global_ref are interchanged +// Engine must have been created already by process_global() + engine->scan_frame(temp_frame, prev_global_ref, + config.global_range_w, config.global_range_h, + config.global_block_w, config.global_block_h, + config.block_x, config.block_y, + config.tracking_object, config.tracking_type, + config.action_type, config.horizontal_only, + config.vertical_only, get_source_position(), + config.global_positions, total_dx, total_dy, + 0, 0, 2, load_ok, load_dx, load_dy); + +// Translation correction is to be added to motion vector + current_dx += engine->dx_result; + current_dy += engine->dy_result; + total_dx += engine->dx_result; + total_dy += engine->dy_result; + +// Refine saved results + if( config.tracking_type == MotionScan::SAVE ) { + save_dx = current_dx; + save_dy = current_dy; + } + +// Clamp accumulation vector + if( config.magnitude < 100 ) { + int block_x_orig = lrint(config.block_x * prev_global_ref->get_w() / 100); + int block_y_orig = lrint(config.block_y * prev_global_ref->get_h() / 100); + int max_block_x = (int64_t)(prev_global_ref->get_w() - block_x_orig) + * OVERSAMPLE * config.magnitude / 100; + int max_block_y = (int64_t)(prev_global_ref->get_h() - block_y_orig) + * OVERSAMPLE * config.magnitude / 100; + int min_block_x = (int64_t)-block_x_orig + * OVERSAMPLE * config.magnitude / 100; + int min_block_y = (int64_t)-block_y_orig + * OVERSAMPLE * config.magnitude / 100; + + CLAMP(total_dx, min_block_x, max_block_x); + CLAMP(total_dy, min_block_y, max_block_y); + } + +#ifdef DEBUG +printf("MotionMain::refine_global 2 total_dx=%.02f total_dy=%.02f\n", + (float)total_dx / OVERSAMPLE, (float)total_dy / OVERSAMPLE); +#endif + if( config.tracking_object != MotionScan::TRACK_SINGLE && !config.rotate ) { // Transfer current reference frame to previous reference frame and update // counter. Must wait for rotate to compare. @@ -439,20 +647,20 @@ printf("MotionMain::process_global 2 total_dx=%.02f total_dy=%.02f\n", // Decide what to do with target based on requested operation int interpolation = NEAREST_NEIGHBOR; - float dx = 0., dy = 0.; + dx = dy = 0.; switch(config.action_type) { case MotionScan::NOTHING: global_target_dst->copy_from(global_target_src); break; case MotionScan::TRACK_PIXEL: interpolation = NEAREST_NEIGHBOR; - dx = (int)(total_dx / OVERSAMPLE); - dy = (int)(total_dy / OVERSAMPLE); + dx = rint((float)total_dx / OVERSAMPLE); + dy = rint((float)total_dy / OVERSAMPLE); break; case MotionScan::STABILIZE_PIXEL: interpolation = NEAREST_NEIGHBOR; - dx = -(int)(total_dx / OVERSAMPLE); - dy = -(int)(total_dy / OVERSAMPLE); + dx = -rint((float)total_dx / OVERSAMPLE); + dy = -rint((float)total_dy / OVERSAMPLE); break; case MotionScan::TRACK: interpolation = CUBIC_LINEAR; @@ -468,6 +676,7 @@ printf("MotionMain::process_global 2 total_dx=%.02f total_dy=%.02f\n", if( config.action_type != MotionScan::NOTHING ) { +// Should be already created elsewhere but try it here just for safety if( !overlayer ) overlayer = new OverlayFrame(PluginClient::get_project_smp() + 1); global_target_dst->clear_frame(); @@ -485,8 +694,11 @@ printf("MotionMain::process_global 2 total_dx=%.02f total_dy=%.02f\n", void MotionMain::process_rotation() { int block_x, block_y; + double tmp_x, tmp_y; -// Convert the previous global reference into the previous rotation reference. +// Here we have to translate current_global_ref into current_rotate_ref +// backwards because prev_rotate_ref and current_rotate_ref are interchanged. +// Also copy prev_global_ref into prev_rotate_ref for comparing. // Convert global target destination into rotation target source. if( config.global ) { if( !overlayer ) @@ -501,41 +713,54 @@ void MotionMain::process_rotation() dy = (float)current_dy / OVERSAMPLE; } - prev_rotate_ref->clear_frame(); - overlayer->overlay(prev_rotate_ref, prev_global_ref, - 0, 0, prev_global_ref->get_w(), prev_global_ref->get_h(), - dx, dy, - (float)prev_global_ref->get_w() + dx, - (float)prev_global_ref->get_h() + dy, + prev_rotate_ref->copy_from(prev_global_ref); + current_rotate_ref->clear_frame(); + overlayer->overlay(current_rotate_ref, current_global_ref, + 0, 0, current_global_ref->get_w(), current_global_ref->get_h(), + -dx, -dy, + (float)current_global_ref->get_w() - dx, + (float)current_global_ref->get_h() - dy, 1, TRANSFER_REPLACE, CUBIC_LINEAR); -// Pivot is destination global position - block_x = (int)(prev_rotate_ref->get_w() * - config.block_x / 100 + (float)total_dx / OVERSAMPLE); - block_y = (int)(prev_rotate_ref->get_h() * - config.block_y / 100 + (float)total_dy / OVERSAMPLE); +// Pivot need not be very accurate, it is attached to the previous frame +// while current frame is moved and rotated +// Nevertheless compute it in floating point and round to int afterwards + tmp_x = prev_rotate_ref->get_w() * config.block_x / 100; + tmp_y = prev_rotate_ref->get_h() * config.block_y / 100; + if(config.tracking_object == MotionScan::TRACK_PREVIOUS) + { +// Pivot is moved along the previous frame + tmp_x += (double)(total_dx-current_dx) / OVERSAMPLE; + tmp_y += (double)(total_dy-current_dy) / OVERSAMPLE; + } // Use the global target output as the rotation target input rotate_target_src->copy_from(global_target_dst); // Transfer current reference frame to previous reference frame for global. - if( config.tracking_object != MotionScan::TRACK_SINGLE ) { + if(config.tracking_object != MotionScan::TRACK_SINGLE && + (!config.twopass || load_ok)) + { prev_global_ref->copy_from(current_global_ref); previous_frame_number = get_source_position(); } } else { -// Pivot is fixed - block_x = (int)(prev_rotate_ref->get_w() * config.block_x / 100); - block_y = (int)(prev_rotate_ref->get_h() * config.block_y / 100); +// Pivot is fixed as translation switched off + tmp_x = prev_rotate_ref->get_w() * config.block_x / 100; + tmp_y = prev_rotate_ref->get_h() * config.block_y / 100; } + block_x = lrint (tmp_x); + block_y = lrint (tmp_y); -// Get rotation +// Get rotation, either single pass or pass 1 if( !motion_rotate ) motion_rotate = new RotateScan(this, get_project_smp() + 1, get_project_smp() + 1); +// Attention, prev_rotate_ref and current_rotate_ref are interchanged current_angle = motion_rotate-> - scan_frame(prev_rotate_ref, current_rotate_ref, block_x, block_y); + scan_frame(current_rotate_ref, prev_rotate_ref, block_x, block_y, config.twopass); + current_angle += dt_offset; -// Write results +// Save result if( config.tracking_type == MotionScan::SAVE ) { save_dt = current_angle; } @@ -551,30 +776,201 @@ void MotionMain::process_rotation() if( config.rotate_magnitude < 90 ) { CLAMP(total_angle, -config.rotate_magnitude, config.rotate_magnitude); } + } + else { + total_angle = current_angle; + } - if( !config.global ) { +#ifdef DEBUG +printf("MotionMain::process_rotation total_angle=%f\n", total_angle); +#endif + +// If there will be 2nd pass, target will be transformed then + if(!config.twopass || load_ok) + { + if( config.tracking_object != MotionScan::TRACK_SINGLE && !config.global ) { // Transfer current reference frame to previous reference frame and update counter. prev_rotate_ref->copy_from(current_rotate_ref); previous_frame_number = get_source_position(); } + +// No 2nd pass, calculate rotation parameters based on requested operation +// Use origin of global stabilize operation for pivot by default +// Compute it in floating point for accuracy and round to int afterwards + tmp_x = rotate_target_src->get_w() * config.block_x / 100; + tmp_y = rotate_target_src->get_h() * config.block_y / 100; + + float angle = 0.; + switch(config.action_type) { + case MotionScan::NOTHING: + rotate_target_dst->copy_from(rotate_target_src); + break; + case MotionScan::TRACK: + case MotionScan::TRACK_PIXEL: + if(config.global) + { +// Use destination of global tracking for pivot. + tmp_x += (double)total_dx / OVERSAMPLE; + tmp_y += (double)total_dy / OVERSAMPLE; + } + angle = total_angle; + break; + case MotionScan::STABILIZE: + case MotionScan::STABILIZE_PIXEL: + angle = -total_angle; + break; + } + block_x = lrint (tmp_x); + block_y = lrint (tmp_y); + + if( config.action_type != MotionScan::NOTHING ) { + if( !rotate_engine ) + rotate_engine = new AffineEngine( + PluginClient::get_project_smp() + 1, + PluginClient::get_project_smp() + 1); + + rotate_target_dst->clear_frame(); + + rotate_engine->set_in_pivot(block_x, block_y); + rotate_engine->set_out_pivot(block_x, block_y); + + rotate_engine->rotate(rotate_target_dst, rotate_target_src, angle); + } + } +} + +void MotionMain::refine_rotation() +{ + int block_x, block_y; + double tmp_x, tmp_y; + float angle; + +// Use temp_frame instead of current_rotate_ref for refined rotation search + allocate_temp(w, h, current_rotate_ref->get_color_model()); + temp_frame->clear_frame(); + + if( config.global ) { +// Here we have to translate current_global_ref into current_rotate_ref +// backwards because prev_rotate_ref and current_rotate_ref are interchanged +// prev_rotate_ref must have been copied already by process_rotation() + if( !overlayer ) + overlayer = new OverlayFrame(PluginClient::get_project_smp() + 1); + float dx, dy; + if( config.tracking_object == MotionScan::TRACK_SINGLE ) { + dx = (float)total_dx / OVERSAMPLE; + dy = (float)total_dy / OVERSAMPLE; + } + else { + dx = (float)current_dx / OVERSAMPLE; + dy = (float)current_dy / OVERSAMPLE; + } + + current_rotate_ref->clear_frame(); + overlayer->overlay(current_rotate_ref, current_global_ref, + 0, 0, current_global_ref->get_w(), current_global_ref->get_h(), + -dx, -dy, + (float)current_global_ref->get_w() - dx, + (float)current_global_ref->get_h() - dy, + 1, TRANSFER_REPLACE, CUBIC_LINEAR); + +// Pivot need not be very accurate, it is attached to the previous frame +// while current frame is moved and rotated +// Nevertheless compute it in floating point and round to int afterwards + tmp_x = current_rotate_ref->get_w() * config.block_x / 100; + tmp_y = current_rotate_ref->get_h() * config.block_y / 100; + if(config.tracking_object == MotionScan::TRACK_PREVIOUS) + { +// Pivot is moved along the previous frame + tmp_x += (double)(total_dx-current_dx) / OVERSAMPLE; + tmp_y += (double)(total_dy-current_dy) / OVERSAMPLE; + } } else { - total_angle = current_angle; +// Pivot is fixed as translation switched off + tmp_x = current_rotate_ref->get_w() * config.block_x / 100; + tmp_y = current_rotate_ref->get_h() * config.block_y / 100; + } + block_x = lrint (tmp_x); + block_y = lrint (tmp_y); + +// Now rotate current_rotate_ref into temp_frame +// backwards because prev_global_ref and current_global_ref are interchanged + if(!rotate_engine) + rotate_engine = new AffineEngine( + PluginClient::get_project_smp() + 1, + PluginClient::get_project_smp() + 1); + + if(config.tracking_object == MotionScan::TRACK_SINGLE) + { + angle = total_angle; + } + else + { + angle = current_angle; + } + + rotate_engine->set_in_pivot(block_x, block_y); + rotate_engine->set_out_pivot(block_x, block_y); + + rotate_engine->rotate(temp_frame, current_rotate_ref, -angle); + +// Determine additional rotation, pass 2 +// Attention, prev_rotate_ref and current_rotate_ref are interchanged +// Engine must have been created already by process_rotation() + angle = motion_rotate-> + scan_frame(temp_frame, prev_rotate_ref, block_x, block_y, 2); + +// Rotation correction is to be added to accumulated angle + current_angle += angle; + total_angle += angle; + +// Refine saved result + if( config.tracking_type == MotionScan::SAVE ) { + save_dt = current_angle; + } + +// Clamp rotation accumulation + if( config.rotate_magnitude < 90 ) { + CLAMP(total_angle, -config.rotate_magnitude, config.rotate_magnitude); } #ifdef DEBUG printf("MotionMain::process_rotation total_angle=%f\n", total_angle); #endif + if(config.tracking_object != MotionScan::TRACK_SINGLE) + { +// Transfer current reference frame to previous reference frame and update +// counter. + if(config.global) + { + prev_global_ref->copy_from(current_global_ref); + } + else + { + prev_rotate_ref->copy_from(current_rotate_ref); + } + previous_frame_number = get_source_position(); + } // Calculate rotation parameters based on requested operation - float angle = 0.; +// Use origin of global stabilize operation for pivot by default +// Compute it in floating point for accuracy and round to int afterwards + tmp_x = rotate_target_src->get_w() * config.block_x / 100; + tmp_y = rotate_target_src->get_h() * config.block_y / 100; + switch(config.action_type) { case MotionScan::NOTHING: rotate_target_dst->copy_from(rotate_target_src); break; case MotionScan::TRACK: case MotionScan::TRACK_PIXEL: + if(config.global) + { +// Use destination of global tracking for pivot. + tmp_x += (double)total_dx / OVERSAMPLE; + tmp_y += (double)total_dy / OVERSAMPLE; + } angle = total_angle; break; case MotionScan::STABILIZE: @@ -582,8 +978,11 @@ printf("MotionMain::process_rotation total_angle=%f\n", total_angle); angle = -total_angle; break; } + block_x = lrint (tmp_x); + block_y = lrint (tmp_y); if( config.action_type != MotionScan::NOTHING ) { +// Should be already created elsewhere but try it here just for safety if( !rotate_engine ) rotate_engine = new AffineEngine( PluginClient::get_project_smp() + 1, @@ -591,50 +990,16 @@ printf("MotionMain::process_rotation total_angle=%f\n", total_angle); rotate_target_dst->clear_frame(); -// Determine pivot based on a number of factors. - switch(config.action_type) { - case MotionScan::TRACK: - case MotionScan::TRACK_PIXEL: -// Use destination of global tracking. - rotate_engine->set_in_pivot(block_x, block_y); - rotate_engine->set_out_pivot(block_x, block_y); - break; - - case MotionScan::STABILIZE: - case MotionScan::STABILIZE_PIXEL: - if( config.global ) { -// Use origin of global stabilize operation - rotate_engine->set_in_pivot( - (int)(rotate_target_dst->get_w() * config.block_x / 100), - (int)(rotate_target_dst->get_h() * config.block_y / 100)); - rotate_engine->set_out_pivot( - (int)(rotate_target_dst->get_w() * config.block_x / 100), - (int)(rotate_target_dst->get_h() * config.block_y / 100)); - } - else { -// Use origin - rotate_engine->set_in_pivot(block_x, block_y); - rotate_engine->set_out_pivot(block_x, block_y); - } - break; - } + rotate_engine->set_in_pivot(block_x, block_y); + rotate_engine->set_out_pivot(block_x, block_y); rotate_engine->rotate(rotate_target_dst, rotate_target_src, angle); -// overlayer->overlay(rotate_target_dst, prev_rotate_ref, -// 0, 0, prev_rotate_ref->get_w(), prev_rotate_ref->get_h(), -// 0, 0, prev_rotate_ref->get_w(), prev_rotate_ref->get_h(), -// 1, TRANSFER_NORMAL, CUBIC_LINEAR); -// overlayer->overlay(rotate_target_dst, current_rotate_ref, -// 0, 0, prev_rotate_ref->get_w(), prev_rotate_ref->get_h(), -// 0, 0, prev_rotate_ref->get_w(), prev_rotate_ref->get_h(), -// 1, TRANSFER_NORMAL, // CUBIC_LINEAR); } } int MotionMain::process_buffer(VFrame **frame, int64_t start_position, double frame_rate) { - int prev_config_tracking_type = config.tracking_type; int need_reconfigure = load_configuration(); int color_model = frame[0]->get_color_model(); w = frame[0]->get_w(); @@ -697,18 +1062,11 @@ printf("MotionMain::process_buffer %d start_position=%jd\n", __LINE__, start_pos //printf("process_realtime: %jd %d %jd %jd\n", start_position, // skip_current, previous_frame_number, actual_previous_number); - if( prev_config_tracking_type != MotionScan::SAVE && - config.tracking_type == MotionScan::SAVE ) { - reset_cache_file(); - char save_file[BCTEXTLEN]; - snprintf(save_file, sizeof(save_file), "%s.bak", config.tracking_file); -#ifdef DEBUG -printf("MotionMain::process_buffer 2 rename tracking file: %s to %s\n", - config.tracking_file, save_file); -#endif - ::rename(config.tracking_file, save_file); - } - else if( !cache_file[0] || active_key > start_position ) + if( ((config.tracking_type != MotionScan::LOAD && + config.tracking_type != MotionScan::SAVE) && cache_fp) || + ((config.tracking_type == MotionScan::LOAD || + config.tracking_type == MotionScan::SAVE) && !cache_fp) || + !cache_file[0] || (active_fp && active_key > start_position) ) reset_cache_file(); // Load match frame and reset vectors @@ -807,7 +1165,6 @@ printf("MotionMain::process_buffer 2 rename tracking file: %s to %s\n", start_position, frame_rate, 0); } - dx_offset = 0; dy_offset = 0; if( config.tracking_type == MotionScan::LOAD ) { if( config.addtrackedframeoffset ) { if( config.track_frame != tracking_frame ) { @@ -815,7 +1172,8 @@ printf("MotionMain::process_buffer 2 rename tracking file: %s to %s\n", int64_t no; int dx, dy; float dt; if( !get_cache_line(tracking_frame) && sscanf(cache_line, "%jd %d %d %f", &no, &dx, &dy, &dt) == 4 ) { - dx_offset = dx; dy_offset = dy; + dx_offset += dx; dy_offset += dy; + dt_offset += dt; } else { eprintf("no offset data frame %jd\n", tracking_frame); @@ -823,7 +1181,19 @@ printf("MotionMain::process_buffer 2 rename tracking file: %s to %s\n", } } else + { + dx_offset = 0; + dy_offset = 0; + dt_offset = 0; tracking_frame = -1; + } + } + else + { + dx_offset = 0; + dy_offset = 0; + dt_offset = 0; + tracking_frame = -1; } if( !skip_current ) { @@ -852,6 +1222,12 @@ printf("MotionMain::process_buffer: no tracking data frame %jd\n", frame_no); process_rotation(); //frame[target_layer]->copy_from(prev_rotate_ref); //frame[target_layer]->copy_from(current_rotate_ref); + if(config.twopass && !load_ok) +// Second pass not needed if coords loaded from cache + { + if(config.global) refine_global(); + if(config.rotate) refine_rotation(); + } // write results to disk if( config.tracking_type == MotionScan::SAVE ) { @@ -896,40 +1272,56 @@ void MotionMain::draw_vectors(VFrame *frame) int block_x3, block_y3, block_x4, block_y4; int search_x1, search_y1, search_x2, search_y2; int search_w, search_h; + double tmp_x1, tmp_y1; + double tmp_x2, tmp_y2; if( config.global ) { -// Get vector -// Start of vector is center of previous block. -// End of vector is total accumulation. +// Get vector as double and round the result for more regular behavior if( config.tracking_object == MotionScan::TRACK_SINGLE ) { - global_x1 = (int64_t)(config.block_x * w / 100); - global_y1 = (int64_t)(config.block_y * h / 100); - global_x2 = global_x1 + total_dx / OVERSAMPLE; - global_y2 = global_y1 + total_dy / OVERSAMPLE; -//printf("MotionMain::draw_vectors %d %d %d %d %d %d\n", total_dx, total_dy, global_x1, global_y1, global_x2, global_y2); +// Start of vector is center of original block. +// Length of vector is total accumulation. + tmp_x1 = config.block_x * w / 100; + tmp_y1 = config.block_y * h / 100; + tmp_x2 = tmp_x1 + (double)total_dx / OVERSAMPLE; + tmp_y2 = tmp_y1 + (double)total_dy / OVERSAMPLE; } -// Start of vector is center of previous block. -// End of vector is current change. else if( config.tracking_object == MotionScan::PREVIOUS_SAME_BLOCK ) { - global_x1 = (int64_t)(config.block_x * w / 100); - global_y1 = (int64_t)(config.block_y * h / 100); - global_x2 = global_x1 + current_dx / OVERSAMPLE; - global_y2 = global_y1 + current_dy / OVERSAMPLE; +// Start of vector is center of original block. +// Length of vector is current change. + tmp_x1 = config.block_x * w / 100; + tmp_y1 = config.block_y * h / 100; + tmp_x2 = tmp_x1 + (double)current_dx / OVERSAMPLE; + tmp_y2 = tmp_y1 + (double)current_dy / OVERSAMPLE; } else { - global_x1 = (int64_t)(config.block_x * w / 100 - + (total_dx - current_dx) / OVERSAMPLE); - global_y1 = (int64_t)(config.block_y * h / 100 - + (total_dy - current_dy) / OVERSAMPLE); - global_x2 = (int64_t)(config.block_x * w / 100 - + total_dx / OVERSAMPLE); - global_y2 = (int64_t)(config.block_y * h / 100 - + total_dy / OVERSAMPLE); +// Start of vector is center of current block. +// Length of vector is current change. + tmp_x1 = config.block_x * w / 100 + + (double)(total_dx - current_dx) / OVERSAMPLE; + tmp_y1 = config.block_y * h / 100 + + (double)(total_dy - current_dy) / OVERSAMPLE; + tmp_x2 = config.block_x * w / 100 + + (double)total_dx / OVERSAMPLE; + tmp_y2 = config.block_y * h / 100 + + (double)total_dy / OVERSAMPLE; } + global_x1 = lrint (tmp_x1); + global_y1 = lrint (tmp_y1); + global_x2 = lrint (tmp_x2); + global_y2 = lrint (tmp_y2); +//printf("MotionMain::draw_vectors %d %d %d %d %d %d\n", total_dx, total_dy, global_x1, global_y1, global_x2, global_y2); block_x = global_x1; block_y = global_y1; + if((config.tracking_object == MotionScan::TRACK_SINGLE || + config.tracking_object == MotionScan::TRACK_PREVIOUS) && + !config.rotate) + { +// Show block at endpoint of motion if no rotation shown + block_x = global_x2; + block_y = global_y2; + } block_w = config.global_block_w * w / 100; block_h = config.global_block_h * h / 100; block_x1 = block_x - block_w / 2; @@ -973,8 +1365,8 @@ void MotionMain::draw_vectors(VFrame *frame) } } else { - block_x = (int64_t)(config.block_x * w / 100); - block_y = (int64_t)(config.block_y * h / 100); + block_x = lrint (config.block_x * w / 100); + block_y = lrint (config.block_y * h / 100); } block_w = config.global_block_w * w / 100; @@ -986,14 +1378,14 @@ void MotionMain::draw_vectors(VFrame *frame) double target_angle1 = base_angle1 + angle; double target_angle2 = base_angle2 + angle; double radius = sqrt(block_w * block_w + block_h * block_h) / 2; - block_x1 = (int)(block_x - cos(target_angle1) * radius); - block_y1 = (int)(block_y - sin(target_angle1) * radius); - block_x2 = (int)(block_x + sin(target_angle2) * radius); - block_y2 = (int)(block_y - cos(target_angle2) * radius); - block_x3 = (int)(block_x - sin(target_angle2) * radius); - block_y3 = (int)(block_y + cos(target_angle2) * radius); - block_x4 = (int)(block_x + cos(target_angle1) * radius); - block_y4 = (int)(block_y + sin(target_angle1) * radius); + block_x1 = lrint(block_x - cos(target_angle1) * radius); + block_y1 = lrint(block_y - sin(target_angle1) * radius); + block_x2 = lrint(block_x + sin(target_angle2) * radius); + block_y2 = lrint(block_y - cos(target_angle2) * radius); + block_x3 = lrint(block_x - sin(target_angle2) * radius); + block_y3 = lrint(block_y + cos(target_angle2) * radius); + block_x4 = lrint(block_x + cos(target_angle1) * radius); + block_y4 = lrint(block_y + sin(target_angle1) * radius); draw_line(frame, block_x1, block_y1, block_x2, block_y2); draw_line(frame, block_x2, block_y2, block_x4, block_y4); @@ -1043,22 +1435,13 @@ void MotionMain::draw_line(VFrame *frame, int x1, int y1, int x2, int y2) #define ARROW_SIZE 10 void MotionMain::draw_arrow(VFrame *frame, int x1, int y1, int x2, int y2) { - double angle = atan((float)(y2 - y1) / (float)(x2 - x1)); - double angle1 = angle + (float)145 / 360 * 2 * 3.14159265; - double angle2 = angle - (float)145 / 360 * 2 * 3.14159265; - int x3, y3, x4, y4; - if( x2 < x1 ) { - x3 = x2 - (int)(ARROW_SIZE * cos(angle1)); - y3 = y2 - (int)(ARROW_SIZE * sin(angle1)); - x4 = x2 - (int)(ARROW_SIZE * cos(angle2)); - y4 = y2 - (int)(ARROW_SIZE * sin(angle2)); - } - else { - x3 = x2 + (int)(ARROW_SIZE * cos(angle1)); - y3 = y2 + (int)(ARROW_SIZE * sin(angle1)); - x4 = x2 + (int)(ARROW_SIZE * cos(angle2)); - y4 = y2 + (int)(ARROW_SIZE * sin(angle2)); - } + double angle = atan2((double)(y2 - y1), (double)(x2 - x1)); + double angle1 = angle + (float)145 / 360 * 2 * M_PI; + double angle2 = angle - (float)145 / 360 * 2 * M_PI; + int x3 = x2 + (int)(ARROW_SIZE * cos(angle1)); + int y3 = y2 + (int)(ARROW_SIZE * sin(angle1)); + int x4 = x2 + (int)(ARROW_SIZE * cos(angle2)); + int y4 = y2 + (int)(ARROW_SIZE * sin(angle2)); // Main vector draw_line(frame, x1, y1, x2, y2); @@ -1139,6 +1522,7 @@ int MotionMain::put_cache_line(const char *line) if( !active_fp ) { close_cache_file(); snprintf(cache_file, sizeof(cache_file), "%s.bak", config.tracking_file); + ::remove(cache_file); ::rename(config.tracking_file, cache_file); if( !(active_fp = fopen(config.tracking_file, "w")) ) { perror(config.tracking_file); @@ -1177,6 +1561,7 @@ void MotionMain::reset_cache_file() else close_cache_file(); strcpy(cache_file, config.tracking_file); + if (!cache_file[0]) strcpy(cache_file, TRACKING_FILE); } @@ -1209,6 +1594,12 @@ void RotateScanUnit::process_package(LoadPackage *package) int color_model = server->previous_frame->get_color_model(); int pixel_size = BC_CModels::calculate_pixelsize(color_model); int row_bytes = server->previous_frame->get_bytes_per_line(); + float angle = pkg->angle; +// Ensure a tiny displacement if angle is nearly exact zero +// As angle is in degree and MIN_ANGLE is in radian, +// displacement of 1/57th of the smallest possible angle can be discarded +// This trick is needed to trigger interpolation + if (fabs (angle) < MIN_ANGLE) angle = MIN_ANGLE; if( !rotater ) rotater = new AffineEngine(1, 1); @@ -1231,7 +1622,7 @@ void RotateScanUnit::process_package(LoadPackage *package) rotater->set_in_pivot(server->block_x, server->block_y); rotater->set_out_pivot(server->block_x, server->block_y); //printf("RotateScanUnit::process_package %d\n", __LINE__); - rotater->rotate(temp, server->previous_frame, pkg->angle); + rotater->rotate(temp, server->previous_frame, angle); // Scan reduced block size //plugin->output_frame->copy_from(server->current_frame); @@ -1256,6 +1647,14 @@ void RotateScanUnit::process_package(LoadPackage *package) row_bytes, x2 - x1, y2 - y1, color_model); //printf("RotateScanUnit::process_package %d\n", __LINE__); server->put_cache(pkg->angle, pkg->difference); +// Dumping rotated frame for debugging +// temp->write_ppm(temp, "/tmp/a%06ld-a%f.ppm", +// plugin->get_source_position(), +// pkg->angle); +// if (pkg->angle == 0) +// temp->write_ppm(server->previous_frame, +// "/tmp/a%06ld-t.ppm", +// plugin->get_source_position()); } #if 0 VFrame png(x2-x1, y2-y1, BC_RGB888, -1); @@ -1293,7 +1692,7 @@ void RotateScan::init_packages() for( int i = 0; i < get_total_packages(); i++ ) { RotateScanPackage *pkg = (RotateScanPackage*)get_package(i); pkg->angle = scan_angle1 + - i * (scan_angle2 - scan_angle1) / (total_steps - 1); + i * (scan_angle2 - scan_angle1) / total_steps; } } @@ -1309,16 +1708,35 @@ LoadPackage* RotateScan::new_package() float RotateScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, - int block_x, int block_y) + int block_x, int block_y, int passno) { +// Attention, process_buffer feeds previous_frame and current_frame interchanged +// Preinitialize sane rotation results skip = 0; this->block_x = block_x; this->block_y = block_y; -//printf("RotateScan::scan_frame %d\n", __LINE__); +// passno == 0: single pass tracking +// passno == 1: 1st pass of two-pass tracking (reduce accuracy) +// passno == 2: 2nd pass of two-pass tracking (reduce angle range) +// Save may be needed for 2nd pass +// float result_saved = 0; + if (passno == 2) + { +// result_saved needed for some debug printing only +// result_saved = result; + result = 0; + } + else + { + result = plugin->config.rotation_center; + } + +//printf("RotateScan::scan_frame %d frame=%ld passno=%d\n", __LINE__, plugin->get_source_position(), passno); switch(plugin->config.tracking_type) { case MotionScan::NO_CALCULATE: result = plugin->config.rotation_center; + if (passno == 2) result = 0; skip = 1; break; @@ -1326,9 +1744,17 @@ float RotateScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, case MotionScan::SAVE: if( plugin->load_ok ) { result = plugin->load_dt; + if (passno == 2) result = 0; skip = 1; } break; + +// Scan from scratch with sane rotation results + default: + result = plugin->config.rotation_center; + if (passno == 2) result = 0; + skip = 0; + break; } this->previous_frame = previous_frame; @@ -1414,10 +1840,13 @@ float RotateScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, // Determine min angle from size of block double angle1 = atan((double)block_h / block_w); double angle2 = atan((double)(block_h - 1) / (block_w + 1)); +// Attention we get min_angle and MIN_ANGLE in radian, but elsewhere use degree double min_angle = fabs(angle2 - angle1) / OVERSAMPLE; min_angle = MAX(min_angle, MIN_ANGLE); +// Convert min_angle to degree for convenience + min_angle *= 180 / M_PI; -//printf("RotateScan::scan_frame %d min_angle=%f\n", __LINE__, min_angle * 360 / 2 / M_PI); +//printf("RotateScan::scan_frame %d min_angle=%f\n", __LINE__, min_angle); cache.remove_all_objects(); @@ -1426,6 +1855,7 @@ float RotateScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, if( previous_frame->data_matches(current_frame) ) { //printf("RotateScan::scan_frame: frames match. Skipping.\n"); result = plugin->config.rotation_center; + if (passno == 2) result = 0; skip = 1; } } @@ -1434,37 +1864,123 @@ float RotateScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, // Initial search range float angle_range = max_angle; result = plugin->config.rotation_center; - total_steps = plugin->config.rotate_positions; + if (passno == 2) result = 0; + + if (passno == 1) + { +// Evtl stop search earlier for 1st pass to gain speed + if (angle_range > 16 && min_angle < 1) min_angle = 1; + else if (angle_range > 4 && min_angle < 0.25) min_angle = angle_range/16; + else if (angle_range > min_angle*4) min_angle *= 4; + } + + if (passno == 2) + { +// Evtl reduce angle_range for refinement pass to gain speed + if (angle_range > 16) angle_range /= 4; + else if (angle_range > 4) angle_range = 4; + if (angle_range < min_angle*4) angle_range = min_angle*4; + if (angle_range > max_angle) angle_range = max_angle; + } +// Pre-negate result as previous_frame and current_frame have been interchanged + result = -result; - while( angle_range >= min_angle * total_steps ) { + while( angle_range >= min_angle ) { scan_angle1 = result - angle_range; scan_angle2 = result + angle_range; +// Find number of required steps, even and no more than configured at once + total_steps = (int)ceil(angle_range*2/min_angle); + if (total_steps & 1) total_steps ++; + if (total_steps > plugin->config.rotate_positions) total_steps = plugin->config.rotate_positions; + +//printf("RotateScan::scan_frame angle_range=%f from=%f to=%f steps=%d\n", angle_range, scan_angle1, scan_angle2, total_steps); - set_package_count(total_steps); +// Use odd number of samples to ensure that rotation center be always included + set_package_count(total_steps+1); //set_package_count(1); process_packages(); - int64_t min_difference = -1; + int64_t min_difference = -1, max_difference = -1, noiselev = -1; for( int i = 0; i < get_total_packages(); i++ ) { RotateScanPackage *pkg = (RotateScanPackage*)get_package(i); if( pkg->difference < min_difference || min_difference == -1 ) { min_difference = pkg->difference; result = pkg->angle; } + if( pkg->difference > max_difference || max_difference == -1 ) { + max_difference = pkg->difference; + } +//printf("RotateScan::scan_frame pkg=%d angle=%f diff=%ld min_diff=%ld max_diff=%ld\n", i, pkg->angle, pkg->difference, min_difference, max_difference); //break; } - - angle_range /= 2; - +// Determine noise level (not active on pass 2) + noiselev = min_difference+(max_difference-min_difference)*plugin->config.noise_rotation/100; + if (passno == 2) noiselev = min_difference; +//printf("RotateScan::scan_frame min_diff=%ld max_diff=%ld noiselev=%ld\n", min_difference, max_difference, noiselev); + for(int i = 0; i < get_total_packages(); i++) + { + RotateScanPackage *pkg = (RotateScanPackage*)get_package(i); +// Already found as the best sample, not necessary to memorize + if(result == pkg->angle) continue; +// Above noise level - a definitely bad sample, skip + if(pkg->difference > noiselev) continue; +// Below noise level but farther from rotation center, skip + if(fabs(pkg->angle-plugin->config.rotation_center) > fabs(result-plugin->config.rotation_center)) continue; +// Below noise level and nearer to rotation center, memorize + if(fabs(pkg->angle-plugin->config.rotation_center) < fabs(result-plugin->config.rotation_center)) + { + min_difference = pkg->difference; + result = pkg->angle; +//printf("RotateScan::scan_frame angle override=%d angle=%f diff=%ld min_diff=%ld\n", i, pkg->angle, pkg->difference, min_difference); + continue; + } +// Equal distances to rotation center, memorize sample with min difference + if(pkg->difference < min_difference) + { + min_difference = pkg->difference; + result = pkg->angle; +//printf("RotateScan::scan_frame difference override=%d angle=%f diff=%ld min_diff=%ld\n", i, pkg->angle, pkg->difference, min_difference); + continue; + } + } + float new_range = 0, angle_diff = 0; + for(int i = 0; i < get_total_packages(); i++) + { + RotateScanPackage *pkg = (RotateScanPackage*)get_package(i); +// Above noise level - skip this angle + if(pkg->difference > noiselev) continue; +// Below noise level - measure max difference from the best angle + if(angle_diff < fabs(result-pkg->angle)) + { + angle_diff = fabs(result-pkg->angle); +//printf("RotateScan::scan_frame angle diff override=%d angle=%f diff=%f\n", i, pkg->angle, angle_diff); + } + } +// Optimum new angle range might be +/- one search step from the best angle + new_range = angle_range * 2 / total_steps; +// Evtl expand angle range to +/- two search steps if some samples below noise + if (angle_diff > 0) new_range = angle_range * 4 / total_steps; +// Evtl expand angle range to include samples below noise level + if (new_range < angle_diff) new_range = angle_diff; +// But always reduce angle range at least twice + if (new_range > angle_range / 2) new_range = angle_range / 2; + angle_range = new_range; //break; } +// Negate result as previous_frame and current_frame have been interchanged +// and get rid of negative zeros in coord files + result = -result; + if (fabs (result) < MIN_ANGLE) result = 0; } - if( plugin->config.tracking_type == MotionScan::SAVE ) { - plugin->save_dt = result; - } -//printf("RotateScan::scan_frame %d angle=%f\n", __LINE__, result); +// Dumping compared frames for debugging +// current_frame->write_ppm(previous_frame, "/tmp/a%06ld-p.ppm", +// plugin->get_source_position()); +// current_frame->write_ppm(current_frame, "/tmp/a%06ld-c.ppm", +// plugin->get_source_position()); + +//printf("RotateScan::scan_frame %d passno=%d saved angle=%f measured angle=%f twopass angle=%f\n", __LINE__, passno, result_saved, result, result_saved+result); return result; } @@ -1474,7 +1990,8 @@ int64_t RotateScan::get_cache(float angle) cache_lock->lock("RotateScan::get_cache"); for( int i = 0; i < cache.total; i++ ) { RotateScanCache *ptr = cache.values[i]; - if( fabs(ptr->angle - angle) <= MIN_ANGLE ) { +// Attention, MIN_ANGLE in radian, while angle and ptr->angle in degree ! + if( fabs(ptr->angle - angle) <= MIN_ANGLE * 90 / M_PI ) { result = ptr->difference; break; } diff --git a/cinelerra-5.1/plugins/motion/motion.h b/cinelerra-5.1/plugins/motion/motion.h index 1ce29e50..56390bad 100644 --- a/cinelerra-5.1/plugins/motion/motion.h +++ b/cinelerra-5.1/plugins/motion/motion.h @@ -59,7 +59,7 @@ class RotateScan; #define MIN_BLOCKS 1 #define MAX_BLOCKS 200 -// Precision of rotation +// Precision of rotation (in radian!) #define MIN_ANGLE 0.0001 #define TRACKING_FILE "/tmp/motion" @@ -98,6 +98,9 @@ public: // Block position in percentage 0 - 100 double block_x; double block_y; +// Noise levels to ignore frame differences as 0-100% of (maxdiff-mindiff) + double noise_level; + double noise_rotation; int horizontal_only; int vertical_only; @@ -111,6 +114,8 @@ public: int tracking_type; // Track a single frame, previous frame, or previous frame same block int tracking_object; +// 1 == two pass tracking + int twopass; // Number of single frame to track relative to timeline start int64_t track_frame; @@ -128,6 +133,8 @@ public: int process_buffer(VFrame **frame, int64_t start_position, double frame_rate); void process_global(); void process_rotation(); + void refine_global(); + void refine_rotation(); void draw_vectors(VFrame *frame); int is_multichannel(); int is_realtime(); @@ -180,6 +187,7 @@ public: int64_t cache_key, active_key; // add constant frame offset values int dx_offset, dy_offset; + float dt_offset; int64_t tracking_frame; // save/load result values int load_ok; @@ -276,7 +284,8 @@ public: VFrame *current_frame, // Pivot int block_x, - int block_y); + int block_y, + int passno); int64_t get_cache(float angle); void put_cache(float angle, int64_t difference); diff --git a/cinelerra-5.1/plugins/motion/motionscan.C b/cinelerra-5.1/plugins/motion/motionscan.C index 0685b656..f5d53653 100644 --- a/cinelerra-5.1/plugins/motion/motionscan.C +++ b/cinelerra-5.1/plugins/motion/motionscan.C @@ -21,7 +21,7 @@ #include "clip.h" //#include "../downsample/downsampleengine.h" -//#include "motion.h" +#include "motion.h" #include "motionscan.h" #include "mutex.h" #include "vframe.h" @@ -78,7 +78,7 @@ void MotionScanUnit::process_package(LoadPackage *package) pkg->block_x2 - pkg->block_x1, pkg->block_y2 - pkg->block_y1, color_model); -// printf("MotionScanUnit::process_package %d search_x=%d search_y=%d diff=%lld\n", +// printf("MotionScanUnit::process_package %d search_x=%d search_y=%d diff=%ld\n", // __LINE__, server->block_x1 - pkg->search_x, server->block_y1 - pkg->search_y, pkg->difference1); server->put_cache(pkg->search_x, pkg->search_y, pkg->difference1); } @@ -129,10 +129,11 @@ void MotionScanUnit::put_cache(int x, int y, int64_t difference) cache_lock->unlock(); } -MotionScan::MotionScan(int total_clients, int total_packages) +MotionScan::MotionScan(MotionMain *plugin, int total_clients, int total_packages) : LoadServer( //1, 1 total_clients, total_packages) { + this->plugin = plugin; test_match = 1; cache_lock = new Mutex("MotionScan::cache_lock"); downsampled_previous = 0; @@ -164,21 +165,37 @@ void MotionScan::init_packages() pkg->dx = pkg->dy = 0; if( !subpixel ) { - pkg->search_x = pkg->scan_x1 + - (pkg->step % x_steps) * (scan_x2 - scan_x1) / x_steps; - pkg->search_y = pkg->scan_y1 + - (pkg->step / x_steps) * (scan_y2 - scan_y1) / y_steps; pkg->sub_x = pkg->sub_y = 0; + int ipkg = pkg->step; + if (ipkg == 0) + { +// First package additionally checks position nearest to the best found so far + pkg->search_x = x_result; + pkg->search_y = y_result; + if (pkg->search_x < pkg->scan_x1) pkg->search_x = pkg->scan_x1; + if (pkg->search_x >= pkg->scan_x2) pkg->search_x = pkg->scan_x2-1; + if (pkg->search_y < pkg->scan_y1) pkg->search_y = pkg->scan_y1; + if (pkg->search_y >= pkg->scan_y2) pkg->search_y = pkg->scan_y2-1; + } + else + { + ipkg --; + pkg->search_x = pkg->scan_x1 + (ipkg % x_steps) * + (scan_x2 - scan_x1) / x_steps; + pkg->search_y = pkg->scan_y1 + (ipkg / x_steps) * + (scan_y2 - scan_y1) / y_steps; + } } else { - pkg->sub_x = pkg->step % (OVERSAMPLE * 2); - pkg->sub_y = pkg->step / (OVERSAMPLE * 2); + pkg->sub_x = pkg->step % x_steps; + pkg->sub_y = pkg->step / x_steps; - if( horizontal_only ) pkg->sub_y = 0; - if( vertical_only ) pkg->sub_x = 0; +// Unidirectional cases already taken into account in x_steps and y_steps +// if( horizontal_only ) pkg->sub_y = 0; +// if( vertical_only ) pkg->sub_x = 0; - pkg->search_x = pkg->scan_x1 + pkg->sub_x / OVERSAMPLE + 1; - pkg->search_y = pkg->scan_y1 + pkg->sub_y / OVERSAMPLE + 1; + pkg->search_x = pkg->scan_x1 + pkg->sub_x / OVERSAMPLE; + pkg->search_y = pkg->scan_y1 + pkg->sub_y / OVERSAMPLE; pkg->sub_x %= OVERSAMPLE; pkg->sub_y %= OVERSAMPLE; @@ -214,7 +231,7 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, int horizontal_only, int vertical_only, int source_position, int total_steps, int total_dx, int total_dy, int global_origin_x, int global_origin_y, - int load_ok, int load_dx, int load_dy) + int passno, int load_ok, int load_dx, int load_dy) { this->previous_frame_arg = previous_frame; this->current_frame_arg = current_frame; @@ -238,23 +255,56 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, int block_w = w * global_block_w / 100; int block_h = h * global_block_h / 100; +// passno == 0: single pass tracking +// passno == 1: 1st pass of two-pass tracking (skip subpixel) +// passno == 2: 2nd pass of two-pass tracking (reduce search area) +// Save may be needed for 2nd pass + int dx_saved = 0; + int dy_saved = 0; + if (passno == 2) + { + dx_saved = dx_result; + dy_saved = dy_result; + } + // Location of block in previous frame - block_x1 = (int)(w * block_x / 100 - block_w / 2); - block_y1 = (int)(h * block_y / 100 - block_h / 2); - block_x2 = (int)(w * block_x / 100 + block_w / 2); - block_y2 = (int)(h * block_y / 100 + block_h / 2); + double tmp_x1, tmp_y1; + double tmp_x2, tmp_y2; + tmp_x1 = w * block_x / 100 - block_w / 2; + tmp_y1 = h * block_y / 100 - block_h / 2; + tmp_x2 = w * block_x / 100 + block_w / 2; + tmp_y2 = h * block_y / 100 + block_h / 2; + +// Attention, process_buffer feeds previous_frame and current_frame interchanged // Offset to location of previous block. This offset needn't be very accurate -// since it's the offset of the previous image and current image we want. +// since it's the offset on the previous image while current image is moved. +// Nevertheless compute it in floating point and round to int afterwards. if( frame_type == MotionScan::TRACK_PREVIOUS ) { - block_x1 += total_dx / OVERSAMPLE; - block_y1 += total_dy / OVERSAMPLE; - block_x2 += total_dx / OVERSAMPLE; - block_y2 += total_dy / OVERSAMPLE; + tmp_x1 += (double)total_dx / OVERSAMPLE; + tmp_y1 += (double)total_dy / OVERSAMPLE; + tmp_x2 += (double)total_dx / OVERSAMPLE; + tmp_y2 += (double)total_dy / OVERSAMPLE; +// Compensate displacement computed in the 1st pass + if (passno == 2) + { + tmp_x1 -= (double)dx_saved / OVERSAMPLE; + tmp_y1 -= (double)dy_saved / OVERSAMPLE; + tmp_x2 -= (double)dx_saved / OVERSAMPLE; + tmp_y2 -= (double)dy_saved / OVERSAMPLE; + } } - + block_x1 = lrint (tmp_x1); + block_y1 = lrint (tmp_y1); + block_x2 = lrint (tmp_x2); + block_y2 = lrint (tmp_y2); + +// Preinitialize sane translation results + dx_result = 0; + dy_result = 0; skip = 0; +//printf("MotionScan::scan_frame %d frame=%ld passno=%d\n", __LINE__, plugin->get_source_position(), passno); switch( tracking_type ) { // Don't calculate case MotionScan::NO_CALCULATE: @@ -267,19 +317,22 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, if( load_ok ) { dx_result = load_dx; dy_result = load_dy; + if (passno == 2) dx_result = dy_result = 0; skip = 1; } break; -// Scan from scratch +// Scan from scratch with sane translation results default: + dx_result = 0; + dy_result = 0; skip = 0; break; } if( !skip && test_match ) { if( previous_frame->data_matches(current_frame) ) { - printf("MotionScan::scan_frame: data matches. skipping.\n"); +// printf("MotionScan::scan_frame: data matches. skipping.\n"); dx_result = dy_result = 0; skip = 1; } @@ -290,13 +343,25 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, // Location of block in current frame int origin_offset_x = this->global_origin_x * w / 100; int origin_offset_y = this->global_origin_y * h / 100; - int x_result = block_x1 + origin_offset_x; - int y_result = block_y1 + origin_offset_y; + x_result = block_x1 + origin_offset_x; + y_result = block_y1 + origin_offset_y; + dx_result = 0; + dy_result = 0; //printf("MotionScan::scan_frame 1 %d %d %d %d %d %d %d %d\n", // block_x1 + block_w / 2, block_y1 + block_h / 2, // block_w, block_h, block_x1, block_y1, block_x2, block_y2); + if (passno == 2) + { +// Evtl reduce scan area for refinement pass to gain speed +// For 1st pass subpixel search will be skipped + if (scan_w > 64) scan_w /= 4; + else if (scan_w > 16) scan_w = 16; + if (scan_h > 64) scan_h /= 4; + else if (scan_h > 16) scan_h = 16; + } + while(1) { // Cache needs to be cleared if downsampling is used because the sums of // different downsamplings can't be compared. @@ -306,6 +371,8 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, scan_y1 = y_result - scan_h / 2; scan_x2 = x_result + scan_w / 2; scan_y2 = y_result + scan_h / 2; + if (scan_x2 <= scan_x1) scan_x2 = scan_x1 + 1; + if (scan_y2 <= scan_y1) scan_y2 = scan_y1 + 1; // Zero out requested values if( horizontal_only ) { @@ -325,7 +392,7 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, // printf("MotionScan::scan_frame 1 %d block_x1=%d block_y1=%d block_x2=%d block_y2=%d\n" // " scan_x1=%d scan_y1=%d scan_x2=%d scan_y2=%d\n" // " x_result=%d y_result=%d\n", __LINE__, block_x1, block_y1, block_x2, block_y2, -// scan_x1, scan_y1, scan_x2, scan_y2, x_result, y_result); +// scan_x1, scan_y1, scan_x2, scan_y2, x_result, y_result); // Give up if invalid coords. if (scan_y2 <= scan_y1 || scan_x2 <= scan_x1 || @@ -336,23 +403,25 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, if( subpixel ) { //printf("MotionScan::scan_frame %d %d %d\n", __LINE__, x_result, y_result); -// Scan every subpixel in a 2 pixel * 2 pixel square - total_pixels = (2 * OVERSAMPLE) * (2 * OVERSAMPLE); - +// Scan every subpixel in a scan_w * scan_h pixel square + x_steps = (scan_x2 - scan_x1) * OVERSAMPLE; + y_steps = (scan_y2 - scan_y1) * OVERSAMPLE; + if(horizontal_only) y_steps = 1; + if(vertical_only) x_steps = 1; + if (x_steps < 1) x_steps = 1; + if (y_steps < 1) y_steps = 1; + + total_pixels = x_steps * y_steps; this->total_steps = total_pixels; -// These aren't used in subpixel - this->x_steps = OVERSAMPLE * 2; - this->y_steps = OVERSAMPLE * 2; set_package_count(this->total_steps); process_packages(); // Get least difference - int64_t min_difference = -1; + int64_t min_difference = -1, max_difference = -1, noiselev = -1; + double radius, best_radius; for( int i = 0; i < get_total_packages(); i++ ) { MotionScanPackage *pkg = (MotionScanPackage *)get_package(i); -//printf("MotionScan::scan_frame %d search_x=%d search_y=%d sub_x=%d sub_y=%d diff1=%lld diff2=%lld\n", -//__LINE__, pkg->search_x, pkg->search_y, pkg->sub_x, pkg->sub_y, pkg->difference1, pkg->difference2); if( pkg->difference1 < min_difference || min_difference == -1 ) { min_difference = pkg->difference1; @@ -363,9 +432,13 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, // Fill in results dx_result = block_x1 * OVERSAMPLE - x_result; dy_result = block_y1 * OVERSAMPLE - y_result; -//printf("MotionScan::scan_frame %d dx_result=%d dy_result=%d diff=%lld\n", +//printf("MotionScan::scan_frame %d dx_result=%d dy_result=%d diff1=%ld\n", //__LINE__, dx_result, dy_result, min_difference); } + if(pkg->difference1 > max_difference || max_difference == -1) + { + max_difference = pkg->difference1; + } if( pkg->difference2 < min_difference ) { min_difference = pkg->difference2; @@ -375,9 +448,63 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, dx_result = block_x1 * OVERSAMPLE - x_result; dy_result = block_y1 * OVERSAMPLE - y_result; -//printf("MotionScan::scan_frame %d dx_result=%d dy_result=%d diff=%lld\n", +//printf("MotionScan::scan_frame %d dx_result=%d dy_result=%d diff2=%ld\n", //__LINE__, dx_result, dy_result, min_difference); } + if(pkg->difference2 > max_difference) + { + max_difference = pkg->difference2; + } +//printf("MotionScan::scan_frame %d pkg=%d search_x=%d search_y=%d sub_x=%d sub_y=%d diff1=%ld diff2=%ld min_diff=%ld max_diff=%ld\n", +//__LINE__, i, pkg->search_x, pkg->search_y, pkg->sub_x, pkg->sub_y, pkg->difference1, pkg->difference2, min_difference, max_difference); + } +// Determine noise level (not active on pass 2) + noiselev = min_difference+(max_difference-min_difference)*plugin->config.noise_level/100; + if (passno == 2) noiselev = min_difference; +//printf("MotionScan::scan_frame min_diff=%ld max_diff=%ld noiselev=%ld\n", min_difference, max_difference, noiselev); + for(int i = 0; i < get_total_packages(); i++) + { + MotionScanPackage *pkg = (MotionScanPackage*)get_package(i); + if(pkg->difference1 <= noiselev) + { +// Below noise level - check subpixel translation radius + radius = hypot( + (double)((block_x1-pkg->search_x)*OVERSAMPLE-pkg->sub_x), + (double)((block_y1-pkg->search_y)*OVERSAMPLE-pkg->sub_y)); + best_radius = hypot((double)dx_result,(double)dy_result); + if(radius < best_radius || + (radius == best_radius && pkg->difference1 < min_difference)) + { +// Below noise level and smaller translation, memorize + min_difference = pkg->difference1; + x_result = pkg->search_x * OVERSAMPLE + pkg->sub_x; + y_result = pkg->search_y * OVERSAMPLE + pkg->sub_y; + dx_result = block_x1 * OVERSAMPLE - x_result; + dy_result = block_y1 * OVERSAMPLE - y_result; +//printf("MotionScan::scan_frame %d diff1 override=%d search_x=%d search_y=%d sub_x=%d sub_y=%d dx_result=%d dy_result=%d diff1=%ld min_diff=%ld\n", +//__LINE__, i, pkg->search_x, pkg->search_y, pkg->sub_x, pkg->sub_y, dx_result, dy_result, pkg->difference1, min_difference); + } + } + if(pkg->difference2 <= noiselev) + { +// Below noise level - check subpixel translation radius + radius = hypot( + (double)((block_x1-pkg->search_x)*OVERSAMPLE+pkg->sub_x), + (double)((block_y1-pkg->search_y)*OVERSAMPLE+pkg->sub_y)); + best_radius = hypot((double)dx_result,(double)dy_result); + if(radius < best_radius || + (radius == best_radius && pkg->difference2 < min_difference)) + { +// Below noise level and smaller translation, memorize + min_difference = pkg->difference2; + x_result = pkg->search_x * OVERSAMPLE - pkg->sub_x; + y_result = pkg->search_y * OVERSAMPLE - pkg->sub_y; + dx_result = block_x1 * OVERSAMPLE - x_result; + dy_result = block_y1 * OVERSAMPLE - y_result; +//printf("MotionScan::scan_frame %d diff2 override=%d search_x=%d search_y=%d sub_x=%d sub_y=%d dx_result=%d dy_result=%d diff2=%ld min_diff=%ld\n", +//__LINE__, i, pkg->search_x, pkg->search_y, pkg->sub_x, pkg->sub_y, dx_result, dy_result, pkg->difference2, min_difference); + } + } } break; @@ -397,6 +524,11 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, x_steps = (int)sqrt(this->total_steps); y_steps = (int)sqrt(this->total_steps); } + if(horizontal_only) y_steps = 1; + if(vertical_only) x_steps = 1; + if (x_steps < 1) x_steps = 1; + if (y_steps < 1) y_steps = 1; + this->total_steps = x_steps * y_steps; // Use downsampled images // if( scan_x2 - scan_x1 > x_steps * 4 || @@ -445,37 +577,107 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, // printf("MotionScan::scan_frame %d this->total_steps=%d\n", // __LINE__, this->total_steps); - set_package_count(this->total_steps); +// One extra step for the position with zero translation + set_package_count(this->total_steps+1); process_packages(); // Get least difference - int64_t min_difference = -1; + int64_t min_difference = -1, max_difference = -1, noiselev = -1; + double radius, best_radius; for( int i = 0; i < get_total_packages(); i++ ) { MotionScanPackage *pkg = (MotionScanPackage *) get_package(i); -//printf("MotionScan::scan_frame %d search_x=%d search_y=%d sub_x=%d sub_y=%d diff1=%lld diff2=%lld\n", -// __LINE__, pkg->search_x, pkg->search_y, pkg->sub_x, pkg->sub_y, pkg->difference1, pkg->difference2); if (pkg->difference1 < min_difference || min_difference == -1) { min_difference = pkg->difference1; x_result = pkg->search_x; y_result = pkg->search_y; - x_result *= OVERSAMPLE; - y_result *= OVERSAMPLE; -//printf("MotionScan::scan_frame %d x_result=%d y_result=%d diff=%lld\n", -//__LINE__, block_x1 * OVERSAMPLE - x_result, block_y1 * OVERSAMPLE - y_result, pkg->difference1); + } + if(pkg->difference1 > max_difference || max_difference == -1) + { + max_difference = pkg->difference1; + } +//printf("MotionScan::scan_frame %d pkg=%d search_x=%d search_y=%d diff=%ld min_diff=%ld max_diff=%ld\n", +//__LINE__, i, pkg->search_x, pkg->search_y, pkg->difference1, min_difference, max_difference); + } +// Determine noise level (not active on pass 2) + noiselev = min_difference+(max_difference-min_difference)*plugin->config.noise_level/100; + if (passno == 2) noiselev = min_difference; +//printf("MotionScan::scan_frame min_diff=%ld max_diff=%ld noiselev=%ld\n", min_difference, max_difference, noiselev); + for(int i = 0; i < get_total_packages(); i++) + { + MotionScanPackage *pkg = (MotionScanPackage*)get_package(i); +// Already found as the best search, not necessary to memorize + if(x_result == pkg->search_x && y_result == pkg->search_y) continue; +// Above noise level - a definitely bad search, skip + if(pkg->difference1 > noiselev) continue; + radius = hypot((double)(block_x1-pkg->search_x),(double)(block_y1-pkg->search_y)); + best_radius = hypot((double)(block_x1-x_result),(double)(block_y1-y_result)); +// Below noise level but bigger translation, skip + if(radius > best_radius) continue; +// Below noise level and smaller translation, memorize + if(radius < best_radius) + { + min_difference = pkg->difference1; + x_result = pkg->search_x; + y_result = pkg->search_y; +//printf("MotionScan::scan_frame %d search override=%d search_x=%d search_y=%d diff=%ld min_diff=%ld\n", +//__LINE__, i, pkg->search_x, pkg->search_y, pkg->difference1, min_difference); + continue; + } +// Equal translations, memorize search with min difference + if(pkg->difference1 < min_difference) + { + min_difference = pkg->difference1; + x_result = pkg->search_x; + y_result = pkg->search_y; +//printf("MotionScan::scan_frame %d difference override=%d search_x=%d search_y=%d diff=%ld min_diff=%ld\n", +//__LINE__, i, pkg->search_x, pkg->search_y, pkg->difference1, min_difference); + continue; + } + } + int rad_x = 0, rad_y = 0; + for(int i = 0; i < get_total_packages(); i++) + { + MotionScanPackage *pkg = (MotionScanPackage*)get_package(i); +// Above noise level - skip this radius + if(pkg->difference1 > noiselev) continue; +// Below noise level - measure max distance from the best position + if(rad_x < abs(x_result-pkg->search_x)) + { + rad_x = abs(x_result-pkg->search_x); +//printf("MotionScan::scan_frame %d X-radius override=%d search_x=%d radius=%d\n", +//__LINE__, i, pkg->search_x, rad_x); + } + if(rad_y < abs(y_result-pkg->search_y)) + { + rad_y = abs(y_result-pkg->search_y); +//printf("MotionScan::scan_frame %d Y-radius override=%d search_y=%d radius=%d\n", +//__LINE__, i, pkg->search_y, rad_y); } } + x_result *= OVERSAMPLE; + y_result *= OVERSAMPLE; +//printf("MotionScan::scan_frame %d dx_result=%d dy_result=%d diff=%ld\n", +//__LINE__, block_x1 * OVERSAMPLE - x_result, block_y1 * OVERSAMPLE - y_result, min_difference); // If a new search is required, rescale results back to pixels. if( this->total_steps >= total_pixels ) { // Single pixel accuracy reached. Now do exhaustive subpixel search. - if( action_type == MotionScan::STABILIZE || - action_type == MotionScan::TRACK || - action_type == MotionScan::NOTHING ) { +// Subpixel search skipped on the 1st pass of a two-pass search. + if((action_type == MotionScan::STABILIZE || + action_type == MotionScan::TRACK || + action_type == MotionScan::NOTHING) && + passno != 1) + { //printf("MotionScan::scan_frame %d %d %d\n", __LINE__, x_result, y_result); +// Subpixel scan area might be +/- one pixel from the best position + scan_w = 2; + scan_h = 2; +// Evtl expand scan area to +/- two pixels if some samples below noise + if (rad_x > 0) scan_w = 4; + if (rad_y > 0) scan_h = 4; x_result /= OVERSAMPLE; y_result /= OVERSAMPLE; - scan_w = scan_h = 2; subpixel = 1; } // Fill in results and quit @@ -488,13 +690,27 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, } // Reduce scan area and try again else { - scan_w = (scan_x2 - scan_x1) / 2; - scan_h = (scan_y2 - scan_y1) / 2; +// Optimum new scan area might be +/- one search step from the best position + scan_w = (scan_x2 - scan_x1) * 2 / x_steps; + scan_h = (scan_y2 - scan_y1) * 2 / y_steps; +// Evtl expand scan area to +/- two search steps if some samples below noise + if (rad_x > 0) scan_w = (scan_x2 - scan_x1) * 4 / x_steps; + if (rad_y > 0) scan_h = (scan_y2 - scan_y1) * 4 / y_steps; +// Evtl expand scan area to include samples below noise level + if (scan_w < rad_x * 2) scan_w = rad_x * 2; + if (scan_h < rad_y * 2) scan_h = rad_y * 2; +// Always reduce scan area at least twice + if (scan_w > (scan_x2 - scan_x1) / 2) scan_w = (scan_x2 - scan_x1) / 2; + if (scan_h > (scan_y2 - scan_y1) / 2) scan_h = (scan_y2 - scan_y1) / 2; +// But retain scan area at least one pixel in size + if (scan_w < 1) scan_w = 1; + if (scan_h < 1) scan_h = 1; x_result /= OVERSAMPLE; y_result /= OVERSAMPLE; } } } +// Negate results as previous_frame and current_frame have been interchanged dx_result = -dx_result; dy_result = -dy_result; } @@ -503,8 +719,14 @@ void MotionScan::scan_frame(VFrame *previous_frame, VFrame *current_frame, if( vertical_only ) dx_result = 0; if( horizontal_only ) dy_result = 0; -//printf("MotionScan::scan_frame %d dx=%.2f dy=%.2f\n", -// __LINE__, (float)this->dx_result / OVERSAMPLE, (float)this->dy_result / OVERSAMPLE); +// printf("MotionScan::scan_frame %d passno=%d saved dx=%.2f dy=%.2f measured dx=%.2f dy=%.2f twopass dx=%.2f dy=%.2f\n", +// __LINE__, passno, +// (float)dx_saved / OVERSAMPLE, +// (float)dy_saved / OVERSAMPLE, +// (float)this->dx_result / OVERSAMPLE, +// (float)this->dy_result / OVERSAMPLE, +// (float)(this->dx_result + dx_saved) / OVERSAMPLE, +// (float)(this->dy_result + dy_saved) / OVERSAMPLE); } int64_t MotionScan::get_cache(int x, int y) diff --git a/cinelerra-5.1/plugins/motion/motionscan.h b/cinelerra-5.1/plugins/motion/motionscan.h index 32aeb5aa..7ba88439 100644 --- a/cinelerra-5.1/plugins/motion/motionscan.h +++ b/cinelerra-5.1/plugins/motion/motionscan.h @@ -89,7 +89,8 @@ public: class MotionScan : public LoadServer { public: - MotionScan(int total_clients, + MotionScan(MotionMain *plugin, + int total_clients, int total_packages); ~MotionScan(); @@ -107,7 +108,7 @@ public: int global_range_w, int global_range_h, int global_block_w, int global_block_h, double block_x, double block_y, int frame_type, int tracking_type, int action_type, int horizontal_only, int vertical_only, int source_position, int total_steps, - int total_dx, int total_dy, int global_origin_x, int global_origin_y, + int total_dx, int total_dy, int global_origin_x, int global_origin_y, int passno, int load_ok=0, int load_dx=0, int load_dy=0); int64_t get_cache(int x, int y); void put_cache(int x, int y, int64_t difference); @@ -126,6 +127,9 @@ public: // OVERSAMPLE int dx_result, dy_result; +// Currently best expected location of scan block + int x_result, y_result; + enum { // action_type TRACK, STABILIZE, @@ -158,6 +162,7 @@ private: // Downsampled frames VFrame *downsampled_previous; VFrame *downsampled_current; + MotionMain *plugin; // Test for identical frames before processing // Faster to skip it if the frames are usually different int test_match; diff --git a/cinelerra-5.1/plugins/motion/motionwindow.C b/cinelerra-5.1/plugins/motion/motionwindow.C index e799aee6..b96bea48 100644 --- a/cinelerra-5.1/plugins/motion/motionwindow.C +++ b/cinelerra-5.1/plugins/motion/motionwindow.C @@ -32,7 +32,7 @@ #include "pluginserver.h" MotionWindow::MotionWindow(MotionMain *plugin) - : PluginClientWindow(plugin, xS(800), yS(640), xS(800), yS(640), 0) + : PluginClientWindow(plugin, xS(800), yS(750), xS(800), yS(750), 0) { this->plugin = plugin; } @@ -43,23 +43,23 @@ MotionWindow::~MotionWindow() void MotionWindow::create_objects() { - int xs5 = xS(5), xs10 = xS(10), xs20 = xS(20), xs50 = xS(50), xs120 = xS(120); - int ys10 = yS(10), ys20 = yS(20), ys50 = yS(50), ys30 = yS(30), ys40 = yS(40), ys60 = yS(60); + int xs10 = xS(10), xs20 = xS(20), xs50 = xS(50), xs120 = xS(120); + int ys10 = yS(10), ys20 = yS(20), ys50 = yS(50), ys30 = yS(30), ys40 = yS(40); int x = xs10, y = ys10; - int x1 = x, x2 = get_w() / 2; + int x2 = get_w() / 2; BC_Title *title; - add_subwindow(global = new MotionGlobal(plugin, this, x1, y)); + add_subwindow(global = new MotionGlobal(plugin, this, x, y)); add_subwindow(rotate = new MotionRotate(plugin, this, x2, y)); y += ys50; - add_subwindow(title = new BC_Title(x1, y, + add_subwindow(title = new BC_Title(x, y, _("Translation search radius:\n(W/H Percent of image)"))); add_subwindow(global_range_w = new GlobalRange(plugin, - x1 + title->get_w() + xs10, y, + x + title->get_w() + xs10, y, &plugin->config.global_range_w)); add_subwindow(global_range_h = new GlobalRange(plugin, - x1 + title->get_w() + xs10 + global_range_w->get_w(), y, + x + title->get_w() + xs10 + global_range_w->get_w(), y, &plugin->config.global_range_h)); add_subwindow(title = new BC_Title(x2, y, @@ -68,13 +68,13 @@ void MotionWindow::create_objects() x2 + title->get_w() + xs10, y)); y += ys50; - add_subwindow(title = new BC_Title(x1, y, + add_subwindow(title = new BC_Title(x, y, _("Translation block size:\n(W/H Percent of image)"))); add_subwindow(global_block_w = - new BlockSize(plugin, x1 + title->get_w() + xs10, y, + new BlockSize(plugin, x + title->get_w() + xs10, y, &plugin->config.global_block_w)); add_subwindow(global_block_h = - new BlockSize(plugin, x1 + title->get_w() + xs10 + + new BlockSize(plugin, x + title->get_w() + xs10 + global_block_w->get_w(), y, &plugin->config.global_block_h)); @@ -91,9 +91,9 @@ void MotionWindow::create_objects() // &plugin->config.rotation_block_h)); y += ys50; - add_subwindow(title = new BC_Title(x1, y, _("Translation search steps:"))); + add_subwindow(title = new BC_Title(x, y, _("Translation search steps:"))); add_subwindow(global_search_positions = - new GlobalSearchPositions(plugin, x1 + title->get_w() + xs10, y, xs120)); + new GlobalSearchPositions(plugin, x + title->get_w() + xs10, y, xs120)); global_search_positions->create_objects(); add_subwindow(title = new BC_Title(x2, y, _("Rotation search steps:"))); @@ -110,11 +110,6 @@ void MotionWindow::create_objects() track_direction->create_objects(); y += ys40; - add_subwindow(title = new BC_Title(x2, y, _("Tracking file:"))); - add_subwindow(tracking_file = new MotionTrackingFile(plugin, - plugin->config.tracking_file, this, x2+title->get_w() + xs20, y)); - - int y1 = y; add_subwindow(title = new BC_Title(x, y + ys10, _("Block X:"))); add_subwindow(block_x = new MotionBlockX(plugin, this, x + title->get_w() + xs10, y)); @@ -122,24 +117,11 @@ void MotionWindow::create_objects() new MotionBlockXText(plugin, this, x + title->get_w() + xs10 + block_x->get_w() + xs10, y + ys10)); - y += ys40; - add_subwindow(title = new BC_Title(x2, y, _("Rotation center:"))); + add_subwindow(title = new BC_Title(x2, y + ys10, _("Rotation center:"))); add_subwindow(rotation_center = new RotationCenter(plugin, x2 + title->get_w() + xs10, y)); y += ys40; - add_subwindow(title = new BC_Title(x2, y + ys10, _("Maximum angle offset:"))); - add_subwindow(rotate_magnitude = - new MotionRMagnitude(plugin, x2 + title->get_w() + xs10, y)); - - y += ys40; - add_subwindow(title = new BC_Title(x2, y + ys10, _("Rotation settling speed:"))); - add_subwindow(rotate_return_speed = - new MotionRReturnSpeed(plugin, x2 + title->get_w() + xs10, y)); - y += ys40; - add_subwindow(vectors = new MotionDrawVectors(plugin, this, x2, y)); - - y = y1 + ys60; add_subwindow(title = new BC_Title(x, y + ys10, _("Block Y:"))); add_subwindow(block_y = new MotionBlockY(plugin, this, x + title->get_w() + xs10, y)); @@ -153,52 +135,96 @@ void MotionWindow::create_objects() x + title->get_w() + xs10, y)); + add_subwindow(title = new BC_Title(x2, y + ys10, _("Maximum angle offset:"))); + add_subwindow(rotate_magnitude = + new MotionRMagnitude(plugin, x2 + title->get_w() + xs10, y)); + y += ys40; add_subwindow(title = new BC_Title(x, y + ys10, _("Motion settling speed:"))); add_subwindow(return_speed = new MotionReturnSpeed(plugin, x + title->get_w() + xs10, y)); + add_subwindow(title = new BC_Title(x2, y + ys10, _("Rotation settling speed:"))); + add_subwindow(rotate_return_speed = + new MotionRReturnSpeed(plugin, x2 + title->get_w() + xs10, y)); + + y += ys40; + add_subwindow(title = new BC_Title(x, y, _("Motion noise level:\n(% of max diff.)"))); + add_subwindow(noise_level = + new MotionNoiseLevel(plugin, this, x + title->get_w() + xs10, y)); + add_subwindow(noise_level_text = + new MotionNoiseLevelText(plugin, this, + x + title->get_w() + xs10 + noise_level->get_w() + xs10, y + ys10)); + + add_subwindow(title = new BC_Title(x2, y, _("Rotation noise level:\n(% of max diff.)"))); + add_subwindow(noise_rotation = + new MotionNoiseRotation(plugin, this, x2 + title->get_w() + xs10, y)); + add_subwindow(noise_rotation_text = + new MotionNoiseRotationText(plugin, this, + x2 + title->get_w() + xs10 + noise_rotation->get_w() + xs10, y + ys10)); + + y += ys50; + add_subwindow(vectors = new MotionDrawVectors(plugin, this, x, y)); + add_subwindow(twopass = new MotionTwopass(plugin, this, x2, y)); + y += ys40; add_subwindow(track_single = new TrackSingleFrame(plugin, this, x, y)); + + add_subwindow(title = new BC_Title(x2, y, _("Frame number:"))); + add_subwindow(track_frame_number = + new TrackFrameNumber(plugin, this, x2 + title->get_w() + xs10, y)); + add_subwindow(frame_current = + new MotionFrameCurrent(plugin, this, x2 + title->get_w() + track_frame_number->get_w() + xs20, y)); + if(plugin->config.tracking_object != MotionScan::TRACK_SINGLE) + { + track_frame_number->disable(); + frame_current->disable(); + } + y += ys20; add_subwindow(track_previous = new TrackPreviousFrame(plugin, this, x, y)); + y += ys20; add_subwindow(previous_same = new PreviousFrameSameBlock(plugin, this, x, y)); + add_subwindow(addtrackedframeoffset = + new AddTrackedFrameOffset(plugin, this, x2, y)); + y += ys40; - x1 = x; y1 = y; - add_subwindow(title = - new BC_Title(x1=x2, y1, _("Frame number:"))); - add_subwindow(track_frame_number = - new TrackFrameNumber(plugin, this, x1 += title->get_w(), y1)); - if(plugin->config.tracking_object != MotionScan::TRACK_SINGLE) - track_frame_number->disable(); + add_subwindow(title = new BC_Title(x, y, _("Tracking file:"))); + add_subwindow(tracking_file = new MotionTrackingFile(plugin, + plugin->config.tracking_file, this, x+title->get_w() + xs10, y)); - add_subwindow(addtrackedframeoffset = - new AddTrackedFrameOffset(plugin, this, x1=x2, y1+=track_frame_number->get_h())); - int pef = client->server->mwindow->edl->session->video_every_frame; - add_subwindow(pef_title = new BC_Title(x1=x2+xs50, y1+=addtrackedframeoffset->get_h() + xs5, - !pef ? _("For best results\n" - " Set: Play every frame\n" - " Preferences-> Playback-> Video Out") : - _("Currently using: Play every frame"), MEDIUMFONT, - !pef ? RED : GREEN)); + add_subwindow(reset_tracking = + new MotionResetTracking(plugin, this, x2, y)); + y += ys40; add_subwindow(title = new BC_Title(x, y, _("Master layer:"))); add_subwindow(master_layer = new MasterLayer(plugin, this, x + title->get_w() + xs10, y)); master_layer->create_objects(); - y += ys30; + add_subwindow(clear_tracking = + new MotionClearTracking(plugin, this, x2, y - ys10)); + + y += ys30; add_subwindow(title = new BC_Title(x, y, _("Action:"))); add_subwindow(action_type = new ActionType(plugin, this, x + title->get_w() + xs10, y)); action_type->create_objects(); - y += ys30; + int pef = client->server->mwindow->edl->session->video_every_frame; + add_subwindow(pef_title = new BC_Title(x2+xs50, y, + !pef ? _("For best results\n" + " Set: Play every frame\n" + " Preferences-> Playback-> Video Out") : + _("Currently using: Play every frame"), MEDIUMFONT, + !pef ? RED : GREEN)); + + y += ys30; add_subwindow(title = new BC_Title(x, y, _("Calculation:"))); add_subwindow(tracking_type = new TrackingType(plugin, this, x + title->get_w() + xs10, y)); @@ -216,6 +242,7 @@ void MotionWindow::update_mode() rotation_range->update(plugin->config.rotation_range, MIN_ROTATION, MAX_ROTATION); vectors->update(plugin->config.draw_vectors); + twopass->update(plugin->config.twopass); tracking_file->update(plugin->config.tracking_file); global->update(plugin->config.global); rotate->update(plugin->config.rotate); @@ -224,7 +251,7 @@ void MotionWindow::update_mode() MotionTrackingFile::MotionTrackingFile(MotionMain *plugin, const char *filename, MotionWindow *gui, int x, int y) - : BC_TextBox(x, y, xS(150), 1, filename) + : BC_TextBox(x, y, gui->get_w()/2-x-xS(10), 1, filename) { this->plugin = plugin; this->gui = gui; @@ -232,7 +259,92 @@ MotionTrackingFile::MotionTrackingFile(MotionMain *plugin, int MotionTrackingFile::handle_event() { - strcpy(plugin->config.tracking_file, get_text()); + strncpy(plugin->config.tracking_file, get_text(), sizeof(plugin->config.tracking_file)); + plugin->reset_cache_file(); + plugin->send_configure_change(); + return 1; +} + +MotionResetTracking::MotionResetTracking(MotionMain *plugin, MotionWindow *gui, int x, int y) + : BC_GenericButton(x, y, _("Generate tracking file name")) +{ + this->plugin = plugin; + this->gui = gui; +}; + +int MotionResetTracking::handle_event() +{ +// First of all, ensure closing current tracking file + plugin->reset_cache_file(); + +// Generate new tracking filename based on the asset filename + const char *sp = TRACKING_FILE; + char *cp = plugin->config.tracking_file, *ep = cp+sizeof(plugin->config.tracking_file)-1; + while( cp < ep && *sp != 0 ) *cp++ = *sp++; + if( cp < ep-1 && (sp=plugin->get_source_path()) ) { + *cp++ = '-'; + const char *bp = strrchr(sp,'/'); + if( bp ) sp = bp+1; + while( cp < ep && *sp != 0 ) { + *cp++ = (*sp>='a' && *sp<='z') || + (*sp>='A' && *sp<='Z') || + (*sp>='0' && *sp<='9') ? *sp : '_'; + ++sp; + } + } + *cp = 0; + + gui->tracking_file->update(plugin->config.tracking_file); + plugin->reset_cache_file(); + +// Revert tracking type to not using tracking file + if( plugin->config.tracking_type == MotionScan::LOAD || + plugin->config.tracking_type == MotionScan::SAVE ) + { + plugin->config.tracking_type = MotionScan::NO_CALCULATE; + gui->tracking_type->set_text(TrackingType::to_text(plugin->config.tracking_type)); + } + + plugin->send_configure_change(); + return 1; +} + +MotionClearTracking::MotionClearTracking(MotionMain *plugin, MotionWindow *gui, int x, int y) + : BC_GenericButton(x, y, _("Clear tracking file contents")) +{ + this->plugin = plugin; + this->gui = gui; +}; + +int MotionClearTracking::handle_event() +{ + char save_file[BCTEXTLEN]; + +// First of all, ensure closing current tracking file + plugin->reset_cache_file(); + +// Suffix .bak not allowed: reserved for intermediate tracking file copy + snprintf(save_file, sizeof(save_file), "%s.old", plugin->config.tracking_file); + ::rename(plugin->config.tracking_file, save_file); + +// Just for safety + ::remove(plugin->config.tracking_file); + plugin->reset_cache_file(); + + return 1; +} + +MotionFrameCurrent::MotionFrameCurrent(MotionMain *plugin, MotionWindow *gui, int x, int y) + : BC_GenericButton(x, y, _("Get current")) +{ + this->plugin = plugin; + this->gui = gui; +}; + +int MotionFrameCurrent::handle_event() +{ + plugin->config.track_frame = plugin->get_source_position(); + gui->track_frame_number->update(plugin->config.track_frame); plugin->send_configure_change(); return 1; } @@ -482,6 +594,21 @@ int MotionRotate::handle_event() return 1; } +MotionTwopass::MotionTwopass(MotionMain *plugin, + MotionWindow *gui, int x, int y) + : BC_CheckBox(x, y, plugin->config.twopass, _("Two pass tracking")) +{ + this->plugin = plugin; + this->gui = gui; +} + +int MotionTwopass::handle_event() +{ + plugin->config.twopass = get_value(); + plugin->send_configure_change(); + return 1; +} + MotionBlockX::MotionBlockX(MotionMain *plugin, MotionWindow *gui, int x, int y) @@ -560,6 +687,89 @@ int MotionBlockYText::handle_event() } +MotionNoiseLevel::MotionNoiseLevel(MotionMain *plugin, + MotionWindow *gui, int x, int y) + : BC_FPot(x, y, plugin->config.noise_level, (float)0, (float)100) +{ + this->plugin = plugin; + this->gui = gui; +} + +int MotionNoiseLevel::handle_event() +{ + float level = plugin->config.noise_level; + level = get_value(); + if (level < 0) level = 0; + if (level > 100) level = 100; + plugin->config.noise_level = level; + gui->noise_level_text->update((float)plugin->config.noise_level); + plugin->send_configure_change(); + return 1; +} + +MotionNoiseLevelText::MotionNoiseLevelText(MotionMain *plugin, + MotionWindow *gui, int x, int y) + : BC_TextBox(x, y, xS(75), 1, (float)plugin->config.noise_level) +{ + this->plugin = plugin; + this->gui = gui; + set_precision(4); +} + +int MotionNoiseLevelText::handle_event() +{ + float level = plugin->config.noise_level; + level = atof(get_text()); + if (level < 0) level = 0; + if (level > 100) level = 100; + plugin->config.noise_level = level; + gui->noise_level->update(plugin->config.noise_level); + plugin->send_configure_change(); + return 1; +} + +MotionNoiseRotation::MotionNoiseRotation(MotionMain *plugin, + MotionWindow *gui, int x, int y) + : BC_FPot(x, y, plugin->config.noise_rotation, (float)0, (float)100) +{ + this->plugin = plugin; + this->gui = gui; +} + +int MotionNoiseRotation::handle_event() +{ + float level = plugin->config.noise_rotation; + level = get_value(); + if (level < 0) level = 0; + if (level > 100) level = 100; + plugin->config.noise_rotation = level; + gui->noise_rotation_text->update((float)plugin->config.noise_rotation); + plugin->send_configure_change(); + return 1; +} + +MotionNoiseRotationText::MotionNoiseRotationText(MotionMain *plugin, + MotionWindow *gui, int x, int y) + : BC_TextBox(x, y, xS(75), 1, (float)plugin->config.noise_rotation) +{ + this->plugin = plugin; + this->gui = gui; + set_precision(4); +} + +int MotionNoiseRotationText::handle_event() +{ + float level = plugin->config.noise_rotation; + level = atof(get_text()); + if (level < 0) level = 0; + if (level > 100) level = 100; + plugin->config.noise_rotation = level; + gui->noise_rotation->update(plugin->config.noise_rotation); + plugin->send_configure_change(); + return 1; +} + + MotionDrawVectors::MotionDrawVectors(MotionMain *plugin, MotionWindow *gui, int x, int y) : BC_CheckBox(x, @@ -598,6 +808,7 @@ int TrackSingleFrame::handle_event() gui->track_previous->update(0); gui->previous_same->update(0); gui->track_frame_number->enable(); + gui->frame_current->enable(); plugin->send_configure_change(); return 1; } @@ -638,6 +849,7 @@ int TrackPreviousFrame::handle_event() gui->track_single->update(0); gui->previous_same->update(0); gui->track_frame_number->disable(); + gui->frame_current->disable(); plugin->send_configure_change(); return 1; } @@ -661,6 +873,7 @@ int PreviousFrameSameBlock::handle_event() gui->track_single->update(0); gui->track_previous->update(0); gui->track_frame_number->disable(); + gui->frame_current->disable(); plugin->send_configure_change(); return 1; } diff --git a/cinelerra-5.1/plugins/motion/motionwindow.h b/cinelerra-5.1/plugins/motion/motionwindow.h index c1f83bd0..b6c28144 100644 --- a/cinelerra-5.1/plugins/motion/motionwindow.h +++ b/cinelerra-5.1/plugins/motion/motionwindow.h @@ -233,6 +233,43 @@ public: }; +class MotionNoiseLevel : public BC_FPot +{ +public: + MotionNoiseLevel(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionWindow *gui; + MotionMain *plugin; +}; + +class MotionNoiseLevelText : public BC_TextBox +{ +public: + MotionNoiseLevelText(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionWindow *gui; + MotionMain *plugin; +}; + +class MotionNoiseRotation : public BC_FPot +{ +public: + MotionNoiseRotation(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionWindow *gui; + MotionMain *plugin; +}; + +class MotionNoiseRotationText : public BC_TextBox +{ +public: + MotionNoiseRotationText(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionWindow *gui; + MotionMain *plugin; +}; + + class MotionDrawVectors : public BC_CheckBox { public: @@ -261,6 +298,33 @@ public: MotionWindow *gui; }; +class MotionResetTracking : public BC_GenericButton +{ +public: + MotionResetTracking(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionMain *plugin; + MotionWindow *gui; +}; + +class MotionClearTracking : public BC_GenericButton +{ +public: + MotionClearTracking(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionMain *plugin; + MotionWindow *gui; +}; + +class MotionFrameCurrent : public BC_GenericButton +{ +public: + MotionFrameCurrent(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionMain *plugin; + MotionWindow *gui; +}; + class MotionGlobal : public BC_CheckBox { public: @@ -279,6 +343,15 @@ public: MotionMain *plugin; }; +class MotionTwopass : public BC_CheckBox +{ +public: + MotionTwopass(MotionMain *plugin, MotionWindow *gui, int x, int y); + int handle_event(); + MotionWindow *gui; + MotionMain *plugin; +}; + class MotionWindow : public PluginClientWindow @@ -309,11 +382,19 @@ public: MotionRMagnitude *rotate_magnitude; MotionReturnSpeed *return_speed; MotionRReturnSpeed *rotate_return_speed; + MotionNoiseLevel *noise_level; + MotionNoiseLevelText *noise_level_text; + MotionNoiseRotation *noise_rotation; + MotionNoiseRotationText *noise_rotation_text; ActionType *action_type; MotionDrawVectors *vectors; MotionTrackingFile *tracking_file; + MotionResetTracking *reset_tracking; + MotionClearTracking *clear_tracking; + MotionFrameCurrent *frame_current; MotionGlobal *global; MotionRotate *rotate; + MotionTwopass *twopass; AddTrackedFrameOffset *addtrackedframeoffset; TrackSingleFrame *track_single; TrackFrameNumber *track_frame_number; diff --git a/cinelerra-5.1/plugins/motion/opencvwrapper.C b/cinelerra-5.1/plugins/motion/opencvwrapper.C deleted file mode 100644 index 7c19be68..00000000 --- a/cinelerra-5.1/plugins/motion/opencvwrapper.C +++ /dev/null @@ -1,551 +0,0 @@ -/* - * CINELERRA - * Copyright (C) 1997-2012 Adam Williams - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - - - -#include "opencvwrapper.h" -#include "vframe.h" - -#include "opencv2/calib3d/calib3d.hpp" -#include "opencv2/objdetect/objdetect.hpp" -#include "opencv2/features2d/features2d.hpp" - - -#include -#include -#include -#include - - - - -using namespace std; - - -// define whether to use approximate nearest-neighbor search -#define USE_FLANN - -// Sizes must be quantized a certain amount for OpenCV -#define QUANTIZE 8 - - - -double -compareSURFDescriptors( const float* d1, const float* d2, double best, int length ) -{ - double total_cost = 0; - assert( length % 4 == 0 ); - for( int i = 0; i < length; i += 4 ) - { - double t0 = d1[i ] - d2[i ]; - double t1 = d1[i+1] - d2[i+1]; - double t2 = d1[i+2] - d2[i+2]; - double t3 = d1[i+3] - d2[i+3]; - total_cost += t0*t0 + t1*t1 + t2*t2 + t3*t3; - if( total_cost > best ) - break; - } - return total_cost; -} - - -int -naiveNearestNeighbor( const float* vec, int laplacian, - const CvSeq* model_keypoints, - const CvSeq* model_descriptors ) -{ - int length = (int)(model_descriptors->elem_size/sizeof(float)); - int i, neighbor = -1; - double d, dist1 = 1e6, dist2 = 1e6; - CvSeqReader reader, kreader; - cvStartReadSeq( model_keypoints, &kreader, 0 ); - cvStartReadSeq( model_descriptors, &reader, 0 ); - - for( i = 0; i < model_descriptors->total; i++ ) - { - const CvSURFPoint* kp = (const CvSURFPoint*)kreader.ptr; - const float* mvec = (const float*)reader.ptr; - CV_NEXT_SEQ_ELEM( kreader.seq->elem_size, kreader ); - CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); - if( laplacian != kp->laplacian ) - continue; - d = compareSURFDescriptors( vec, mvec, dist2, length ); - if( d < dist1 ) - { - dist2 = dist1; - dist1 = d; - neighbor = i; - } - else if ( d < dist2 ) - dist2 = d; - } - if ( dist1 < 0.6*dist2 ) - return neighbor; - return -1; -} - -void -findPairs( const CvSeq* objectKeypoints, const CvSeq* objectDescriptors, - const CvSeq* imageKeypoints, const CvSeq* imageDescriptors, vector& ptpairs ) -{ - int i; - CvSeqReader reader, kreader; - cvStartReadSeq( objectKeypoints, &kreader ); - cvStartReadSeq( objectDescriptors, &reader ); - ptpairs.clear(); - - for( i = 0; i < objectDescriptors->total; i++ ) - { - const CvSURFPoint* kp = (const CvSURFPoint*)kreader.ptr; - const float* descriptor = (const float*)reader.ptr; - CV_NEXT_SEQ_ELEM( kreader.seq->elem_size, kreader ); - CV_NEXT_SEQ_ELEM( reader.seq->elem_size, reader ); - int nearest_neighbor = naiveNearestNeighbor( descriptor, kp->laplacian, imageKeypoints, imageDescriptors ); - if( nearest_neighbor >= 0 ) - { - ptpairs.push_back(i); - ptpairs.push_back(nearest_neighbor); - } - } -} - - -void -flannFindPairs( const CvSeq*, - const CvSeq* objectDescriptors, - const CvSeq*, - const CvSeq* imageDescriptors, - vector& ptpairs ) -{ - int length = (int)(objectDescriptors->elem_size/sizeof(float)); - - cv::Mat m_object(objectDescriptors->total, length, CV_32F); - cv::Mat m_image(imageDescriptors->total, length, CV_32F); - - - // copy descriptors - CvSeqReader obj_reader; - float* obj_ptr = m_object.ptr(0); - cvStartReadSeq( objectDescriptors, &obj_reader ); - for(int i = 0; i < objectDescriptors->total; i++ ) - { - const float* descriptor = (const float*)obj_reader.ptr; - CV_NEXT_SEQ_ELEM( obj_reader.seq->elem_size, obj_reader ); - memcpy(obj_ptr, descriptor, length*sizeof(float)); - obj_ptr += length; - } - CvSeqReader img_reader; - float* img_ptr = m_image.ptr(0); - cvStartReadSeq( imageDescriptors, &img_reader ); - for(int i = 0; i < imageDescriptors->total; i++ ) - { - const float* descriptor = (const float*)img_reader.ptr; - CV_NEXT_SEQ_ELEM( img_reader.seq->elem_size, img_reader ); - memcpy(img_ptr, descriptor, length*sizeof(float)); - img_ptr += length; - } - - // find nearest neighbors using FLANN - cv::Mat m_indices(objectDescriptors->total, 2, CV_32S); - cv::Mat m_dists(objectDescriptors->total, 2, CV_32F); - cv::flann::Index flann_index(m_image, cv::flann::KDTreeIndexParams(4)); // using 4 randomized kdtrees - flann_index.knnSearch(m_object, m_indices, m_dists, 2, cv::flann::SearchParams(64) ); // maximum number of leafs checked - - int* indices_ptr = m_indices.ptr(0); - float* dists_ptr = m_dists.ptr(0); -//printf("flannFindPairs %d m_indices.rows=%d\n", __LINE__, m_indices.rows); - for (int i = 0; i < m_indices.rows; ++i) - { -//printf("flannFindPairs %d dists=%f %f\n", __LINE__, dists_ptr[2 * i], 0.6 * dists_ptr[2 * i + 1]); - if (dists_ptr[2 * i] < 0.6 * dists_ptr[2 * i + 1]) - { -//printf("flannFindPairs %d pairs=%d\n", __LINE__, ptpairs.size()); - ptpairs.push_back(i); - ptpairs.push_back(indices_ptr[2*i]); - } - } -} - - -/* a rough implementation for object location */ -int OpenCVWrapper::locatePlanarObject(const CvSeq* objectKeypoints, - const CvSeq* objectDescriptors, - const CvSeq* imageKeypoints, - const CvSeq* imageDescriptors, - const CvPoint src_corners[4], - int *(*point_pairs), - int (*total_pairs)) -{ - double h[9]; - CvMat _h = cvMat(3, 3, CV_64F, h); - vector ptpairs; - vector pt1, pt2; - CvMat _pt1, _pt2; - int i, n; - - (*point_pairs) = 0; - (*total_pairs) = 0; - -#ifdef USE_FLANN - flannFindPairs( objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, ptpairs ); -#else - findPairs( objectKeypoints, objectDescriptors, imageKeypoints, imageDescriptors, ptpairs ); -#endif - - -// Store keypoints - (*point_pairs) = (int*)calloc(ptpairs.size(), sizeof(int)); - (*total_pairs) = ptpairs.size() / 2; - - - for(int i = 0; i < (int)ptpairs.size(); i++) - { - (*point_pairs)[i] = ptpairs[i]; - } - - - - n = (int)(ptpairs.size()/2); - if( n < 4 ) - return 0; - - pt1.resize(n); - pt2.resize(n); - for( i = 0; i < n; i++ ) - { - pt1[i] = ((CvSURFPoint*)cvGetSeqElem(objectKeypoints,ptpairs[i*2]))->pt; - pt2[i] = ((CvSURFPoint*)cvGetSeqElem(imageKeypoints,ptpairs[i*2+1]))->pt; - } - - _pt1 = cvMat(1, n, CV_32FC2, &pt1[0] ); - _pt2 = cvMat(1, n, CV_32FC2, &pt2[0] ); - if( !cvFindHomography( &_pt1, &_pt2, &_h, CV_RANSAC, 5 )) - return 0; - - for( i = 0; i < 4; i++ ) - { - double x = src_corners[i].x, y = src_corners[i].y; - double Z = 1./(h[6]*x + h[7]*y + h[8]); - double X = (h[0]*x + h[1]*y + h[2])*Z; - double Y = (h[3]*x + h[4]*y + h[5])*Z; - dst_corners[i * 2] = X; - dst_corners[i * 2 + 1] = Y; - } - - return 1; -} - - - - -OpenCVWrapper::OpenCVWrapper() -{ - object_image = 0; - scene_image = 0; - object_image_w = 0; - object_image_h = 0; - scene_image_w = 0; - scene_image_h = 0; - storage = 0; - object_keypoints = 0; - object_descriptors = 0; - scene_keypoints = 0; - scene_descriptors = 0; - point_pairs = 0; - total_pairs = 0; -} - - - - - - -OpenCVWrapper::~OpenCVWrapper() -{ -// This releases all the arrays - if(storage) cvReleaseMemStorage(&storage); - if(object_image) cvReleaseImage(&object_image); - if(scene_image) cvReleaseImage(&scene_image); - if(point_pairs) free(point_pairs); -} - - - - - -int OpenCVWrapper::scan(VFrame *object_frame, - VFrame *scene_frame, - int object_x1, - int object_y1, - int object_x2, - int object_y2, - int scene_x1, - int scene_y1, - int scene_x2, - int scene_y2) -{ - int result = 0; - int object_w = object_x2 - object_x1; - int object_h = object_y2 - object_y1; - int scene_w = scene_x2 - scene_x1; - int scene_h = scene_y2 - scene_y1; - - -//object_frame->write_png("/tmp/object.png"); -//scene_frame->write_png("/tmp/scene.png"); - -// Get quantized sizes - int object_image_w = object_w; - int object_image_h = object_h; - int scene_image_w = scene_w; - int scene_image_h = scene_h; - if(object_w % QUANTIZE) object_image_w += QUANTIZE - (object_w % QUANTIZE); - if(object_h % QUANTIZE) object_image_h += QUANTIZE - (object_h % QUANTIZE); - if(scene_w % QUANTIZE) scene_image_w += QUANTIZE - (scene_w % QUANTIZE); - if(scene_h % QUANTIZE) scene_image_h += QUANTIZE - (scene_h % QUANTIZE); - - if(object_image && - (object_image_w != this->object_image_w || - object_image_h != this->object_image_h)) - { - cvReleaseImage(&object_image); - object_image = 0; - } - this->object_image_w = object_image_w; - this->object_image_h = object_image_h; - - if(scene_image && - (scene_image_w != this->scene_image_w || - scene_image_h != this->scene_image_h)) - { - cvReleaseImage(&scene_image); - scene_image = 0; - } - this->scene_image_w = scene_image_w; - this->scene_image_h = scene_image_h; - - - if(!object_image) - { -// Only does greyscale - object_image = cvCreateImage( - cvSize(object_image_w, object_image_h), - 8, - 1); - } - - if(!scene_image) - { -// Only does greyscale - scene_image = cvCreateImage( - cvSize(scene_image_w, scene_image_h), - 8, - 1); - } - -// Select only region with image size -// Does this do anything? - cvSetImageROI( object_image, cvRect( 0, 0, object_w, object_h ) ); - cvSetImageROI( scene_image, cvRect( 0, 0, scene_w, scene_h ) ); - - grey_crop((unsigned char*)scene_image->imageData, - scene_frame, - scene_x1, - scene_y1, - scene_x2, - scene_y2, - scene_image_w, - scene_image_h); - grey_crop((unsigned char*)object_image->imageData, - object_frame, - object_x1, - object_y1, - object_x2, - object_y2, - object_image_w, - object_image_h); - - - if(!storage) storage = cvCreateMemStorage(0); - CvSURFParams params = cvSURFParams(500, 1); - - -//printf("OpenCVWrapper::process_buffer %d\n", __LINE__); - -// TODO: make the surf data persistent & check for image changes instead - if(object_keypoints) cvClearSeq(object_keypoints); - if(object_descriptors) cvClearSeq(object_descriptors); - if(scene_keypoints) cvClearSeq(scene_keypoints); - if(scene_descriptors) cvClearSeq(scene_descriptors); - object_keypoints = 0; - object_descriptors = 0; - scene_keypoints = 0; - scene_descriptors = 0; - -// Free the image structures - if(point_pairs) free(point_pairs); - point_pairs = 0; - - - cvExtractSURF(object_image, - 0, - &object_keypoints, - &object_descriptors, - storage, - params, - 0); - -//printf("OpenCVWrapper::scan %d object keypoints=%d\n", __LINE__, object_keypoints->total); -// Draw the keypoints -// for(int i = 0; i < object_keypoints->total; i++) -// { -// CvSURFPoint* r1 = (CvSURFPoint*)cvGetSeqElem( object_keypoints, i ); -// int size = r1->size / 4; -// draw_rect(frame[object_layer], -// r1->pt.x + object_x1 - size, -// r1->pt.y + object_y1 - size, -// r1->pt.x + object_x1 + size, -// r1->pt.y + object_y1 + size); -// } - - -//printf("OpenCVWrapper::process_buffer %d\n", __LINE__); - - cvExtractSURF(scene_image, - 0, - &scene_keypoints, - &scene_descriptors, - storage, - params, - 0); - -// Draw the keypoints -// for(int i = 0; i < scene_keypoints->total; i++) -// { -// CvSURFPoint* r1 = (CvSURFPoint*)cvGetSeqElem( scene_keypoints, i ); -// int size = r1->size / 4; -// draw_rect(frame[scene_layer], -// r1->pt.x + scene_x1 - size, -// r1->pt.y + scene_y1 - size, -// r1->pt.x + scene_x1 + size, -// r1->pt.y + scene_y1 + size); -// } - -// printf("OpenCVWrapper::scan %d %d %d scene keypoints=%d\n", -// __LINE__, -// scene_w, -// scene_h, -// scene_keypoints->total); - - CvPoint src_corners[4] = - { - { 0, 0 }, - { object_w, 0 }, - { object_w, object_h }, - { 0, object_h } - }; - - for(int i = 0; i < 8; i++) - { - dst_corners[i] = 0; - } - - - -//printf("OpenCVWrapper::process_buffer %d\n", __LINE__); - if(scene_keypoints->total && - object_keypoints->total && - locatePlanarObject(object_keypoints, - object_descriptors, - scene_keypoints, - scene_descriptors, - src_corners, - &point_pairs, - &total_pairs)) - { - result = 1; - } - - - return result; -} - - - - - - -// Convert to greyscale & crop -void OpenCVWrapper::grey_crop(unsigned char *dst, - VFrame *src, - int x1, - int y1, - int x2, - int y2, - int dst_w, - int dst_h) -{ -// Dimensions of dst frame - int w = x2 - x1; - int h = y2 - y1; - - bzero(dst, dst_w * dst_h); - -//printf("OpenCVWrapper::grey_crop %d %d %d\n", __LINE__, w, h); - for(int i = 0; i < h; i++) - { - switch(src->get_color_model()) - { - case BC_RGB888: - break; - - case BC_YUV888: - { - unsigned char *input = src->get_rows()[i + y1] + x1 * 3; - unsigned char *output = dst + i * dst_w; - - for(int j = 0; j < w; j++) - { -// Y channel only - *output = *input; - input += 3; - output++; - } - break; - } - } - } -} - - -float OpenCVWrapper::get_dst_x(int number) -{ - return dst_corners[number * 2]; -} - -float OpenCVWrapper::get_dst_y(int number) -{ - return dst_corners[number * 2 + 1]; -} - - - - - diff --git a/cinelerra-5.1/plugins/motion/opencvwrapper.h b/cinelerra-5.1/plugins/motion/opencvwrapper.h deleted file mode 100644 index a33bec7f..00000000 --- a/cinelerra-5.1/plugins/motion/opencvwrapper.h +++ /dev/null @@ -1,95 +0,0 @@ -/* - * CINELERRA - * Copyright (C) 1997-2012 Adam Williams - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - - - -#ifndef OPENCVWRAPPER_H -#define OPENCVWRAPPER_H - -#include "opencv2/core/core_c.h" - -#include "vframe.inc" - -class OpenCVWrapper -{ -public: - OpenCVWrapper(); - ~OpenCVWrapper(); - - - float get_dst_x(int number); - float get_dst_y(int number); - - void grey_crop(unsigned char *dst, - VFrame *src, - int x1, - int y1, - int x2, - int y2, - int dst_w, - int dst_h); - -// Returns 1 when it got something - int scan(VFrame *object_frame, - VFrame *scene_frame, - int object_x1, - int object_y1, - int object_x2, - int object_y2, - int scene_x1, - int scene_y1, - int scene_x2, - int scene_y2); - -private: - int locatePlanarObject(const CvSeq* objectKeypoints, - const CvSeq* objectDescriptors, - const CvSeq* imageKeypoints, - const CvSeq* imageDescriptors, - const CvPoint src_corners[4], - int *(*point_pairs), - int (*total_pairs)); - -// Images in the format OpenCV requires - IplImage *object_image; - IplImage *scene_image; -// Quantized sizes - int object_image_w; - int object_image_h; - int scene_image_w; - int scene_image_h; - CvSeq *object_keypoints; - CvSeq *object_descriptors; - CvSeq *scene_keypoints; - CvSeq *scene_descriptors; - CvMemStorage *storage; - int *point_pairs; - int total_pairs; - -// x, y pairs - float dst_corners[8]; -}; - - - - -#endif - - diff --git a/cinelerra-5.1/plugins/motion/opencvwrapper.inc b/cinelerra-5.1/plugins/motion/opencvwrapper.inc deleted file mode 100644 index aaa0d184..00000000 --- a/cinelerra-5.1/plugins/motion/opencvwrapper.inc +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef OPENCVWRAPPER_INC -#define OPENCVWRAPPER_INC - - -class OpenCVWrapper; - - -#endif - - - - -