VDMC参考软件TM4.0代码阅读——帧间MV编码

文章详细介绍了基于VDMC草案的basemesh帧间编码框架,包括对Pbasemesh和Skipbasemesh的处理,以及MV的计算与编码。在MV编码中,提出了直接编码和预测编码MVD两种方式,并针对重复顶点进行了优化。代码示例展示了如何构建顶点相邻表、计算MV并进行编码决策的过程。
摘要由CSDN通过智能技术生成

1. base mesh帧间编码框架

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编码过程
现有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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值