1. base mesh帧间编码框架
VDMC现有草案理论上是将base mesh划分成若干个submesh进行独立编解码处理,但是再参考软件中实际上是将一整个base mesh作为一个submesh进行处理的。base mesh编码相关的功能由compressBaseMesh实现,其会判断base mesh的类型,分别执行不同的操作。针对帧间base mesh,会先进行当前帧base mesh的量化,如果是P base mesh,则会利用量化后的base mesh与参考帧量化后的 base mesh进行MV的计算与编码,主要由函数compressMotion执行。针对Skip base mesh,其不用编码任何信息,只需要复用参考帧的base mesh即可。base mesh在编码过程中由于拆非流形或者是量化会产生重复点,若考虑到MV的高效编码,则需要进行去重操作,该步骤由removeDuplicatedVertices函数执行。最后需要对当前帧的base mesh进行细分,用于后续displacement的计算。
2. P submesh的MV编码
现有MV编码方式有两种:
1.直接编码MV
2.对MV进行预测,编码MVD
- 为了节省内存开销,TM软件中对MV预测时参考顶点数进行了限制,最大为3。compressMotion函数首先会遍历当前帧base mesh的三角面片,建立每个顶点相邻可参考顶点的索引列表。
- 第二步是计算MV,MV分为三种情况:非重复点MV、重复点相同MV、重复点不同MV。非重复点MV是指参考帧中顶点和当前帧中顶点都不是重复顶点时,两者之间的MV;重复点相同MV是指参考帧中的重复点在当前帧中的对应点也是重复点,所以重复点的MV相同;重复点不同MV是指参考帧中的重复点在当前帧中的对应点不是重复点,其MV不同。对于重复顶点MV相同的情况,其不需要进行重复编码,只用编码一次MV即可。而重复顶点MV不同时,则需要将所有MV进行编码,其计算时存放在motion_no_skip数据结构中。对于非重复点的MV则存放在motion数据结构中。
- 第三步是用头信息中的skip_mode_bits来区分base mesh重复点MV编码的不同情况:base mesh不存在重复顶点、base mesh中重复顶点的MV均相同、base mesh中重复顶点的MV存在不同。
- 第四步将非重复点MV和重复点不同MV进行合并
- 最后是按顶点索引顺序,将MV划分成组进行编码。每个组最多包含16个顶点,分别计算直接编码MV和进行均值预测后编码MVD的bit开销,选择bit开销小的编码方式。
3. 相关代码
bool VMCEncoder::compressMotion(
const std::vector<Vec3<int32_t>>& triangles,
const std::vector<Vec3<int32_t>>& reference,
const std::vector<Vec2<int32_t>>& baseIntegrateIndices,
const std::vector<Vec3<int32_t>>& current,
BaseMeshTileLayer& bmtl,
const VMCEncoderParameters& params) {
const auto pointCount = int32_t(current.size());
const auto maxAcBufLen = pointCount * 3 * 4 + 1024;
VMCMotionACContext ctx;
EntropyEncoder arithmeticEncoder;
arithmeticEncoder.setBuffer(maxAcBufLen, nullptr);
arithmeticEncoder.start();
//构建每个顶点相邻可参考顶点索引表,每个最多可参考3个相邻点的MV进行预测
if (createVertexAdjTableMotion) {
computeVertexAdjTableMotion(
triangles, pointCount, params.maxNumNeighborsMotion);
createVertexAdjTableMotion = false;
}
std::vector<Vec3<int32_t>> motion;
std::vector<Vec3<int32_t>> motion_no_skip;//用于存储重复顶点MV不同的MV
motion.reserve(pointCount);
std::vector<uint8_t> no_skip_vindices;//存放MV不同的
std::vector<int32_t> no_skip_refvindices;//参考帧中重复顶点具有不同MV的顶点索引
for (int vindex = 0; vindex < pointCount; ++vindex) {
auto it =
std::lower_bound(baseIntegrateIndices.begin(),
baseIntegrateIndices.end(),
Vec2<int32_t>(vindex, 0),
[](const Vec2<int32_t>& a, const Vec2<int32_t>& b) {
return a[0] < b[0];
});
//当前顶点是重复顶点
if (it != baseIntegrateIndices.end() && (*it)[0] == vindex) {
auto integrate_from = (*it)[0];
auto integrate_to = (*it)[1];
//integrate_from和Integrate_to代表参考帧中重复顶点的索引
//当对应两个顶点在当前帧base mesh中坐标相同时,代表重复点的MV相同,跳过其编码
if (current[integrate_from] == current[integrate_to]) {
// skip (same motion vector)
} else {//重复顶点具有不同MV
auto no_skip_vindex = static_cast<int32_t>(
std::distance(baseIntegrateIndices.begin(), it));//记录到表头的距离
if (no_skip_vindex > 255) {
std::cout << "[DEBUG][error] no_skip_vindex: " << no_skip_vindex
<< std::endl;
return 1;
}
no_skip_vindices.push_back(
static_cast<decltype(no_skip_vindices)::value_type>(no_skip_vindex));
it =
std::lower_bound(baseIntegrateIndices.begin(),
baseIntegrateIndices.end(),
Vec2<int32_t>(integrate_to, 0),
[](const Vec2<int32_t>& a, const Vec2<int32_t>& b) {
return a[0] < b[0];
});
auto shift = std::distance(baseIntegrateIndices.begin(), it);
auto refvindex = static_cast<int32_t>(integrate_to - shift);//获取该点在参考帧中的索引值
no_skip_refvindices.push_back(refvindex);
motion_no_skip.push_back(current[vindex] - reference[refvindex]);//计算MV
std::cout << "[DEBUG][stat] MV of " << integrate_from << " and "
<< integrate_to
<< " are not same: " << *motion_no_skip.rbegin() << " vs "
<< (current[integrate_to] - reference[refvindex])
<< ". Total MVs: "
<< static_cast<int32_t>(baseIntegrateIndices.size())
<< ". vertex index: " << no_skip_vindex << "\n";
}
} else {//非重复顶点
auto shift = std::distance(baseIntegrateIndices.begin(), it);
auto refvindex = vindex - shift;
motion.push_back(current[vindex] - reference[refvindex]);
}
}//进行MV计算
std::cout << "[DEBUG][stat] total vertex num: " << pointCount
<< " duplicated vertex num: "
<< (current.size() - reference.size())
<< " non-skippable MV num: " << no_skip_vindices.size()
<< std::endl;
bool all_skip_mode = no_skip_vindices.empty();//重复顶点MV是否都相同的标志
auto no_skip_num = no_skip_vindices.size();//重复顶点MV不同的个数
// here we should add all_skip_mode into bitstream before starting to encode the MVs
//在开始编码MV之前在码流中添加all_skip_mode
//1 bit用于编码all_skip_mode,7 bits用于编码no_skip_num
uint8_t skip_mode_bits; // 1 bit for all_skip_mode, 7 bits for no_skip_num
if (no_skip_num > 127) {
std::cout << "[DEBUG][error] no_skip_num: " << no_skip_num << std::endl;
return 1;
};
auto& bmth = bmtl.getHeader();
auto& bmtdu = bmtl.getDataUnit();
//编码规则
if (baseIntegrateIndices.empty()) {//若不存在重复顶点
skip_mode_bits = 255;
bmtdu.getMotionSkipFlag() = false;
bmtdu.getMotionSkipCount() = 0;
} else {
bmtdu.getMotionSkipFlag() = true;
if (all_skip_mode) {//重复顶点MV都相同
skip_mode_bits = 128;
bmtdu.getMotionSkipAll() = true;
bmtdu.getMotionSkipCount() = 0;
} else {//重复顶点中有MV不同的
skip_mode_bits = static_cast<uint8_t>(no_skip_num);
bmtdu.getMotionSkipFlag() = true;
bmtdu.getMotionSkipAll() = false;
bmtdu.getMotionSkipCount() = no_skip_num;
}
}
// bitstream.write(skip_mode_bits);
if (!all_skip_mode) {//有重复顶点的MV不同
bmtdu.getMotionSkipVextexIndices() = no_skip_vindices;//MV不同的顶点索引信息
motion.insert(motion.end(), motion_no_skip.begin(), motion_no_skip.end());//将motion_no_skip插在motion的末尾
}
printf("MotionSkipFlag = %d \n", bmtdu.getMotionSkipFlag());
printf("MotionSkipAll = %d \n", bmtdu.getMotionSkipAll());
printf("MotionSkipCount = %d \n", bmtdu.getMotionSkipCount());
printf("skip_mode_bits = %u \n", skip_mode_bits);
const auto motionCount = static_cast<int32_t>(motion.size());//MV总数
const auto refPointCount = static_cast<int32_t>(reference.size());//参考顶点总数
printf("pointCount = %d motionCount = %d \n", pointCount, motionCount);
fflush(stdout);
int32_t remainP = motionCount;//待编码MV数
int32_t vindexS = 0, vindexE = 0, vCount = 0;
//遍历MV进行编码
while (remainP) {
vindexS = vindexE;
std::vector<Vec3<int32_t>> resV0, resV1;
resV0.resize(0);
resV1.resize(0);
//两种模式的标志位
auto bits0 = ctx.estimatePred(0);
auto bits1 = ctx.estimatePred(1);
vCount = 0;//vCount用于进行记录当前Group内顶点数
while ((vCount < params.motionGroupSize) && (remainP)) {
int32_t vindex0 = vindexE;
++vindexE;
--remainP;
int vindex = vindex0;
//重复顶点MV处理
if (vindex0 >= refPointCount) {
vindex = no_skip_refvindices[vindex0 - refPointCount];
}
Vec3<int32_t> pred(0);
int32_t predCount = numNeighborsMotion[vindex];//当前MV相邻可用参考顶点个数
for (int32_t i = 0; i < predCount; ++i) {
const auto vindex1 =
vertexAdjTableMotion[vindex * params.maxNumNeighborsMotion + i];
const auto& mv1 = motion[vindex1];
for (int32_t k = 0; k < 3; ++k) { pred[k] += mv1[k]; }
}
if (predCount > 1) {
const auto bias = predCount >> 1;
for (int32_t k = 0; k < 3; ++k) {
pred[k] = pred[k] >= 0 ? (pred[k] + bias) / predCount
: -(-pred[k] + bias) / predCount;
}
}
const auto res0 = motion[vindex0];
const auto res1 = motion[vindex0] - pred;
++vCount;
resV0.push_back(res0);
resV1.push_back(res1);
bits0 += ctx.estimateRes(res0);//直接编码MV的bit开销
bits1 += ctx.estimateRes(res1);//编码MVD的bit开销
}
std::vector<Vec3<int32_t>> resV;
Vec3<int32_t> res{};
//编码模式
if (bits0 <= bits1) {
resV = resV0;
arithmeticEncoder.encode(0, ctx.ctxPred);
} else {
resV = resV1;
arithmeticEncoder.encode(1, ctx.ctxPred);
}
vCount = -1;
for (int vindex0 = vindexS; vindex0 < vindexE; ++vindex0) {
++vCount;
res = resV[vCount];//对应顶点的MV或者MVD
for (int32_t k = 0; k < 3; ++k) {//遍历X、y、z三个分量
auto value = res[k];
arithmeticEncoder.encode(static_cast<int>(value != 0),
ctx.ctxCoeffGtN[0][k]);//值是否为零
if (value == 0) { continue; }
arithmeticEncoder.encode(static_cast<int>(value < 0), ctx.ctxSign[k]);//编码正负号
value = std::abs(value) - 1;//值绝对值减1
arithmeticEncoder.encode(static_cast<int>(value != 0),
ctx.ctxCoeffGtN[1][k]);//绝对值减1是为0
if (value == 0) { continue; }
assert(value > 0);
arithmeticEncoder.encodeExpGolomb(--value, 0, ctx.ctxCoeffRemPrefix);//指数哥伦布码进行编码
}
}
}
const auto length = arithmeticEncoder.stop();//统计编码MV花费总bit数
std::cout << "Motion byte count = " << length << '\n';
assert(length <= std::numeric_limits<uint32_t>::max());
bmtdu.getData().resize(length);
std::copy(arithmeticEncoder.buffer(),
arithmeticEncoder.buffer() + length,
bmtdu.getData().vector().begin());
return true;
}