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);