V-DMC基础网格编码顶点顺序映射

前言

由于帧内编码基础网格时,采用EdgeBreaker编码器对基础网格顶点几何位置进行编码,会导致解码端解码出的顶点顺序与编码端不一致,不做处理的话会导致解码端解码出的MV以及位移添加到错误的位置上。
因此,在编码端编码I_BASEMESH时,会计算一个mapping,用于调整顶点顺序,使得解码端获取的MV和位移能够叠加在正确位置上。

代码

默认情况下,I_BASEMESH会调用computeDracoMapping函数计算编码前与编码后顶点索引之间的对应关系,其中会对base mesh进行无损编码,为的就是获得解码后的顶点索引顺序。为了构建编码前顶点索引与解码顶点索引之间的映射关系,会使用顶点的3维几何坐标和2维纹理坐标进行点与点之间的匹配。

bool
VMCEncoder::computeDracoMapping(TriangleMesh<MeshType>      base,
                                std::vector<int32_t>&       mapping,
                                const int32_t               frameIndex,
                                const VMCEncoderParameters& params) const {
  // Save intermediate files
  if (params.keepBaseMesh) {
    base.save(_keepFilesPathPrefix + "fr_" + std::to_string(frameIndex)
              + "_mapping_src.ply");
  }
  // Scale
  const auto scalePosition = 1 << (18 - params.bitDepthPosition);
  const auto scaleTexCoord = 1 << 18;
  for (int32_t v = 0, vcount = base.pointCount(); v < vcount; ++v) {
    base.setPoint(v, (1 << 18) + Round(base.point(v) * scalePosition));
  }//量化几何坐标
  for (int32_t tc = 0, tccount = base.texCoordCount(); tc < tccount; ++tc) {
    base.setTexCoord(tc, Round(base.texCoord(tc) * scaleTexCoord));
  }//量化纹理坐标
  // Save intermediate files
  if (params.keepBaseMesh) {
    base.save(_keepFilesPathPrefix + "fr_" + std::to_string(frameIndex)
              + "_mapping_scale.ply");
  }

  // Encode
  GeometryEncoderParameters encoderParams;
  // remove this one ??
  encoderParams.cl_ = 10;  // this is the default for draco
  //  were not specified before, using scale, extend to 20bits as base values may extend beyond the original scale
  encoderParams.qp_ = 20;
  // in practice 19 bits are used for texcoords as 1.0 is scaled as 0x4000
  // this only has incidence when qp-bits packing is used in the encoder
  encoderParams.qt_ = 19;
  // end remove those
  encoderParams.dracoUsePosition_  = params.dracoUsePosition;
  encoderParams.dracoUseUV_        = params.dracoUseUV;
  encoderParams.dracoMeshLossless_ = params.dracoMeshLossless;
  encoderParams.predCoder_         = params.predCoder;
  encoderParams.topoCoder_         = params.topoCoder;
  encoderParams.traversal_         = params.baseMeshVertexTraversal;
  encoderParams.reindexOutput_     = params.motionVertexTraversal;
  encoderParams.baseMeshDeduplicatePositions_ =
    params.baseMeshDeduplicatePositions;
  encoderParams.verbose = false;

  TriangleMesh<MeshType> rec;//重建
  std::vector<uint8_t>   geometryBitstream;
  auto encoder = GeometryEncoder<MeshType>::create(params.meshCodecId);
  printf("DracoMapping: use_position = %d use_uv = %d mesh_lossless = %d \n",
         encoderParams.dracoUsePosition_,
         encoderParams.dracoUseUV_,
         encoderParams.dracoMeshLossless_);
  encoder->encode(base, encoderParams, geometryBitstream, rec);//模拟进行编解码

  // Save intermediate files
  if (params.keepBaseMesh) {
    auto prefix =
      _keepFilesPathPrefix + "fr_" + std::to_string(frameIndex) + "_mapping";
    base.save(prefix + "_enc.ply");
    rec.save(prefix + "_rec.ply");
    save(prefix + ".drc", geometryBitstream);
  }

  // Geometry parametrisation base
  auto fsubdiv0 = base;
  fsubdiv0.subdivideMidPoint(params.liftingSubdivisionIterationCount);//先对base进行细分(包括纹理坐标)
  if (params.keepBaseMesh) {
    fsubdiv0.save(_keepFilesPathPrefix + "fr_" + std::to_string(frameIndex)
                  + "_mapping_fsubdiv0.ply");
  }
  std::vector<int32_t> texCoordToPoint0;
  if (fsubdiv0.texCoordCount() > 0)
    fsubdiv0.computeTexCoordToPointMapping(texCoordToPoint0);//计算纹理坐标索引与顶点索引之间的对应关系  textCoordToPoint0[纹理索引] = 顶点几何索引
  struct ArrayHasher {
    std::size_t operator()(const std::array<double, 5>& a) const {
      std::size_t h = 0;
      for (auto e : a) {
        h ^= std::hash<double>{}(e) + 0x9e3779b9 + (h << 6) + (h >> 2);
      }
      return h;
    }
  };
  std::map<std::array<double, 5>, int32_t> map0;
  const auto texCoordCount0 = fsubdiv0.texCoordCount();//纹理坐标个数
  const auto coordCount0    = fsubdiv0.pointCount();//细分后顶点个数
  int32_t    vCount = texCoordCount0 > 0 ? texCoordCount0 : coordCount0;
  for (int32_t v = 0; v < vCount; ++v) {//遍历纹理坐标个数
    const auto& point0 = texCoordCount0 > 0
                           ? fsubdiv0.point(std::max(0, texCoordToPoint0[v]))
                           : fsubdiv0.point(v);//顶点几何坐标(三维)
    const auto& texCoord0 =
      texCoordCount0 > 0 ? fsubdiv0.texCoord(v) : Vec2<MeshType>(0.0);//顶点纹理坐标(二维)
    const std::array<double, 5> vertex0{
      point0[0], point0[1], point0[2], texCoord0[0], texCoord0[1]};//将几何坐标和UV坐标作为索引信息
    map0[vertex0] = texCoordCount0 > 0 ? texCoordToPoint0[v] : v;//mapp0中存储的是编码前对应的((顶点位置,UV坐标),顶点索引)
  }

  // Geometry parametrisation rec (after compression)
  auto fsubdiv1 = rec;
  fsubdiv1.subdivideMidPoint(params.liftingSubdivisionIterationCount);//对解码重建的base进行细分
  if (params.keepBaseMesh) {
    fsubdiv1.save(_keepFilesPathPrefix + "fr_" + std::to_string(frameIndex)
                  + "_mapping_fsubdiv1.ply");
  }
  const auto pointCount1 = fsubdiv1.pointCount();//重建细分顶点个数
  mapping.resize(pointCount1, -1);//mapping的size是解码后细分的顶点个数
  std::vector<bool> tags(pointCount1, false);//标记是否处理过该顶点
  for (int32_t t = 0, tcount = fsubdiv1.triangleCount(); t < tcount; ++t) {//遍历细分后的三角
    const auto& tri = fsubdiv1.triangle(t);//三角面片
    const auto& triUV =
      fsubdiv1.texCoordCount() > 0 ? fsubdiv1.texCoordTriangle(t) : tri;//三个纹理坐标索引
    for (int32_t k = 0; k < 3; ++k) {
      const auto indexPos = tri[k];//解码后顶点索引
      if (tags[indexPos]) { continue; }
      tags[indexPos]                            = true;
      const auto                  indexTexCoord = triUV[k];//获取顶点对应的纹理索引
      const auto&                 point1        = fsubdiv1.point(indexPos);//获取顶点几何坐标
      const auto&                 texCoord1     = fsubdiv1.texCoordCount() > 0
                                                    ? fsubdiv1.texCoord(indexTexCoord)
                                                    : Vec2<MeshType>(0.0);//获取顶点纹理坐标
      const std::array<double, 5> vertex1       = {
        point1[0], point1[1], point1[2], texCoord1[0], texCoord1[1]};
      const auto it = map0.find(vertex1);//在mapp0里面查找当前顶点,依据是顶点几何坐标和纹理坐标
      if (it != map0.end()) {
        mapping[indexPos] = map0[vertex1];//构建解码顶点索引与编码前顶点索引的映射关系
      } else {
        assert(0);
      }
    }
  }
  return true;
}

第一个受影响的base mesh编码,帧内由于其直接编码顶点坐标,所以其顶点索引顺序编解码端不一致不会有什么影响,但是帧间base mesh编码时,需要计算当前帧base mesh(编码前)相对于参考帧base mesh(解码后)的MV,因此涉及到顶点索引的不一致问题,需要在计算MV前,将base的顶点顺序调整成和解码端一致,这样MV计算才能对的上。
其对应的在compressBaseMesh中的代码如下:

 bmsh.setSmhType(P_BASEMESH);
    auto& bmsdu = bmsl.getSubmeshDataunitInter();

    //TODO: update gof to reference list
    //frame.referenceFrameIndex : 0~31
    const auto& refFrame             = gof[frame.referenceFrameIndex];
    auto&       mapping              = frame.mapping;
    auto&       qpositions           = frame.qpositions;
    mapping                          = refFrame.mapping;//参考帧mapping
    const auto pointCountRecBaseMesh = refFrame.base.pointCount();//参考帧重建base(解码后的顶点索引顺序)
    qpositions.resize(pointCountRecBaseMesh);
    for (int32_t v = 0; v < pointCountRecBaseMesh; ++v) {//遍历解码顶点索引
      assert(mapping[v] >= 0 && mapping[v] < base.pointCount());
      qpositions[v] = (base.point(mapping[v]) * scalePosition).round();//mapping[v]是编码端的顶点位置,所以qposition存储的顺序是按解码顺序存储的
    }
    base = refFrame.base;
    for (int32_t v = 0, vcount = base.pointCount(); v < vcount; ++v) {
      base.point(v) = qpositions[v];//将编码端顶点位置调整成解码端一致的
    }
    // decide to use P_BASEMESH or SKIP_BASEMESH
    auto frameInfoInterOrSkipType =
      chooseSkipOrInter(qpositions, refFrame.qpositions);
    if (frameInfoInterOrSkipType == P_BASEMESH) {
      std::cout << "\n(compressBaseMesh) FrameIndex: " << frame.frameIndex
                << " submeshIndex: " << submeshIndex
                << " submeshType: P_SUBMESH\t";
      std::cout << "referenceFrameIndex : " << frame.referenceFrameIndex
                << " \n";
      fflush(stdout);
      compressMotion(refFrame, qpositions, bmsl, params, frame.frameIndex);
      for (int32_t v = 0, vcount = base.pointCount(); v < vcount; ++v) {
        base.point(v) = qpositions[v];
      }

除了MV编码之外,第二个受影响的就是位移编码,因为位移本质上就是重建的细分结果(rcsubdiv)与编码端的frame.subdiv顶点之间的位置差异。由于重建细分的顶点索引会受到重建的base mesh索引顺序的影响,所以无论是I_BASEMESH还是P_BASEMESH,其重建的细分顶点顺序与编码端的frame.subdiv顶点顺序一定不一致,需要进行调整。
其对应的在compress函数中的代码如下:

    if (!params.dracoMeshLossless) {
      auto rsubdiv = rec;//重建的细分结果
      std::swap(rsubdiv, frame.subdiv);//交换重建结果与frame.subdiv
      const auto& mapping = frame.mapping;
      for (int32_t v = 0, vcount = frame.subdiv.pointCount(); v < vcount;
           ++v) {
        const auto vindex = mapping[v];//v是解码顺序,vindex是编码顺序
        assert(vindex >= 0 && vindex < vcount);
        frame.subdiv.setPoint(v, rsubdiv.point(vindex));
      }
    }
  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值