PCL库中Marching Cubes(移动立方体)算法的解析

2023-05-16

PCL库中Marching Cubes(移动立方体)算法解析

1. Marching Cubes算法的原理这里不再赘述,不懂的话,提供一下文献资源:

链接:MARCHING CUBES A HIGH RESOLUTION 3D SURFACE CONSTRUCTION ALGORITHM.pdf
提取码:n0wb
或者看这里的讲解:MarchingCubes算法提取等值面的基本原理

2. 提供一下PCL里面的源码,有需要的可以下载:

链接:marchingCubes.zip
提取码:oyw1
打开之后,有四个文件:
在这里插入图片描述
marching_cubes文件中实现了marching_cubes类,实现了大部分的Marching Cubes算法,但它是一个抽象类,不可实例化。
marching_cubes_hoppe文件实现了marching_cubes_hoppe类,继承自marching_cubes,实现了marching_cubes中的纯虚函数vexelizeData(),可以实例化。

3. 此篇博文是参考下面博文得出的,希望能讲的更透彻一些

PCL源码剖析之MarchingCubes算法

—————————————————————————————————————————————————————————————
—————————————————————————————————————————————————————————————

下面我们开始正式分析PCL中Marching Cubes的实现方案。
步骤:点云数据体素化->确定顶点与等值面的关系->确定等值面与voxel各个边的交点->根据交点划分三角面。

4. 打开marching_cubes.h,找到顶点编号图

在这里插入图片描述
这个主要是帮助我们理解算法,8个顶点,12条边,都有各自的编号。更具体的如下图:
在这里插入图片描述

5. 打开marching_cubes.hpp,找到performReconstruction()函数。

这里是主函数体,通过调用具体的实现函数,完成了从点云数据到三角面的转换操作。

// 输入参数:无 
// 输出参数:1. points:重建得到的三角面,以点序列的形式存储,每三个点代表一个三角面
//			 2. polygons:三角面之间的连接关系,对理解算法基本无帮助
template <typename PointNT> void
pcl::MarchingCubes<PointNT>::performReconstruction (pcl::PointCloud<PointNT> &points,
                                                    std::vector<pcl::Vertices> &polygons)
{
	// iso_level_:等值面的数值,用于判定网格点的两种状态(即在等值面的内外)。
	//这里为什么限制取值在(0,1)之间,我也很困惑。不太影响理解,因此略过。
  if (!(iso_level_ >= 0 && iso_level_ < 1))
  {
    PCL_ERROR ("[pcl::%s::performReconstruction] Invalid iso level %f! Please use a number between 0 and 1.\n", 
        getClassName ().c_str (), iso_level_);
    points.width = points.height = 0;
    points.points.clear ();
    polygons.clear ();
    return;
  }
  // intermediate_cloud:存储三角面的临时点序列
  pcl::PointCloud<PointNT> intermediate_cloud;

  // res_x_: x方向划分网格的数量
  // grid_:存储网格点与等值面的距离,用以和iso_level比较,确定网格点的状态(等值面内外)
  grid_ = std::vector<float> (res_x_*res_y_*res_z_, NAN);

  // input_:输入的点云数据
  // tree_:建立点云数据的KDtree,得到点云内点的关系
  tree_->setInputCloud (input_);

  // getBoundingBox:计算点云的包围盒,即三个方向的最大最小值,比较简单,不赘述
  getBoundingBox ();
  // upper_boundary_:包围盒最大值
  // lower_boundary_:包围盒最小值
  // size_voxel_:体素网格的大小,根据网格的数量和包围盒大小来计算
  size_voxel_ = (upper_boundary_ - lower_boundary_) 
    * Eigen::Array3f (res_x_, res_y_, res_z_).inverse ();

  // voxelizeData():将点云数据转化为体素网格的数据,即确定网格点的状态(在等值面的内外)
  // 这个函数是纯虚函数,在marching_cubes_hoppe中实现的
  voxelizeData ();

  // 为三角面的点序列预分配内存。极端情况下,存在6个面的边界Voxel,即2*(YZ+XZ+XY)。假设边界voxel均产生两个三角面,即6个点。
  double size_reserve = std::min((double) intermediate_cloud.points.max_size (),
      2.0 * 6.0 * (double) (res_y_*res_z_ + res_x_*res_z_ + res_x_*res_y_));
  intermediate_cloud.reserve ((size_t) size_reserve);
  
  //对每个Voxel进行三角化操作
  for (int x = 1; x < res_x_-1; ++x)
    for (int y = 1; y < res_y_-1; ++y)
      for (int z = 1; z < res_z_-1; ++z)
      {
		  //index_3d:存储当前voxel的的位置信息xyz
		  //leaf_node:存储当前voxel中8个顶点(网格点)与等值面的距离
        Eigen::Vector3i index_3d (x, y, z);
        std::vector<float> leaf_node;
		// getNeighborList1D:根据index_3d获得leaf_node的值,即voxel顶点的状态,比较简单,不再赘述
        getNeighborList1D (leaf_node, index_3d);
        if (!leaf_node.empty ())
			// createSurface:根据index_3d和leaf_node得到三角面的点序列intermediate_cloud
          createSurface (leaf_node, index_3d, intermediate_cloud);
      }
	  // 将临时点序列intermediate_cloud的内容放到输出点序列points中
  points.swap (intermediate_cloud);
 
 // 存储三角面之间的关系,处理简单,用处不大。
  polygons.resize (points.size () / 3);
  for (size_t i = 0; i < polygons.size (); ++i)
  {
    pcl::Vertices v;
    v.vertices.resize (3);
    for (int j = 0; j < 3; ++j)
      v.vertices[j] = static_cast<int> (i) * 3 + j;
    polygons[i] = v;
  }
}

这里我做了比较详细的注释,一般情况下看懂是没问题的。
其中iso_level比较难以理解,如果不明白,可以继续往下看。

6. 打开marching_cubes_hoppe.hpp,找到voxelizeData()函数。

这里完成的是点云体素化,确定顶点与等值面的关系。

template <typename PointNT> void
pcl::MarchingCubesHoppe<PointNT>::voxelizeData ()
{
	//dist_ignore_:限制网格点与点云的最远距离,如果大于此距离,认为网格点与边界过远,不计算。
	//is_far_ignored:判定对距离是否有限制。
  const bool is_far_ignored = dist_ignore_ > 0.0f;

  for (int x = 0; x < res_x_; ++x)
  {
    const int y_start = x * res_y_ * res_z_;

    for (int y = 0; y < res_y_; ++y)
    {
      const int z_start = y_start + y * res_z_;

      for (int z = 0; z < res_z_; ++z)
      {
		  // nn_indices:用于存储当前网格点最近邻点的索引
		  // nn_sqr_dists:用于存储最近邻点与当前网格点的距离
        std::vector<int> nn_indices (1, 0);
        std::vector<float> nn_sqr_dists (1, 0.0f);
		
		// point:当前网格点的绝对位置
        const Eigen::Vector3f point = (lower_boundary_ + size_voxel_ * Eigen::Array3f (x, y, z)).matrix ();
        PointNT p;
        p.getVector3fMap () = point;
		
		// 搜索第一个近邻点(由第二个参数决定),即最近邻点,得到其索引和距离
        tree_->nearestKSearch (p, 1, nn_indices, nn_sqr_dists);
		
		//如果对最近邻点的距离没有限制或者距离在限制范围内,继续计算
        if (!is_far_ignored || nn_sqr_dists[0] < dist_ignore_)
        {
			// 得到最近邻点的单位法向量
          const Eigen::Vector3f normal = input_->points[nn_indices[0]].getNormalVector3fMap ();
		  // 如果向量存在,进行下一步
          if (!std::isnan (normal (0)) && normal.norm () > 0.5f)
			  //求网格点与等值面的距离,计算方法为最近邻点法向量点乘网格点与最近邻点形成的向量,因为点乘是映射过程
            grid_[z_start + z] = normal.dot (
                point - input_->points[nn_indices[0]].getVector3fMap ());
        }
      }
    }
  }
}

在这里插入图片描述
q是网格点,p是最近邻点,grid_[]里面存储的就是q点与 p点所在平面 之间的距离d

7. 打开marching_cubes.hpp,找到createSurface()函数。

这里完成的是 确定等值面与voxel各个边的交点并根据交点划分三角面。

//输入参数:1. leaf_node:存储当前voxel中8个顶点(网格点)与等值面的距离
//			2. index_3d:存储当前voxel的相对位置信息xyz
//输出参数:1. cloud:生成的三角面点序列
template <typename PointNT> void
pcl::MarchingCubes<PointNT>::createSurface (const std::vector<float> &leaf_node,
                                            const Eigen::Vector3i &index_3d,
                                            pcl::PointCloud<PointNT> &cloud)
{
	//cubeindex:存储8个顶点的状态,认为有0000 0000共8位,大于iso_level_的顶点置为0,小于的置为1.(共256种状态)
  int cubeindex = 0;
  if (leaf_node[0] < iso_level_) cubeindex |= 1;
  if (leaf_node[1] < iso_level_) cubeindex |= 2;
  if (leaf_node[2] < iso_level_) cubeindex |= 4;
  if (leaf_node[3] < iso_level_) cubeindex |= 8;
  if (leaf_node[4] < iso_level_) cubeindex |= 16;
  if (leaf_node[5] < iso_level_) cubeindex |= 32;
  if (leaf_node[6] < iso_level_) cubeindex |= 64;
  if (leaf_node[7] < iso_level_) cubeindex |= 128;

  // edgeTable:存储边的状态,对应于点的状态,共256种情况,共12位数据,1代表该边被等值面切削,0代表没有。(在marching_cubes.h中)
  // cubeindex=255或0时,这时顶点全在等值面一侧,根据表格内容,edgeTable=0,不存在三角面。
  if (edgeTable[cubeindex] == 0)
    return;

	//center:当前体素(接近零点位置的顶点)的绝对位置
  const Eigen::Vector3f center = lower_boundary_ 
    + size_voxel_ * index_3d.cast<float> ().array ();

	//p: 存储8个体素顶点的位置
  std::vector<Eigen::Vector3f, Eigen::aligned_allocator<Eigen::Vector3f> > p;
  p.resize (8);
  for (int i = 0; i < 8; ++i)
  {
    Eigen::Vector3f point = center;
    if (i & 0x4)
      point[1] = static_cast<float> (center[1] + size_voxel_[1]);

    if (i & 0x2)
      point[2] = static_cast<float> (center[2] + size_voxel_[2]);

    if ((i & 0x1) ^ ((i >> 1) & 0x1))
      point[0] = static_cast<float> (center[0] + size_voxel_[0]);

    p[i] = point;
  }

  // 确定体素边与等值面的交点
  // vertex_list:存储12个交点的值
  std::vector<Eigen::Vector3f, Eigen::aligned_allocator<Eigen::Vector3f> > vertex_list;
  vertex_list.resize (12);
  //判断如果该边与等值面有交点的话,进行线行插值得到交点
  if (edgeTable[cubeindex] & 1)
    interpolateEdge (p[0], p[1], leaf_node[0], leaf_node[1], vertex_list[0]);
  if (edgeTable[cubeindex] & 2)
    interpolateEdge (p[1], p[2], leaf_node[1], leaf_node[2], vertex_list[1]);
  if (edgeTable[cubeindex] & 4)
    interpolateEdge (p[2], p[3], leaf_node[2], leaf_node[3], vertex_list[2]);
  if (edgeTable[cubeindex] & 8)
    interpolateEdge (p[3], p[0], leaf_node[3], leaf_node[0], vertex_list[3]);
  if (edgeTable[cubeindex] & 16)
    interpolateEdge (p[4], p[5], leaf_node[4], leaf_node[5], vertex_list[4]);
  if (edgeTable[cubeindex] & 32)
    interpolateEdge (p[5], p[6], leaf_node[5], leaf_node[6], vertex_list[5]);
  if (edgeTable[cubeindex] & 64)
    interpolateEdge (p[6], p[7], leaf_node[6], leaf_node[7], vertex_list[6]);
  if (edgeTable[cubeindex] & 128)
    interpolateEdge (p[7], p[4], leaf_node[7], leaf_node[4], vertex_list[7]);
  if (edgeTable[cubeindex] & 256)
    interpolateEdge (p[0], p[4], leaf_node[0], leaf_node[4], vertex_list[8]);
  if (edgeTable[cubeindex] & 512)
    interpolateEdge (p[1], p[5], leaf_node[1], leaf_node[5], vertex_list[9]);
  if (edgeTable[cubeindex] & 1024)
    interpolateEdge (p[2], p[6], leaf_node[2], leaf_node[6], vertex_list[10]);
  if (edgeTable[cubeindex] & 2048)
    interpolateEdge (p[3], p[7], leaf_node[3], leaf_node[7], vertex_list[11]);

  // 根据12条边交点的情况生成三角形
  //triTable是256*16的数组(在marching_cubes.h中),第一维的256对应256种边界情况,第二维的16对应每种情况下三角形的索引...
  //我们知道,一个体素内最多有5个三角面,因此最多需要3*5=15个顶点来存储,这里第16个值均为-1,是为了跳出循环。
  for (int i = 0; triTable[cubeindex][i] != -1; i += 3)
  {
	  //根据边界情况得到三角形的交点,存储起来,即完成了整个三角化过程。
    PointNT p1, p2, p3;
    p1.getVector3fMap () = vertex_list[triTable[cubeindex][i]];
    cloud.push_back (p1);
    p2.getVector3fMap () = vertex_list[triTable[cubeindex][i+1]];
    cloud.push_back (p2);
    p3.getVector3fMap () = vertex_list[triTable[cubeindex][i+2]];
    cloud.push_back (p3);
  }
}

举个栗子:
假如我们的根据leaf_node得出cube_index=15=0000 1111,意味着0,1,2,3端点在等值面内部。我们在edgeTable(在marching_cubes.h中)表中查找第16个数据,edgeTable[15]=0xf00=1111 0000 0000,意味着8,9,10,11四条边与等值面相交,如下:
在这里插入图片描述
执行线性插值,即可得到四个交点的具体位置。这里得到了一个空间四边形,下一步需要将其划分为两个三角形,我们就要用到表triTable(在marching_cubes.h中),根据cube_index=15,我们找到triTable[15]={9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};取前三个边索引得到 三角形9-8-10,放入三角面点序列;取第4-6个边索引得到 三角形10-8-11,放入点序列;第7个索引边为-1,跳出循环,就完成了三角面的划分。结果如下:
在这里插入图片描述

8. 到这里基本就讲完了整套算法,有些简单的并没有介绍,都在文件中。可能有些地方我的理解存在错误,如有发现,还望告知,与君共勉!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

PCL库中Marching Cubes(移动立方体)算法的解析 的相关文章

  • Jetpack Compose 从入门到入门(一)

    Jetpack Compose 是用于构建原生 Android 界面的新工具包 它使用更少的代码 强大的工具和直观的 Kotlin API xff0c 可以帮助您简化并加快 Android 界面开发 xff0c 打造生动而精彩的应用 它可让
  • Jetpack Compose 从入门到入门(二)

    开始布局部分 这部分我个人感觉没有必要每个组件 属性都详细说到 xff0c 否则篇幅会很长 建立起Compose中的组件与 Android Views的一个对应关系就够了 具体还是需要在实际的使用中去熟悉 1 Column 子元素按竖直顺序
  • Jetpack Compose 从入门到入门(三)

    本篇开始介绍Jetpack Compose 中的修饰符Modifier 修饰符可以用来执行以下操作 xff1a 更改可组合项的大小 布局 行为和外观 添加信息 xff0c 如无障碍标签 处理用户输入 添加高级互动 xff0c 如使元素可点击
  • 【操作系统】Linux系统中直接优化提升CPU性能(已解决)

    文章目录 问题 xff1a 服务器CPU没有调用最高性能 xff0c 导致跑算法的时候处理速度慢一 BIOS方法二 终端直接设置CPU调节器方法1 查看当前CPU调节器2 安装各种依赖库3 最后安装cpufrequtis工具包并设置CPU调
  • Jetpack Compose 从入门到入门(四)

    本篇开始介绍Jetpack Compose 中常用的组件 有一部分之前的文章中也出现过 xff0c 今天详细说明一下 1 Text 日常最常用的应该就是显示文字 xff0c 所以有必要说一下Text控件 首先源码如下 xff1a span
  • Jetpack Compose 从入门到入门(五)

    应用中的状态是指可以随时间变化的任何值 这是一个非常宽泛的定义 xff0c 从 Room 数据库到类的变量 xff0c 全部涵盖在内 由于Compose是声明式UI xff0c 会根据状态变化来更新UI xff0c 因此状态的处理至关重要
  • Jetpack Compose 从入门到入门(六)

    本篇说说Compose中的Canvas 1 Canvas span class token annotation builtin 64 Composable span span class token keyword fun span sp
  • Jetpack Compose 从入门到入门(七)

    本篇进入Compose 动画部分 1 动画预览 在本系列第一篇中我们提到过 xff0c 64 Preview可以帮我们实现UI的预览功能 xff0c 简单的交互和播放动画 在Android Studio Bumblebee xff08 大黄
  • Android 12 变更及适配攻略

    这几个月有点忙 xff0c 一年一篇的适配文章来的有点晚了 但其实也还好 xff0c 因为我们项目也是下半年才适配 我这边也是提前调研踩坑 xff0c 评估一下工作量 这个时间点也完全跟得上Google Play的审核要求 xff08 11
  • Jetpack Compose 从入门到入门(八)

    接着上一篇的动画部分 xff0c 本篇主要是自定义动画与Animatable AnimationSpec 上一篇中 xff0c 出现了多次animationSpec属性 xff0c 它是用来自定义动画规范的 例如 xff1a span cl
  • Jetpack Compose 从入门到入门(九)

    本篇是Compose的手势部分 点击 添加clickable修饰符就可以轻松实现元素的点击 此外它还提供无障碍功能 xff0c 并在点按时显示水波纹效果 span class token annotation builtin 64 Comp
  • 记参加 2022 Google开发者大会

    前几天有幸参加了2022年Google 开发者大会 Google Developer Summit xff0c 上一次参加Google开发者大会还是2019年 这期间因为众所周知的原因 xff0c 开发者大会都改为了线上举办 和上次相比可以
  • Jetpack Compose 从入门到入门(十)

    本篇介绍如何将Jetpack Compose 添加到已有应用中 xff0c 毕竟大多数情况都是在现有项目中使用 Jetpack Compose 旨在配合既有的基于 View 的界面构造方式一起使用 如果您要构建新应用 xff0c 最好的选择
  • Flutter状态管理之Riverpod 2.0

    两年前分享过一篇Flutter状态管理之Riverpod xff0c 当时riverpod的版本还是0 8 0 xff08 后来文章更新到0 14版本 xff09 当时提到过有一些不足之处 xff1a 毕竟诞生不久 xff0c 它还不能保证
  • Python:元组和字典简述

    目录 1 列表的方法2 for循环遍历列表2 1 语法2 2 range 函数 3 元组3 1 元组的基本概念3 2 元组的创建3 3 元组的解包3 3 1 号在解包中的用法 4 字典4 1 字典的基本概念4 2 字典的使用4 2 1 字典
  • 七种常见软件开发模型

    目录 瀑布模型 xff08 面向文档的软件开发模型 xff09 演化模型 螺旋模型 增量模型 构件组装模型 统一过程 xff08 up xff09 xff08 迭代的软件过程 xff0c 以架构为中心 xff09 敏捷开发模型 瀑布模型 x
  • IP安全策略:只允许指定IP连接远程桌面,限制IP登录

    一 xff0c 新建IP安全策略 WIN 43 R打开运行对话框 xff0c 输入gpedit msc进入组策略编辑器 依次打开 本地计算机 策略 计算机配置 Windows设置 安全设置 IP安全策略 在 本地计算机上 在右面的空白处右击
  • 2022年终总结

    不知不觉就到了年末 xff0c 感叹时间过的真快 我自己坚持写了七年多的博客 xff0c 但这其实是我第一次去写年终总结 也不知道怎么写 xff0c 就简单聊聊 写博客的初衷就是个人收获 xff0c 学习的记录 xff0c 分享出来如果能帮
  • Rust库交叉编译以及在Android与iOS中使用

    本篇是关于交叉编译Rust库 xff0c 生成Android和iOS的二进制文件 xff08 so与a文件 xff09 xff0c 以及简单的集成使用 1 环境 系统 xff1a macOS 13 0 M1 Pro xff0c Window
  • 利用Rust与Flutter开发一款小工具

    1 起因 起因是年前看到了一篇Rust 43 iOS amp Android xff5c 未入门也能用来造轮子 xff1f 的文章 xff0c 作者使用Rust做了个实时查看埋点的工具 其中作者的一段话给了我启发 xff1a 无论是 Loo

随机推荐

  • 在Android与iOS中使用LLDB调试Rust程序

    在Rust中通过println 打印的日志信息在Xcode中可以显示 xff0c 但是Android Studio里不显示 所以Android可以使用android logger实现日志输出 但是开发中仅使用打印日志的方式进行调试还是不够的
  • 使用jni-rs实现Rust与Android代码互相调用

    本篇主要是介绍如何使用jni rs 有关jni rs内容基于版本0 20 0 xff0c 新版本写法有所不同 入门用法 在Rust库交叉编译以及在Android与iOS中使用中我简单说明了jni rs及demo代码 xff0c 现在接着补充
  • Android 13 变更及适配攻略

    准备工作 首先将我们项目中的 targetSdkVersion和compileSdkVersion 升至 33 影响Android 13上所有应用 1 通知受限 对新安装的应用的影响 xff1a 如果用户在搭载 Android 13 或更高
  • 洛谷 P1185 绘制二叉树

    一道极为恐怖的模拟题 xff0c 以定义函数的方式确定每个点的x xff0c y就能轻松的做出这道题 xff0c 参考神犇题解 洛谷 P1185 KH 39 s blog 洛谷博客 遇到这种题估计就是放弃了 AC代码 xff08 抄的 xf
  • 洛谷 P3366 【模板】最小生成树#Kruskal+并查集

    说了最小生成树 xff0c 那么就用经典的Prim或者Kruskal xff0c 不过Prim实现代码有点多 xff0c 这里用Kruskal举例 注意事项 1 Kruskal是用来找最小生成树的 根据树的定义可以知道 树是无向图 所以Kr
  • STM32MP157AAA3裸机点灯(汇编)

    STM32MP157AAA3裸机点灯 汇编 MP157的A7核裸机点灯 使用的开发板为华清远见的MP157开发板 xff0c 默认板内emmc已经烧写好了uboot 这篇就只记录一下汇编点灯过程 xff0c uboot等内容暂不涉及 xff
  • 用tkinter写出you-get下载器界面,并用pyinstaller打包成exe文件

    写在前面 xff1a 本文为笔者最早于 2019 05 11 23 15 以 64 拼命三郎 的身份发表于博客园 本文为原创文章 xff0c 转载请标明出处 一 you get介绍 you get是一个基于 python3 的下载工具 xf
  • Linux网络协议栈4--bridge收发包

    bridge 是linux上的虚拟交换机 xff0c 具有交换机的功能 网卡收到包后 xff0c 走到 netif receive skb core后 xff0c 剥完vlan找到vlan子接口 xff08 如果有的话 xff09 xff0
  • linux redis启动时报错WARNING overcommit_memory is set to 0! Background save may fail under low memory con

    报错 xff1a WARNING overcommit memory is set to 0 Background save may fail under low memory condition To fix this issue add
  • STM32编程语言介绍

    STM32入门100步 第8期 编程语言介绍 杜洋 洋桃电子 上一期我们在电脑上安装好了KEIL软件 xff0c 也新建了工程 xff0c 在工程中安装了固件库 准备工作完成后 xff0c 接着就是在工程中编写程序了 只有程序使ARM内核有
  • VMware虚拟机安装Linux教程(超详细)

    写给读者 为了帮助Linux系统初学者学习的小伙伴更好的学习 xff0c VMware虚拟机是不可避免的 xff0c 因此下载 安装VMware和完成Linux的系统安装是非常必要的 接下来 xff0c 我们就来系统的学习一下VMware虚
  • Markdown中的LaTeX公式——希腊字母详解

    若要在Markdown中使用 xff0c 则在两个美元符号之间敲入对应LaTeX代码实现公式行显示效果 xff0c 若为公式块 xff0c 则要在四个美元符号中间敲入 xff0c 类似Markdown代码行和代码块 共24个希腊字母 xff
  • FFmpeg学习(一)-- ffmpeg 播放器的基础

    FFmpeg学习 xff08 一 xff09 FFmpeg学习 xff08 二 xff09 FFmpeg学习 xff08 三 xff09 FFmpeg的的是一套可以用来记录 xff0c 转换数字音频 xff0c 视频 xff0c 并能将其转
  • ios Instruments之Allocations

    文章目录 一 span class hljs function Allocations 监测内存分配 span 1 简介 2 如何使用 一 Allocations 1 简介 性能优化中使用Instruments Allocations工具进
  • linux-Centos-7-64位:4、 mysql安装

    从最新版本的Linux系统开始 xff0c 默认的是 Mariadb而不是MySQL xff01 这里依旧以mysql为例进行展示 1 先检查系统是否装有mysql rpm qa span class hljs string style c
  • Win10 WSL忘记用户密码,重置密码

    win10中WSL登录是不用密码的 xff0c 当需要使用用户权限但是忘记密码的时候 xff0c 可以使用如下办法以root身份登录WSL并重置密码 1 以管理员身份打开 PowerShell 2 输入命令 wsl exe user roo
  • 51单片机定时时间的计算

    单片机根据计时 计数模式的不同 xff0c 来进行计算 M1 M0 工作模式 说明 0 0 0 13位计时计数器 xff08 8192 xff09 0 1 1 16位计时计数器 xff08 65536 xff09 1 0 2 8位计时计数器
  • Go语言之禅

    本文翻译自Go社区知名Gopher和博主Dave Cheney的文章 The Zen of Go 本文来自我在GopherCon Israel 2020上的演讲 文章很长 如果您希望阅读精简版 xff0c 请移步到the zen of go
  • UIScrollView及其子类停止滚动的监测

    作为iOS中最重要的滑动控件 UIScrollView居然没有停止滚动的Delegate方法 这有点蛋疼 但是我们可以根据滚动状态来判断是否滚动 span class hljs preprocessor pragma mark scroll
  • PCL库中Marching Cubes(移动立方体)算法的解析

    PCL库中Marching Cubes xff08 移动立方体 xff09 算法解析 1 Marching Cubes算法的原理这里不再赘述 xff0c 不懂的话 xff0c 提供一下文献资源 xff1a 链接 xff1a MARCHING