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;
action_type = MotionScan::STABILIZE;
global = 1;
rotate = 1;
+ twopass = 0;
addtrackedframeoffset = 0;
strcpy(tracking_file, TRACKING_FILE);
tracking_type = MotionScan::SAVE; //MotionScan::NO_CALCULATE;
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 &&
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 &&
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;
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;
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);
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));
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);
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);
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,
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.
// 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;
(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.
// 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;
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();
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 )
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;
}
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:
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,
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();
//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
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 ) {
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);
}
}
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 ) {
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 ) {
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;
}
}
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;
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);
#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);
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);
else
close_cache_file();
strcpy(cache_file, config.tracking_file);
+ if (!cache_file[0]) strcpy(cache_file, TRACKING_FILE);
}
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);
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);
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);
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;
}
}
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;
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;
// 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();
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;
}
}
// 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;
}
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;
}
#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"
// 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;
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;
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();
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;
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);
#include "clip.h"
//#include "../downsample/downsampleengine.h"
-//#include "motion.h"
+#include "motion.h"
#include "motionscan.h"
#include "mutex.h"
#include "vframe.h"
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);
}
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;
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;
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;
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:
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;
}
// 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.
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 ) {
// 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 ||
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;
// 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;
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;
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 ||
// 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
}
// 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;
}
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)
class MotionScan : public LoadServer
{
public:
- MotionScan(int total_clients,
+ MotionScan(MotionMain *plugin,
+ int total_clients,
int total_packages);
~MotionScan();
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);
// OVERSAMPLE
int dx_result, dy_result;
+// Currently best expected location of scan block
+ int x_result, y_result;
+
enum { // action_type
TRACK,
STABILIZE,
// 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;
#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;
}
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,
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));
// &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:")));
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));
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));
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));
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);
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;
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;
}
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)
}
+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,
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;
}
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;
}
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;
}
};
+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:
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:
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
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;
+++ /dev/null
-/*
- * CINELERRA
- * Copyright (C) 1997-2012 Adam Williams <broadcast at earthling dot net>
- *
- * 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 <iostream>
-#include <vector>
-#include <stdio.h>
-#include <stdlib.h>
-
-
-
-
-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<int>& 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<int>& 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<float>(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<float>(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<int>(0);
- float* dists_ptr = m_dists.ptr<float>(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<int> ptpairs;
- vector<CvPoint2D32f> 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];
-}
-
-
-
-
-
+++ /dev/null
-/*
- * CINELERRA
- * Copyright (C) 1997-2012 Adam Williams <broadcast at earthling dot net>
- *
- * 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
-
-
+++ /dev/null
-#ifndef OPENCVWRAPPER_INC
-#define OPENCVWRAPPER_INC
-
-
-class OpenCVWrapper;
-
-
-#endif
-
-
-
-
-