|  | /* | 
|  | * Copyright (c) 2016, Alliance for Open Media. All rights reserved | 
|  | * | 
|  | * This source code is subject to the terms of the BSD 2 Clause License and | 
|  | * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License | 
|  | * was not distributed with this source code in the LICENSE file, you can | 
|  | * obtain it at www.aomedia.org/license/software. If the Alliance for Open | 
|  | * Media Patent License 1.0 was not distributed with this source code in the | 
|  | * PATENTS file, you can obtain it at www.aomedia.org/license/patent. | 
|  | */ | 
|  | #include <math.h> | 
|  | #include <limits.h> | 
|  |  | 
|  | #include "config/aom_config.h" | 
|  | #include "av1/common/av1_common_int.h" | 
|  | #include "av1/encoder/encoder.h" | 
|  | #include "av1/encoder/mathutils.h" | 
|  | #include "av1/encoder/optical_flow.h" | 
|  | #include "av1/encoder/sparse_linear_solver.h" | 
|  | #include "av1/encoder/reconinter_enc.h" | 
|  | #include "aom_mem/aom_mem.h" | 
|  |  | 
|  | #if CONFIG_OPTICAL_FLOW_API | 
|  |  | 
|  | void av1_init_opfl_params(OPFL_PARAMS *opfl_params) { | 
|  | opfl_params->pyramid_levels = OPFL_PYRAMID_LEVELS; | 
|  | opfl_params->warping_steps = OPFL_WARPING_STEPS; | 
|  | opfl_params->lk_params = NULL; | 
|  | } | 
|  |  | 
|  | void av1_init_lk_params(LK_PARAMS *lk_params) { | 
|  | lk_params->window_size = OPFL_WINDOW_SIZE; | 
|  | } | 
|  |  | 
|  | // Helper function to determine whether a frame is encoded with high bit-depth. | 
|  | static INLINE int is_frame_high_bitdepth(const YV12_BUFFER_CONFIG *frame) { | 
|  | return (frame->flags & YV12_FLAG_HIGHBITDEPTH) ? 1 : 0; | 
|  | } | 
|  |  | 
|  | // Helper function to determine whether optical flow method is sparse. | 
|  | static INLINE int is_sparse(const OPFL_PARAMS *opfl_params) { | 
|  | return (opfl_params->flags & OPFL_FLAG_SPARSE) ? 1 : 0; | 
|  | } | 
|  |  | 
|  | static void gradients_over_window(const YV12_BUFFER_CONFIG *frame, | 
|  | const YV12_BUFFER_CONFIG *ref_frame, | 
|  | const double x_coord, const double y_coord, | 
|  | const int window_size, const int bit_depth, | 
|  | double *ix, double *iy, double *it, | 
|  | LOCALMV *mv); | 
|  |  | 
|  | // coefficients for bilinear interpolation on unit square | 
|  | static int pixel_interp(const double x, const double y, const double b00, | 
|  | const double b01, const double b10, const double b11) { | 
|  | const int xint = (int)x; | 
|  | const int yint = (int)y; | 
|  | const double xdec = x - xint; | 
|  | const double ydec = y - yint; | 
|  | const double a = (1 - xdec) * (1 - ydec); | 
|  | const double b = xdec * (1 - ydec); | 
|  | const double c = (1 - xdec) * ydec; | 
|  | const double d = xdec * ydec; | 
|  | // if x, y are already integers, this results to b00 | 
|  | int interp = (int)round(a * b00 + b * b01 + c * b10 + d * b11); | 
|  | return interp; | 
|  | } | 
|  |  | 
|  | // bilinear interpolation to find subpixel values | 
|  | static AOM_INLINE int get_subpixels(const YV12_BUFFER_CONFIG *frame, int *pred, | 
|  | const int w, const int h, LOCALMV mv, | 
|  | const double x_coord, | 
|  | const double y_coord) { | 
|  | double left = x_coord + mv.row; | 
|  | double top = y_coord + mv.col; | 
|  | const int fromedge = 2; | 
|  | const int height = frame->y_crop_height; | 
|  | const int width = frame->y_crop_width; | 
|  | if (left < 1) left = 1; | 
|  | if (top < 1) top = 1; | 
|  | // could use elements past boundary where stride > width | 
|  | if (top > height - fromedge) top = height - fromedge; | 
|  | if (left > width - fromedge) left = width - fromedge; | 
|  | const uint8_t *buf = frame->y_buffer; | 
|  | const int s = frame->y_stride; | 
|  | int prev = -1; | 
|  |  | 
|  | int xint; | 
|  | int yint; | 
|  | int idx = 0; | 
|  | for (int y = prev; y < prev + h; y++) { | 
|  | for (int x = prev; x < prev + w; x++) { | 
|  | double xx = left + x; | 
|  | double yy = top + y; | 
|  | xint = (int)xx; | 
|  | yint = (int)yy; | 
|  | int interp = pixel_interp( | 
|  | xx, yy, buf[yint * s + xint], buf[yint * s + (xint + 1)], | 
|  | buf[(yint + 1) * s + xint], buf[(yint + 1) * s + (xint + 1)]); | 
|  | pred[idx++] = interp; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | // Scharr filter to compute spatial gradient | 
|  | static void spatial_gradient(const YV12_BUFFER_CONFIG *frame, const int x_coord, | 
|  | const int y_coord, const int direction, | 
|  | double *derivative) { | 
|  | double *filter; | 
|  | // Scharr filters | 
|  | double gx[9] = { -3, 0, 3, -10, 0, 10, -3, 0, 3 }; | 
|  | double gy[9] = { -3, -10, -3, 0, 0, 0, 3, 10, 3 }; | 
|  | if (direction == 0) {  // x direction | 
|  | filter = gx; | 
|  | } else {  // y direction | 
|  | filter = gy; | 
|  | } | 
|  | int idx = 0; | 
|  | double d = 0; | 
|  | for (int yy = -1; yy <= 1; yy++) { | 
|  | for (int xx = -1; xx <= 1; xx++) { | 
|  | d += filter[idx] * | 
|  | frame->y_buffer[(y_coord + yy) * frame->y_stride + (x_coord + xx)]; | 
|  | idx++; | 
|  | } | 
|  | } | 
|  | // normalization scaling factor for scharr | 
|  | *derivative = d / 32.0; | 
|  | } | 
|  |  | 
|  | // Determine the spatial gradient at subpixel locations | 
|  | // For example, when reducing images for pyramidal LK, | 
|  | // corners found in original image may be at subpixel locations. | 
|  | static void gradient_interp(double *fullpel_deriv, const double x_coord, | 
|  | const double y_coord, const int w, const int h, | 
|  | double *derivative) { | 
|  | const int xint = (int)x_coord; | 
|  | const int yint = (int)y_coord; | 
|  | double interp; | 
|  | if (xint + 1 > w - 1 || yint + 1 > h - 1) { | 
|  | interp = fullpel_deriv[yint * w + xint]; | 
|  | } else { | 
|  | interp = pixel_interp(x_coord, y_coord, fullpel_deriv[yint * w + xint], | 
|  | fullpel_deriv[yint * w + (xint + 1)], | 
|  | fullpel_deriv[(yint + 1) * w + xint], | 
|  | fullpel_deriv[(yint + 1) * w + (xint + 1)]); | 
|  | } | 
|  |  | 
|  | *derivative = interp; | 
|  | } | 
|  |  | 
|  | static void temporal_gradient(const YV12_BUFFER_CONFIG *frame, | 
|  | const YV12_BUFFER_CONFIG *frame2, | 
|  | const double x_coord, const double y_coord, | 
|  | const int bit_depth, double *derivative, | 
|  | LOCALMV *mv) { | 
|  | const int w = 2; | 
|  | const int h = 2; | 
|  | uint8_t pred1[4]; | 
|  | uint8_t pred2[4]; | 
|  |  | 
|  | const int y = (int)y_coord; | 
|  | const int x = (int)x_coord; | 
|  | const double ydec = y_coord - y; | 
|  | const double xdec = x_coord - x; | 
|  | const int is_intrabc = 0;  // Is intra-copied? | 
|  | const int is_high_bitdepth = is_frame_high_bitdepth(frame2); | 
|  | const int subsampling_x = 0, subsampling_y = 0;  // for y-buffer | 
|  | const int_interpfilters interp_filters = | 
|  | av1_broadcast_interp_filter(MULTITAP_SHARP); | 
|  | const int plane = 0;  // y-plane | 
|  | const struct buf_2d ref_buf2 = { NULL, frame2->y_buffer, frame2->y_crop_width, | 
|  | frame2->y_crop_height, frame2->y_stride }; | 
|  | struct scale_factors scale; | 
|  | av1_setup_scale_factors_for_frame(&scale, frame->y_crop_width, | 
|  | frame->y_crop_height, frame->y_crop_width, | 
|  | frame->y_crop_height); | 
|  | InterPredParams inter_pred_params; | 
|  | av1_init_inter_params(&inter_pred_params, w, h, y, x, subsampling_x, | 
|  | subsampling_y, bit_depth, is_high_bitdepth, is_intrabc, | 
|  | &scale, &ref_buf2, interp_filters); | 
|  | inter_pred_params.interp_filter_params[0] = | 
|  | &av1_interp_filter_params_list[interp_filters.as_filters.x_filter]; | 
|  | inter_pred_params.interp_filter_params[1] = | 
|  | &av1_interp_filter_params_list[interp_filters.as_filters.y_filter]; | 
|  | inter_pred_params.conv_params = get_conv_params(0, plane, bit_depth); | 
|  | MV newmv = { .row = (int16_t)round((mv->row + xdec) * 8), | 
|  | .col = (int16_t)round((mv->col + ydec) * 8) }; | 
|  | av1_enc_build_one_inter_predictor(pred2, w, &newmv, &inter_pred_params); | 
|  | const struct buf_2d ref_buf1 = { NULL, frame->y_buffer, frame->y_crop_width, | 
|  | frame->y_crop_height, frame->y_stride }; | 
|  | av1_init_inter_params(&inter_pred_params, w, h, y, x, subsampling_x, | 
|  | subsampling_y, bit_depth, is_high_bitdepth, is_intrabc, | 
|  | &scale, &ref_buf1, interp_filters); | 
|  | inter_pred_params.interp_filter_params[0] = | 
|  | &av1_interp_filter_params_list[interp_filters.as_filters.x_filter]; | 
|  | inter_pred_params.interp_filter_params[1] = | 
|  | &av1_interp_filter_params_list[interp_filters.as_filters.y_filter]; | 
|  | inter_pred_params.conv_params = get_conv_params(0, plane, bit_depth); | 
|  | MV zeroMV = { .row = (int16_t)round(xdec * 8), | 
|  | .col = (int16_t)round(ydec * 8) }; | 
|  | av1_enc_build_one_inter_predictor(pred1, w, &zeroMV, &inter_pred_params); | 
|  |  | 
|  | *derivative = pred2[0] - pred1[0]; | 
|  | } | 
|  |  | 
|  | // Numerical differentiate over window_size x window_size surrounding (x,y) | 
|  | // location. Alters ix, iy, it to contain numerical partial derivatives | 
|  | static void gradients_over_window(const YV12_BUFFER_CONFIG *frame, | 
|  | const YV12_BUFFER_CONFIG *ref_frame, | 
|  | const double x_coord, const double y_coord, | 
|  | const int window_size, const int bit_depth, | 
|  | double *ix, double *iy, double *it, | 
|  | LOCALMV *mv) { | 
|  | const double left = x_coord - window_size / 2.0; | 
|  | const double top = y_coord - window_size / 2.0; | 
|  | // gradient operators need pixel before and after (start at 1) | 
|  | const double x_start = AOMMAX(1, left); | 
|  | const double y_start = AOMMAX(1, top); | 
|  | const int frame_height = frame->y_crop_height; | 
|  | const int frame_width = frame->y_crop_width; | 
|  | double deriv_x; | 
|  | double deriv_y; | 
|  | double deriv_t; | 
|  |  | 
|  | const double x_end = AOMMIN(x_coord + window_size / 2.0, frame_width - 2); | 
|  | const double y_end = AOMMIN(y_coord + window_size / 2.0, frame_height - 2); | 
|  | const int xs = (int)AOMMAX(1, x_start - 1); | 
|  | const int ys = (int)AOMMAX(1, y_start - 1); | 
|  | const int xe = (int)AOMMIN(x_end + 2, frame_width - 2); | 
|  | const int ye = (int)AOMMIN(y_end + 2, frame_height - 2); | 
|  | // with normalization, gradients may be double values | 
|  | double *fullpel_dx = aom_malloc((ye - ys) * (xe - xs) * sizeof(deriv_x)); | 
|  | double *fullpel_dy = aom_malloc((ye - ys) * (xe - xs) * sizeof(deriv_y)); | 
|  | // TODO(any): This could be more efficient in the case that x_coord | 
|  | // and y_coord are integers.. but it may look more messy. | 
|  |  | 
|  | // calculate spatial gradients at full pixel locations | 
|  | for (int j = ys; j < ye; j++) { | 
|  | for (int i = xs; i < xe; i++) { | 
|  | spatial_gradient(frame, i, j, 0, &deriv_x); | 
|  | spatial_gradient(frame, i, j, 1, &deriv_y); | 
|  | int idx = (j - ys) * (xe - xs) + (i - xs); | 
|  | fullpel_dx[idx] = deriv_x; | 
|  | fullpel_dy[idx] = deriv_y; | 
|  | } | 
|  | } | 
|  | // compute numerical differentiation for every pixel in window | 
|  | // (this potentially includes subpixels) | 
|  | for (double j = y_start; j < y_end; j++) { | 
|  | for (double i = x_start; i < x_end; i++) { | 
|  | temporal_gradient(frame, ref_frame, i, j, bit_depth, &deriv_t, mv); | 
|  | gradient_interp(fullpel_dx, i - xs, j - ys, xe - xs, ye - ys, &deriv_x); | 
|  | gradient_interp(fullpel_dy, i - xs, j - ys, xe - xs, ye - ys, &deriv_y); | 
|  | int idx = (int)(j - top) * window_size + (int)(i - left); | 
|  | ix[idx] = deriv_x; | 
|  | iy[idx] = deriv_y; | 
|  | it[idx] = deriv_t; | 
|  | } | 
|  | } | 
|  | // TODO(any): to avoid setting deriv arrays to zero for every iteration, | 
|  | // could instead pass these two values back through function call | 
|  | // int first_idx = (int)(y_start - top) * window_size + (int)(x_start - left); | 
|  | // int width = window_size - ((int)(x_start - left) + (int)(left + window_size | 
|  | // - x_end)); | 
|  |  | 
|  | aom_free(fullpel_dx); | 
|  | aom_free(fullpel_dy); | 
|  | } | 
|  |  | 
|  | // To compute eigenvalues of 2x2 matrix: Solve for lambda where | 
|  | // Determinant(matrix - lambda*identity) == 0 | 
|  | static void eigenvalues_2x2(const double *matrix, double *eig) { | 
|  | const double a = 1; | 
|  | const double b = -1 * matrix[0] - matrix[3]; | 
|  | const double c = -1 * matrix[1] * matrix[2] + matrix[0] * matrix[3]; | 
|  | // quadratic formula | 
|  | const double discriminant = b * b - 4 * a * c; | 
|  | eig[0] = (-b - sqrt(discriminant)) / (2.0 * a); | 
|  | eig[1] = (-b + sqrt(discriminant)) / (2.0 * a); | 
|  | // double check that eigenvalues are ordered by magnitude | 
|  | if (fabs(eig[0]) > fabs(eig[1])) { | 
|  | double tmp = eig[0]; | 
|  | eig[0] = eig[1]; | 
|  | eig[1] = tmp; | 
|  | } | 
|  | } | 
|  |  | 
|  | // Shi-Tomasi corner detection criteria | 
|  | static double corner_score(const YV12_BUFFER_CONFIG *frame_to_filter, | 
|  | const YV12_BUFFER_CONFIG *ref_frame, const int x, | 
|  | const int y, double *i_x, double *i_y, double *i_t, | 
|  | const int n, const int bit_depth) { | 
|  | double eig[2]; | 
|  | LOCALMV mv = { .row = 0, .col = 0 }; | 
|  | // TODO(any): technically, ref_frame and i_t are not used by corner score | 
|  | // so these could be replaced by dummy variables, | 
|  | // or change this to spatial gradient function over window only | 
|  | gradients_over_window(frame_to_filter, ref_frame, x, y, n, bit_depth, i_x, | 
|  | i_y, i_t, &mv); | 
|  | double Mres1[1] = { 0 }, Mres2[1] = { 0 }, Mres3[1] = { 0 }; | 
|  | multiply_mat(i_x, i_x, Mres1, 1, n * n, 1); | 
|  | multiply_mat(i_x, i_y, Mres2, 1, n * n, 1); | 
|  | multiply_mat(i_y, i_y, Mres3, 1, n * n, 1); | 
|  | double M[4] = { Mres1[0], Mres2[0], Mres2[0], Mres3[0] }; | 
|  | eigenvalues_2x2(M, eig); | 
|  | return fabs(eig[0]); | 
|  | } | 
|  |  | 
|  | // Finds corners in frame_to_filter | 
|  | // For less strict requirements (i.e. more corners), decrease threshold | 
|  | static int detect_corners(const YV12_BUFFER_CONFIG *frame_to_filter, | 
|  | const YV12_BUFFER_CONFIG *ref_frame, | 
|  | const int maxcorners, int *ref_corners, | 
|  | const int bit_depth) { | 
|  | const int frame_height = frame_to_filter->y_crop_height; | 
|  | const int frame_width = frame_to_filter->y_crop_width; | 
|  | // TODO(any): currently if maxcorners is decreased, then it only means | 
|  | // corners will be omited from bottom-right of image. if maxcorners | 
|  | // is actually used, then this algorithm would need to re-iterate | 
|  | // and choose threshold based on that | 
|  | assert(maxcorners == frame_height * frame_width); | 
|  | int countcorners = 0; | 
|  | const double threshold = 0.1; | 
|  | double score; | 
|  | const int n = 3; | 
|  | double i_x[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | 
|  | double i_y[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | 
|  | double i_t[9] = { 0, 0, 0, 0, 0, 0, 0, 0, 0 }; | 
|  | const int fromedge = n; | 
|  | double max_score = corner_score(frame_to_filter, ref_frame, fromedge, | 
|  | fromedge, i_x, i_y, i_t, n, bit_depth); | 
|  | // rough estimate of max corner score in image | 
|  | for (int x = fromedge; x < frame_width - fromedge; x += 1) { | 
|  | for (int y = fromedge; y < frame_height - fromedge; y += frame_height / 5) { | 
|  | for (int i = 0; i < n * n; i++) { | 
|  | i_x[i] = 0; | 
|  | i_y[i] = 0; | 
|  | i_t[i] = 0; | 
|  | } | 
|  | score = corner_score(frame_to_filter, ref_frame, x, y, i_x, i_y, i_t, n, | 
|  | bit_depth); | 
|  | if (score > max_score) { | 
|  | max_score = score; | 
|  | } | 
|  | } | 
|  | } | 
|  | // score all the points and choose corners over threshold | 
|  | for (int x = fromedge; x < frame_width - fromedge; x += 1) { | 
|  | for (int y = fromedge; | 
|  | (y < frame_height - fromedge) && countcorners < maxcorners; y += 1) { | 
|  | for (int i = 0; i < n * n; i++) { | 
|  | i_x[i] = 0; | 
|  | i_y[i] = 0; | 
|  | i_t[i] = 0; | 
|  | } | 
|  | score = corner_score(frame_to_filter, ref_frame, x, y, i_x, i_y, i_t, n, | 
|  | bit_depth); | 
|  | if (score > threshold * max_score) { | 
|  | ref_corners[countcorners * 2] = x; | 
|  | ref_corners[countcorners * 2 + 1] = y; | 
|  | countcorners++; | 
|  | } | 
|  | } | 
|  | } | 
|  | return countcorners; | 
|  | } | 
|  |  | 
|  | // weights is an nxn matrix. weights is filled with a gaussian function, | 
|  | // with independent variable: distance from the center point. | 
|  | static void gaussian(const double sigma, const int n, const int normalize, | 
|  | double *weights) { | 
|  | double total_weight = 0; | 
|  | for (int j = 0; j < n; j++) { | 
|  | for (int i = 0; i < n; i++) { | 
|  | double distance = sqrt(pow(n / 2 - i, 2) + pow(n / 2 - j, 2)); | 
|  | double weight = exp(-0.5 * pow(distance / sigma, 2)); | 
|  | weights[j * n + i] = weight; | 
|  | total_weight += weight; | 
|  | } | 
|  | } | 
|  | if (normalize == 1) { | 
|  | for (int j = 0; j < n; j++) { | 
|  | weights[j] = weights[j] / total_weight; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static double convolve(const double *filter, const int *img, const int size) { | 
|  | double result = 0; | 
|  | for (int i = 0; i < size; i++) { | 
|  | result += filter[i] * img[i]; | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // Applies a Gaussian low-pass smoothing filter to produce | 
|  | // a corresponding lower resolution image with halved dimensions | 
|  | static void reduce(uint8_t *img, int height, int width, int stride, | 
|  | uint8_t *reduced_img) { | 
|  | const int new_width = width / 2; | 
|  | const int window_size = 5; | 
|  | const double gaussian_filter[25] = { | 
|  | 1. / 256, 1.0 / 64, 3. / 128, 1. / 64,  1. / 256, 1. / 64, 1. / 16, | 
|  | 3. / 32,  1. / 16,  1. / 64,  3. / 128, 3. / 32,  9. / 64, 3. / 32, | 
|  | 3. / 128, 1. / 64,  1. / 16,  3. / 32,  1. / 16,  1. / 64, 1. / 256, | 
|  | 1. / 64,  3. / 128, 1. / 64,  1. / 256 | 
|  | }; | 
|  | // filter is 5x5 so need prev and forward 2 pixels | 
|  | int img_section[25]; | 
|  | for (int y = 0; y < height - 1; y += 2) { | 
|  | for (int x = 0; x < width - 1; x += 2) { | 
|  | int i = 0; | 
|  | for (int yy = y - window_size / 2; yy <= y + window_size / 2; yy++) { | 
|  | for (int xx = x - window_size / 2; xx <= x + window_size / 2; xx++) { | 
|  | int yvalue = yy; | 
|  | int xvalue = xx; | 
|  | // copied pixels outside the boundary | 
|  | if (yvalue < 0) yvalue = 0; | 
|  | if (xvalue < 0) xvalue = 0; | 
|  | if (yvalue >= height) yvalue = height - 1; | 
|  | if (xvalue >= width) xvalue = width - 1; | 
|  | img_section[i++] = img[yvalue * stride + xvalue]; | 
|  | } | 
|  | } | 
|  | reduced_img[(y / 2) * new_width + (x / 2)] = (uint8_t)convolve( | 
|  | gaussian_filter, img_section, window_size * window_size); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | static int cmpfunc(const void *a, const void *b) { | 
|  | return (*(int *)a - *(int *)b); | 
|  | } | 
|  | static void filter_mvs(const MV_FILTER_TYPE mv_filter, const int frame_height, | 
|  | const int frame_width, LOCALMV *localmvs, MV *mvs) { | 
|  | const int n = 5;  // window size | 
|  | // for smoothing filter | 
|  | const double gaussian_filter[25] = { | 
|  | 1. / 256, 1. / 64,  3. / 128, 1. / 64,  1. / 256, 1. / 64, 1. / 16, | 
|  | 3. / 32,  1. / 16,  1. / 64,  3. / 128, 3. / 32,  9. / 64, 3. / 32, | 
|  | 3. / 128, 1. / 64,  1. / 16,  3. / 32,  1. / 16,  1. / 64, 1. / 256, | 
|  | 1. / 64,  3. / 128, 1. / 64,  1. / 256 | 
|  | }; | 
|  | // for median filter | 
|  | int mvrows[25]; | 
|  | int mvcols[25]; | 
|  | if (mv_filter != MV_FILTER_NONE) { | 
|  | for (int y = 0; y < frame_height; y++) { | 
|  | for (int x = 0; x < frame_width; x++) { | 
|  | int center_idx = y * frame_width + x; | 
|  | int i = 0; | 
|  | double filtered_row = 0; | 
|  | double filtered_col = 0; | 
|  | for (int yy = y - n / 2; yy <= y + n / 2; yy++) { | 
|  | for (int xx = x - n / 2; xx <= x + n / 2; xx++) { | 
|  | int yvalue = yy; | 
|  | int xvalue = xx; | 
|  | // copied pixels outside the boundary | 
|  | if (yvalue < 0) yvalue = 0; | 
|  | if (xvalue < 0) xvalue = 0; | 
|  | if (yvalue >= frame_height) yvalue = frame_height - 1; | 
|  | if (xvalue >= frame_width) xvalue = frame_width - 1; | 
|  | int index = yvalue * frame_width + xvalue; | 
|  | if (mv_filter == MV_FILTER_SMOOTH) { | 
|  | filtered_row += mvs[index].row * gaussian_filter[i]; | 
|  | filtered_col += mvs[index].col * gaussian_filter[i]; | 
|  | } else if (mv_filter == MV_FILTER_MEDIAN) { | 
|  | mvrows[i] = mvs[index].row; | 
|  | mvcols[i] = mvs[index].col; | 
|  | } | 
|  | i++; | 
|  | } | 
|  | } | 
|  |  | 
|  | MV mv = mvs[center_idx]; | 
|  | if (mv_filter == MV_FILTER_SMOOTH) { | 
|  | mv.row = (int16_t)filtered_row; | 
|  | mv.col = (int16_t)filtered_col; | 
|  | } else if (mv_filter == MV_FILTER_MEDIAN) { | 
|  | qsort(mvrows, 25, sizeof(mv.row), cmpfunc); | 
|  | qsort(mvcols, 25, sizeof(mv.col), cmpfunc); | 
|  | mv.row = mvrows[25 / 2]; | 
|  | mv.col = mvcols[25 / 2]; | 
|  | } | 
|  | LOCALMV localmv = { .row = ((double)mv.row) / 8, | 
|  | .col = ((double)mv.row) / 8 }; | 
|  | localmvs[y * frame_width + x] = localmv; | 
|  | // if mvs array is immediately updated here, then the result may | 
|  | // propagate to other pixels. | 
|  | } | 
|  | } | 
|  | for (int i = 0; i < frame_height * frame_width; i++) { | 
|  | MV mv = { .row = (int16_t)round(8 * localmvs[i].row), | 
|  | .col = (int16_t)round(8 * localmvs[i].col) }; | 
|  | mvs[i] = mv; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Computes optical flow at a single pyramid level, | 
|  | // using Lucas-Kanade algorithm. | 
|  | // Modifies mvs array. | 
|  | static void lucas_kanade(const YV12_BUFFER_CONFIG *from_frame, | 
|  | const YV12_BUFFER_CONFIG *to_frame, const int level, | 
|  | const LK_PARAMS *lk_params, const int num_ref_corners, | 
|  | int *ref_corners, const int mv_stride, | 
|  | const int bit_depth, LOCALMV *mvs) { | 
|  | assert(lk_params->window_size > 0 && lk_params->window_size % 2 == 0); | 
|  | const int n = lk_params->window_size; | 
|  | // algorithm is sensitive to window size | 
|  | double *i_x = (double *)aom_malloc(n * n * sizeof(*i_x)); | 
|  | double *i_y = (double *)aom_malloc(n * n * sizeof(*i_y)); | 
|  | double *i_t = (double *)aom_malloc(n * n * sizeof(*i_t)); | 
|  | const int expand_multiplier = (int)pow(2, level); | 
|  | double sigma = 0.2 * n; | 
|  | double *weights = (double *)aom_malloc(n * n * sizeof(*weights)); | 
|  | // normalizing doesn't really affect anything since it's applied | 
|  | // to every component of M and b | 
|  | gaussian(sigma, n, 0, weights); | 
|  | for (int i = 0; i < num_ref_corners; i++) { | 
|  | const double x_coord = 1.0 * ref_corners[i * 2] / expand_multiplier; | 
|  | const double y_coord = 1.0 * ref_corners[i * 2 + 1] / expand_multiplier; | 
|  | int highres_x = ref_corners[i * 2]; | 
|  | int highres_y = ref_corners[i * 2 + 1]; | 
|  | int mv_idx = highres_y * (mv_stride) + highres_x; | 
|  | LOCALMV mv_old = mvs[mv_idx]; | 
|  | mv_old.row = mv_old.row / expand_multiplier; | 
|  | mv_old.col = mv_old.col / expand_multiplier; | 
|  | // using this instead of memset, since it's not completely | 
|  | // clear if zero memset works on double arrays | 
|  | for (int j = 0; j < n * n; j++) { | 
|  | i_x[j] = 0; | 
|  | i_y[j] = 0; | 
|  | i_t[j] = 0; | 
|  | } | 
|  | gradients_over_window(from_frame, to_frame, x_coord, y_coord, n, bit_depth, | 
|  | i_x, i_y, i_t, &mv_old); | 
|  | double Mres1[1] = { 0 }, Mres2[1] = { 0 }, Mres3[1] = { 0 }; | 
|  | double bres1[1] = { 0 }, bres2[1] = { 0 }; | 
|  | for (int j = 0; j < n * n; j++) { | 
|  | Mres1[0] += weights[j] * i_x[j] * i_x[j]; | 
|  | Mres2[0] += weights[j] * i_x[j] * i_y[j]; | 
|  | Mres3[0] += weights[j] * i_y[j] * i_y[j]; | 
|  | bres1[0] += weights[j] * i_x[j] * i_t[j]; | 
|  | bres2[0] += weights[j] * i_y[j] * i_t[j]; | 
|  | } | 
|  | double M[4] = { Mres1[0], Mres2[0], Mres2[0], Mres3[0] }; | 
|  | double b[2] = { -1 * bres1[0], -1 * bres2[0] }; | 
|  | double eig[2] = { 1, 1 }; | 
|  | eigenvalues_2x2(M, eig); | 
|  | double threshold = 0.1; | 
|  | if (fabs(eig[0]) > threshold) { | 
|  | // if M is not invertible, then displacement | 
|  | // will default to zeros | 
|  | double u[2] = { 0, 0 }; | 
|  | linsolve(2, M, 2, b, u); | 
|  | int mult = 1; | 
|  | if (level != 0) | 
|  | mult = expand_multiplier;  // mv doubles when resolution doubles | 
|  | LOCALMV mv = { .row = (mult * (u[0] + mv_old.row)), | 
|  | .col = (mult * (u[1] + mv_old.col)) }; | 
|  | mvs[mv_idx] = mv; | 
|  | mvs[mv_idx] = mv; | 
|  | } | 
|  | } | 
|  | aom_free(weights); | 
|  | aom_free(i_t); | 
|  | aom_free(i_x); | 
|  | aom_free(i_y); | 
|  | } | 
|  |  | 
|  | // Warp the src_frame to warper_frame according to mvs. | 
|  | // mvs point to src_frame | 
|  | static void warp_back_frame(YV12_BUFFER_CONFIG *warped_frame, | 
|  | const YV12_BUFFER_CONFIG *src_frame, | 
|  | const LOCALMV *mvs, int mv_stride) { | 
|  | int w, h; | 
|  | const int fw = src_frame->y_crop_width; | 
|  | const int fh = src_frame->y_crop_height; | 
|  | const int src_fs = src_frame->y_stride, warped_fs = warped_frame->y_stride; | 
|  | const uint8_t *src_buf = src_frame->y_buffer; | 
|  | uint8_t *warped_buf = warped_frame->y_buffer; | 
|  | double temp; | 
|  | for (h = 0; h < fh; h++) { | 
|  | for (w = 0; w < fw; w++) { | 
|  | double cord_x = (double)w + mvs[h * mv_stride + w].col; | 
|  | double cord_y = (double)h + mvs[h * mv_stride + w].row; | 
|  | cord_x = fclamp(cord_x, 0, (double)(fw - 1)); | 
|  | cord_y = fclamp(cord_y, 0, (double)(fh - 1)); | 
|  | const int floorx = (int)floor(cord_x); | 
|  | const int floory = (int)floor(cord_y); | 
|  | const double fracx = cord_x - (double)floorx; | 
|  | const double fracy = cord_y - (double)floory; | 
|  |  | 
|  | temp = 0; | 
|  | for (int hh = 0; hh < 2; hh++) { | 
|  | const double weighth = hh ? (fracy) : (1 - fracy); | 
|  | for (int ww = 0; ww < 2; ww++) { | 
|  | const double weightw = ww ? (fracx) : (1 - fracx); | 
|  | int y = floory + hh; | 
|  | int x = floorx + ww; | 
|  | y = clamp(y, 0, fh - 1); | 
|  | x = clamp(x, 0, fw - 1); | 
|  | temp += (double)src_buf[y * src_fs + x] * weightw * weighth; | 
|  | } | 
|  | } | 
|  | warped_buf[h * warped_fs + w] = (uint8_t)round(temp); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Same as warp_back_frame, but using a better interpolation filter. | 
|  | static void warp_back_frame_intp(YV12_BUFFER_CONFIG *warped_frame, | 
|  | const YV12_BUFFER_CONFIG *src_frame, | 
|  | const LOCALMV *mvs, int mv_stride) { | 
|  | int w, h; | 
|  | const int fw = src_frame->y_crop_width; | 
|  | const int fh = src_frame->y_crop_height; | 
|  | const int warped_fs = warped_frame->y_stride; | 
|  | uint8_t *warped_buf = warped_frame->y_buffer; | 
|  | const int blk = 2; | 
|  | uint8_t temp_blk[4]; | 
|  |  | 
|  | const int is_intrabc = 0;  // Is intra-copied? | 
|  | const int is_high_bitdepth = is_frame_high_bitdepth(src_frame); | 
|  | const int subsampling_x = 0, subsampling_y = 0;  // for y-buffer | 
|  | const int_interpfilters interp_filters = | 
|  | av1_broadcast_interp_filter(MULTITAP_SHARP2); | 
|  | const int plane = 0;  // y-plane | 
|  | const struct buf_2d ref_buf2 = { NULL, src_frame->y_buffer, | 
|  | src_frame->y_crop_width, | 
|  | src_frame->y_crop_height, | 
|  | src_frame->y_stride }; | 
|  | const int bit_depth = src_frame->bit_depth; | 
|  | struct scale_factors scale; | 
|  | av1_setup_scale_factors_for_frame( | 
|  | &scale, src_frame->y_crop_width, src_frame->y_crop_height, | 
|  | src_frame->y_crop_width, src_frame->y_crop_height); | 
|  |  | 
|  | for (h = 0; h < fh; h++) { | 
|  | for (w = 0; w < fw; w++) { | 
|  | InterPredParams inter_pred_params; | 
|  | av1_init_inter_params(&inter_pred_params, blk, blk, h, w, subsampling_x, | 
|  | subsampling_y, bit_depth, is_high_bitdepth, | 
|  | is_intrabc, &scale, &ref_buf2, interp_filters); | 
|  | inter_pred_params.interp_filter_params[0] = | 
|  | &av1_interp_filter_params_list[interp_filters.as_filters.x_filter]; | 
|  | inter_pred_params.interp_filter_params[1] = | 
|  | &av1_interp_filter_params_list[interp_filters.as_filters.y_filter]; | 
|  | inter_pred_params.conv_params = get_conv_params(0, plane, bit_depth); | 
|  | MV newmv = { .row = (int16_t)round((mvs[h * mv_stride + w].row) * 8), | 
|  | .col = (int16_t)round((mvs[h * mv_stride + w].col) * 8) }; | 
|  | av1_enc_build_one_inter_predictor(temp_blk, blk, &newmv, | 
|  | &inter_pred_params); | 
|  | warped_buf[h * warped_fs + w] = temp_blk[0]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | #define DERIVATIVE_FILTER_LENGTH 7 | 
|  | double filter[DERIVATIVE_FILTER_LENGTH] = { -1.0 / 60, 9.0 / 60,  -45.0 / 60, 0, | 
|  | 45.0 / 60, -9.0 / 60, 1.0 / 60 }; | 
|  |  | 
|  | // Get gradient of the whole frame | 
|  | static void get_frame_gradients(const YV12_BUFFER_CONFIG *from_frame, | 
|  | const YV12_BUFFER_CONFIG *to_frame, double *ix, | 
|  | double *iy, double *it, int grad_stride) { | 
|  | int w, h, k, idx; | 
|  | const int fw = from_frame->y_crop_width; | 
|  | const int fh = from_frame->y_crop_height; | 
|  | const int from_fs = from_frame->y_stride, to_fs = to_frame->y_stride; | 
|  | const uint8_t *from_buf = from_frame->y_buffer; | 
|  | const uint8_t *to_buf = to_frame->y_buffer; | 
|  |  | 
|  | const int lh = DERIVATIVE_FILTER_LENGTH; | 
|  | const int hleft = (lh - 1) / 2; | 
|  |  | 
|  | for (h = 0; h < fh; h++) { | 
|  | for (w = 0; w < fw; w++) { | 
|  | // x | 
|  | ix[h * grad_stride + w] = 0; | 
|  | for (k = 0; k < lh; k++) { | 
|  | // if we want to make this block dependent, need to extend the | 
|  | // boundaries using other initializations. | 
|  | idx = w + k - hleft; | 
|  | idx = clamp(idx, 0, fw - 1); | 
|  | ix[h * grad_stride + w] += filter[k] * 0.5 * | 
|  | ((double)from_buf[h * from_fs + idx] + | 
|  | (double)to_buf[h * to_fs + idx]); | 
|  | } | 
|  | // y | 
|  | iy[h * grad_stride + w] = 0; | 
|  | for (k = 0; k < lh; k++) { | 
|  | // if we want to make this block dependent, need to extend the | 
|  | // boundaries using other initializations. | 
|  | idx = h + k - hleft; | 
|  | idx = clamp(idx, 0, fh - 1); | 
|  | iy[h * grad_stride + w] += filter[k] * 0.5 * | 
|  | ((double)from_buf[idx * from_fs + w] + | 
|  | (double)to_buf[idx * to_fs + w]); | 
|  | } | 
|  | // t | 
|  | it[h * grad_stride + w] = | 
|  | (double)to_buf[h * to_fs + w] - (double)from_buf[h * from_fs + w]; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Solve for linear equations given by the H-S method | 
|  | static void solve_horn_schunck(const double *ix, const double *iy, | 
|  | const double *it, int grad_stride, int width, | 
|  | int height, const LOCALMV *init_mvs, | 
|  | int init_mv_stride, LOCALMV *mvs, | 
|  | int mv_stride) { | 
|  | // TODO(bohanli): May just need to allocate the buffers once per optical flow | 
|  | // calculation | 
|  | int *row_pos = aom_calloc(width * height * 28, sizeof(*row_pos)); | 
|  | int *col_pos = aom_calloc(width * height * 28, sizeof(*col_pos)); | 
|  | double *values = aom_calloc(width * height * 28, sizeof(*values)); | 
|  | double *mv_vec = aom_calloc(width * height * 2, sizeof(*mv_vec)); | 
|  | double *mv_init_vec = aom_calloc(width * height * 2, sizeof(*mv_init_vec)); | 
|  | double *temp_b = aom_calloc(width * height * 2, sizeof(*temp_b)); | 
|  | double *b = aom_calloc(width * height * 2, sizeof(*b)); | 
|  |  | 
|  | // the location idx for neighboring pixels, k < 4 are the 4 direct neighbors | 
|  | const int check_locs_y[12] = { 0, 0, -1, 1, -1, -1, 1, 1, 0, 0, -2, 2 }; | 
|  | const int check_locs_x[12] = { -1, 1, 0, 0, -1, 1, -1, 1, -2, 2, 0, 0 }; | 
|  |  | 
|  | int h, w, checkh, checkw, k; | 
|  | const int offset = height * width; | 
|  | SPARSE_MTX A; | 
|  | int c = 0; | 
|  | const double lambda = 100; | 
|  |  | 
|  | for (w = 0; w < width; w++) { | 
|  | for (h = 0; h < height; h++) { | 
|  | mv_init_vec[w * height + h] = init_mvs[h * init_mv_stride + w].col; | 
|  | mv_init_vec[w * height + h + offset] = | 
|  | init_mvs[h * init_mv_stride + w].row; | 
|  | } | 
|  | } | 
|  |  | 
|  | // get matrix A | 
|  | for (w = 0; w < width; w++) { | 
|  | for (h = 0; h < height; h++) { | 
|  | int center_num_direct = 4; | 
|  | const int center_idx = w * height + h; | 
|  | if (w == 0 || w == width - 1) center_num_direct--; | 
|  | if (h == 0 || h == height - 1) center_num_direct--; | 
|  | // diagonal entry for this row from the center pixel | 
|  | double cor_w = center_num_direct * center_num_direct + center_num_direct; | 
|  | row_pos[c] = center_idx; | 
|  | col_pos[c] = center_idx; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | row_pos[c] = center_idx + offset; | 
|  | col_pos[c] = center_idx + offset; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | // other entries from direct neighbors | 
|  | for (k = 0; k < 4; k++) { | 
|  | checkh = h + check_locs_y[k]; | 
|  | checkw = w + check_locs_x[k]; | 
|  | if (checkh < 0 || checkh >= height || checkw < 0 || checkw >= width) { | 
|  | continue; | 
|  | } | 
|  | int this_idx = checkw * height + checkh; | 
|  | int this_num_direct = 4; | 
|  | if (checkw == 0 || checkw == width - 1) this_num_direct--; | 
|  | if (checkh == 0 || checkh == height - 1) this_num_direct--; | 
|  | cor_w = -center_num_direct - this_num_direct; | 
|  | row_pos[c] = center_idx; | 
|  | col_pos[c] = this_idx; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | row_pos[c] = center_idx + offset; | 
|  | col_pos[c] = this_idx + offset; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | } | 
|  | // entries from neighbors on the diagonal corners | 
|  | for (k = 4; k < 8; k++) { | 
|  | checkh = h + check_locs_y[k]; | 
|  | checkw = w + check_locs_x[k]; | 
|  | if (checkh < 0 || checkh >= height || checkw < 0 || checkw >= width) { | 
|  | continue; | 
|  | } | 
|  | int this_idx = checkw * height + checkh; | 
|  | cor_w = 2; | 
|  | row_pos[c] = center_idx; | 
|  | col_pos[c] = this_idx; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | row_pos[c] = center_idx + offset; | 
|  | col_pos[c] = this_idx + offset; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | } | 
|  | // entries from neighbors with dist of 2 | 
|  | for (k = 8; k < 12; k++) { | 
|  | checkh = h + check_locs_y[k]; | 
|  | checkw = w + check_locs_x[k]; | 
|  | if (checkh < 0 || checkh >= height || checkw < 0 || checkw >= width) { | 
|  | continue; | 
|  | } | 
|  | int this_idx = checkw * height + checkh; | 
|  | cor_w = 1; | 
|  | row_pos[c] = center_idx; | 
|  | col_pos[c] = this_idx; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | row_pos[c] = center_idx + offset; | 
|  | col_pos[c] = this_idx + offset; | 
|  | values[c] = lambda * cor_w; | 
|  | c++; | 
|  | } | 
|  | } | 
|  | } | 
|  | av1_init_sparse_mtx(row_pos, col_pos, values, c, 2 * width * height, | 
|  | 2 * width * height, &A); | 
|  | // subtract init mv part from b | 
|  | av1_mtx_vect_multi_left(&A, mv_init_vec, temp_b, 2 * width * height); | 
|  | for (int i = 0; i < 2 * width * height; i++) { | 
|  | b[i] = -temp_b[i]; | 
|  | } | 
|  | av1_free_sparse_mtx_elems(&A); | 
|  |  | 
|  | // add cross terms to A and modify b with ExEt / EyEt | 
|  | for (w = 0; w < width; w++) { | 
|  | for (h = 0; h < height; h++) { | 
|  | int curidx = w * height + h; | 
|  | // modify b | 
|  | b[curidx] += -ix[h * grad_stride + w] * it[h * grad_stride + w]; | 
|  | b[curidx + offset] += -iy[h * grad_stride + w] * it[h * grad_stride + w]; | 
|  | // add cross terms to A | 
|  | row_pos[c] = curidx; | 
|  | col_pos[c] = curidx + offset; | 
|  | values[c] = ix[h * grad_stride + w] * iy[h * grad_stride + w]; | 
|  | c++; | 
|  | row_pos[c] = curidx + offset; | 
|  | col_pos[c] = curidx; | 
|  | values[c] = ix[h * grad_stride + w] * iy[h * grad_stride + w]; | 
|  | c++; | 
|  | } | 
|  | } | 
|  | // Add diagonal terms to A | 
|  | for (int i = 0; i < c; i++) { | 
|  | if (row_pos[i] == col_pos[i]) { | 
|  | if (row_pos[i] < offset) { | 
|  | w = row_pos[i] / height; | 
|  | h = row_pos[i] % height; | 
|  | values[i] += pow(ix[h * grad_stride + w], 2); | 
|  | } else { | 
|  | w = (row_pos[i] - offset) / height; | 
|  | h = (row_pos[i] - offset) % height; | 
|  | values[i] += pow(iy[h * grad_stride + w], 2); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | av1_init_sparse_mtx(row_pos, col_pos, values, c, 2 * width * height, | 
|  | 2 * width * height, &A); | 
|  |  | 
|  | // solve for the mvs | 
|  | av1_conjugate_gradient_sparse(&A, b, 2 * width * height, mv_vec); | 
|  | // copy mvs | 
|  | for (w = 0; w < width; w++) { | 
|  | for (h = 0; h < height; h++) { | 
|  | mvs[h * mv_stride + w].col = mv_vec[w * height + h]; | 
|  | mvs[h * mv_stride + w].row = mv_vec[w * height + h + offset]; | 
|  | } | 
|  | } | 
|  | aom_free(row_pos); | 
|  | aom_free(col_pos); | 
|  | aom_free(values); | 
|  | aom_free(mv_vec); | 
|  | aom_free(mv_init_vec); | 
|  | aom_free(b); | 
|  | aom_free(temp_b); | 
|  | av1_free_sparse_mtx_elems(&A); | 
|  | } | 
|  |  | 
|  | // Calculate optical flow from from_frame to to_frame using the H-S method. | 
|  | static void horn_schunck(const YV12_BUFFER_CONFIG *from_frame, | 
|  | const YV12_BUFFER_CONFIG *to_frame, const int level, | 
|  | const int mv_stride, const int mv_height, | 
|  | const int mv_width, const OPFL_PARAMS *opfl_params, | 
|  | LOCALMV *mvs) { | 
|  | // mvs are always on level 0, here we define two new mv arrays that is of size | 
|  | // of this level. | 
|  | const int fw = from_frame->y_crop_width; | 
|  | const int fh = from_frame->y_crop_height; | 
|  | const int factor = (int)pow(2, level); | 
|  | int w, h, k, init_mv_stride; | 
|  | LOCALMV *init_mvs; | 
|  | if (level == 0) { | 
|  | init_mvs = mvs; | 
|  | init_mv_stride = mv_stride; | 
|  | } else { | 
|  | init_mvs = aom_calloc(fw * fh, sizeof(*mvs)); | 
|  | init_mv_stride = fw; | 
|  | for (h = 0; h < fh; h++) { | 
|  | for (w = 0; w < fw; w++) { | 
|  | init_mvs[h * init_mv_stride + w].row = | 
|  | mvs[h * factor * mv_stride + w * factor].row / (double)factor; | 
|  | init_mvs[h * init_mv_stride + w].col = | 
|  | mvs[h * factor * mv_stride + w * factor].col / (double)factor; | 
|  | } | 
|  | } | 
|  | } | 
|  | LOCALMV *refine_mvs = aom_calloc(fw * fh, sizeof(*mvs)); | 
|  | // temp frame for warping | 
|  | YV12_BUFFER_CONFIG temp_frame; | 
|  | temp_frame.y_buffer = | 
|  | (uint8_t *)aom_calloc(fh * fw, sizeof(*temp_frame.y_buffer)); | 
|  | temp_frame.y_crop_height = fh; | 
|  | temp_frame.y_crop_width = fw; | 
|  | temp_frame.y_stride = fw; | 
|  | // gradient buffers | 
|  | double *ix = aom_calloc(fw * fh, sizeof(*ix)); | 
|  | double *iy = aom_calloc(fw * fh, sizeof(*iy)); | 
|  | double *it = aom_calloc(fw * fh, sizeof(*it)); | 
|  | // For each warping step | 
|  | for (k = 0; k < opfl_params->warping_steps; k++) { | 
|  | // warp from_frame with init_mv | 
|  | if (level == 0) { | 
|  | warp_back_frame_intp(&temp_frame, to_frame, init_mvs, init_mv_stride); | 
|  | } else { | 
|  | warp_back_frame(&temp_frame, to_frame, init_mvs, init_mv_stride); | 
|  | } | 
|  | // calculate frame gradients | 
|  | get_frame_gradients(from_frame, &temp_frame, ix, iy, it, fw); | 
|  | // form linear equations and solve mvs | 
|  | solve_horn_schunck(ix, iy, it, fw, fw, fh, init_mvs, init_mv_stride, | 
|  | refine_mvs, fw); | 
|  | // update init_mvs | 
|  | for (h = 0; h < fh; h++) { | 
|  | for (w = 0; w < fw; w++) { | 
|  | init_mvs[h * init_mv_stride + w].col += refine_mvs[h * fw + w].col; | 
|  | init_mvs[h * init_mv_stride + w].row += refine_mvs[h * fw + w].row; | 
|  | } | 
|  | } | 
|  | } | 
|  | // copy back the mvs if needed | 
|  | if (level != 0) { | 
|  | for (h = 0; h < mv_height; h++) { | 
|  | for (w = 0; w < mv_width; w++) { | 
|  | mvs[h * mv_stride + w].row = | 
|  | init_mvs[h / factor * init_mv_stride + w / factor].row * | 
|  | (double)factor; | 
|  | mvs[h * mv_stride + w].col = | 
|  | init_mvs[h / factor * init_mv_stride + w / factor].col * | 
|  | (double)factor; | 
|  | } | 
|  | } | 
|  | } | 
|  | if (level != 0) aom_free(init_mvs); | 
|  | aom_free(refine_mvs); | 
|  | aom_free(temp_frame.y_buffer); | 
|  | aom_free(ix); | 
|  | aom_free(iy); | 
|  | aom_free(it); | 
|  | } | 
|  |  | 
|  | // Apply optical flow iteratively at each pyramid level | 
|  | static void pyramid_optical_flow(const YV12_BUFFER_CONFIG *from_frame, | 
|  | const YV12_BUFFER_CONFIG *to_frame, | 
|  | const int bit_depth, | 
|  | const OPFL_PARAMS *opfl_params, | 
|  | const OPTFLOW_METHOD method, LOCALMV *mvs) { | 
|  | assert(opfl_params->pyramid_levels > 0 && | 
|  | opfl_params->pyramid_levels <= MAX_PYRAMID_LEVELS); | 
|  | int levels = opfl_params->pyramid_levels; | 
|  | const int frame_height = from_frame->y_crop_height; | 
|  | const int frame_width = from_frame->y_crop_width; | 
|  | if ((frame_height / pow(2.0, levels - 1) < 50 || | 
|  | frame_height / pow(2.0, levels - 1) < 50) && | 
|  | levels > 1) | 
|  | levels = levels - 1; | 
|  | uint8_t *images1[MAX_PYRAMID_LEVELS]; | 
|  | uint8_t *images2[MAX_PYRAMID_LEVELS]; | 
|  | images1[0] = from_frame->y_buffer; | 
|  | images2[0] = to_frame->y_buffer; | 
|  | YV12_BUFFER_CONFIG *buffers1 = aom_malloc(levels * sizeof(*buffers1)); | 
|  | YV12_BUFFER_CONFIG *buffers2 = aom_malloc(levels * sizeof(*buffers2)); | 
|  | buffers1[0] = *from_frame; | 
|  | buffers2[0] = *to_frame; | 
|  | int fw = frame_width; | 
|  | int fh = frame_height; | 
|  | for (int i = 1; i < levels; i++) { | 
|  | // TODO(bohanli): may need to extend buffers for better interpolation SIMD | 
|  | images1[i] = (uint8_t *)aom_calloc(fh / 2 * fw / 2, sizeof(*images1[i])); | 
|  | images2[i] = (uint8_t *)aom_calloc(fh / 2 * fw / 2, sizeof(*images2[i])); | 
|  | int stride; | 
|  | if (i == 1) | 
|  | stride = from_frame->y_stride; | 
|  | else | 
|  | stride = fw; | 
|  | reduce(images1[i - 1], fh, fw, stride, images1[i]); | 
|  | reduce(images2[i - 1], fh, fw, stride, images2[i]); | 
|  | fh /= 2; | 
|  | fw /= 2; | 
|  | YV12_BUFFER_CONFIG a = { .y_buffer = images1[i], | 
|  | .y_crop_width = fw, | 
|  | .y_crop_height = fh, | 
|  | .y_stride = fw }; | 
|  | YV12_BUFFER_CONFIG b = { .y_buffer = images2[i], | 
|  | .y_crop_width = fw, | 
|  | .y_crop_height = fh, | 
|  | .y_stride = fw }; | 
|  | buffers1[i] = a; | 
|  | buffers2[i] = b; | 
|  | } | 
|  | // Compute corners for specific frame | 
|  | int *ref_corners = NULL; | 
|  | int num_ref_corners = 0; | 
|  | if (is_sparse(opfl_params)) { | 
|  | int maxcorners = from_frame->y_crop_width * from_frame->y_crop_height; | 
|  | ref_corners = aom_malloc(maxcorners * 2 * sizeof(*ref_corners)); | 
|  | num_ref_corners = detect_corners(from_frame, to_frame, maxcorners, | 
|  | ref_corners, bit_depth); | 
|  | } | 
|  | const int stop_level = 0; | 
|  | for (int i = levels - 1; i >= stop_level; i--) { | 
|  | if (method == LUCAS_KANADE) { | 
|  | assert(is_sparse(opfl_params)); | 
|  | lucas_kanade(&buffers1[i], &buffers2[i], i, opfl_params->lk_params, | 
|  | num_ref_corners, ref_corners, buffers1[0].y_crop_width, | 
|  | bit_depth, mvs); | 
|  | } else if (method == HORN_SCHUNCK) { | 
|  | assert(!is_sparse(opfl_params)); | 
|  | horn_schunck(&buffers1[i], &buffers2[i], i, buffers1[0].y_crop_width, | 
|  | buffers1[0].y_crop_height, buffers1[0].y_crop_width, | 
|  | opfl_params, mvs); | 
|  | } | 
|  | } | 
|  | for (int i = 1; i < levels; i++) { | 
|  | aom_free(images1[i]); | 
|  | aom_free(images2[i]); | 
|  | } | 
|  | aom_free(ref_corners); | 
|  | aom_free(buffers1); | 
|  | aom_free(buffers2); | 
|  | } | 
|  | // Computes optical flow by applying algorithm at | 
|  | // multiple pyramid levels of images (lower-resolution, smoothed images) | 
|  | // This accounts for larger motions. | 
|  | // Inputs: | 
|  | //   from_frame Frame buffer. | 
|  | //   to_frame: Frame buffer. MVs point from_frame -> to_frame. | 
|  | //   from_frame_idx: Index of from_frame. | 
|  | //   to_frame_idx: Index of to_frame. Return all zero MVs when idx are equal. | 
|  | //   bit_depth: | 
|  | //   opfl_params: contains algorithm-specific parameters. | 
|  | //   mv_filter: MV_FILTER_NONE, MV_FILTER_SMOOTH, or MV_FILTER_MEDIAN. | 
|  | //   method: LUCAS_KANADE, HORN_SCHUNCK | 
|  | //   mvs: pointer to MVs. Contains initialization, and modified | 
|  | //   based on optical flow. Must have | 
|  | //   dimensions = from_frame->y_crop_width * from_frame->y_crop_height | 
|  | void av1_optical_flow(const YV12_BUFFER_CONFIG *from_frame, | 
|  | const YV12_BUFFER_CONFIG *to_frame, | 
|  | const int from_frame_idx, const int to_frame_idx, | 
|  | const int bit_depth, const OPFL_PARAMS *opfl_params, | 
|  | const MV_FILTER_TYPE mv_filter, | 
|  | const OPTFLOW_METHOD method, MV *mvs) { | 
|  | const int frame_height = from_frame->y_crop_height; | 
|  | const int frame_width = from_frame->y_crop_width; | 
|  | // TODO(any): deal with the case where frames are not of the same dimensions | 
|  | assert(frame_height == to_frame->y_crop_height && | 
|  | frame_width == to_frame->y_crop_width); | 
|  | if (from_frame_idx == to_frame_idx) { | 
|  | // immediately return all zero mvs when frame indices are equal | 
|  | for (int yy = 0; yy < frame_height; yy++) { | 
|  | for (int xx = 0; xx < frame_width; xx++) { | 
|  | MV mv = { .row = 0, .col = 0 }; | 
|  | mvs[yy * frame_width + xx] = mv; | 
|  | } | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Initialize double mvs based on input parameter mvs array | 
|  | LOCALMV *localmvs = | 
|  | aom_malloc(frame_height * frame_width * sizeof(*localmvs)); | 
|  |  | 
|  | filter_mvs(MV_FILTER_SMOOTH, frame_height, frame_width, localmvs, mvs); | 
|  |  | 
|  | for (int i = 0; i < frame_width * frame_height; i++) { | 
|  | MV mv = mvs[i]; | 
|  | LOCALMV localmv = { .row = ((double)mv.row) / 8, | 
|  | .col = ((double)mv.col) / 8 }; | 
|  | localmvs[i] = localmv; | 
|  | } | 
|  | // Apply optical flow algorithm | 
|  | pyramid_optical_flow(from_frame, to_frame, bit_depth, opfl_params, method, | 
|  | localmvs); | 
|  |  | 
|  | // Update original mvs array | 
|  | for (int j = 0; j < frame_height; j++) { | 
|  | for (int i = 0; i < frame_width; i++) { | 
|  | int idx = j * frame_width + i; | 
|  | if (j + localmvs[idx].row < 0 || j + localmvs[idx].row >= frame_height || | 
|  | i + localmvs[idx].col < 0 || i + localmvs[idx].col >= frame_width) { | 
|  | continue; | 
|  | } | 
|  | MV mv = { .row = (int16_t)round(8 * localmvs[idx].row), | 
|  | .col = (int16_t)round(8 * localmvs[idx].col) }; | 
|  | mvs[idx] = mv; | 
|  | } | 
|  | } | 
|  |  | 
|  | filter_mvs(mv_filter, frame_height, frame_width, localmvs, mvs); | 
|  |  | 
|  | aom_free(localmvs); | 
|  | } | 
|  | #endif |