该技术的核心是当前块与参考块之间存在线性光照变化,并且此变化是线性变化。通过当前块和参考块的相邻重构像素(模板),拟合出线性函数a*p[x]+b来补偿光照变化。其中p[x]为参考块,a为缩放因子,b为偏移量,如下图所示
其中a和b利用最小二乘法推导得到:


LIC使用条件:
- CIIP模式和IBC模式禁用
- 面积小于32的块禁用
- LIC flag 没有时域继承性
- 生成merge候选列表时无需基于LIC flag进行修剪
- 不可用于双向预测()
- LIC应用于1616的处理单元时,使用当前CU中左上角1616的单元进行参数推导,并且用于CU内其他部分,如图所示
- LIC 应用于子块模式,其中 LIC 参数是根据在子块基础上导出的样本导出的,但仅限于左上角的第一个 16x16 处理单元,如图所示
编码端的优化:
- 未开启LIC时的运动搜索结果将在LIC开启时重复使用(JVET-MJVET-M0182提出)
- 若相邻4 * 4子块的LIC使用率超过阈值TH(0.10),则开启LIC下,以MR-SAD为标准进行运动搜索
代码:
LIC的主函数为xLocalIlluComp,该函数由xPredInterBlk所调用。函数内部主要分为三个步骤:
- 获取当前块和参考块的模板像素 ——xGetSublkTemplate函数
- 根据模板像素,利用最小二乘法,确定线性变换的参数a、b ——xGetLICParamGeneral函数
- 将当前块的像素带入线性变换中,进行光照补偿 ——linearTransform函数
oid InterPrediction::xLocalIlluComp(const PredictionUnit& pu,
const ComponentID compID,
const Picture& refPic,
const Mv& mv,
const bool biPred,
PelBuf& dstBuf
)
{
Pel* refLeftTemplate = m_pcLICRefLeftTemplate;//LJY:用于保存参考块的模板像素
Pel* refAboveTemplate = m_pcLICRefAboveTemplate;
Pel* recLeftTemplate = m_pcLICRecLeftTemplate;//LJY:用于保存当前快的模板像素指针
Pel* recAboveTemplate = m_pcLICRecAboveTemplate;
int numTemplate[2] = { 0 , 0 }; // 0:Above, 1:Left
//LJY:获取当前块和参考块的模板像素
xGetSublkTemplate(*pu.cu, compID, refPic, mv, pu.blocks[compID].width, pu.blocks[compID].height, 0, 0, numTemplate, refLeftTemplate, refAboveTemplate, recLeftTemplate, recAboveTemplate);
//LJY:根据最小二乘法,确定参数a、b
int shift = 0, scale = 0, offset = 0;
xGetLICParamGeneral(*pu.cu, compID, numTemplate, refLeftTemplate, refAboveTemplate, recLeftTemplate, recAboveTemplate, shift, scale, offset);
const ClpRng& clpRng = pu.cu->cs->slice->clpRng(compID);
dstBuf.linearTransform(scale, shift, offset, true, clpRng);//LJY:将当前块的像素带入表达式,进行线性变换,修正像素值
}
xGetSublkTemplate函数用于获取当前块和参考块的模板像素,其中主要通过xGetPredBlkTpl函数获取。
void InterPrediction::xGetSublkTemplate(const CodingUnit& cu,
const ComponentID compID,
const Picture& refPic,
const Mv& mv,
const int sublkWidth,
const int sublkHeight,
const int posW,
const int posH,
int* numTemplate,
Pel* refLeftTemplate,
Pel* refAboveTemplate,
Pel* recLeftTemplate,
Pel* recAboveTemplate)
{
const int bitDepth = cu.cs->sps->getBitDepth(toChannelType(compID));
const int precShift = std::max(0, bitDepth - 12);
const Picture& currPic = *cu.cs->picture;
const CodingUnit* const cuAbove = cu.cs->getCU(cu.blocks[compID].pos().offset(0, -1), toChannelType(compID));
const CodingUnit* const cuLeft = cu.cs->getCU(cu.blocks[compID].pos().offset(-1, 0), toChannelType(compID));
const CPelBuf recBuf = cuAbove || cuLeft ? currPic.getRecoBuf(cu.cs->picture->blocks[compID]) : CPelBuf();//LJY:获取当前块的像素
const CPelBuf refBuf = cuAbove || cuLeft ? refPic.getRecoBuf(refPic.blocks[compID]) : CPelBuf();//LJY:获取参考块的像素
std::vector<Pel>& invLUT = m_pcReshape->getInvLUT();
// above
if (cuAbove && posH == 0)//LJY:获取上模板像素
{
xGetPredBlkTpl<true>(cu, compID, refBuf, mv, posW, posH, sublkWidth, refAboveTemplate);//LJY:获取参考块的模板像素,保存在refAboveTemplate
const Pel* rec = recBuf.bufAt(cu.blocks[compID].pos().offset(0, -1));
for (int k = posW; k < posW + sublkWidth; k++)//LJY:逐点进行处理,移位,放入容器中
{
int refVal = refAboveTemplate[k];
int recVal = rec[k];
if (isLuma(compID) && cu.cs->picHeader->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag())
{
recVal = invLUT[recVal];
}
recVal >>= precShift;
refVal >>= precShift;
refAboveTemplate[k] = refVal;
recAboveTemplate[k] = recVal;
numTemplate[0]++;//LJY:该值不为0说明存在上相邻模板,后期拟合系数需要
}
}
// left
if (cuLeft && posW == 0)//LJY:获取左模板像素
{
xGetPredBlkTpl<false>(cu, compID, refBuf, mv, posW, posH, sublkHeight, refLeftTemplate);
const Pel* rec = recBuf.bufAt(cu.blocks[compID].pos().offset(-1, 0));
for (int k = posH; k < posH + sublkHeight; k++)
{
int refVal = refLeftTemplate[k];
int recVal = rec[recBuf.stride * k];
if (isLuma(compID) && cu.cs->picHeader->getLmcsEnabledFlag() && m_pcReshape->getCTUFlag())
{
recVal = invLUT[recVal];
}
recVal >>= precShift;
refVal >>= precShift;
refLeftTemplate[k] = refVal;
recLeftTemplate[k] = recVal;
numTemplate[1]++;//LJY:该值不为0说明存在左相邻模板,后期拟合系数需要
}
}
}
xGetLICParamGeneral函数采用最小二乘法,确定亮度变化线性关系中的参数a和b
void InterPrediction::xGetLICParamGeneral(const CodingUnit& cu,
const ComponentID compID,
int* numTemplate,
Pel* refLeftTemplate,
Pel* refAboveTemplate,
Pel* recLeftTemplate,
Pel* recAboveTemplate,
int& shift,
int& scale,
int& offset
)
{
const int cuWidth = cu.blocks[compID].width;
const int cuHeight = cu.blocks[compID].height;
const int bitDepth = cu.cs->sps->getBitDepth(toChannelType(compID));
const int precShift = std::max(0, bitDepth - 12);
const int maxNumMinus1 = 30 - 2 * std::min(bitDepth, 12) - 1;
const int minDimBit = floorLog2(std::min(cuHeight, cuWidth));
const int minDim = 1 << minDimBit;
int minStepBit = minDim > 8 ? 1 : 0;
while (minDimBit > minStepBit + maxNumMinus1) { minStepBit++; } //make sure log2(2*minDim/tmpStep) + 2*min(bitDepth,12) <= 30
const int numSteps = minDim >> minStepBit;
const int dimShift = minDimBit - minStepBit;
//----- get correlation data -----
int x = 0, y = 0, xx = 0, xy = 0, cntShift = 0;//LJY:初始化最小二乘所需的参数
// above
if (numTemplate[0] != 0)//LJY:说明上相邻模板存在,进行拟合,得到最小二乘的参数
{
for (int k = 0; k < numSteps; k++)
{
CHECK(((k * cuWidth) >> dimShift) >= cuWidth, "Out of range");
int refVal = refAboveTemplate[((k * cuWidth) >> dimShift)];
int recVal = recAboveTemplate[((k * cuWidth) >> dimShift)];
x += refVal;
y += recVal;
xx += refVal * refVal;
xy += refVal * recVal;
}
cntShift = dimShift;
}
// left
if (numTemplate[1] != 0)//LJY:说明左相邻模板存在,进行拟合,得到最小二乘的参数
{
for (int k = 0; k < numSteps; k++)
{
CHECK(((k * cuHeight) >> dimShift) >= cuHeight, "Out of range");
int refVal = refLeftTemplate[((k * cuHeight) >> dimShift)];
int recVal = recLeftTemplate[((k * cuHeight) >> dimShift)];
x += refVal;
y += recVal;
xx += refVal * refVal;
xy += refVal * recVal;
}
cntShift += (cntShift ? 1 : dimShift);
}
//----- determine scale and offset -----
shift = m_LICShift;
if (cntShift == 0)
{
scale = (1 << shift);
offset = 0;
return;
}
const int cropShift = std::max(0, bitDepth - precShift + cntShift - 15);
const int xzOffset = (xx >> m_LICRegShift);
const int sumX = x << precShift;
const int sumY = y << precShift;
const int sumXX = ((xx + xzOffset) >> (cropShift << 1)) << cntShift;
const int sumXY = ((xy + xzOffset) >> (cropShift << 1)) << cntShift;
const int sumXsumX = (x >> cropShift) * (x >> cropShift);
const int sumXsumY = (x >> cropShift) * (y >> cropShift);
int a1 = sumXY - sumXsumY;
int a2 = sumXX - sumXsumX;
int scaleShiftA2 = getMSB(abs(a2)) - 6;
int scaleShiftA1 = scaleShiftA2 - m_LICShiftDiff;
scaleShiftA2 = std::max(0, scaleShiftA2);
scaleShiftA1 = std::max(0, scaleShiftA1);
const int scaleShiftA = scaleShiftA2 + 15 - shift - scaleShiftA1;
a1 = a1 >> scaleShiftA1;
a2 = Clip3(0, 63, a2 >> scaleShiftA2);
scale = int((int64_t(a1) * int64_t(m_LICMultApprox[a2])) >> scaleShiftA);//LJY:缩放a
scale = Clip3(0, 1 << (shift + 2), scale);
const int maxOffset = (1 << (bitDepth - 1)) - 1;
const int minOffset = -1 - maxOffset;
offset = (sumY - ((scale * sumX) >> shift) + ((1 << (cntShift)) >> 1)) >> cntShift;//LJY:偏移量b
offset = Clip3(minOffset, maxOffset, offset);
}