本文转载自http://blog.csdn.net/lin453701006/article/details/78109934,在原作者的基础上修改了部分结构以及添加代码实现部分,在此对原作者表示感谢!
在360Lib对应的Software Manual中,定义了与HEVC客观质量评价PSNR不同的评价指标WS-PSNR,下文对这个指标进行分析。
WS-PSNR
参考提案:JVET-D0040,JVET-F1003 (http://phenix.int-evry.fr/jvet/)
WS-PSNR相较于PSNR的改进之处在于,它在计算块失真时考虑了块在当前图像中的纬度信息,原文是“Calculate PSNR based on all samples; the distortion is weighted by sample area on corresponding spherical surface”.
WS-PSNR对应的代码在
项目Tlib360中的
TWSPSNRMetricCalc.h和
TWSPSNRMetricCalc.cpp中
先分析
TWSPSNRMetricCalc.h
class TWSPSNRMetric
{
private:
Bool m_bEnabled;
Double m_dWSPSNR[3];
Int m_outputBitDepth[MAX_NUM_CHANNEL_TYPE]; ///< bit-depth of output file
Int m_referenceBitDepth[MAX_NUM_CHANNEL_TYPE]; ///< bit-depth of reference file
//各投影格式权重,Y:亮度权重,C:色度权重
Double* m_fErpWeight_Y;
Double* m_fErpWeight_C;
Double* m_fCubeWeight_Y;
Double* m_fCubeWeight_C;
Double* m_fEapWeight_Y;
Double* m_fEapWeight_C;
Double* m_fOctaWeight_Y;
Double* m_fOctaWeight_C;
Double* m_fIcoWeight_Y;
Double* m_fIcoWeight_C;
#if SVIDEO_WSPSNR_SSP
Double* m_fSspWeight_Y;
Double* m_fSspWeight_C;
#endif
Int m_codingGeoType; //编码几何类型
Int m_iCodingFaceWidth; //编码面宽度
Int m_iCodingFaceHeight; //编码面高度
Int m_iChromaSampleLocType; //采样色度格式
#if SVIDEO_WSPSNR_E2E
//for E2E WS-PSNR calculation;
#if !SVIDEO_E2E_METRICS
TVideoIOYuv *m_pcTVideoIOYuvInputFile; //note: reference;
TGeometry *m_pRefGeometry;
TGeometry *m_pRecGeometry;
TComPicYuv *m_pcOrgPicYuv;
TComPicYuv *m_pcRecPicYuv; //in original geometry domain;
#endif
#if !SVIDEO_E2E_METRICS
Int m_iLastFrmPOC;
UInt m_temporalSubsampleRatio;
Int m_iInputWidth;
Int m_iInputHeight;
ChromaFormat m_inputChromaFomat;
#endif
#endif
public:
TWSPSNRMetric();
virtual ~TWSPSNRMetric();
Bool getWSPSNREnabled() { return m_bEnabled; } //获取m_bEnabled
Void setWSPSNREnabledFlag(Bool bEnabledFlag) { m_bEnabled = bEnabledFlag; } //设置m_bEnabled
Void setOutputBitDepth(Int iOutputBitDepth[MAX_NUM_CHANNEL_TYPE]); //输出图像bit深度
Void setReferenceBitDepth(Int iReferenceBitDepth[MAX_NUM_CHANNEL_TYPE]); //参考图像bit深度
//设置视频几何信息
Void setCodingGeoInfo(SVideoInfo& sVidInfo, Int iChromaSampleLocType) { m_codingGeoType = sVidInfo.geoType; m_iCodingFaceWidth = sVidInfo.iFaceWidth; m_iCodingFaceHeight = sVidInfo.iFaceHeight; m_iChromaSampleLocType =iChromaSampleLocType; }
#if SVIDEO_WSPSNR_E2E
#if SVIDEO_E2E_METRICS
Void setCodingGeoInfo2(SVideoInfo& sRefVideoInfo, SVideoInfo& sRecVideoInfo, InputGeoParam *pInGeoParam);
Void xCalculateE2EWSPSNR(TComPicYuv *pcRecPicYuv, TComPicYuv *pcOrigPicYuv);
#else
Void setCodingGeoInfo2(SVideoInfo& sRefVideoInfo, SVideoInfo& sRecVideoInfo, InputGeoParam *pInGeoParam, TVideoIOYuv& yuvInputFile, Int iInputWidth, Int iInputHeight, UInt tempSubsampleRatio);
Void xCalculateE2EWSPSNR(TComPicYuv *pcPicD, Int iPOC);
#endif
#endif
Double* getWSPSNR() {return m_dWSPSNR;} //获取WS-PSNR
Void createTable(TComPicYuv* pcPicD, TGeometry *pcCodingGeomtry); //创建投影格式对应的权重列表
Void xCalculateWSPSNR( TComPicYuv* pcOrgPicYuv, TComPicYuv* pcPicD ); //计算WS-PSNR
//inline Int round(POSType t) { return (Int)(t+ (t>=0? 0.5 :-0.5)); };
};
再分析
TWSPSNRMetricCalc.cpp中的函数,这个cpp文件中除去构造、析构、以及setCodingGeoInfo2()外,只剩下如下两个函数。
void TWSPSNRMetric::createTable():创建投影格式对应的权重列表。这里只截取了最常用的ERP和CMP对应的部分。每一种投影格式都对应一种权重,可以看到ERP的权重只与高度位置相关,CMP的权重与所在面的x,y坐标位置相关。
void TWSPSNRMetric::createTable(TComPicYuv* pcPicD, TGeometry *pcCodingGeomtry)
{
if(!m_bEnabled)
{
return;
}
SVideoInfo *pCodingSVideoInfo = pcCodingGeomtry->getSVideoInfo();
Int iFaceWidth = pCodingSVideoInfo->iFaceWidth;
Int iFaceHeight = pCodingSVideoInfo->iFaceHeight;
Int iScaleX = pcPicD->getComponentScaleX(COMPONENT_Cb);
Int iScaleY = pcPicD->getComponentScaleY(COMPONENT_Cb);
Double dChromaOffset[2] = {0.0, 0.0}; //[0: X; 1: Y];
if(pcPicD->getChromaFormat() == CHROMA_420)
{
dChromaOffset[0] = (m_iChromaSampleLocType == 0 || m_iChromaSampleLocType == 2)? 0 : 0.5;
dChromaOffset[1] = (m_iChromaSampleLocType == 2 || m_iChromaSampleLocType == 3)? 0 : 0.5;
}
//ERP投影
if(pcCodingGeomtry->getType()==SVIDEO_EQUIRECT)
{
Double fWeightSum_Y=0;
Double fWeightSum_C=0;
Int iWidth = pcPicD->getWidth(COMPONENT_Y) ; //获取亮度宽度
Int iHeight = pcPicD->getHeight(COMPONENT_Y) ; //获取亮度高度
Int iWidthC = pcPicD->getWidth(COMPONENT_Cb) ; //获取色度宽度
Int iHeightC = pcPicD->getHeight(COMPONENT_Cb) ; //获取色度高度
m_fErpWeight_Y=(Double*)malloc(iHeight*sizeof(Double)); //亮度权重和高度有关
m_fErpWeight_C=(Double*)malloc(iHeightC*sizeof(Double)); //色度权重和高度有关
//计算亮度和色度的纬度权重
for(Int y=0; y< iHeight; y++)
{
m_fErpWeight_Y[y]=scos((y-(iHeight/2-0.5))*S_PI/iHeight); //F1003 公式(72),计算亮度权重
fWeightSum_Y += m_fErpWeight_Y[y];
}
for(Int y=0; y< iHeightC; y++)
{
m_fErpWeight_C[y]=scos(((y<<iScaleY)+dChromaOffset[1]+0.5-iHeight/2)*S_PI/iHeight); //F1003 公式(72),计算色度权重
fWeightSum_C += m_fErpWeight_C[y];
}
for(Int y=0; y< iHeight; y++)
{
m_fErpWeight_Y[y]=m_fErpWeight_Y[y]/fWeightSum_Y/iWidth;
}
for(Int y=0; y< iHeightC; y++)
{
m_fErpWeight_C[y]=m_fErpWeight_C[y]/fWeightSum_C/(iWidthC);
}
}
//CMP投影
else if(pcCodingGeomtry->getType()==SVIDEO_CUBEMAP)
{
Double fWeightSum_Y=0;
Double fWeightSum_C=0;
//亮度、色度权重和面的高度宽度都有关
m_fCubeWeight_Y=(Double*)malloc(iFaceHeight * iFaceWidth*sizeof(Double));
m_fCubeWeight_C=(Double*)malloc((iFaceHeight >> iScaleY) * (iFaceWidth >> iScaleX)*sizeof(Double));
//计算亮度和色度权重
for(Int y = 0; y < iFaceHeight; y++ )
{
for(Int x=0; x < iFaceWidth; x++)
{
Int ci, cj, r2;
Double d2;
ci= iFaceWidth/2;
cj= iFaceHeight/2;
d2 = (x+0.5-ci)*(x+0.5-ci)+(y+0.5-cj)*(y+0.5-cj);
r2 = (iFaceWidth/2)*(iFaceWidth/2);
Double weight= 1.0/((1+d2/r2)*ssqrt(1.0*(1+d2/r2)));
m_fCubeWeight_Y[iFaceWidth*y+x] = weight;
fWeightSum_Y += weight;
}
}
for(Int y = 0; y < iFaceHeight; y++ )
{
for(Int x=0; x<iFaceWidth; x++)
{
m_fCubeWeight_Y[iFaceHeight*y+x] = (m_fCubeWeight_Y[iFaceHeight*y+x])/fWeightSum_Y/6.0;
}
}
for(Int y = 0; y < (iFaceHeight>>iScaleY); y++ )
{
for(Int x=0; x< (iFaceWidth>>iScaleX); x++)
{
Int ci, cj, r2;
Double d2;
ci= iFaceWidth/2;
cj= iFaceHeight/2;
d2 = (x*(1<<iScaleX)+dChromaOffset[0]+0.5 - ci)*(x*(1<<iScaleX)+dChromaOffset[0]+0.5 - ci) + (y*(1<<iScaleY)+dChromaOffset[1]+0.5 -cj)*(y*(1<<iScaleY)+dChromaOffset[1]+0.5 -cj);
r2 = (iFaceWidth/2)*(iFaceWidth/2);
Double weight= 1.0/((1+d2/r2)*sqrt(1.0*(1+d2/r2)));
m_fCubeWeight_C[(iFaceWidth>>iScaleX)*y+x]=weight;
fWeightSum_C += weight;
}
}
for(Int y = 0; y < (iFaceHeight>>iScaleY); y++ )
{
for(Int x=0; x< (iFaceWidth>>iScaleX); x++)
{
m_fCubeWeight_C[(iFaceWidth>>iScaleX)*y+x]=(m_fCubeWeight_C[(iFaceWidth>>iScaleX)*y+x])/fWeightSum_C/6.0;
}
}
}
}
Void TWSPSNRMetric::xCalculateWSPSNR()
Void TWSPSNRMetric::xCalculateWSPSNR( TComPicYuv* pcOrgPicYuv, TComPicYuv* pcPicD )
{
//初始化亮度色度bit信息
Int iBitDepthForPSNRCalc[MAX_NUM_CHANNEL_TYPE];
Int iReferenceBitShift[MAX_NUM_CHANNEL_TYPE];
Int iOutputBitShift[MAX_NUM_CHANNEL_TYPE];
iBitDepthForPSNRCalc[CHANNEL_TYPE_LUMA] = std::max(m_outputBitDepth[CHANNEL_TYPE_LUMA], m_referenceBitDepth[CHANNEL_TYPE_LUMA]);
iBitDepthForPSNRCalc[CHANNEL_TYPE_CHROMA] = std::max(m_outputBitDepth[CHANNEL_TYPE_CHROMA], m_referenceBitDepth[CHANNEL_TYPE_CHROMA]);
iReferenceBitShift[CHANNEL_TYPE_LUMA] = iBitDepthForPSNRCalc[CHANNEL_TYPE_LUMA] - m_referenceBitDepth[CHANNEL_TYPE_LUMA];
iReferenceBitShift[CHANNEL_TYPE_CHROMA] = iBitDepthForPSNRCalc[CHANNEL_TYPE_CHROMA] - m_referenceBitDepth[CHANNEL_TYPE_CHROMA];
iOutputBitShift[CHANNEL_TYPE_LUMA] = iBitDepthForPSNRCalc[CHANNEL_TYPE_LUMA] - m_outputBitDepth[CHANNEL_TYPE_LUMA];
iOutputBitShift[CHANNEL_TYPE_CHROMA] = iBitDepthForPSNRCalc[CHANNEL_TYPE_CHROMA] - m_outputBitDepth[CHANNEL_TYPE_CHROMA];
memset(m_dWSPSNR, 0, sizeof(Double)*3);
TComPicYuv &picd=*pcPicD;
//Double SSDspsnr[3]={0, 0 ,0};
//ChromaFormat chromaFormat = pcPicD->getChromaFormat();
for(Int chan=0; chan<pcPicD->getNumberValidComponents(); chan++)
{
const ComponentID ch=ComponentID(chan);
const Pel* pOrg = pcOrgPicYuv->getAddr(ch);
const Int iOrgStride = pcOrgPicYuv->getStride(ch);
const Pel* pRec = picd.getAddr(ch);
const Int iRecStride = picd.getStride(ch);
const Int iWidth = pcPicD->getWidth (ch) ;
const Int iHeight = pcPicD->getHeight(ch) ;
Double fWeight =1;
Double fWeightSum=0;
//Int iSize = iWidth*iHeight;
Double SSDwpsnr=0;
//WS-PSNR
for(Int y = 0; y < iHeight; y++ ) //得到宽高后,对每一个点的失真进行统计
{
//ERP格式只与高度有关
if (m_codingGeoType==SVIDEO_EQUIRECT)
{
if(!chan) //亮度
{
fWeight=m_fErpWeight_Y[y];
}
else //色度
{
fWeight=m_fErpWeight_C[y];
}
}
for(Int x = 0; x < iWidth; x++ )
{
//参考图像和输出图像差值
Intermediate_Int iDiff = (Intermediate_Int)( (pOrg[x]<<iReferenceBitShift[toChannelType(ch)]) - (pRec[x]<<iOutputBitShift[toChannelType(ch)]) );
//CMP格式和x,y都有关
if (m_codingGeoType==SVIDEO_CUBEMAP)
{
if(!chan)
{
if(iWidth/4 == iHeight/3 && x >= iWidth/4 && (y< iHeight/3 || y>= 2*iHeight/3))
{
fWeight=0;
}
else
{
fWeight=m_fCubeWeight_Y[(m_iCodingFaceWidth)*(y%(m_iCodingFaceHeight)) +(x%(m_iCodingFaceWidth))];
}
}
else
{
if(iWidth/4 == iHeight/3 && x >= iWidth/4 && (y< iHeight/3 || y>= 2*iHeight/3))
{
fWeight=0;
}
else
{
fWeight=m_fCubeWeight_C[(m_iCodingFaceWidth>>(pcPicD->getComponentScaleX(COMPONENT_Cb)))*(y%(m_iCodingFaceHeight>>(pcPicD->getComponentScaleY(COMPONENT_Cb)))) +(x%(m_iCodingFaceWidth>>(pcPicD->getComponentScaleX(COMPONENT_Cb))))];
}
}
}
if(fWeight>0)
fWeightSum += fWeight;
SSDwpsnr += iDiff * iDiff*fWeight; //引入了fWeight,即对每个点的失真进行一个权重处理。最终得到总的SSD
}
pOrg += iOrgStride;
pRec += iRecStride;
}
const Int maxval = 255<<(iBitDepthForPSNRCalc[toChannelType(ch)]-8) ;
//const Double fRefValue = (Double) maxval * maxval * iSize;
m_dWSPSNR[ch] = ( SSDwpsnr ? 10.0 * log10( (maxval * maxval*fWeightSum) / (Double)SSDwpsnr ) : 999.99 ); //计算PSNR
}
}