Add adaptive QP for 360 degree video

Adds option --erp-aqp for enabling adaptive QP for 360 degree video with
equirectangular projection. When projected into a spherical surface,
the middle part of the video covers relatively larger area than the top
and bottom parts. Enabling --erp-aqp sets up a ROI delta QP array which
uses higher QPs for the top and bottom of the video and lower QPs for
the middle part.
This commit is contained in:
Arttu Ylä-Outinen 2017-04-19 15:47:47 +03:00
parent 79cb3a2fd3
commit ee3d4d0e78
6 changed files with 117 additions and 4 deletions

View file

@ -100,6 +100,8 @@ Video structure:
delta QP values in raster order.
The delta QP map can be any size or aspect
ratio, and will be mapped to LCU's.
--(no-)erp-aqp : Use adaptive QP for 360 video with
equirectangular projection
Compression tools:
--deblock [<beta:tc>] : Deblocking

View file

@ -1,4 +1,4 @@
.TH KVAZAAR "1" "February 2017" "kvazaar v1.1.0" "User Commands"
.TH KVAZAAR "1" "April 2017" "kvazaar v1.1.0" "User Commands"
.SH NAME
kvazaar \- open source HEVC encoder
.SH SYNOPSIS
@ -131,6 +131,10 @@ Use a delta QP map for region of interest
delta QP values in raster order.
The delta QP map can be any size or aspect
ratio, and will be mapped to LCU's.
.TP
\fB\-\-(no\-)erp\-aqp
Use adaptive QP for 360 video with
equirectangular projection
.SS "Compression tools:"
.TP

View file

@ -119,6 +119,8 @@ int kvz_config_init(kvz_config *cfg)
cfg->roi.height = 0;
cfg->roi.dqps = NULL;
cfg->erp_aqp = false;
cfg->slices = KVZ_SLICES_NONE;
return 1;
@ -1045,6 +1047,8 @@ int kvz_config_parse(kvz_config *cfg, const char *name, const char *value)
fclose(f);
}
else if OPT("erp-aqp")
cfg->erp_aqp = (bool)atobool(value);
else
return 0;
#undef OPT

View file

@ -119,6 +119,8 @@ static const struct option long_options[] = {
{ "implicit-rdpcm", no_argument, NULL, 0 },
{ "no-implicit-rdpcm", no_argument, NULL, 0 },
{ "roi", required_argument, NULL, 0 },
{ "erp-aqp", no_argument, NULL, 0 },
{ "no-erp-aqp", no_argument, NULL, 0 },
{0, 0, 0, 0}
};
@ -388,6 +390,8 @@ void print_help(void)
" delta QP values in raster order.\n"
" The delta QP map can be any size or aspect\n"
" ratio, and will be mapped to LCU's.\n"
" --(no-)erp-aqp : Use adaptive QP for 360 video with\n"
" equirectangular projection\n"
"\n"
/* Word wrap to this width to stay under 80 characters (including ") ************/
"Compression tools:\n"

View file

@ -20,6 +20,9 @@
#include "encoder.h"
// This define is required for M_PI on Windows.
#define _USE_MATH_DEFINES
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@ -27,6 +30,14 @@
#include "strategyselector.h"
/**
* \brief Strength of QP adjustments when using adaptive QP for 360 video.
*
* Determined empirically.
*/
static const double ERP_AQP_STRENGTH = 3.0;
static int encoder_control_init_gop_layer_weights(encoder_control_t * const);
static int size_of_wpp_ends(int threads)
@ -114,6 +125,82 @@ static unsigned cfg_num_threads(void)
}
/**
* \brief Return weight for 360 degree ERP video
*
* Returns the scaling factor of area from equirectangular projection to
* spherical surface.
*
* \param y y-coordinate of the pixel
* \param h height of the picture
*/
static double ws_weight(int y, int h)
{
return cos((y - 0.5 * h + 0.5) * (M_PI / h));
}
/**
* \brief Update ROI QPs for 360 video with equirectangular projection.
*
* Writes updated ROI parameters to encoder->cfg.roi.
*
* \param encoder encoder control
* \param orig_roi original delta QPs or NULL
* \param orig_width width of orig_roi
* \param orig_height height of orig_roi
*/
static void init_erp_aqp_roi(encoder_control_t* encoder,
int8_t *orig_roi,
int32_t orig_width,
int32_t orig_height)
{
// Update ROI with WS-PSNR delta QPs.
int height = encoder->in.height_in_lcu;
int width = orig_roi ? orig_width : 1;
int frame_height = encoder->in.real_height;
encoder->cfg.roi.width = width;
encoder->cfg.roi.height = height;
encoder->cfg.roi.dqps = calloc(width * height, sizeof(orig_roi[0]));
double total_weight = 0.0;
for (int y = 0; y < frame_height; y++) {
total_weight += ws_weight(y, frame_height);
}
for (int y_lcu = 0; y_lcu < height; y_lcu++) {
int y_orig = LCU_WIDTH * y_lcu;
int lcu_height = MIN(LCU_WIDTH, frame_height - y_orig);
double lcu_weight = 0.0;
for (int y = y_orig; y < y_orig + lcu_height; y++) {
lcu_weight += ws_weight(y, frame_height);
}
// Normalize.
lcu_weight = (lcu_weight * frame_height) / (total_weight * lcu_height);
int8_t qp_delta = round(-ERP_AQP_STRENGTH * log2(lcu_weight));
if (orig_roi) {
// If a ROI array already exists, we copy the existing values to the
// new array while adding qp_delta to each.
int y_roi = y_lcu * orig_height / height;
for (int x = 0; x < width; x++) {
encoder->cfg.roi.dqps[x + y_lcu * width] =
CLIP(-51, 51, orig_roi[x + y_roi * width] + qp_delta);
}
} else {
// Otherwise, simply write qp_delta to the ROI array.
encoder->cfg.roi.dqps[y_lcu] = qp_delta;
}
}
}
/**
* \brief Allocate and initialize an encoder control structure.
*
@ -219,16 +306,23 @@ encoder_control_t* kvz_encoder_control_init(const kvz_config *const cfg)
goto init_failed;
}
if (cfg->erp_aqp) {
init_erp_aqp_roi(encoder,
cfg->roi.dqps,
cfg->roi.width,
cfg->roi.height);
} else if (cfg->roi.dqps) {
// Copy delta QP array for ROI coding.
if (cfg->roi.dqps) {
const size_t roi_size = encoder->cfg.roi.width * encoder->cfg.roi.height;
encoder->cfg.roi.dqps = calloc(roi_size, sizeof(cfg->roi.dqps[0]));
memcpy(encoder->cfg.roi.dqps,
cfg->roi.dqps,
roi_size * sizeof(*cfg->roi.dqps));
}
encoder->lcu_dqp_enabled = cfg->target_bitrate > 0 || cfg->roi.dqps;
encoder->lcu_dqp_enabled = cfg->target_bitrate > 0 || encoder->cfg.roi.dqps;
//Tiles
encoder->tiles_enable = encoder->cfg.tiles_width_count > 1 ||

View file

@ -339,6 +339,11 @@ typedef struct kvz_config
} roi; /*!< \since 3.14.0 \brief Map of delta QPs for region of interest coding. */
unsigned slices; /*!< \since 3.15.0 \brief How to map slices to frame. */
/**
* \brief Use adaptive QP for 360 video with equirectangular projection.
*/
int32_t erp_aqp;
} kvz_config;
/**