summaryrefslogtreecommitdiff
path: root/media/libaom/src/av1/encoder/global_motion.c
diff options
context:
space:
mode:
Diffstat (limited to 'media/libaom/src/av1/encoder/global_motion.c')
-rw-r--r--media/libaom/src/av1/encoder/global_motion.c832
1 files changed, 774 insertions, 58 deletions
diff --git a/media/libaom/src/av1/encoder/global_motion.c b/media/libaom/src/av1/encoder/global_motion.c
index e9f8b0bb4..9623ec301 100644
--- a/media/libaom/src/av1/encoder/global_motion.c
+++ b/media/libaom/src/av1/encoder/global_motion.c
@@ -15,8 +15,12 @@
#include <math.h>
#include <assert.h>
+#include "config/aom_dsp_rtcd.h"
+
#include "av1/encoder/global_motion.h"
+#include "av1/common/convolve.h"
+#include "av1/common/resize.h"
#include "av1/common/warped_motion.h"
#include "av1/encoder/segmentation.h"
@@ -24,7 +28,6 @@
#include "av1/encoder/corner_match.h"
#include "av1/encoder/ransac.h"
-#define MAX_CORNERS 4096
#define MIN_INLIER_PROB 0.1
#define MIN_TRANS_THRESH (1 * GM_TRANS_DECODE_FACTOR)
@@ -32,11 +35,37 @@
// Border over which to compute the global motion
#define ERRORADV_BORDER 0
-static const double erroradv_tr[] = { 0.65, 0.60, 0.55 };
-static const double erroradv_prod_tr[] = { 20000, 18000, 16000 };
+// Number of pyramid levels in disflow computation
+#define N_LEVELS 2
+// Size of square patches in the disflow dense grid
+#define PATCH_SIZE 8
+// Center point of square patch
+#define PATCH_CENTER ((PATCH_SIZE + 1) >> 1)
+// Step size between patches, lower value means greater patch overlap
+#define PATCH_STEP 1
+// Minimum size of border padding for disflow
+#define MIN_PAD 7
+// Warp error convergence threshold for disflow
+#define DISFLOW_ERROR_TR 0.01
+// Max number of iterations if warp convergence is not found
+#define DISFLOW_MAX_ITR 10
+
+// Struct for an image pyramid
+typedef struct {
+ int n_levels;
+ int pad_size;
+ int has_gradient;
+ int widths[N_LEVELS];
+ int heights[N_LEVELS];
+ int strides[N_LEVELS];
+ int level_loc[N_LEVELS];
+ unsigned char *level_buffer;
+ double *level_dx_buffer;
+ double *level_dy_buffer;
+} ImagePyramid;
-int is_enough_erroradvantage(double best_erroradvantage, int params_cost,
- int erroradv_type) {
+int av1_is_enough_erroradvantage(double best_erroradvantage, int params_cost,
+ int erroradv_type) {
assert(erroradv_type < GM_ERRORADV_TR_TYPES);
return best_erroradvantage < erroradv_tr[erroradv_type] &&
best_erroradvantage * params_cost < erroradv_prod_tr[erroradv_type];
@@ -75,9 +104,10 @@ static void convert_to_params(const double *params, int32_t *model) {
}
}
-void convert_model_to_params(const double *params, WarpedMotionParams *model) {
+void av1_convert_model_to_params(const double *params,
+ WarpedMotionParams *model) {
convert_to_params(params, model->wmmat);
- model->wmtype = get_gmtype(model);
+ model->wmtype = get_wmtype(model);
model->invalid = 0;
}
@@ -131,12 +161,122 @@ static void force_wmtype(WarpedMotionParams *wm, TransformationType wmtype) {
wm->wmtype = wmtype;
}
-int64_t refine_integerized_param(WarpedMotionParams *wm,
- TransformationType wmtype, int use_hbd, int bd,
- uint8_t *ref, int r_width, int r_height,
- int r_stride, uint8_t *dst, int d_width,
- int d_height, int d_stride, int n_refinements,
- int64_t best_frame_error) {
+#if CONFIG_AV1_HIGHBITDEPTH
+static int64_t highbd_warp_error(
+ WarpedMotionParams *wm, const uint16_t *const ref, int width, int height,
+ int stride, const uint16_t *const dst, int p_col, int p_row, int p_width,
+ int p_height, int p_stride, int subsampling_x, int subsampling_y, int bd,
+ int64_t best_error, uint8_t *segment_map, int segment_map_stride) {
+ int64_t gm_sumerr = 0;
+ const int error_bsize_w = AOMMIN(p_width, WARP_ERROR_BLOCK);
+ const int error_bsize_h = AOMMIN(p_height, WARP_ERROR_BLOCK);
+ uint16_t tmp[WARP_ERROR_BLOCK * WARP_ERROR_BLOCK];
+
+ ConvolveParams conv_params = get_conv_params(0, 0, bd);
+ conv_params.use_dist_wtd_comp_avg = 0;
+ for (int i = p_row; i < p_row + p_height; i += WARP_ERROR_BLOCK) {
+ for (int j = p_col; j < p_col + p_width; j += WARP_ERROR_BLOCK) {
+ int seg_x = j >> WARP_ERROR_BLOCK_LOG;
+ int seg_y = i >> WARP_ERROR_BLOCK_LOG;
+ // Only compute the error if this block contains inliers from the motion
+ // model
+ if (!segment_map[seg_y * segment_map_stride + seg_x]) continue;
+ // avoid warping extra 8x8 blocks in the padded region of the frame
+ // when p_width and p_height are not multiples of WARP_ERROR_BLOCK
+ const int warp_w = AOMMIN(error_bsize_w, p_col + p_width - j);
+ const int warp_h = AOMMIN(error_bsize_h, p_row + p_height - i);
+ highbd_warp_plane(wm, ref, width, height, stride, tmp, j, i, warp_w,
+ warp_h, WARP_ERROR_BLOCK, subsampling_x, subsampling_y,
+ bd, &conv_params);
+ gm_sumerr += av1_calc_highbd_frame_error(tmp, WARP_ERROR_BLOCK,
+ dst + j + i * p_stride, warp_w,
+ warp_h, p_stride, bd);
+ if (gm_sumerr > best_error) return INT64_MAX;
+ }
+ }
+ return gm_sumerr;
+}
+#endif
+
+static int64_t warp_error(WarpedMotionParams *wm, const uint8_t *const ref,
+ int width, int height, int stride,
+ const uint8_t *const dst, int p_col, int p_row,
+ int p_width, int p_height, int p_stride,
+ int subsampling_x, int subsampling_y,
+ int64_t best_error, uint8_t *segment_map,
+ int segment_map_stride) {
+ int64_t gm_sumerr = 0;
+ int warp_w, warp_h;
+ const int error_bsize_w = AOMMIN(p_width, WARP_ERROR_BLOCK);
+ const int error_bsize_h = AOMMIN(p_height, WARP_ERROR_BLOCK);
+ DECLARE_ALIGNED(16, uint8_t, tmp[WARP_ERROR_BLOCK * WARP_ERROR_BLOCK]);
+ ConvolveParams conv_params = get_conv_params(0, 0, 8);
+ conv_params.use_dist_wtd_comp_avg = 0;
+
+ for (int i = p_row; i < p_row + p_height; i += WARP_ERROR_BLOCK) {
+ for (int j = p_col; j < p_col + p_width; j += WARP_ERROR_BLOCK) {
+ int seg_x = j >> WARP_ERROR_BLOCK_LOG;
+ int seg_y = i >> WARP_ERROR_BLOCK_LOG;
+ // Only compute the error if this block contains inliers from the motion
+ // model
+ if (!segment_map[seg_y * segment_map_stride + seg_x]) continue;
+ // avoid warping extra 8x8 blocks in the padded region of the frame
+ // when p_width and p_height are not multiples of WARP_ERROR_BLOCK
+ warp_w = AOMMIN(error_bsize_w, p_col + p_width - j);
+ warp_h = AOMMIN(error_bsize_h, p_row + p_height - i);
+ warp_plane(wm, ref, width, height, stride, tmp, j, i, warp_w, warp_h,
+ WARP_ERROR_BLOCK, subsampling_x, subsampling_y, &conv_params);
+
+ gm_sumerr +=
+ av1_calc_frame_error(tmp, WARP_ERROR_BLOCK, dst + j + i * p_stride,
+ warp_w, warp_h, p_stride);
+ if (gm_sumerr > best_error) return INT64_MAX;
+ }
+ }
+ return gm_sumerr;
+}
+
+int64_t av1_warp_error(WarpedMotionParams *wm, int use_hbd, int bd,
+ const uint8_t *ref, int width, int height, int stride,
+ uint8_t *dst, int p_col, int p_row, int p_width,
+ int p_height, int p_stride, int subsampling_x,
+ int subsampling_y, int64_t best_error,
+ uint8_t *segment_map, int segment_map_stride) {
+ if (wm->wmtype <= AFFINE)
+ if (!av1_get_shear_params(wm)) return INT64_MAX;
+#if CONFIG_AV1_HIGHBITDEPTH
+ if (use_hbd)
+ return highbd_warp_error(wm, CONVERT_TO_SHORTPTR(ref), width, height,
+ stride, CONVERT_TO_SHORTPTR(dst), p_col, p_row,
+ p_width, p_height, p_stride, subsampling_x,
+ subsampling_y, bd, best_error, segment_map,
+ segment_map_stride);
+#endif
+ (void)use_hbd;
+ (void)bd;
+ return warp_error(wm, ref, width, height, stride, dst, p_col, p_row, p_width,
+ p_height, p_stride, subsampling_x, subsampling_y,
+ best_error, segment_map, segment_map_stride);
+}
+
+// Factors used to calculate the thresholds for av1_warp_error
+static double thresh_factors[GM_REFINEMENT_COUNT] = { 1.25, 1.20, 1.15, 1.10,
+ 1.05 };
+
+static INLINE int64_t calc_approx_erroradv_threshold(
+ double scaling_factor, int64_t erroradv_threshold) {
+ return erroradv_threshold <
+ (int64_t)(((double)INT64_MAX / scaling_factor) + 0.5)
+ ? (int64_t)(scaling_factor * erroradv_threshold + 0.5)
+ : INT64_MAX;
+}
+
+int64_t av1_refine_integerized_param(
+ WarpedMotionParams *wm, TransformationType wmtype, int use_hbd, int bd,
+ uint8_t *ref, int r_width, int r_height, int r_stride, uint8_t *dst,
+ int d_width, int d_height, int d_stride, int n_refinements,
+ int64_t best_frame_error, uint8_t *segment_map, int segment_map_stride,
+ int64_t erroradv_threshold) {
static const int max_trans_model_params[TRANS_TYPES] = { 0, 2, 4, 6 };
const int border = ERRORADV_BORDER;
int i = 0, p;
@@ -149,13 +289,16 @@ int64_t refine_integerized_param(WarpedMotionParams *wm,
int32_t best_param;
force_wmtype(wm, wmtype);
- best_error = av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
- dst + border * d_stride + border, border, border,
- d_width - 2 * border, d_height - 2 * border,
- d_stride, 0, 0, best_frame_error);
+ best_error =
+ av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
+ dst + border * d_stride + border, border, border,
+ d_width - 2 * border, d_height - 2 * border, d_stride, 0,
+ 0, best_frame_error, segment_map, segment_map_stride);
best_error = AOMMIN(best_error, best_frame_error);
step = 1 << (n_refinements - 1);
for (i = 0; i < n_refinements; i++, step >>= 1) {
+ int64_t error_adv_thresh =
+ calc_approx_erroradv_threshold(thresh_factors[i], erroradv_threshold);
for (p = 0; p < n_params; ++p) {
int step_dir = 0;
// Skip searches for parameters that are forced to be 0
@@ -168,7 +311,8 @@ int64_t refine_integerized_param(WarpedMotionParams *wm,
av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
dst + border * d_stride + border, border, border,
d_width - 2 * border, d_height - 2 * border, d_stride,
- 0, 0, best_error);
+ 0, 0, AOMMIN(best_error, error_adv_thresh),
+ segment_map, segment_map_stride);
if (step_error < best_error) {
best_error = step_error;
best_param = *param;
@@ -181,7 +325,8 @@ int64_t refine_integerized_param(WarpedMotionParams *wm,
av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
dst + border * d_stride + border, border, border,
d_width - 2 * border, d_height - 2 * border, d_stride,
- 0, 0, best_error);
+ 0, 0, AOMMIN(best_error, error_adv_thresh),
+ segment_map, segment_map_stride);
if (step_error < best_error) {
best_error = step_error;
best_param = *param;
@@ -197,7 +342,8 @@ int64_t refine_integerized_param(WarpedMotionParams *wm,
av1_warp_error(wm, use_hbd, bd, ref, r_width, r_height, r_stride,
dst + border * d_stride + border, border, border,
d_width - 2 * border, d_height - 2 * border,
- d_stride, 0, 0, best_error);
+ d_stride, 0, 0, AOMMIN(best_error, error_adv_thresh),
+ segment_map, segment_map_stride);
if (step_error < best_error) {
best_error = step_error;
best_param = *param;
@@ -209,21 +355,11 @@ int64_t refine_integerized_param(WarpedMotionParams *wm,
}
}
force_wmtype(wm, wmtype);
- wm->wmtype = get_gmtype(wm);
+ wm->wmtype = get_wmtype(wm);
return best_error;
}
-static INLINE RansacFunc get_ransac_type(TransformationType type) {
- switch (type) {
- case AFFINE: return ransac_affine;
- case ROTZOOM: return ransac_rotzoom;
- case TRANSLATION: return ransac_translation;
- default: assert(0); return NULL;
- }
-}
-
-static unsigned char *downconvert_frame(YV12_BUFFER_CONFIG *frm,
- int bit_depth) {
+unsigned char *av1_downconvert_frame(YV12_BUFFER_CONFIG *frm, int bit_depth) {
int i, j;
uint16_t *orig_buf = CONVERT_TO_SHORTPTR(frm->y_buffer);
uint8_t *buf_8bit = frm->y_buffer_8bit;
@@ -240,59 +376,639 @@ static unsigned char *downconvert_frame(YV12_BUFFER_CONFIG *frm,
return buf_8bit;
}
-int compute_global_motion_feature_based(TransformationType type,
- YV12_BUFFER_CONFIG *frm,
- YV12_BUFFER_CONFIG *ref, int bit_depth,
- int *num_inliers_by_motion,
- double *params_by_motion,
- int num_motions) {
+static void get_inliers_from_indices(MotionModel *params,
+ int *correspondences) {
+ int *inliers_tmp = (int *)aom_malloc(2 * MAX_CORNERS * sizeof(*inliers_tmp));
+ memset(inliers_tmp, 0, 2 * MAX_CORNERS * sizeof(*inliers_tmp));
+
+ for (int i = 0; i < params->num_inliers; i++) {
+ int index = params->inliers[i];
+ inliers_tmp[2 * i] = correspondences[4 * index];
+ inliers_tmp[2 * i + 1] = correspondences[4 * index + 1];
+ }
+ memcpy(params->inliers, inliers_tmp, sizeof(*inliers_tmp) * 2 * MAX_CORNERS);
+ aom_free(inliers_tmp);
+}
+
+#define FEAT_COUNT_TR 3
+#define SEG_COUNT_TR 0.40
+void av1_compute_feature_segmentation_map(uint8_t *segment_map, int width,
+ int height, int *inliers,
+ int num_inliers) {
+ int seg_count = 0;
+ memset(segment_map, 0, sizeof(*segment_map) * width * height);
+
+ for (int i = 0; i < num_inliers; i++) {
+ int x = inliers[i * 2];
+ int y = inliers[i * 2 + 1];
+ int seg_x = x >> WARP_ERROR_BLOCK_LOG;
+ int seg_y = y >> WARP_ERROR_BLOCK_LOG;
+ segment_map[seg_y * width + seg_x] += 1;
+ }
+
+ for (int i = 0; i < height; i++) {
+ for (int j = 0; j < width; j++) {
+ uint8_t feat_count = segment_map[i * width + j];
+ segment_map[i * width + j] = (feat_count >= FEAT_COUNT_TR);
+ seg_count += (segment_map[i * width + j]);
+ }
+ }
+
+ // If this motion does not make up a large enough portion of the frame,
+ // use the unsegmented version of the error metric
+ if (seg_count < (width * height * SEG_COUNT_TR))
+ memset(segment_map, 1, width * height * sizeof(*segment_map));
+}
+
+static int compute_global_motion_feature_based(
+ TransformationType type, unsigned char *frm_buffer, int frm_width,
+ int frm_height, int frm_stride, int *frm_corners, int num_frm_corners,
+ YV12_BUFFER_CONFIG *ref, int bit_depth, int *num_inliers_by_motion,
+ MotionModel *params_by_motion, int num_motions) {
int i;
- int num_frm_corners, num_ref_corners;
+ int num_ref_corners;
int num_correspondences;
int *correspondences;
- int frm_corners[2 * MAX_CORNERS], ref_corners[2 * MAX_CORNERS];
- unsigned char *frm_buffer = frm->y_buffer;
+ int ref_corners[2 * MAX_CORNERS];
unsigned char *ref_buffer = ref->y_buffer;
- RansacFunc ransac = get_ransac_type(type);
+ RansacFunc ransac = av1_get_ransac_type(type);
- if (frm->flags & YV12_FLAG_HIGHBITDEPTH) {
- // The frame buffer is 16-bit, so we need to convert to 8 bits for the
- // following code. We cache the result until the frame is released.
- frm_buffer = downconvert_frame(frm, bit_depth);
- }
if (ref->flags & YV12_FLAG_HIGHBITDEPTH) {
- ref_buffer = downconvert_frame(ref, bit_depth);
+ ref_buffer = av1_downconvert_frame(ref, bit_depth);
}
- // compute interest points in images using FAST features
- num_frm_corners = fast_corner_detect(frm_buffer, frm->y_width, frm->y_height,
- frm->y_stride, frm_corners, MAX_CORNERS);
- num_ref_corners = fast_corner_detect(ref_buffer, ref->y_width, ref->y_height,
- ref->y_stride, ref_corners, MAX_CORNERS);
+ num_ref_corners =
+ av1_fast_corner_detect(ref_buffer, ref->y_width, ref->y_height,
+ ref->y_stride, ref_corners, MAX_CORNERS);
// find correspondences between the two images
correspondences =
(int *)malloc(num_frm_corners * 4 * sizeof(*correspondences));
- num_correspondences = determine_correspondence(
+ num_correspondences = av1_determine_correspondence(
frm_buffer, (int *)frm_corners, num_frm_corners, ref_buffer,
- (int *)ref_corners, num_ref_corners, frm->y_width, frm->y_height,
- frm->y_stride, ref->y_stride, correspondences);
+ (int *)ref_corners, num_ref_corners, frm_width, frm_height, frm_stride,
+ ref->y_stride, correspondences);
ransac(correspondences, num_correspondences, num_inliers_by_motion,
params_by_motion, num_motions);
+ // Set num_inliers = 0 for motions with too few inliers so they are ignored.
+ for (i = 0; i < num_motions; ++i) {
+ if (num_inliers_by_motion[i] < MIN_INLIER_PROB * num_correspondences ||
+ num_correspondences == 0) {
+ num_inliers_by_motion[i] = 0;
+ } else {
+ get_inliers_from_indices(&params_by_motion[i], correspondences);
+ }
+ }
+
free(correspondences);
- // Set num_inliers = 0 for motions with too few inliers so they are ignored.
+ // Return true if any one of the motions has inliers.
for (i = 0; i < num_motions; ++i) {
+ if (num_inliers_by_motion[i] > 0) return 1;
+ }
+ return 0;
+}
+
+// Don't use points around the frame border since they are less reliable
+static INLINE int valid_point(int x, int y, int width, int height) {
+ return (x > (PATCH_SIZE + PATCH_CENTER)) &&
+ (x < (width - PATCH_SIZE - PATCH_CENTER)) &&
+ (y > (PATCH_SIZE + PATCH_CENTER)) &&
+ (y < (height - PATCH_SIZE - PATCH_CENTER));
+}
+
+static int determine_disflow_correspondence(int *frm_corners,
+ int num_frm_corners, double *flow_u,
+ double *flow_v, int width,
+ int height, int stride,
+ double *correspondences) {
+ int num_correspondences = 0;
+ int x, y;
+ for (int i = 0; i < num_frm_corners; ++i) {
+ x = frm_corners[2 * i];
+ y = frm_corners[2 * i + 1];
+ if (valid_point(x, y, width, height)) {
+ correspondences[4 * num_correspondences] = x;
+ correspondences[4 * num_correspondences + 1] = y;
+ correspondences[4 * num_correspondences + 2] = x + flow_u[y * stride + x];
+ correspondences[4 * num_correspondences + 3] = y + flow_v[y * stride + x];
+ num_correspondences++;
+ }
+ }
+ return num_correspondences;
+}
+
+static double getCubicValue(double p[4], double x) {
+ return p[1] + 0.5 * x *
+ (p[2] - p[0] +
+ x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] +
+ x * (3.0 * (p[1] - p[2]) + p[3] - p[0])));
+}
+
+static void get_subcolumn(unsigned char *ref, double col[4], int stride, int x,
+ int y_start) {
+ int i;
+ for (i = 0; i < 4; ++i) {
+ col[i] = ref[(i + y_start) * stride + x];
+ }
+}
+
+static double bicubic(unsigned char *ref, double x, double y, int stride) {
+ double arr[4];
+ int k;
+ int i = (int)x;
+ int j = (int)y;
+ for (k = 0; k < 4; ++k) {
+ double arr_temp[4];
+ get_subcolumn(ref, arr_temp, stride, i + k - 1, j - 1);
+ arr[k] = getCubicValue(arr_temp, y - j);
+ }
+ return getCubicValue(arr, x - i);
+}
+
+// Interpolate a warped block using bicubic interpolation when possible
+static unsigned char interpolate(unsigned char *ref, double x, double y,
+ int width, int height, int stride) {
+ if (x < 0 && y < 0)
+ return ref[0];
+ else if (x < 0 && y > height - 1)
+ return ref[(height - 1) * stride];
+ else if (x > width - 1 && y < 0)
+ return ref[width - 1];
+ else if (x > width - 1 && y > height - 1)
+ return ref[(height - 1) * stride + (width - 1)];
+ else if (x < 0) {
+ int v;
+ int i = (int)y;
+ double a = y - i;
+ if (y > 1 && y < height - 2) {
+ double arr[4];
+ get_subcolumn(ref, arr, stride, 0, i - 1);
+ return clamp((int)(getCubicValue(arr, a) + 0.5), 0, 255);
+ }
+ v = (int)(ref[i * stride] * (1 - a) + ref[(i + 1) * stride] * a + 0.5);
+ return clamp(v, 0, 255);
+ } else if (y < 0) {
+ int v;
+ int j = (int)x;
+ double b = x - j;
+ if (x > 1 && x < width - 2) {
+ double arr[4] = { ref[j - 1], ref[j], ref[j + 1], ref[j + 2] };
+ return clamp((int)(getCubicValue(arr, b) + 0.5), 0, 255);
+ }
+ v = (int)(ref[j] * (1 - b) + ref[j + 1] * b + 0.5);
+ return clamp(v, 0, 255);
+ } else if (x > width - 1) {
+ int v;
+ int i = (int)y;
+ double a = y - i;
+ if (y > 1 && y < height - 2) {
+ double arr[4];
+ get_subcolumn(ref, arr, stride, width - 1, i - 1);
+ return clamp((int)(getCubicValue(arr, a) + 0.5), 0, 255);
+ }
+ v = (int)(ref[i * stride + width - 1] * (1 - a) +
+ ref[(i + 1) * stride + width - 1] * a + 0.5);
+ return clamp(v, 0, 255);
+ } else if (y > height - 1) {
+ int v;
+ int j = (int)x;
+ double b = x - j;
+ if (x > 1 && x < width - 2) {
+ int row = (height - 1) * stride;
+ double arr[4] = { ref[row + j - 1], ref[row + j], ref[row + j + 1],
+ ref[row + j + 2] };
+ return clamp((int)(getCubicValue(arr, b) + 0.5), 0, 255);
+ }
+ v = (int)(ref[(height - 1) * stride + j] * (1 - b) +
+ ref[(height - 1) * stride + j + 1] * b + 0.5);
+ return clamp(v, 0, 255);
+ } else if (x > 1 && y > 1 && x < width - 2 && y < height - 2) {
+ return clamp((int)(bicubic(ref, x, y, stride) + 0.5), 0, 255);
+ } else {
+ int i = (int)y;
+ int j = (int)x;
+ double a = y - i;
+ double b = x - j;
+ int v = (int)(ref[i * stride + j] * (1 - a) * (1 - b) +
+ ref[i * stride + j + 1] * (1 - a) * b +
+ ref[(i + 1) * stride + j] * a * (1 - b) +
+ ref[(i + 1) * stride + j + 1] * a * b);
+ return clamp(v, 0, 255);
+ }
+}
+
+// Warps a block using flow vector [u, v] and computes the mse
+static double compute_warp_and_error(unsigned char *ref, unsigned char *frm,
+ int width, int height, int stride, int x,
+ int y, double u, double v, int16_t *dt) {
+ int i, j;
+ unsigned char warped;
+ double x_w, y_w;
+ double mse = 0;
+ int16_t err = 0;
+ for (i = y; i < y + PATCH_SIZE; ++i)
+ for (j = x; j < x + PATCH_SIZE; ++j) {
+ x_w = (double)j + u;
+ y_w = (double)i + v;
+ warped = interpolate(ref, x_w, y_w, width, height, stride);
+ err = warped - frm[j + i * stride];
+ mse += err * err;
+ dt[(i - y) * PATCH_SIZE + (j - x)] = err;
+ }
+
+ mse /= (PATCH_SIZE * PATCH_SIZE);
+ return mse;
+}
+
+// Computes the components of the system of equations used to solve for
+// a flow vector. This includes:
+// 1.) The hessian matrix for optical flow. This matrix is in the
+// form of:
+//
+// M = |sum(dx * dx) sum(dx * dy)|
+// |sum(dx * dy) sum(dy * dy)|
+//
+// 2.) b = |sum(dx * dt)|
+// |sum(dy * dt)|
+// Where the sums are computed over a square window of PATCH_SIZE.
+static INLINE void compute_flow_system(const double *dx, int dx_stride,
+ const double *dy, int dy_stride,
+ const int16_t *dt, int dt_stride,
+ double *M, double *b) {
+ for (int i = 0; i < PATCH_SIZE; i++) {
+ for (int j = 0; j < PATCH_SIZE; j++) {
+ M[0] += dx[i * dx_stride + j] * dx[i * dx_stride + j];
+ M[1] += dx[i * dx_stride + j] * dy[i * dy_stride + j];
+ M[3] += dy[i * dy_stride + j] * dy[i * dy_stride + j];
+
+ b[0] += dx[i * dx_stride + j] * dt[i * dt_stride + j];
+ b[1] += dy[i * dy_stride + j] * dt[i * dt_stride + j];
+ }
+ }
+
+ M[2] = M[1];
+}
+
+// Solves a general Mx = b where M is a 2x2 matrix and b is a 2x1 matrix
+static INLINE void solve_2x2_system(const double *M, const double *b,
+ double *output_vec) {
+ double M_0 = M[0];
+ double M_3 = M[3];
+ double det = (M_0 * M_3) - (M[1] * M[2]);
+ if (det < 1e-5) {
+ // Handle singular matrix
+ // TODO(sarahparker) compare results using pseudo inverse instead
+ M_0 += 1e-10;
+ M_3 += 1e-10;
+ det = (M_0 * M_3) - (M[1] * M[2]);
+ }
+ const double det_inv = 1 / det;
+ const double mult_b0 = det_inv * b[0];
+ const double mult_b1 = det_inv * b[1];
+ output_vec[0] = M_3 * mult_b0 - M[1] * mult_b1;
+ output_vec[1] = -M[2] * mult_b0 + M_0 * mult_b1;
+}
+
+/*
+static INLINE void image_difference(const uint8_t *src, int src_stride,
+ const uint8_t *ref, int ref_stride,
+ int16_t *dst, int dst_stride, int height,
+ int width) {
+ const int block_unit = 8;
+ // Take difference in 8x8 blocks to make use of optimized diff function
+ for (int i = 0; i < height; i += block_unit) {
+ for (int j = 0; j < width; j += block_unit) {
+ aom_subtract_block(block_unit, block_unit, dst + i * dst_stride + j,
+ dst_stride, src + i * src_stride + j, src_stride,
+ ref + i * ref_stride + j, ref_stride);
+ }
+ }
+}
+*/
+
+// Compute an image gradient using a sobel filter.
+// If dir == 1, compute the x gradient. If dir == 0, compute y. This function
+// assumes the images have been padded so that they can be processed in units
+// of 8.
+static INLINE void sobel_xy_image_gradient(const uint8_t *src, int src_stride,
+ double *dst, int dst_stride,
+ int height, int width, int dir) {
+ double norm = 1.0;
+ // TODO(sarahparker) experiment with doing this over larger block sizes
+ const int block_unit = 8;
+ // Filter in 8x8 blocks to eventually make use of optimized convolve function
+ for (int i = 0; i < height; i += block_unit) {
+ for (int j = 0; j < width; j += block_unit) {
+ av1_convolve_2d_sobel_y_c(src + i * src_stride + j, src_stride,
+ dst + i * dst_stride + j, dst_stride,
+ block_unit, block_unit, dir, norm);
+ }
+ }
+}
+
+static ImagePyramid *alloc_pyramid(int width, int height, int pad_size,
+ int compute_gradient) {
+ ImagePyramid *pyr = aom_malloc(sizeof(*pyr));
+ pyr->has_gradient = compute_gradient;
+ // 2 * width * height is the upper bound for a buffer that fits
+ // all pyramid levels + padding for each level
+ const int buffer_size = sizeof(*pyr->level_buffer) * 2 * width * height +
+ (width + 2 * pad_size) * 2 * pad_size * N_LEVELS;
+ pyr->level_buffer = aom_malloc(buffer_size);
+ memset(pyr->level_buffer, 0, buffer_size);
+
+ if (compute_gradient) {
+ const int gradient_size =
+ sizeof(*pyr->level_dx_buffer) * 2 * width * height +
+ (width + 2 * pad_size) * 2 * pad_size * N_LEVELS;
+ pyr->level_dx_buffer = aom_malloc(gradient_size);
+ pyr->level_dy_buffer = aom_malloc(gradient_size);
+ memset(pyr->level_dx_buffer, 0, gradient_size);
+ memset(pyr->level_dy_buffer, 0, gradient_size);
+ }
+ return pyr;
+}
+
+static void free_pyramid(ImagePyramid *pyr) {
+ aom_free(pyr->level_buffer);
+ if (pyr->has_gradient) {
+ aom_free(pyr->level_dx_buffer);
+ aom_free(pyr->level_dy_buffer);
+ }
+ aom_free(pyr);
+}
+
+static INLINE void update_level_dims(ImagePyramid *frm_pyr, int level) {
+ frm_pyr->widths[level] = frm_pyr->widths[level - 1] >> 1;
+ frm_pyr->heights[level] = frm_pyr->heights[level - 1] >> 1;
+ frm_pyr->strides[level] = frm_pyr->widths[level] + 2 * frm_pyr->pad_size;
+ // Point the beginning of the next level buffer to the correct location inside
+ // the padded border
+ frm_pyr->level_loc[level] =
+ frm_pyr->level_loc[level - 1] +
+ frm_pyr->strides[level - 1] *
+ (2 * frm_pyr->pad_size + frm_pyr->heights[level - 1]);
+}
+
+// Compute coarse to fine pyramids for a frame
+static void compute_flow_pyramids(unsigned char *frm, const int frm_width,
+ const int frm_height, const int frm_stride,
+ int n_levels, int pad_size, int compute_grad,
+ ImagePyramid *frm_pyr) {
+ int cur_width, cur_height, cur_stride, cur_loc;
+ assert((frm_width >> n_levels) > 0);
+ assert((frm_height >> n_levels) > 0);
+
+ // Initialize first level
+ frm_pyr->n_levels = n_levels;
+ frm_pyr->pad_size = pad_size;
+ frm_pyr->widths[0] = frm_width;
+ frm_pyr->heights[0] = frm_height;
+ frm_pyr->strides[0] = frm_width + 2 * frm_pyr->pad_size;
+ // Point the beginning of the level buffer to the location inside
+ // the padded border
+ frm_pyr->level_loc[0] =
+ frm_pyr->strides[0] * frm_pyr->pad_size + frm_pyr->pad_size;
+ // This essentially copies the original buffer into the pyramid buffer
+ // without the original padding
+ av1_resize_plane(frm, frm_height, frm_width, frm_stride,
+ frm_pyr->level_buffer + frm_pyr->level_loc[0],
+ frm_pyr->heights[0], frm_pyr->widths[0],
+ frm_pyr->strides[0]);
+
+ if (compute_grad) {
+ cur_width = frm_pyr->widths[0];
+ cur_height = frm_pyr->heights[0];
+ cur_stride = frm_pyr->strides[0];
+ cur_loc = frm_pyr->level_loc[0];
+ assert(frm_pyr->has_gradient && frm_pyr->level_dx_buffer != NULL &&
+ frm_pyr->level_dy_buffer != NULL);
+ // Computation x gradient
+ sobel_xy_image_gradient(frm_pyr->level_buffer + cur_loc, cur_stride,
+ frm_pyr->level_dx_buffer + cur_loc, cur_stride,
+ cur_height, cur_width, 1);
+
+ // Computation y gradient
+ sobel_xy_image_gradient(frm_pyr->level_buffer + cur_loc, cur_stride,
+ frm_pyr->level_dy_buffer + cur_loc, cur_stride,
+ cur_height, cur_width, 0);
+ }
+
+ // Start at the finest level and resize down to the coarsest level
+ for (int level = 1; level < n_levels; ++level) {
+ update_level_dims(frm_pyr, level);
+ cur_width = frm_pyr->widths[level];
+ cur_height = frm_pyr->heights[level];
+ cur_stride = frm_pyr->strides[level];
+ cur_loc = frm_pyr->level_loc[level];
+
+ av1_resize_plane(frm_pyr->level_buffer + frm_pyr->level_loc[level - 1],
+ frm_pyr->heights[level - 1], frm_pyr->widths[level - 1],
+ frm_pyr->strides[level - 1],
+ frm_pyr->level_buffer + cur_loc, cur_height, cur_width,
+ cur_stride);
+
+ if (compute_grad) {
+ assert(frm_pyr->has_gradient && frm_pyr->level_dx_buffer != NULL &&
+ frm_pyr->level_dy_buffer != NULL);
+ // Computation x gradient
+ sobel_xy_image_gradient(frm_pyr->level_buffer + cur_loc, cur_stride,
+ frm_pyr->level_dx_buffer + cur_loc, cur_stride,
+ cur_height, cur_width, 1);
+
+ // Computation y gradient
+ sobel_xy_image_gradient(frm_pyr->level_buffer + cur_loc, cur_stride,
+ frm_pyr->level_dy_buffer + cur_loc, cur_stride,
+ cur_height, cur_width, 0);
+ }
+ }
+}
+
+static INLINE void compute_flow_at_point(unsigned char *frm, unsigned char *ref,
+ double *dx, double *dy, int x, int y,
+ int width, int height, int stride,
+ double *u, double *v) {
+ double M[4] = { 0 };
+ double b[2] = { 0 };
+ double tmp_output_vec[2] = { 0 };
+ double error = 0;
+ int16_t dt[PATCH_SIZE * PATCH_SIZE];
+ double o_u = *u;
+ double o_v = *v;
+
+ for (int itr = 0; itr < DISFLOW_MAX_ITR; itr++) {
+ error = compute_warp_and_error(ref, frm, width, height, stride, x, y, *u,
+ *v, dt);
+ if (error <= DISFLOW_ERROR_TR) break;
+ compute_flow_system(dx, stride, dy, stride, dt, PATCH_SIZE, M, b);
+ solve_2x2_system(M, b, tmp_output_vec);
+ *u += tmp_output_vec[0];
+ *v += tmp_output_vec[1];
+ }
+ if (fabs(*u - o_u) > PATCH_SIZE || fabs(*v - o_u) > PATCH_SIZE) {
+ *u = o_u;
+ *v = o_v;
+ }
+}
+
+// make sure flow_u and flow_v start at 0
+static void compute_flow_field(ImagePyramid *frm_pyr, ImagePyramid *ref_pyr,
+ double *flow_u, double *flow_v) {
+ int cur_width, cur_height, cur_stride, cur_loc, patch_loc, patch_center;
+ double *u_upscale =
+ aom_malloc(frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_u));
+ double *v_upscale =
+ aom_malloc(frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_v));
+
+ assert(frm_pyr->n_levels == ref_pyr->n_levels);
+
+ // Compute flow field from coarsest to finest level of the pyramid
+ for (int level = frm_pyr->n_levels - 1; level >= 0; --level) {
+ cur_width = frm_pyr->widths[level];
+ cur_height = frm_pyr->heights[level];
+ cur_stride = frm_pyr->strides[level];
+ cur_loc = frm_pyr->level_loc[level];
+
+ for (int i = PATCH_SIZE; i < cur_height - PATCH_SIZE; i += PATCH_STEP) {
+ for (int j = PATCH_SIZE; j < cur_width - PATCH_SIZE; j += PATCH_STEP) {
+ patch_loc = i * cur_stride + j;
+ patch_center = patch_loc + PATCH_CENTER * cur_stride + PATCH_CENTER;
+ compute_flow_at_point(frm_pyr->level_buffer + cur_loc,
+ ref_pyr->level_buffer + cur_loc,
+ frm_pyr->level_dx_buffer + cur_loc + patch_loc,
+ frm_pyr->level_dy_buffer + cur_loc + patch_loc, j,
+ i, cur_width, cur_height, cur_stride,
+ flow_u + patch_center, flow_v + patch_center);
+ }
+ }
+ // TODO(sarahparker) Replace this with upscale function in resize.c
+ if (level > 0) {
+ int h_upscale = frm_pyr->heights[level - 1];
+ int w_upscale = frm_pyr->widths[level - 1];
+ int s_upscale = frm_pyr->strides[level - 1];
+ for (int i = 0; i < h_upscale; ++i) {
+ for (int j = 0; j < w_upscale; ++j) {
+ u_upscale[j + i * s_upscale] =
+ flow_u[(int)(j >> 1) + (int)(i >> 1) * cur_stride];
+ v_upscale[j + i * s_upscale] =
+ flow_v[(int)(j >> 1) + (int)(i >> 1) * cur_stride];
+ }
+ }
+ memcpy(flow_u, u_upscale,
+ frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_u));
+ memcpy(flow_v, v_upscale,
+ frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_v));
+ }
+ }
+ aom_free(u_upscale);
+ aom_free(v_upscale);
+}
+
+static int compute_global_motion_disflow_based(
+ TransformationType type, unsigned char *frm_buffer, int frm_width,
+ int frm_height, int frm_stride, int *frm_corners, int num_frm_corners,
+ YV12_BUFFER_CONFIG *ref, int bit_depth, int *num_inliers_by_motion,
+ MotionModel *params_by_motion, int num_motions) {
+ unsigned char *ref_buffer = ref->y_buffer;
+ const int ref_width = ref->y_width;
+ const int ref_height = ref->y_height;
+ const int pad_size = AOMMAX(PATCH_SIZE, MIN_PAD);
+ int num_correspondences;
+ double *correspondences;
+ RansacFuncDouble ransac = av1_get_ransac_double_prec_type(type);
+ assert(frm_width == ref_width);
+ assert(frm_height == ref_height);
+
+ // Ensure the number of pyramid levels will work with the frame resolution
+ const int msb =
+ frm_width < frm_height ? get_msb(frm_width) : get_msb(frm_height);
+ const int n_levels = AOMMIN(msb, N_LEVELS);
+
+ if (ref->flags & YV12_FLAG_HIGHBITDEPTH) {
+ ref_buffer = av1_downconvert_frame(ref, bit_depth);
+ }
+
+ // TODO(sarahparker) We will want to do the source pyramid computation
+ // outside of this function so it doesn't get recomputed for every
+ // reference. We also don't need to compute every pyramid level for the
+ // reference in advance, since lower levels can be overwritten once their
+ // flow field is computed and upscaled. I'll add these optimizations
+ // once the full implementation is working.
+ // Allocate frm image pyramids
+ int compute_gradient = 1;
+ ImagePyramid *frm_pyr =
+ alloc_pyramid(frm_width, frm_height, pad_size, compute_gradient);
+ compute_flow_pyramids(frm_buffer, frm_width, frm_height, frm_stride, n_levels,
+ pad_size, compute_gradient, frm_pyr);
+ // Allocate ref image pyramids
+ compute_gradient = 0;
+ ImagePyramid *ref_pyr =
+ alloc_pyramid(ref_width, ref_height, pad_size, compute_gradient);
+ compute_flow_pyramids(ref_buffer, ref_width, ref_height, ref->y_stride,
+ n_levels, pad_size, compute_gradient, ref_pyr);
+
+ double *flow_u =
+ aom_malloc(frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_u));
+ double *flow_v =
+ aom_malloc(frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_v));
+
+ memset(flow_u, 0,
+ frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_u));
+ memset(flow_v, 0,
+ frm_pyr->strides[0] * frm_pyr->heights[0] * sizeof(*flow_v));
+
+ compute_flow_field(frm_pyr, ref_pyr, flow_u, flow_v);
+
+ // find correspondences between the two images using the flow field
+ correspondences = aom_malloc(num_frm_corners * 4 * sizeof(*correspondences));
+ num_correspondences = determine_disflow_correspondence(
+ frm_corners, num_frm_corners, flow_u, flow_v, frm_width, frm_height,
+ frm_pyr->strides[0], correspondences);
+ ransac(correspondences, num_correspondences, num_inliers_by_motion,
+ params_by_motion, num_motions);
+
+ free_pyramid(frm_pyr);
+ free_pyramid(ref_pyr);
+ aom_free(correspondences);
+ aom_free(flow_u);
+ aom_free(flow_v);
+ // Set num_inliers = 0 for motions with too few inliers so they are ignored.
+ for (int i = 0; i < num_motions; ++i) {
if (num_inliers_by_motion[i] < MIN_INLIER_PROB * num_correspondences) {
num_inliers_by_motion[i] = 0;
}
}
// Return true if any one of the motions has inliers.
- for (i = 0; i < num_motions; ++i) {
+ for (int i = 0; i < num_motions; ++i) {
if (num_inliers_by_motion[i] > 0) return 1;
}
return 0;
}
+
+int av1_compute_global_motion(TransformationType type,
+ unsigned char *frm_buffer, int frm_width,
+ int frm_height, int frm_stride, int *frm_corners,
+ int num_frm_corners, YV12_BUFFER_CONFIG *ref,
+ int bit_depth,
+ GlobalMotionEstimationType gm_estimation_type,
+ int *num_inliers_by_motion,
+ MotionModel *params_by_motion, int num_motions) {
+ switch (gm_estimation_type) {
+ case GLOBAL_MOTION_FEATURE_BASED:
+ return compute_global_motion_feature_based(
+ type, frm_buffer, frm_width, frm_height, frm_stride, frm_corners,
+ num_frm_corners, ref, bit_depth, num_inliers_by_motion,
+ params_by_motion, num_motions);
+ case GLOBAL_MOTION_DISFLOW_BASED:
+ return compute_global_motion_disflow_based(
+ type, frm_buffer, frm_width, frm_height, frm_stride, frm_corners,
+ num_frm_corners, ref, bit_depth, num_inliers_by_motion,
+ params_by_motion, num_motions);
+ default: assert(0 && "Unknown global motion estimation type");
+ }
+ return 0;
+}