Mercurial > x265
changeset 12539:21db162c8622 draft
AQ: New AQ mode with Variance and Edge information
author | Akil Ayyappan<akil@multicorewareinc.com> |
---|---|
date | Fri, 12 Jul 2019 16:22:24 +0530 |
parents | 147fb92c5ed5 |
children | 93f090f7eaf3 |
files | doc/reST/cli.rst source/CMakeLists.txt source/common/lowres.cpp source/common/lowres.h source/common/param.cpp source/encoder/slicetype.cpp source/encoder/slicetype.h source/test/regression-tests.txt source/x265.h source/x265cli.h |
diffstat | 10 files changed, 199 insertions(+-), 9 deletions(-) [+] |
line wrap: on
line diff
--- a/doc/reST/cli.rst Mon Jul 08 10:39:27 2019 +0530 +++ b/doc/reST/cli.rst Fri Jul 12 16:22:24 2019 +0530 @@ -1,3 +1,4 @@ + ********************* Command Line Options ********************* @@ -1645,7 +1646,7 @@ Quality, rate control and rate distortio ignored. Slower presets will generally achieve better compression efficiency (and generate smaller bitstreams). Default disabled. -.. option:: --aq-mode <0|1|2|3> +.. option:: --aq-mode <0|1|2|3|4> Adaptive Quantization operating mode. Raise or lower per-block quantization based on complexity analysis of the source image. The @@ -1659,6 +1660,7 @@ Quality, rate control and rate distortio 3. AQ enabled with auto-variance and bias to dark scenes. This is recommended for 8-bit encodes or low-bitrate 10-bit encodes, to prevent color banding/blocking. + 4. AQ enabled with auto-variance and edge information. .. option:: --aq-strength <float>
--- a/source/CMakeLists.txt Mon Jul 08 10:39:27 2019 +0530 +++ b/source/CMakeLists.txt Fri Jul 12 16:22:24 2019 +0530 @@ -29,7 +29,7 @@ option(NATIVE_BUILD "Target the build CP option(STATIC_LINK_CRT "Statically link C runtime for release builds" OFF) mark_as_advanced(FPROFILE_USE FPROFILE_GENERATE NATIVE_BUILD) # X265_BUILD must be incremented each time the public API is changed -set(X265_BUILD 177) +set(X265_BUILD 178) configure_file("${PROJECT_SOURCE_DIR}/x265.def.in" "${PROJECT_BINARY_DIR}/x265.def") configure_file("${PROJECT_SOURCE_DIR}/x265_config.h.in"
--- a/source/common/lowres.cpp Mon Jul 08 10:39:27 2019 +0530 +++ b/source/common/lowres.cpp Fri Jul 12 16:22:24 2019 +0530 @@ -80,6 +80,7 @@ bool Lowres::create(x265_param* param, P CHECKED_MALLOC_ZERO(qpCuTreeOffset, double, cuCountFullRes); if (qgSize == 8) CHECKED_MALLOC_ZERO(invQscaleFactor8x8, int, cuCount); + CHECKED_MALLOC_ZERO(edgeInclined, int, cuCountFullRes); } if (origPic->m_param->bAQMotion) @@ -231,6 +232,7 @@ void Lowres::destroy() X265_FREE(qpCuTreeOffset); X265_FREE(propagateCost); X265_FREE(invQscaleFactor8x8); + X265_FREE(edgeInclined); X265_FREE(qpAqMotionOffset); X265_FREE(blockVariance); if (maxAQDepth > 0)
--- a/source/common/lowres.h Mon Jul 08 10:39:27 2019 +0530 +++ b/source/common/lowres.h Fri Jul 12 16:22:24 2019 +0530 @@ -220,6 +220,8 @@ struct Lowres : public ReferencePlanes uint64_t wp_ssd[3]; // This is different than SSDY, this is sum(pixel^2) - sum(pixel)^2 for entire frame uint64_t wp_sum[3]; double frameVariance; + int* edgeInclined; + /* cutree intermediate data */ PicQPAdaptationLayer* pAQLayer;
--- a/source/common/param.cpp Mon Jul 08 10:39:27 2019 +0530 +++ b/source/common/param.cpp Fri Jul 12 16:22:24 2019 +0530 @@ -1546,7 +1546,7 @@ int x265_check_params(x265_param* param) "Lookahead depth must be less than 256"); CHECK(param->lookaheadSlices > 16 || param->lookaheadSlices < 0, "Lookahead slices must between 0 and 16"); - CHECK(param->rc.aqMode < X265_AQ_NONE || X265_AQ_AUTO_VARIANCE_BIASED < param->rc.aqMode, + CHECK(param->rc.aqMode < X265_AQ_NONE || X265_AQ_EDGE < param->rc.aqMode, "Aq-Mode is out of range"); CHECK(param->rc.aqStrength < 0 || param->rc.aqStrength > 3, "Aq-Strength is out of range");
--- a/source/encoder/slicetype.cpp Mon Jul 08 10:39:27 2019 +0530 +++ b/source/encoder/slicetype.cpp Fri Jul 12 16:22:24 2019 +0530 @@ -85,6 +85,140 @@ inline uint32_t acEnergyPlane(Frame *cur } // end anonymous namespace +void edgeFilter(Frame *curFrame, pixel *pic1, pixel *pic2, pixel *pic3, intptr_t stride, int height, int width) +{ + pixel *src = (pixel*)curFrame->m_fencPic->m_picOrg[0]; + pixel *edgePic = pic1 + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; + pixel *refPic = pic2 + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; + pixel *edgeTheta = pic3 + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; + + for (int i = 0; i < height; i++) + { + memcpy(edgePic, src, width * sizeof(pixel)); + memcpy(refPic, src, width * sizeof(pixel)); + src += stride; + edgePic += stride; + refPic += stride; + } + + //Applying Gaussian filter on the picture + src = (pixel*)curFrame->m_fencPic->m_picOrg[0]; + refPic = pic2 + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; + pixel pixelValue = 0; + + for (int rowNum = 0; rowNum < height; rowNum++) + { + for (int colNum = 0; colNum < width; colNum++) + { + if ((rowNum >= 2) && (colNum >= 2) && (rowNum != height - 2) && (colNum != width - 2)) //Ignoring the border pixels of the picture + { + /* 5x5 Gaussian filter + [2 4 5 4 2] + 1 [4 9 12 9 4] + --- [5 12 15 12 5] + 159 [4 9 12 9 4] + [2 4 5 4 2]*/ + + const intptr_t rowOne = (rowNum - 2)*stride, colOne = colNum - 2; + const intptr_t rowTwo = (rowNum - 1)*stride, colTwo = colNum - 1; + const intptr_t rowThree = rowNum * stride, colThree = colNum; + const intptr_t rowFour = (rowNum + 1)*stride, colFour = colNum + 1; + const intptr_t rowFive = (rowNum + 2)*stride, colFive = colNum + 2; + const intptr_t index = (rowNum*stride) + colNum; + + pixelValue = ((2 * src[rowOne + colOne] + 4 * src[rowOne + colTwo] + 5 * src[rowOne + colThree] + 4 * src[rowOne + colFour] + 2 * src[rowOne + colFive] + + 4 * src[rowTwo + colOne] + 9 * src[rowTwo + colTwo] + 12 * src[rowTwo + colThree] + 9 * src[rowTwo + colFour] + 4 * src[rowTwo + colFive] + + 5 * src[rowThree + colOne] + 12 * src[rowThree + colTwo] + 15 * src[rowThree + colThree] + 12 * src[rowThree + colFour] + 5 * src[rowThree + colFive] + + 4 * src[rowFour + colOne] + 9 * src[rowFour + colTwo] + 12 * src[rowFour + colThree] + 9 * src[rowFour + colFour] + 4 * src[rowFour + colFive] + + 2 * src[rowFive + colOne] + 4 * src[rowFive + colTwo] + 5 * src[rowFive + colThree] + 4 * src[rowFive + colFour] + 2 * src[rowFive + colFive]) / 159); + refPic[index] = pixelValue; + } + } + } + +#if HIGH_BIT_DEPTH //10-bit build + float_t threshold = 1023; + pixel whitePixel = 1023; +#else + float_t threshold = 255; + pixel whitePixel = 255; +#endif +#define PI 3.14159265 + + float_t gradientH = 0, gradientV = 0, radians = 0, theta = 0; + float_t gradientMagnitude = 0; + pixel blackPixel = 0; + edgePic = pic1 + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; + //Applying Sobel filter on the gaussian filtered picture + for (int rowNum = 0; rowNum < height; rowNum++) + { + for (int colNum = 0; colNum < width; colNum++) + { + edgeTheta[(rowNum*stride) + colNum] = 0; + if ((rowNum != 0) && (colNum != 0) && (rowNum != height - 1) && (colNum != width - 1)) //Ignoring the border pixels of the picture + { + /*Horizontal and vertical gradients + [ -3 0 3 ] [-3 -10 -3 ] + gH = [ -10 0 10] gV = [ 0 0 0 ] + [ -3 0 3 ] [ 3 10 3 ]*/ + + const intptr_t rowOne = (rowNum - 1)*stride, colOne = colNum -1; + const intptr_t rowTwo = rowNum * stride, colTwo = colNum; + const intptr_t rowThree = (rowNum + 1)*stride, colThree = colNum + 1; + const intptr_t index = (rowNum*stride) + colNum; + + gradientH = (float_t)(-3 * refPic[rowOne + colOne] + 3 * refPic[rowOne + colThree] - 10 * refPic[rowTwo + colOne] + 10 * refPic[rowTwo + colThree] - 3 * refPic[rowThree + colOne] + 3 * refPic[rowThree + colThree]); + gradientV = (float_t)(-3 * refPic[rowOne + colOne] - 10 * refPic[rowOne + colTwo] - 3 * refPic[rowOne + colThree] + 3 * refPic[rowThree + colOne] + 10 * refPic[rowThree + colTwo] + 3 * refPic[rowThree + colThree]); + + gradientMagnitude = sqrtf(gradientH * gradientH + gradientV * gradientV); + radians = atan2(gradientV, gradientH); + theta = (float_t)((radians * 180) / PI); + if (theta < 0) + theta = 180 + theta; + edgeTheta[(rowNum*stride) + colNum] = (pixel)theta; + + edgePic[index] = gradientMagnitude >= threshold ? whitePixel : blackPixel; + } + } + } +} + +//Find the angle of a block by averaging the pixel angles +inline void findAvgAngle(const pixel* block, intptr_t stride, uint32_t size, uint32_t &angle) +{ + int sum = 0; + for (uint32_t y = 0; y < size; y++) + { + for (uint32_t x = 0; x < size; x++) + { + sum += block[x]; + } + block += stride; + } + angle = sum / (size*size); +} + +uint32_t LookaheadTLD::edgeDensityCu(Frame* curFrame,pixel *edgeImage, pixel *edgeTheta, uint32_t &avgAngle, uint32_t blockX, uint32_t blockY, uint32_t qgSize) +{ + intptr_t srcStride = curFrame->m_fencPic->m_stride; + intptr_t blockOffsetLuma = blockX + (blockY * srcStride); + int plane = 0; // Sobel filter is applied only on Y component + uint32_t var; + + if (qgSize == 8) + { + findAvgAngle(edgeTheta + blockOffsetLuma, srcStride, qgSize, avgAngle); + var = acEnergyVar(curFrame, primitives.cu[BLOCK_8x8].var(edgeImage + blockOffsetLuma, srcStride), 6, plane); + } + else + { + findAvgAngle(edgeTheta + blockOffsetLuma, srcStride, 16, avgAngle); + var = acEnergyVar(curFrame, primitives.cu[BLOCK_16x16].var(edgeImage + blockOffsetLuma, srcStride), 8, plane); + } + x265_emms(); + return var; +} + /* Find the total AC energy of each block in all planes */ uint32_t LookaheadTLD::acEnergyCu(Frame* curFrame, uint32_t blockX, uint32_t blockY, int csp, uint32_t qgSize) { @@ -342,20 +476,55 @@ void LookaheadTLD::calcAdaptiveQuantFram } else { - int blockXY = 0; +#define AQ_EDGE_BIAS 0.5 +#define EDGE_INCLINATION 45 + uint32_t numCuInHeight = (maxRow + param->maxCUSize - 1) / param->maxCUSize; + int maxHeight = numCuInHeight * param->maxCUSize; + intptr_t stride = curFrame->m_fencPic->m_stride; + pixel *edgePic = X265_MALLOC(pixel, stride * (maxHeight + (curFrame->m_fencPic->m_lumaMarginY * 2))); + pixel *gaussianPic = X265_MALLOC(pixel, stride * (maxHeight + (curFrame->m_fencPic->m_lumaMarginY * 2))); + pixel *thetaPic = X265_MALLOC(pixel, stride * (maxHeight + (curFrame->m_fencPic->m_lumaMarginY * 2))); + memset(edgePic, 0, stride * (maxHeight + (curFrame->m_fencPic->m_lumaMarginY * 2)) * sizeof(pixel)); + memset(gaussianPic, 0, stride * (maxHeight + (curFrame->m_fencPic->m_lumaMarginY * 2)) * sizeof(pixel)); + memset(thetaPic, 0, stride * (maxHeight + (curFrame->m_fencPic->m_lumaMarginY * 2)) * sizeof(pixel)); + if (param->rc.aqMode == X265_AQ_EDGE) + edgeFilter(curFrame, edgePic, gaussianPic, thetaPic, stride, maxRow, maxCol); + + int blockXY = 0, inclinedEdge = 0; double avg_adj_pow2 = 0, avg_adj = 0, qp_adj = 0; double bias_strength = 0.f; double strength = 0.f; - if (param->rc.aqMode == X265_AQ_AUTO_VARIANCE || param->rc.aqMode == X265_AQ_AUTO_VARIANCE_BIASED) + if (param->rc.aqMode == X265_AQ_AUTO_VARIANCE || param->rc.aqMode == X265_AQ_AUTO_VARIANCE_BIASED || param->rc.aqMode == X265_AQ_EDGE) { double bit_depth_correction = 1.f / (1 << (2 * (X265_DEPTH - 8))); - for (int blockY = 0; blockY < maxRow; blockY += loopIncr) { for (int blockX = 0; blockX < maxCol; blockX += loopIncr) { - uint32_t energy = acEnergyCu(curFrame, blockX, blockY, param->internalCsp, param->rc.qgSize); - qp_adj = pow(energy * bit_depth_correction + 1, 0.1); + uint32_t energy, edgeDensity, avgAngle; + energy = acEnergyCu(curFrame, blockX, blockY, param->internalCsp, param->rc.qgSize); + if (param->rc.aqMode == X265_AQ_EDGE) + { + pixel *edgeImage = edgePic + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; + pixel *edgeTheta = thetaPic + curFrame->m_fencPic->m_lumaMarginY * stride + curFrame->m_fencPic->m_lumaMarginX; + edgeDensity = edgeDensityCu(curFrame, edgeImage, edgeTheta, avgAngle, blockX, blockY, param->rc.qgSize); + if (edgeDensity) + { + qp_adj = pow(edgeDensity * bit_depth_correction + 1, 0.1); + //Increasing the QP of a block if its edge orientation lies around the multiples of 45 degree + if ((avgAngle >= EDGE_INCLINATION - 15 && avgAngle <= EDGE_INCLINATION + 15) || (avgAngle >= EDGE_INCLINATION + 75 && avgAngle <= EDGE_INCLINATION + 105)) + curFrame->m_lowres.edgeInclined[blockXY] = 1; + else + curFrame->m_lowres.edgeInclined[blockXY] = 0; + } + else + { + qp_adj = pow(energy * bit_depth_correction + 1, 0.1); + curFrame->m_lowres.edgeInclined[blockXY] = 0; + } + } + else + qp_adj = pow(energy * bit_depth_correction + 1, 0.1); curFrame->m_lowres.qpCuTreeOffset[blockXY] = qp_adj; avg_adj += qp_adj; avg_adj_pow2 += qp_adj * qp_adj; @@ -371,6 +540,9 @@ void LookaheadTLD::calcAdaptiveQuantFram else strength = param->rc.aqStrength * 1.0397f; + X265_FREE(edgePic); + X265_FREE(gaussianPic); + X265_FREE(thetaPic); blockXY = 0; for (int blockY = 0; blockY < maxRow; blockY += loopIncr) { @@ -386,6 +558,15 @@ void LookaheadTLD::calcAdaptiveQuantFram qp_adj = curFrame->m_lowres.qpCuTreeOffset[blockXY]; qp_adj = strength * (qp_adj - avg_adj); } + else if (param->rc.aqMode == X265_AQ_EDGE) + { + inclinedEdge = curFrame->m_lowres.edgeInclined[blockXY]; + qp_adj = curFrame->m_lowres.qpCuTreeOffset[blockXY]; + if(inclinedEdge && (qp_adj - avg_adj > 0)) + qp_adj = ((strength + AQ_EDGE_BIAS) * (qp_adj - avg_adj)); + else + qp_adj = strength * (qp_adj - avg_adj); + } else { uint32_t energy = acEnergyCu(curFrame, blockX, blockY, param->internalCsp, param->rc.qgSize);
--- a/source/encoder/slicetype.h Mon Jul 08 10:39:27 2019 +0530 +++ b/source/encoder/slicetype.h Fri Jul 12 16:22:24 2019 +0530 @@ -92,6 +92,7 @@ struct LookaheadTLD protected: uint32_t acEnergyCu(Frame* curFrame, uint32_t blockX, uint32_t blockY, int csp, uint32_t qgSize); + uint32_t edgeDensityCu(Frame*curFrame, pixel *edgeImage, pixel *edgeTheta, uint32_t &avgAngle, uint32_t blockX, uint32_t blockY, uint32_t qgSize); uint32_t lumaSumCu(Frame* curFrame, uint32_t blockX, uint32_t blockY, uint32_t qgSize); uint32_t weightCostLuma(Lowres& fenc, Lowres& ref, WeightParam& wp); bool allocWeightedRef(Lowres& fenc);
--- a/source/test/regression-tests.txt Mon Jul 08 10:39:27 2019 +0530 +++ b/source/test/regression-tests.txt Fri Jul 12 16:22:24 2019 +0530 @@ -154,6 +154,7 @@ big_buck_bunny_360p24.y4m, --keyint 60 - BasketballDrive_1920x1080_50.y4m, --preset medium --no-open-gop --keyint 50 --min-keyint 50 --radl 2 --vbv-maxrate 5000 --vbv-bufsize 5000 big_buck_bunny_360p24.y4m, --bitrate 500 --fades 720p50_parkrun_ter.y4m,--preset medium --bitrate 400 --hme +ducks_take_off_420_1_720p50.y4m,--preset medium --aq-mode 4 --crf 22 --no-cutree # Main12 intraCost overflow bug test 720p50_parkrun_ter.y4m,--preset medium
--- a/source/x265.h Mon Jul 08 10:39:27 2019 +0530 +++ b/source/x265.h Fri Jul 12 16:22:24 2019 +0530 @@ -561,6 +561,7 @@ typedef enum #define X265_AQ_VARIANCE 1 #define X265_AQ_AUTO_VARIANCE 2 #define X265_AQ_AUTO_VARIANCE_BIASED 3 +#define X265_AQ_EDGE 4 #define x265_ADAPT_RD_STRENGTH 4 #define X265_REFINE_INTER_LEVELS 3 /* NOTE! For this release only X265_CSP_I420 and X265_CSP_I444 are supported */
--- a/source/x265cli.h Mon Jul 08 10:39:27 2019 +0530 +++ b/source/x265cli.h Fri Jul 12 16:22:24 2019 +0530 @@ -554,7 +554,7 @@ static void showHelp(x265_param *param) " - 0 : Disabled.\n" " - 1 : Store/Load ctu distortion to/from the file specified in analysis-save/load.\n" " Default 0 - Disabled\n"); - H0(" --aq-mode <integer> Mode for Adaptive Quantization - 0:none 1:uniform AQ 2:auto variance 3:auto variance with bias to dark scenes. Default %d\n", param->rc.aqMode); + H0(" --aq-mode <integer> Mode for Adaptive Quantization - 0:none 1:uniform AQ 2:auto variance 3:auto variance with bias to dark scenes 4:auto variance with edge information. Default %d\n", param->rc.aqMode); H0(" --[no-]hevc-aq Mode for HEVC Adaptive Quantization. Default %s\n", OPT(param->rc.hevcAq)); H0(" --aq-strength <float> Reduces blocking and blurring in flat and textured areas (0 to 3.0). Default %.2f\n", param->rc.aqStrength); H0(" --qp-adaptation-range <float> Delta QP range by QP adaptation based on a psycho-visual model (1.0 to 6.0). Default %.2f\n", param->rc.qpAdaptationRange);