// 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;
}
// coefficients for bilinear interpolation on unit square static int pixel_interp(constdouble x, constdouble y, constdouble b00, constdouble b01, constdouble b10, constdouble b11) { const int xint = (int)x; const int yint = (int)y; constdouble xdec = x - xint; constdouble ydec = y - yint; constdouble a = (1 - xdec) * (1 - ydec); constdouble b = xdec * (1 - ydec); constdouble c = (1 - xdec) * ydec; constdouble 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;
}
// Scharr filter to compute spatial gradient staticvoid 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. staticvoid gradient_interp(double *fullpel_deriv, constdouble x_coord, constdouble 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;
}
staticvoid temporal_gradient(const YV12_BUFFER_CONFIG *frame, const YV12_BUFFER_CONFIG *frame2, constdouble x_coord, constdouble 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];
// 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 staticvoid eigenvalues_2x2(constdouble *matrix, double *eig) { constdouble a = 1; constdouble b = -1 * matrix[0] - matrix[3]; constdouble c = -1 * matrix[1] * matrix[2] + matrix[0] * matrix[3]; // quadratic formula constdouble 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 staticdouble 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; constdouble 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. staticvoid gaussian(constdouble 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;
}
}
}
staticdouble convolve(constdouble *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 staticvoid 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; constdouble 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(constvoid *a, constvoid *b) { return (*(int *)a - *(int *)b);
} staticvoid 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 constdouble 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. staticvoid 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)); double *weights = (double *)aom_malloc(n * n * sizeof(*weights));
if (!i_x || !i_y || !i_t || !weights) goto free_lk_buf;
const int expand_multiplier = (int)pow(2, level); double sigma = 0.2 * n; // 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++) { constdouble x_coord = 1.0 * ref_corners[i * 2] / expand_multiplier; constdouble 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;
}
}
free_lk_buf:
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 staticvoid 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); constdouble fracx = cord_x - (double)floorx; constdouble fracy = cord_y - (double)floory;
// Same as warp_back_frame, but using a better interpolation filter. staticvoid 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 conststruct 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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.