diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp index 86afb73ab8be1dedd06e1e666250a7251870d105..57245829e08e8b9f46b3999283bfbfc04b0d5a5f 100644 --- a/source/App/EncoderApp/EncApp.cpp +++ b/source/App/EncoderApp/EncApp.cpp @@ -246,6 +246,7 @@ void EncApp::xInitLibCfg() m_cEncLib.setPrintFrameMSE ( m_printFrameMSE); m_cEncLib.setPrintHexPsnr(m_printHexPsnr); m_cEncLib.setPrintSequenceMSE ( m_printSequenceMSE); + m_cEncLib.setPrintMSSSIM ( m_printMSSSIM ); m_cEncLib.setCabacZeroWordPaddingEnabled ( m_cabacZeroWordPaddingEnabled ); m_cEncLib.setFrameRate ( m_iFrameRate ); diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp index faea0ed16167c400a79673b7c9a56ddfb8089c9d..e6dab95a814bc6aa2f2498c66ee9265f6dd31e06 100644 --- a/source/App/EncoderApp/EncAppCfg.cpp +++ b/source/App/EncoderApp/EncAppCfg.cpp @@ -717,6 +717,7 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] ) ("PrintHexPSNR", m_printHexPsnr, false, "0 (default) don't emit hexadecimal PSNR for each frame, 1 = also emit hexadecimal PSNR values") ("PrintFrameMSE", m_printFrameMSE, false, "0 (default) emit only bit count and PSNRs for each frame, 1 = also emit MSE values") ("PrintSequenceMSE", m_printSequenceMSE, false, "0 (default) emit only bit rate and PSNRs for the whole sequence, 1 = also emit MSE values") + ("PrintMSSSIM", m_printMSSSIM, false, "0 (default) do not print MS-SSIM scores, 1 = print MS-SSIM scores for each frame and for the whole sequence") ("CabacZeroWordPaddingEnabled", m_cabacZeroWordPaddingEnabled, true, "0 do not add conforming cabac-zero-words to bit streams, 1 (default) = add cabac-zero-words as required") ("ChromaFormatIDC,-cf", tmpChromaFormat, 0, "ChromaFormatIDC (400|420|422|444 or set 0 (default) for same as InputChromaFormat)") ("ConformanceMode", m_conformanceWindowMode, 0, "Deprecated alias of ConformanceWindowMode") @@ -3767,6 +3768,7 @@ void EncAppCfg::xPrintParameter() msg( DETAILS, "Hexadecimal PSNR output : %s\n", ( m_printHexPsnr ? "Enabled" : "Disabled" ) ); msg( DETAILS, "Sequence MSE output : %s\n", ( m_printSequenceMSE ? "Enabled" : "Disabled" ) ); msg( DETAILS, "Frame MSE output : %s\n", ( m_printFrameMSE ? "Enabled" : "Disabled" ) ); + msg( DETAILS, "MS-SSIM output : %s\n", ( m_printMSSSIM ? "Enabled" : "Disabled") ); msg( DETAILS, "Cabac-zero-word-padding : %s\n", ( m_cabacZeroWordPaddingEnabled ? "Enabled" : "Disabled" ) ); if (m_isField) { diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h index 7f8d114143fcd8d48b52a476efc032c74f59c3fd..1cdb1073483d85897d42f044aa46131ec4c41069 100644 --- a/source/App/EncoderApp/EncAppCfg.h +++ b/source/App/EncoderApp/EncAppCfg.h @@ -126,6 +126,7 @@ protected: bool m_printHexPsnr; bool m_printFrameMSE; bool m_printSequenceMSE; + bool m_printMSSSIM; bool m_cabacZeroWordPaddingEnabled; bool m_bClipInputVideoToRec709Range; bool m_bClipOutputVideoToRec709Range; diff --git a/source/Lib/EncoderLib/Analyze.h b/source/Lib/EncoderLib/Analyze.h index c4faaad1c5eb3aed1bfdd79e9ab131332cc11bb5..d17b9fa091f24a6a7f29b8b01bcc59d2d1ac34da 100644 --- a/source/Lib/EncoderLib/Analyze.h +++ b/source/Lib/EncoderLib/Analyze.h @@ -66,10 +66,11 @@ class Analyze private: double m_dPSNRSum[MAX_NUM_COMPONENT]; double m_dAddBits; - uint32_t m_uiNumPic; + uint32_t m_uiNumPic; double m_dFrmRate; //--CFG_KDY double m_MSEyuvframe[MAX_NUM_COMPONENT]; // sum of MSEs double m_upscaledPSNR[MAX_NUM_COMPONENT]; + double m_msssim[MAX_NUM_COMPONENT]; #if EXTENSION_360_VIDEO TExt360EncAnalyze m_ext360; #endif @@ -82,10 +83,8 @@ public: virtual ~Analyze() {} Analyze() { clear(); } - void addResult( double psnr[MAX_NUM_COMPONENT], double bits, const double MSEyuvframe[MAX_NUM_COMPONENT] - , const double upscaledPSNR[MAX_NUM_COMPONENT] - , bool isEncodeLtRef - ) + void addResult( double psnr[MAX_NUM_COMPONENT], double bits, const double MSEyuvframe[MAX_NUM_COMPONENT], + const double upscaledPSNR[MAX_NUM_COMPONENT], const double msssim[MAX_NUM_COMPONENT], bool isEncodeLtRef ) { m_dAddBits += bits; if (isEncodeLtRef) @@ -95,6 +94,7 @@ public: m_dPSNRSum[i] += psnr[i]; m_MSEyuvframe[i] += MSEyuvframe[i]; m_upscaledPSNR[i] += upscaledPSNR[i]; + m_msssim[i] += msssim[i]; } m_uiNumPic++; @@ -103,6 +103,7 @@ public: double getWPSNR (const ComponentID compID) const { return m_dPSNRSum[compID] / (double)m_uiNumPic; } #endif double getPsnr(ComponentID compID) const { return m_dPSNRSum[compID]; } + double getMsssim(ComponentID compID) const { return m_msssim[compID]; } #if JVET_O0756_CALCULATE_HDRMETRICS double getDeltaE() const { return m_logDeltaESum[0]; } double getPsnrL() const { return m_psnrLSum[0]; } @@ -133,6 +134,7 @@ public: m_dPSNRSum[i] = 0; m_MSEyuvframe[i] = 0; m_upscaledPSNR[i] = 0; + m_msssim[i] = 0; } m_uiNumPic = 0; #if EXTENSION_360_VIDEO @@ -188,13 +190,15 @@ public: } #if ENABLE_QPA || WCG_WPSNR - void printOut( char cDelim, const ChromaFormat chFmt, const bool printMSEBasedSNR, const bool printSequenceMSE, const bool printHexPsnr, const bool printRprPSNR, const BitDepths &bitDepths, const bool useWPSNR = false + void printOut( char cDelim, const ChromaFormat chFmt, const bool printMSEBasedSNR, const bool printSequenceMSE, const bool printMSSSIM, + const bool printHexPsnr, const bool printRprPSNR, const BitDepths &bitDepths, const bool useWPSNR = false #if JVET_O0756_CALCULATE_HDRMETRICS , const bool printHdrMetrics = false #endif ) #else - void printOut ( char cDelim, const ChromaFormat chFmt, const bool printMSEBasedSNR, const bool printSequenceMSE, const bool printHexPsnr, const BitDepths &bitDepths + void printOut ( char cDelim, const ChromaFormat chFmt, const bool printMSEBasedSNR, const bool printSequenceMSE, const bool printMSSSIM, + const bool printHexPsnr, const BitDepths &bitDepths #if JVET_O0756_CALCULATE_HDRMETRICS , const bool printHdrMetrics = false #endif @@ -252,6 +256,10 @@ public: msg(e_msg_level, "xY-PSNR "); } + if (printMSSSIM) + { + msg(e_msg_level, " Y-MS-SSIM"); + } if (printSequenceMSE) { msg( e_msg_level, " Y-MSE\n" ); @@ -283,6 +291,10 @@ public: msg(e_msg_level, " %16" PRIx64 " ", xPsnr); } + if (printMSSSIM) + { + printf(" %8.6lf", getMsssim(COMPONENT_Y) / (double)getNumPic()); + } if (printSequenceMSE) { msg( e_msg_level, " %8.4lf\n", m_MSEyuvframe[COMPONENT_Y] / (double)getNumPic() ); @@ -311,6 +323,10 @@ public: msg(e_msg_level, "xY-PSNR "); } + if (printMSSSIM) + { + printf( "Y-MS-SSIM"); + } if (printSequenceMSE) { msg( e_msg_level, " Y-MSE\n" ); @@ -342,6 +358,10 @@ public: msg(e_msg_level, " %16" PRIx64 " ", xPsnr); } + if (printMSSSIM) + { + printf("%8.6lf", getMsssim(COMPONENT_Y) / (double)getNumPic()); + } if (printSequenceMSE) { msg( e_msg_level, " %8.4lf\n", m_MSEyuvframe[COMPONENT_Y] / (double)getNumPic() ); @@ -375,6 +395,10 @@ public: msg(e_msg_level, "xY-PSNR " "xU-PSNR " "xV-PSNR "); } + if (printMSSSIM) + { + printf(" Y-MS-SSIM " "U-MS-SSIM " "V-MS-SSIM "); + } if (printSequenceMSE) { msg( e_msg_level, " Y-MSE " "U-MSE " "V-MSE " "YUV-MSE \n" ); @@ -417,6 +441,13 @@ public: msg(e_msg_level, " %16" PRIx64 " %16" PRIx64 " %16" PRIx64, xPsnr[COMPONENT_Y], xPsnr[COMPONENT_Cb], xPsnr[COMPONENT_Cr]); } + if (printMSSSIM) + { + printf(" %8.6lf " "%8.6lf " "%8.6lf ", + getMsssim(COMPONENT_Y) / (double)getNumPic(), + getMsssim(COMPONENT_Cb) / (double)getNumPic(), + getMsssim(COMPONENT_Cr) / (double)getNumPic()); + } if (printSequenceMSE) { msg( e_msg_level, " %8.4lf " "%8.4lf " "%8.4lf " "%8.4lf\n", @@ -460,6 +491,10 @@ public: { msg(e_msg_level, "xY-PSNR " "xU-PSNR " "xV-PSNR "); } + if (printMSSSIM) + { + printf(" Y-MS-SSIM " "U-MS-SSIM " "V-MS-SSIM "); + } #if JVET_O0756_CALCULATE_HDRMETRICS if (printHdrMetrics && printHexPsnr) { @@ -517,6 +552,13 @@ public: } msg(e_msg_level, " %16" PRIx64 " %16" PRIx64 " %16" PRIx64 , xPsnr[COMPONENT_Y], xPsnr[COMPONENT_Cb], xPsnr[COMPONENT_Cr]); } + if (printMSSSIM) + { + printf(" %8.6lf " "%8.6lf " "%8.6lf ", + getMsssim(COMPONENT_Y) / (double)getNumPic(), + getMsssim(COMPONENT_Cb) / (double)getNumPic(), + getMsssim(COMPONENT_Cr) / (double)getNumPic()); + } #if JVET_O0756_CALCULATE_HDRMETRICS if (printHexPsnr && printHdrMetrics) { diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h index 1f15e503e1b5343ca95726ac46d23228304a6df2..1ea0f4f67a60c9b2e3b0ed20212921899e64a234 100644 --- a/source/Lib/EncoderLib/EncCfg.h +++ b/source/Lib/EncoderLib/EncCfg.h @@ -173,6 +173,7 @@ protected: bool m_printHexPsnr; bool m_printFrameMSE; bool m_printSequenceMSE; + bool m_printMSSSIM; bool m_cabacZeroWordPaddingEnabled; bool m_gciPresentFlag; @@ -914,6 +915,9 @@ public: bool getPrintSequenceMSE () const { return m_printSequenceMSE; } void setPrintSequenceMSE (bool value) { m_printSequenceMSE = value; } + bool getPrintMSSSIM () const { return m_printMSSSIM; } + void setPrintMSSSIM (bool value) { m_printMSSSIM = value; } + bool getCabacZeroWordPaddingEnabled() const { return m_cabacZeroWordPaddingEnabled; } void setCabacZeroWordPaddingEnabled(bool value) { m_cabacZeroWordPaddingEnabled = value; } diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp index cb5dd2b16fe64584e9edeaebd6dc02c494248dbb..c6450510e410a8ab1898a85a2078f87d27d56c42 100644 --- a/source/Lib/EncoderLib/EncGOP.cpp +++ b/source/Lib/EncoderLib/EncGOP.cpp @@ -1937,10 +1937,8 @@ void EncGOP::xPicInitLMCS(Picture *pic, PicHeader *picHeader, Slice *slice) // ==================================================================================================================== void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic, std::list<PelUnitBuf*>& rcListPicYuvRecOut, - bool isField, bool isTff, const InputColourSpaceConversion snr_conversion, const bool printFrameMSE - , bool isEncodeLtRef - , const int picIdInGOP -) + bool isField, bool isTff, const InputColourSpaceConversion snr_conversion, + const bool printFrameMSE, const bool printMSSSIM, bool isEncodeLtRef, const int picIdInGOP) { // TODO: Split this function up. @@ -3522,7 +3520,8 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic, m_pcCfg->setEncodedFlag(iGOPid, true); double PSNR_Y; - xCalculateAddPSNRs(isField, isTff, iGOPid, pcPic, accessUnit, rcListPic, encTime, snr_conversion, printFrameMSE, &PSNR_Y, isEncodeLtRef ); + xCalculateAddPSNRs(isField, isTff, iGOPid, pcPic, accessUnit, rcListPic, encTime, snr_conversion, + printFrameMSE, printMSSSIM, &PSNR_Y, isEncodeLtRef ); xWriteTrailingSEIMessages(trailingSeiMessages, accessUnit, pcSlice->getTLayer()); @@ -3636,7 +3635,9 @@ void EncGOP::compressGOP( int iPOCLast, int iNumPicRcvd, PicList& rcListPic, CHECK( m_iNumPicCoded > 1, "Unspecified error" ); } -void EncGOP::printOutSummary( uint32_t uiNumAllPicCoded, bool isField, const bool printMSEBasedSNR, const bool printSequenceMSE, const bool printHexPsnr, const bool printRprPSNR, const BitDepths &bitDepths ) +void EncGOP::printOutSummary( uint32_t uiNumAllPicCoded, bool isField, const bool printMSEBasedSNR, + const bool printSequenceMSE, const bool printMSSSIM, const bool printHexPsnr, const bool printRprPSNR, + const BitDepths &bitDepths ) { #if ENABLE_QPA const bool useWPSNR = m_pcEncLib->getUseWPSNR(); @@ -3672,32 +3673,37 @@ void EncGOP::printOutSummary( uint32_t uiNumAllPicCoded, bool isField, const boo const bool calculateHdrMetrics = m_pcEncLib->getCalcluateHdrMetrics(); #endif #if ENABLE_QPA - m_gcAnalyzeAll.printOut( 'a', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, printRprPSNR, bitDepths, useWPSNR + m_gcAnalyzeAll.printOut( 'a', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, printHexPsnr, + printRprPSNR, bitDepths, useWPSNR #if JVET_O0756_CALCULATE_HDRMETRICS , calculateHdrMetrics #endif ); #else - m_gcAnalyzeAll.printOut('a', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, bitDepths + m_gcAnalyzeAll.printOut('a', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, printHexPsnr, bitDepths #if JVET_O0756_CALCULATE_HDRMETRICS , calculateHdrMetrics #endif ); #endif msg( DETAILS, "\n\nI Slices--------------------------------------------------------\n" ); - m_gcAnalyzeI.printOut( 'i', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, printRprPSNR, bitDepths ); + m_gcAnalyzeI.printOut( 'i', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, + printHexPsnr, printRprPSNR, bitDepths ); msg( DETAILS, "\n\nP Slices--------------------------------------------------------\n" ); - m_gcAnalyzeP.printOut( 'p', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, printRprPSNR, bitDepths ); + m_gcAnalyzeP.printOut( 'p', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, + printHexPsnr, printRprPSNR, bitDepths ); msg( DETAILS, "\n\nB Slices--------------------------------------------------------\n" ); - m_gcAnalyzeB.printOut( 'b', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, printRprPSNR, bitDepths ); + m_gcAnalyzeB.printOut( 'b', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, + printHexPsnr, printRprPSNR, bitDepths ); #if WCG_WPSNR if (useLumaWPSNR) { msg(DETAILS, "\nWPSNR SUMMARY --------------------------------------------------------\n"); - m_gcAnalyzeWPSNR.printOut( 'w', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, printRprPSNR, bitDepths, useLumaWPSNR ); + m_gcAnalyzeWPSNR.printOut( 'w', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, + printHexPsnr, printRprPSNR, bitDepths, useLumaWPSNR ); } #endif if (!m_pcCfg->getSummaryOutFilename().empty()) @@ -3727,9 +3733,11 @@ void EncGOP::printOutSummary( uint32_t uiNumAllPicCoded, bool isField, const boo msg( INFO,"\n\nSUMMARY INTERLACED ---------------------------------------------\n" ); #if ENABLE_QPA - m_gcAnalyzeAll_in.printOut( 'a', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, printRprPSNR, bitDepths, useWPSNR ); + m_gcAnalyzeAll_in.printOut( 'a', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, + printHexPsnr, printRprPSNR, bitDepths, useWPSNR ); #else - m_gcAnalyzeAll_in.printOut('a', chFmt, printMSEBasedSNR, printSequenceMSE, printHexPsnr, bitDepths); + m_gcAnalyzeAll_in.printOut('a', chFmt, printMSEBasedSNR, printSequenceMSE, printMSSSIM, + printHexPsnr, bitDepths); #endif if (!m_pcCfg->getSummaryOutFilename().empty()) { @@ -4044,13 +4052,13 @@ double EncGOP::xFindDistortionPlaneWPSNR(const CPelBuf& pic0, const CPelBuf& pic } #endif -void EncGOP::xCalculateAddPSNRs( const bool isField, const bool isFieldTopFieldFirst, const int iGOPid, Picture* pcPic, const AccessUnit&accessUnit, PicList &rcListPic, const int64_t dEncTime, const InputColourSpaceConversion snr_conversion, const bool printFrameMSE, double* PSNR_Y - , bool isEncodeLtRef -) +void EncGOP::xCalculateAddPSNRs( const bool isField, const bool isFieldTopFieldFirst, + const int iGOPid, Picture* pcPic, const AccessUnit&accessUnit, PicList &rcListPic, + const int64_t dEncTime, const InputColourSpaceConversion snr_conversion, + const bool printFrameMSE, const bool printMSSSIM, double* PSNR_Y, bool isEncodeLtRef) { - xCalculateAddPSNR(pcPic, pcPic->getRecoBuf(), accessUnit, (double)dEncTime, snr_conversion, printFrameMSE, PSNR_Y - , isEncodeLtRef - ); + xCalculateAddPSNR(pcPic, pcPic->getRecoBuf(), accessUnit, (double)dEncTime, snr_conversion, + printFrameMSE, printMSSSIM, PSNR_Y, isEncodeLtRef); //In case of field coding, compute the interlaced PSNR for both fields if(isField) @@ -4105,23 +4113,22 @@ void EncGOP::xCalculateAddPSNRs( const bool isField, const bool isFieldTopFieldF if ((pcPic->topField && isFieldTopFieldFirst) || (!pcPic->topField && !isFieldTopFieldFirst)) { - xCalculateInterlacedAddPSNR(pcPic, correspondingFieldPic, pcPic->getRecoBuf(), correspondingFieldPic->getRecoBuf(), snr_conversion, printFrameMSE, PSNR_Y - , isEncodeLtRef - ); + xCalculateInterlacedAddPSNR(pcPic, correspondingFieldPic, pcPic->getRecoBuf(), + correspondingFieldPic->getRecoBuf(), snr_conversion, printFrameMSE, printMSSSIM, + PSNR_Y, isEncodeLtRef); } else { - xCalculateInterlacedAddPSNR(correspondingFieldPic, pcPic, correspondingFieldPic->getRecoBuf(), pcPic->getRecoBuf(), snr_conversion, printFrameMSE, PSNR_Y - , isEncodeLtRef - ); + xCalculateInterlacedAddPSNR(correspondingFieldPic, pcPic, correspondingFieldPic->getRecoBuf(), + pcPic->getRecoBuf(), snr_conversion, printFrameMSE, printMSSSIM, PSNR_Y, isEncodeLtRef); } } } } -void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUnit& accessUnit, double dEncTime, const InputColourSpaceConversion conversion, const bool printFrameMSE, double* PSNR_Y - , bool isEncodeLtRef -) +void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUnit& accessUnit, + double dEncTime, const InputColourSpaceConversion conversion, const bool printFrameMSE, const bool printMSSSIM, + double* PSNR_Y, bool isEncodeLtRef) { const SPS& sps = *pcPic->cs->sps; const CPelUnitBuf& pic = cPicD; @@ -4132,6 +4139,7 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni const bool useWPSNR = m_pcEncLib->getUseWPSNR(); #endif double dPSNR[MAX_NUM_COMPONENT]; + double msssim[MAX_NUM_COMPONENT] = {0.0}; #if WCG_WPSNR const bool useLumaWPSNR = m_pcEncLib->getLumaLevelToDeltaQPMapping().isEnabled() || (m_pcCfg->getLmcs() && m_pcCfg->getReshapeSignalType() == RESHAPE_SIGNAL_PQ); double dPSNRWeighted[MAX_NUM_COMPONENT]; @@ -4229,6 +4237,10 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni const double fRefValue = (double)maxval * maxval * size; dPSNR[comp] = uiSSDtemp ? 10.0 * log10(fRefValue / (double)uiSSDtemp) : 999.99; MSEyuvframe[comp] = (double)uiSSDtemp / size; + if(printMSSSIM) + { + msssim[comp] = xCalculateMSSSIM (o.bufAt(0, 0), o.stride, p.bufAt(0, 0), p.stride, width, height, bitDepth); + } #if WCG_WPSNR const double uiSSDtempWeighted = xFindDistortionPlaneWPSNR(recPB, orgPB, 0, org.get(COMPONENT_Y), compID, format); if (useLumaWPSNR) @@ -4238,6 +4250,7 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni } #endif + if (m_pcEncLib->isResChangeInClvsEnabled()) { const CPelBuf& upscaledOrg = (sps.getUseLmcs() || m_pcCfg->getGopBasedTemporalFilterEnabled()) ? pcPic->M_BUFS( 0, PIC_TRUE_ORIGINAL_INPUT).get( compID ) : pcPic->M_BUFS( 0, PIC_ORIGINAL_INPUT).get( compID ); @@ -4304,10 +4317,7 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni m_vRVM_RP.push_back( uibits ); //===== add PSNR ===== - m_gcAnalyzeAll.addResult(dPSNR, (double)uibits, MSEyuvframe - , upscaledPSNR - , isEncodeLtRef - ); + m_gcAnalyzeAll.addResult(dPSNR, (double)uibits, MSEyuvframe, upscaledPSNR, msssim, isEncodeLtRef); #if EXTENSION_360_VIDEO m_ext360.addResult(m_gcAnalyzeAll); #endif @@ -4319,10 +4329,7 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni #endif if (pcSlice->isIntra()) { - m_gcAnalyzeI.addResult(dPSNR, (double)uibits, MSEyuvframe - , upscaledPSNR - , isEncodeLtRef - ); + m_gcAnalyzeI.addResult(dPSNR, (double)uibits, MSEyuvframe, upscaledPSNR, msssim, isEncodeLtRef); *PSNR_Y = dPSNR[COMPONENT_Y]; #if EXTENSION_360_VIDEO m_ext360.addResult(m_gcAnalyzeI); @@ -4336,10 +4343,7 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni } if (pcSlice->isInterP()) { - m_gcAnalyzeP.addResult(dPSNR, (double)uibits, MSEyuvframe - , upscaledPSNR - , isEncodeLtRef - ); + m_gcAnalyzeP.addResult(dPSNR, (double)uibits, MSEyuvframe, upscaledPSNR, msssim, isEncodeLtRef); *PSNR_Y = dPSNR[COMPONENT_Y]; #if EXTENSION_360_VIDEO m_ext360.addResult(m_gcAnalyzeP); @@ -4353,10 +4357,7 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni } if (pcSlice->isInterB()) { - m_gcAnalyzeB.addResult(dPSNR, (double)uibits, MSEyuvframe - , upscaledPSNR - , isEncodeLtRef - ); + m_gcAnalyzeB.addResult(dPSNR, (double)uibits, MSEyuvframe, upscaledPSNR, msssim, isEncodeLtRef); *PSNR_Y = dPSNR[COMPONENT_Y]; #if EXTENSION_360_VIDEO m_ext360.addResult(m_gcAnalyzeB); @@ -4371,7 +4372,7 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni #if WCG_WPSNR if (useLumaWPSNR) { - m_gcAnalyzeWPSNR.addResult( dPSNRWeighted, (double)uibits, MSEyuvframeWeighted, upscaledPSNR, isEncodeLtRef ); + m_gcAnalyzeWPSNR.addResult( dPSNRWeighted, (double)uibits, MSEyuvframeWeighted, upscaledPSNR, msssim, isEncodeLtRef ); } #endif @@ -4414,6 +4415,10 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni m_ext360.printPerPOCInfo(NOTICE, true); #endif } + if (printMSSSIM) + { + msg( NOTICE, " [MS-SSIM Y %1.6lf U %1.6lf V %1.6lf]", msssim[COMPONENT_Y], msssim[COMPONENT_Cb], msssim[COMPONENT_Cr] ); + } if( printFrameMSE ) { @@ -4527,6 +4532,174 @@ void EncGOP::xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUni } } +double EncGOP::xCalculateMSSSIM (const Pel* org, const int orgStride, const Pel* rec, const int recStride, const int width, const int height, const uint32_t bitDepth) +{ + const int MAX_MSSSIM_SCALE = 5; + const int WEIGHTING_MID_TAP = 5; + const int WEIGHTING_SIZE = WEIGHTING_MID_TAP*2+1; + + uint32_t maxScale; + + // For low resolution videos determine number of scales + if (width < 22 || height < 22) + { + maxScale = 1; + } + else if (width < 44 || height < 44) + { + maxScale = 2; + } + else if (width < 88 || height < 88) + { + maxScale = 3; + } + else if (width < 176 || height < 176) + { + maxScale = 4; + } + else + { + maxScale = 5; + } + + assert(maxScale>0 && maxScale<=MAX_MSSSIM_SCALE); + + //Normalized Gaussian mask design, 11*11, s.d. 1.5 + double weights[WEIGHTING_SIZE][WEIGHTING_SIZE]; + double coeffSum=0.0; + for(int y=0; y<WEIGHTING_SIZE; y++) + { + for(int x=0; x<WEIGHTING_SIZE; x++) + { + weights[y][x]=exp(-((y-WEIGHTING_MID_TAP)*(y-WEIGHTING_MID_TAP)+(x-WEIGHTING_MID_TAP)*(x-WEIGHTING_MID_TAP))/(WEIGHTING_MID_TAP-0.5)); + coeffSum +=weights[y][x]; + } + } + + for(int y=0; y<WEIGHTING_SIZE; y++) + { + for(int x=0; x<WEIGHTING_SIZE; x++) + { + weights[y][x] /=coeffSum; + } + } + + //Resolution based weights + const double exponentWeights[MAX_MSSSIM_SCALE][MAX_MSSSIM_SCALE] = {{1.0, 0, 0, 0, 0 }, + {0.1356, 0.8644, 0, 0, 0 }, + {0.0711, 0.4530, 0.4760, 0, 0 }, + {0.0517, 0.3295, 0.3462, 0.2726, 0 }, + {0.0448, 0.2856, 0.3001, 0.2363, 0.1333}}; + + //Downsampling of data: + std::vector<double> original[MAX_MSSSIM_SCALE]; + std::vector<double> recon[MAX_MSSSIM_SCALE]; + + for(uint32_t scale=0; scale<maxScale; scale++) + { + const int scaledHeight = height >> scale; + const int scaledWidth = width >> scale; + original[scale].resize(scaledHeight*scaledWidth, double(0)); + recon[scale].resize(scaledHeight*scaledWidth, double(0)); + } + + // Initial [0] arrays to be a copy of the source data (but stored in array "double", not Pel array). + for(int y=0; y<height; y++) + { + for(int x=0; x<width; x++) + { + original[0][y*width+x] = org[y*orgStride+x]; + recon[0][ y*width+x] = rec[y*recStride+x]; + } + } + + // Set up other arrays to be average value of each 2x2 sample. + for(uint32_t scale=1; scale<maxScale; scale++) + { + const int scaledHeight = height >> scale; + const int scaledWidth = width >> scale; + for(int y=0; y<scaledHeight; y++) + { + for(int x=0; x<scaledWidth; x++) + { + original[scale][y*scaledWidth+x]= (original[scale-1][ 2*y *(2*scaledWidth)+2*x ] + + original[scale-1][ 2*y *(2*scaledWidth)+2*x+1] + + original[scale-1][(2*y+1)*(2*scaledWidth)+2*x ] + + original[scale-1][(2*y+1)*(2*scaledWidth)+2*x+1]) / 4.0; + recon[scale][y*scaledWidth+x]= ( recon[scale-1][ 2*y *(2*scaledWidth)+2*x ] + + recon[scale-1][ 2*y *(2*scaledWidth)+2*x+1] + + recon[scale-1][(2*y+1)*(2*scaledWidth)+2*x ] + + recon[scale-1][(2*y+1)*(2*scaledWidth)+2*x+1]) / 4.0; + } + } + } + + // Calculate MS-SSIM: + const uint32_t maxValue = (1<<bitDepth)-1; + const double c1 = (0.01*maxValue)*(0.01*maxValue); + const double c2 = (0.03*maxValue)*(0.03*maxValue); + + double finalMSSSIM = 1.0; + + for(uint32_t scale=0; scale<maxScale; scale++) + { + const int scaledHeight = height >> scale; + const int scaledWidth = width >> scale; + const int blocksPerRow = scaledWidth-WEIGHTING_SIZE+1; + const int blocksPerColumn = scaledHeight-WEIGHTING_SIZE+1; + const int totalBlocks = blocksPerRow*blocksPerColumn; + + double meanSSIM= 0.0; + + for(int blockIndexY=0; blockIndexY<blocksPerColumn; blockIndexY++) + { + for(int blockIndexX=0; blockIndexX<blocksPerRow; blockIndexX++) + { + double muOrg =0.0; + double muRec =0.0; + double muOrigSqr =0.0; + double muRecSqr =0.0; + double muOrigMultRec =0.0; + + for(int y=0; y<WEIGHTING_SIZE; y++) + { + for(int x=0;x<WEIGHTING_SIZE; x++) + { + const double gaussianWeight=weights[y][x]; + const int sampleOffset=(blockIndexY+y)*scaledWidth+(blockIndexX+x); + const double orgPel=original[scale][sampleOffset]; + const double recPel= recon[scale][sampleOffset]; + + muOrg +=orgPel* gaussianWeight; + muRec +=recPel* gaussianWeight; + muOrigSqr +=orgPel*orgPel*gaussianWeight; + muRecSqr +=recPel*recPel*gaussianWeight; + muOrigMultRec+=orgPel*recPel*gaussianWeight; + } + } + + const double sigmaSqrOrig = muOrigSqr -(muOrg*muOrg); + const double sigmaSqrRec = muRecSqr -(muRec*muRec); + const double sigmaOrigRec = muOrigMultRec-(muOrg*muRec); + + double blockSSIMVal = ((2.0*sigmaOrigRec + c2)/(sigmaSqrOrig+sigmaSqrRec + c2)); + if(scale == maxScale-1) + { + blockSSIMVal*=(2.0*muOrg*muRec + c1)/(muOrg*muOrg+muRec*muRec + c1); + } + + meanSSIM += blockSSIMVal; + } + } + + meanSSIM /=totalBlocks; + + finalMSSSIM *= pow(meanSSIM, exponentWeights[maxScale-1][scale]); + } + + return finalMSSSIM; +} + #if JVET_O0756_CALCULATE_HDRMETRICS void EncGOP::xCalculateHDRMetrics( Picture* pcPic, double deltaE[hdrtoolslib::NB_REF_WHITE], double psnrL[hdrtoolslib::NB_REF_WHITE]) { @@ -4626,9 +4799,8 @@ void EncGOP::copyBuftoFrame( Picture* pcPic ) void EncGOP::xCalculateInterlacedAddPSNR( Picture* pcPicOrgFirstField, Picture* pcPicOrgSecondField, PelUnitBuf cPicRecFirstField, PelUnitBuf cPicRecSecondField, - const InputColourSpaceConversion conversion, const bool printFrameMSE, double* PSNR_Y - , bool isEncodeLtRef -) + const InputColourSpaceConversion conversion, const bool printFrameMSE, + const bool printMSSSIM, double* PSNR_Y, bool isEncodeLtRef) { const SPS &sps = *pcPicOrgFirstField->cs->sps; const ChromaFormat format = sps.getChromaFormatIdc(); @@ -4657,6 +4829,7 @@ void EncGOP::xCalculateInterlacedAddPSNR( Picture* pcPicOrgFirstField, Picture* //===== calculate PSNR ===== double MSEyuvframe[MAX_NUM_COMPONENT] = {0, 0, 0}; + double msssim[MAX_NUM_COMPONENT] = {0.0}; CHECK(!(acPicRecFields[0].chromaFormat==acPicRecFields[1].chromaFormat), "Unspecified error"); const uint32_t numValidComponents = ::getNumberValidComponents( acPicRecFields[0].chromaFormat ); @@ -4672,6 +4845,7 @@ void EncGOP::xCalculateInterlacedAddPSNR( Picture* pcPicOrgFirstField, Picture* const uint32_t height = acPicRecFields[0].get(ch).height - ((m_pcEncLib->getPad(1) >> 1) >> ::getComponentScaleY(ch, format)); const uint32_t bitDepth = sps.getBitDepth(toChannelType(ch)); + double sumOverFieldsMSSSIM = 0; for(uint32_t fieldNum=0; fieldNum<2; fieldNum++) { CHECK(!(conversion == IPCOLOURSPACE_UNCHANGED), "Unspecified error"); @@ -4680,6 +4854,16 @@ void EncGOP::xCalculateInterlacedAddPSNR( Picture* pcPicOrgFirstField, Picture* #else uiSSDtemp += xFindDistortionPlane( acPicRecFields[fieldNum].get(ch), apcPicOrgFields[fieldNum]->getOrigBuf().get(ch), 0 ); #endif + if(printMSSSIM) + { + CPelBuf o = apcPicOrgFields[fieldNum]->getOrigBuf().get(ch); + CPelBuf p = acPicRecFields[fieldNum].get(ch); + sumOverFieldsMSSSIM += xCalculateMSSSIM(o.bufAt(0, 0), o.stride, p.bufAt(0, 0), p.stride, width, height, bitDepth); + } + } + if (printMSSSIM) + { + msssim[ch] = sumOverFieldsMSSSIM / 2; } const uint32_t maxval = 255 << (bitDepth - 8); const uint32_t size = width * height * 2; @@ -4691,14 +4875,15 @@ void EncGOP::xCalculateInterlacedAddPSNR( Picture* pcPicOrgFirstField, Picture* uint32_t uibits = 0; // the number of bits for the pair is not calculated here - instead the overall total is used elsewhere. //===== add PSNR ===== - m_gcAnalyzeAll_in.addResult (dPSNR, (double)uibits, MSEyuvframe - , MSEyuvframe - , isEncodeLtRef - ); + m_gcAnalyzeAll_in.addResult (dPSNR, (double)uibits, MSEyuvframe, MSEyuvframe, msssim, isEncodeLtRef); *PSNR_Y = dPSNR[COMPONENT_Y]; msg( INFO, "\n Interlaced frame %d: [Y %6.4lf dB U %6.4lf dB V %6.4lf dB]", pcPicOrgSecondField->getPOC()/2, dPSNR[COMPONENT_Y], dPSNR[COMPONENT_Cb], dPSNR[COMPONENT_Cr] ); + if (printMSSSIM) + { + printf(" [MS-SSIM Y %1.6lf U %1.6lf V %1.6lf]", msssim[COMPONENT_Y], msssim[COMPONENT_Cb], msssim[COMPONENT_Cr] ); + } if (printFrameMSE) { msg( DETAILS, " [Y MSE %6.4lf U MSE %6.4lf V MSE %6.4lf]", MSEyuvframe[COMPONENT_Y], MSEyuvframe[COMPONENT_Cb], MSEyuvframe[COMPONENT_Cr] ); diff --git a/source/Lib/EncoderLib/EncGOP.h b/source/Lib/EncoderLib/EncGOP.h index 41750c24c4da036d4f5c98126e1c822b19831904..3236d9c055c71c8d7966ab8e73682ec4a9ce91a2 100644 --- a/source/Lib/EncoderLib/EncGOP.h +++ b/source/Lib/EncoderLib/EncGOP.h @@ -211,10 +211,8 @@ public: void init ( EncLib* pcEncLib ); void compressGOP ( int iPOCLast, int iNumPicRcvd, PicList& rcListPic, std::list<PelUnitBuf*>& rcListPicYuvRec, - bool isField, bool isTff, const InputColourSpaceConversion snr_conversion, const bool printFrameMSE - , bool isEncodeLtRef - , const int picIdInGOP - ); + bool isField, bool isTff, const InputColourSpaceConversion snr_conversion, const bool printFrameMSE, + bool printMSSSIM, bool isEncodeLtRef, const int picIdInGOP); void xAttachSliceDataToNalUnit (OutputNALUnit& rNalu, OutputBitstream* pcBitstreamRedirect); @@ -236,7 +234,8 @@ public: void setLastLTRefPoc(int iLastLTRefPoc) { m_lastLTRefPoc = iLastLTRefPoc; } int getLastLTRefPoc() const { return m_lastLTRefPoc; } - void printOutSummary( uint32_t uiNumAllPicCoded, bool isField, const bool printMSEBasedSNR, const bool printSequenceMSE, const bool printHexPsnr, const bool printRprPSNR, const BitDepths &bitDepths ); + void printOutSummary( uint32_t uiNumAllPicCoded, bool isField, const bool printMSEBasedSNR, const bool printSequenceMSE, + const bool printMSSSIM, const bool printHexPsnr, const bool printRprPSNR, const BitDepths &bitDepths ); #if W0038_DB_OPT uint64_t preLoopFilterPicAndCalcDist( Picture* pcPic ); #endif @@ -274,18 +273,17 @@ protected: void copyBuftoFrame ( Picture* pcPic ); #endif - void xCalculateAddPSNRs(const bool isField, const bool isFieldTopFieldFirst, const int iGOPid, Picture* pcPic, const AccessUnit&accessUnit, PicList &rcListPic, int64_t dEncTime, const InputColourSpaceConversion snr_conversion, const bool printFrameMSE, double* PSNR_Y - , bool isEncodeLtRef - ); - void xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUnit&, double dEncTime, const InputColourSpaceConversion snr_conversion, const bool printFrameMSE, double* PSNR_Y - , bool isEncodeLtRef - ); + void xCalculateAddPSNRs(const bool isField, const bool isFieldTopFieldFirst, const int iGOPid, Picture* pcPic, + const AccessUnit&accessUnit, PicList &rcListPic, int64_t dEncTime, const InputColourSpaceConversion snr_conversion, + const bool printFrameMSE, const bool printMSSSIM, double* PSNR_Y, bool isEncodeLtRef); + void xCalculateAddPSNR(Picture* pcPic, PelUnitBuf cPicD, const AccessUnit&, double dEncTime, const InputColourSpaceConversion snr_conversion, + const bool printFrameMSE, const bool printMSSSIM, double* PSNR_Y, bool isEncodeLtRef); void xCalculateInterlacedAddPSNR( Picture* pcPicOrgFirstField, Picture* pcPicOrgSecondField, PelUnitBuf cPicRecFirstField, PelUnitBuf cPicRecSecondField, - const InputColourSpaceConversion snr_conversion, const bool printFrameMSE, double* PSNR_Y - , bool isEncodeLtRef - ); - + const InputColourSpaceConversion snr_conversion, const bool printFrameMSE, + const bool printMSSSIM, double* PSNR_Y, bool isEncodeLtRef); + double xCalculateMSSSIM (const Pel* org, const int orgStride, const Pel* rec, const int recStride, + const int width, const int height, const uint32_t bitDepth); uint64_t xFindDistortionPlane(const CPelBuf& pic0, const CPelBuf& pic1, const uint32_t rshift #if ENABLE_QPA , const uint32_t chromaShiftHor = 0, const uint32_t chromaShiftVer = 0 diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp index a8599060f02da7deb427bea2d0ebbb6df3b4f082..18ed510de0b929491075b7f37c72dcec8d53ab72 100644 --- a/source/Lib/EncoderLib/EncLib.cpp +++ b/source/Lib/EncoderLib/EncLib.cpp @@ -624,7 +624,8 @@ bool EncLib::encodePrep( bool flush, PelStorage* pcPicYuvOrg, PelStorage* cPicYu m_cRateCtrl.initRCGOP( m_iNumPicRcvd ); } - m_cGOPEncoder.compressGOP( m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, false, false, snrCSC, m_printFrameMSE, true, 0 ); + m_cGOPEncoder.compressGOP( m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, false, false, + snrCSC, m_printFrameMSE, m_printMSSSIM, true, 0 ); #if JVET_O0756_CALCULATE_HDRMETRICS m_metricTime = m_cGOPEncoder.getMetricTime(); @@ -775,7 +776,7 @@ bool EncLib::encode( const InputColourSpaceConversion snrCSC, std::list<PelUnitB { // compress GOP m_cGOPEncoder.compressGOP( m_iPOCLast, m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, - false, false, snrCSC, m_printFrameMSE, false, m_picIdInGOP ); + false, false, snrCSC, m_printFrameMSE, m_printMSSSIM, false, m_picIdInGOP ); m_picIdInGOP++; @@ -911,7 +912,8 @@ bool EncLib::encode( const InputColourSpaceConversion snrCSC, std::list<PelUnitB m_iPOCLast = m_iPOCLast < 2 ? fieldNum : m_iPOCLast; // compress GOP - m_cGOPEncoder.compressGOP( m_iPOCLast, m_iPOCLast < 2 ? m_iPOCLast + 1 : m_iNumPicRcvd, m_cListPic, rcListPicYuvRecOut, true, isTff, snrCSC, m_printFrameMSE, false, m_picIdInGOP ); + m_cGOPEncoder.compressGOP( m_iPOCLast, m_iPOCLast < 2 ? m_iPOCLast + 1 : m_iNumPicRcvd, m_cListPic, + rcListPicYuvRecOut, true, isTff, snrCSC, m_printFrameMSE, m_printMSSSIM, false, m_picIdInGOP ); #if JVET_O0756_CALCULATE_HDRMETRICS m_metricTime = m_cGOPEncoder.getMetricTime(); #endif diff --git a/source/Lib/EncoderLib/EncLib.h b/source/Lib/EncoderLib/EncLib.h index c8885d9873b322b73978ad3bb186879a4017c476..3ce8bfba74a64d6477a685d16bde2289c936735e 100644 --- a/source/Lib/EncoderLib/EncLib.h +++ b/source/Lib/EncoderLib/EncLib.h @@ -289,7 +289,8 @@ public: int& iNumEncoded, bool isTff ); - void printSummary(bool isField) { m_cGOPEncoder.printOutSummary(m_uiNumAllPicCoded, isField, m_printMSEBasedSequencePSNR, m_printSequenceMSE, m_printHexPsnr, m_resChangeInClvsEnabled, m_spsMap.getFirstPS()->getBitDepths()); } + void printSummary(bool isField) { m_cGOPEncoder.printOutSummary(m_uiNumAllPicCoded, isField, m_printMSEBasedSequencePSNR, + m_printSequenceMSE, m_printMSSSIM, m_printHexPsnr, m_resChangeInClvsEnabled, m_spsMap.getFirstPS()->getBitDepths()); } int getLayerId() const { return m_layerId; } VPS* getVPS() { return m_vps; }