Jack Huang's Blog


  • 首页

  • 标签

  • 归档

  • 搜索

机器学习之相关概念

发表于 2018-11-19 | 更新于 2024-12-18

机器学习是人工智能的一个分支。人工智能的研究历史有着一条从以“推理”为重点,到以“知识”为重点,再到以“学习”为重点的自然、清晰的脉络。显然,机器学习是实现人工智能的一个途径,即以机器学习为手段解决人工智能中的问题。

定义

机器学习有下面几种定义:

  • 机器学习是一门人工智能的科学,该领域的主要研究对象是人工智能,特别是如何在经验学习中改善具体算法的性能。
  • 机器学习是对能通过经验自动改进的计算机算法的研究。
  • 机器学习是用数据或以往的经验,以此优化计算机程序的性能标准。

一种经常引用的英文定义是:A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by P, improves with experience E.

分类

机器学习可以分成下面几种类别:

  • 监督学习:从给定的训练数据集中学习出一个函数,当新的数据到来时,可以根据这个函数预测结果。监督学习的训练集要求是包括输入和输出,也可以说是特征和目标。训练集中的目标是由人标注的。常见的监督学习算法包括回归分析和统计分类。

监督学习和非监督学习的差别就是训练集目标是否人标注。他们都有训练集且都有输入和输出。

  • 无监督学习:与监督学习相比,训练集没有人为标注的结果。常见的无监督学习算法有生成对抗网络(GAN)、聚类。
  • 半监督学习:介于监督学习与无监督学习之间。
  • 强化学习:通过观察来学习做成如何的动作。每个动作都会对环境有所影响,学习对象根据观察到的周围环境的反馈来做出判断。

算法

具体的机器学习算法有:

  • 构造间隔理论分布:聚类分析和模式识别
    • 人工神经网络
    • 决策树
    • 感知器
    • 支持向量机
    • 集成学习AdaBoost
    • 降维与度量学习
    • 聚类
    • 贝叶斯分类器
  • 构造条件概率:回归分析和统计分类
    • 高斯过程回归
    • 线性判别分析
    • 最近邻居法
    • 径向基函数核
  • 通过再生模型构造概率密度函数:
    • 最大期望算法
    • 概率图模型:包括贝叶斯网和Markov随机场
    • Generative Topographic Mapping
  • 近似推断技术:
    • 马尔可夫链
    • 蒙特卡罗方法
    • 变分法
  • 最优化:大多数以上方法,直接或者间接使用最优化算法。

机器学习基础

标签

标签是我们要预测的事物,即简单线性回归中的 y 变量。标签可以是小麦未来的价格、图片中显示的动物品种、音频剪辑的含义或任何事物。

特征

特征是输入变量,即简单线性回归中的 x 变量。简单的机器学习项目可能会使用单个特征,而比较复杂的机器学习项目可能会使用数百万个特征,按如下方式指定:
$${ x_1,x_2,…x_N } $$

在垃圾邮件检测器示例中,特征可能包括:

  • 电子邮件文本中的字词
  • 发件人的地址
  • 发送电子邮件的时段
  • 电子邮件中包含“一种奇怪的把戏”这样的短语。

样本

样本是指数据的特定实例:x。(我们采用粗体 x 表示它是一个矢量。)我们将样本分为以下两类:

  • 有标签样本
  • 无标签样本

有标签样本同时包含特征和标签,常用于训练模型。。即:

1
labeled examples: {features, label}: (x, y)

无标签样本包含特征,但不包含标签,常用于模型预测。即:

1
unlabeled examples: {features, ?}: (x, ?)

模型

模型定义了特征与标签之间的关系。例如,垃圾邮件检测模型可能会将某些特征与“垃圾邮件”紧密联系起来。我们来重点介绍一下模型生命周期的两个阶段:

  • 训练表示创建或学习模型。向模型展示有标签样本,让模型逐渐学习特征与标签之间的关系。

  • 推断表示将训练后的模型应用于无标签样本。使用训练后的模型来做出有用的预测 (y’)。

回归与分类

回归模型可预测连续值。例如,回归模型做出的预测可回答如下问题:

  • 加利福尼亚州一栋房产的价值是多少?

  • 用户点击此广告的概率是多少?

分类模型可预测离散值。例如,分类模型做出的预测可回答如下问题:

  • 某个指定电子邮件是垃圾邮件还是非垃圾邮件?

  • 这是一张狗、猫还是仓鼠图片?

损失

训练模型表示通过有标签样本来学习(确定)所有权重和偏差的理想值。在监督式学习中,机器学习算法通过以下方式构建模型:检查多个样本并尝试找出可最大限度地减少损失的模型;这一过程称为经验风险最小化。

损失是对糟糕预测的惩罚。也就是说,损失是一个数值,表示对于单个样本而言模型预测的准确程度。如果模型的预测完全准确,则损失为零,否则损失会较大。训练模型的目标是从所有样本中找到一组平均损失“较小”的权重和偏差。

平方损失:又称为 $L_2$ 损失,一种常见的损失函数。例如单个样本的平方损失如下:

1
2
3
= the square of the difference between the label and the prediction
= (observation - prediction(x))^2
= (y - y')^2

均方误差 (MSE) 指的是每个样本的平均平方损失。要计算 MSE,请求出各个样本的所有平方损失之和,然后除以样本数量:
$$
MSE = \frac{1}{N} \sum_{(x,y)\in D} (y - prediction(x))^2
$$

其中:

  • (x,y)指的是样本,其中
    • x指的是模型进行预测时使用的特征集(例如,温度、年龄和交配成功率)。
    • y指的是样本的标签(例如,每分钟的鸣叫次数)。
  • prediction(x)指的是权重和偏差与特征集 x 结合的函数。
  • D指的是包含多个有标签样本(即 (x,y))的数据集。
  • N指的是D中的样本数量。

迭代方法

下图显示了机器学习算法用于训练模型的迭代试错过程:

用于训练模型的迭代方法

图1 用于训练模型的迭代方法

梯度下降法

计算参数更新的目标是在模型的迭代试错过程中,使损失越来越小。而常用的方法就是梯度下降法。

回归问题产生的损失与权重图为凸形

图2 回归问题产生的损失与权重图为凸形

对于图2所示的凸形问题,刚好存在一个斜率正好为 0 的位置,即是损失函数的收敛之处。梯度下降法的第一个阶段是为$w_1$ 选择一个起始值(起点)。

然后,梯度下降法算法会计算损失曲线在起点处的梯度。简而言之,梯度是偏导数的矢量;它可以让您了解哪个方向距离目标“更近”或“更远”。

请注意,梯度是一个矢量,因此具有以下两个特征:

  • 方向
  • 大小

梯度始终指向损失函数中增长最为迅猛的方向。梯度下降法算法会沿着负梯度的方向走一步,以便尽快降低损失。

为了确定损失函数曲线上的下一个点,梯度下降法算法会将梯度大小的一部分与起点相加,如图3所示:

一个梯度步长将我们移动到损失曲线上的下一个点

图3 一个梯度步长将我们移动到损失曲线上的下一个点

然后,梯度下降法会重复此过程,逐渐接近最低点。

学习速率

梯度下降法算法用梯度乘以一个称为学习速率(有时也称为步长)的标量,以确定下一个点的位置。例如,如果梯度大小为 2.5,学习速率为 0.01,则梯度下降法算法会选择距离前一个点 0.025 的位置作为下一个点。

超参数是编程人员在机器学习算法中用于调整的旋钮。大多数机器学习编程人员会花费相当多的时间来调整学习速率。如果您选择的学习速率过小,就会花费太长的学习时间。如果您指定的学习速率过大,下一个点将永远在 U 形曲线的底部随意弹跳。

随机梯度下降法

在梯度下降法中,批量指的是用于在单次迭代中计算梯度的样本总数。到目前为止,我们一直假定批量是指整个数据集。就 Google 的规模而言,数据集通常包含数十亿甚至数千亿个样本。此外,Google 数据集通常包含海量特征。因此,一个批量可能相当巨大。如果是超大批量,则单次迭代就可能要花费很长时间进行计算。

通过从我们的数据集中随机选择样本,我们可以通过小得多的数据集估算(尽管过程非常杂乱)出较大的平均值。 随机梯度下降法 (SGD) 将这种想法运用到极致,它每次迭代只使用一个样本(批量大小为 1)。如果进行足够的迭代,SGD 也可以发挥作用,但过程会非常杂乱。“随机”这一术语表示构成各个批量的一个样本都是随机选择的。

小批量随机梯度下降法(小批量 SGD)是介于全批量迭代与 SGD 之间的折衷方案。小批量通常包含 10-1000 个随机选择的样本。小批量 SGD 可以减少 SGD 中的杂乱样本数量,但仍然比全批量更高效。

泛化(Generalization)

泛化是指机器学习对从真实概率分布(已隐藏)中抽取的新数据做出良好预测的能力。要取得良好的泛化能力,机器学习必须满足以下基本假设,同时防止过拟合。

  • 机器学习的基本假设:

    • 从分布中随机抽取独立同分布 (i.i.d) 的样本。换言之,样本之间不会互相影响。(另一种解释:i.i.d. 是表示变量随机性的一种方式)。
    • 分布是平稳的;即分布在数据集内不会发生变化。
    • 从同一分布的数据划分中抽取样本。
  • 过拟合模型在训练过程中产生的损失很低,但在预测新数据方面的表现却非常糟糕。

训练集与测试集

机器学习模型旨在根据以前未见过的新数据做出良好预测。但是,如果您要根据数据集构建模型,如何获得以前未见过的数据呢?一种方法是将您的数据集分成两个子集:

  • 训练集 - 用于训练模型的子集。
  • 测试集 - 用于测试模型的子集。

测试集应满足以下两个条件:

  • 规模足够大,可产生具有统计意义的结果。
  • 能代表整个数据集。换言之,挑选的测试集的特征应该与训练集的特征相同。

验证集

将数据集划分为训练集和测试集两个子集是个不错的想法,但不是万能良方。通过将数据集划分为训练集、验证集、测试集三个子集,可以大幅降低过拟合的发生几率。

使用验证集评估训练集的效果。然后,在模型“通过”验证集之后,使用测试集再次检查评估结果。图4展示了这一新工作流程:

使用验证集的工作流程

图4 使用验证集的工作流程

特征表示

特征工程指的是将原始数据转换为特征矢量。进行特征工程预计需要大量时间。

  • 映射数值: 机器学习模型根据浮点值进行训练,因此整数和浮点原始数据不需要特殊编码。
  • 映射字符串值: 首先为要表示的所有特征的字符串值定义一个词汇表。然后使用该词汇表创建一个独热编码,用于将指定字符串值表示为二元矢量。
  • 映射分类(枚举)值

通过独热编码映射字符串值

图5 通过独热编码映射字符串值

良好特征的特点

  • 避免很少使用的离散特征值。

良好的特征值应该在数据集中出现大约 5 次以上。这样一来,模型就可以学习该特征值与标签是如何关联的。

  • 最好具有清晰明确的含义。

每个特征对于项目中的任何人来说都应该具有清晰明确的含义。例如,下面的房龄适合作为特征,可立即识别为年龄:

1
house_age: 27
  • 不要将“神奇”的值与实际数据混为一谈

良好的浮点特征不包含超出范围的异常断点或“神奇”的值。例如,假设一个特征具有 0 到 1 之间的浮点值。那么,如下值是可以接受的:

1
2
quality_rating: 0.82
quality_rating: 0.37

不过,如果用户没有输入 quality_rating,则数据集可能使用如下神奇值来表示不存在该值:

1
quality_rating: -1

为解决神奇值的问题,需将该特征转换为两个特征:

1
2
3
一个特征只存储质量评分,不含神奇值。

一个特征存储布尔值,表示是否提供了 quality_rating。为该布尔值特征指定一个名称,例如 is_quality_rating_defined。
  • 考虑上游不稳定性

特征的定义不应随时间发生变化。例如,下列值是有用的,因为城市名称一般不会改变。(注意,我们仍然需要将“br/sao_paulo”这样的字符串转换为独热矢量。)

1
city_id: "br/sao_paulo"

但收集由其他模型推理的值会产生额外成本。可能值“219”目前代表圣保罗,但这种表示在未来运行其他模型时可能轻易发生变化:

1
inferred_city_cluster: "219"

清理数据

即使是非常少量的坏样本会破坏掉一个大规模数据集,因此需花费大量的时间挑出坏样本并加工可以挽救的样本。

  1. 缩放特征值: 缩放是指将浮点特征值从自然范围(例如 100 到 900)转换为标准范围(例如 0 到 1 或 -1 到 +1)。如果特征集包含多个特征,则缩放特征可以带来以下优势:

    • 帮助梯度下降法更快速地收敛。
    • 帮助避免“NaN 陷阱”。
    • 帮助模型为每个特征确定合适的权重。
  2. 处理极端离群值

    • 对每个值取对数
    • 将最大值“限制”为某个任意值
  3. 分箱

  4. 清查

数据集中的很多样本是不可靠的,原因有以下一种或多种:

  • 遗漏值。 例如,有人忘记为某个房屋的年龄输入值。
  • 重复样本。 例如,服务器错误地将同一条记录上传了两次。
  • 不良标签。 例如,有人错误地将一颗橡树的图片标记为枫树。
  • 不良特征值。 例如,有人输入了多余的位数,或者温度计被遗落在太阳底下。

正则化

图6泛化曲线显示的是训练集和验证集相对于训练迭代次数的损失。

训练集和验证集损失

图6 训练集和验证集损失

图6显示的是某个模型的训练损失逐渐减少,但验证损失最终增加。换言之,该泛化曲线显示该模型与训练集中的数据过拟合。根据奥卡姆剃刀定律,或许我们可以通过降低复杂模型的复杂度来防止过拟合,这种原则称为正则化。

正则化以最小化损失和复杂度为目标,这称为结构风险最小化:
$$\text{minimize(Loss(Data|Model) + complexity(Model))}$$

现在,训练优化算法是一个由两项内容组成的函数:一个是损失项,用于衡量模型与数据的拟合度,另一个是正则化项,用于衡量模型复杂度。

有两种常用衡量模型复杂度的方法:

  • 将模型复杂度作为模型中所有特征的权重的函数。
  • 将模型复杂度作为具有非零权重的特征总数的函数。

如果模型复杂度是权重的函数,则特征权重的绝对值越高,对模型复杂度的贡献就越大。

L2正则化

可以使用 L2 正则化公式来量化复杂度,该公式将正则化项定义为所有特征权重的平方和:
$$L_2\text{ regularization term} = ||\boldsymbol w||_2^2 = {w_1^2 + w_2^2 + … + w_n^2}$$

在这个公式中,接近于 0 的权重对模型复杂度几乎没有影响,而离群值权重则可能会产生巨大的影响。

模型开发者通过以下方式来调整正则化项的整体影响:用正则化项的值乘以名为 lambda(又称为正则化率)的标量。也就是说,模型开发者会执行以下运算:

$$\text{minimize(Loss(Data|Model)} + \lambda \text{ complexity(Model))}$$

执行 L2 正则化对模型具有以下影响:

  • 使权重值接近于 0(但并非正好为 0)
  • 使权重的平均值接近于 0,且呈正态(钟形曲线或高斯曲线)分布。

在选择 lambda 值时,目标是在简单化和训练数据拟合之间达到适当的平衡:

  • 如果您的 lambda 值过高,则模型会非常简单,但是您将面临数据欠拟合的风险。您的模型将无法从训练数据中获得足够的信息来做出有用的预测。

  • 如果您的 lambda 值过低,则模型会比较复杂,并且您将面临数据过拟合的风险。您的模型将因获得过多训练数据特点方面的信息而无法泛化到新数据。

L1正则化

稀疏矢量通常包含许多维度。创建特征组合会导致包含更多维度。由于使用此类高维度特征矢量,因此模型可能会非常庞大,并且需要大量的 RAM。

在高维度稀疏矢量中,最好尽可能使权重正好降至 0。正好为 0 的权重基本上会使相应特征从模型中移除。 将特征设为 0 可节省 RAM 空间,且可以减少模型中的噪点。

L1 正则化使模型中很多信息缺乏的系数正好为 0,从而在推理时节省 RAM,同时具有凸优化的优势,可有效进行计算。

L2 和 L1 采用不同的方式降低权重:

  • L2 会降低权重2。
  • L1 会降低 |权重|。

因此,L2 和 L1 具有不同的导数:

  • L2 的导数为 2 * 权重。
  • L1 的导数为 k(一个常数,其值与权重无关)。

逻辑回归

许多问题需要将概率估算值作为输出。逻辑回归是一种极其高效的概率计算机制。实际上,您可以通过下两种方式之一使用返回的概率:

  • “按原样”
  • 转换成二元类别。

在很多情况下,您会将逻辑回归输出映射到二元分类问题的解决方案,该二元分类问题的目标是正确预测两个可能的标签(例如,“垃圾邮件”或“非垃圾邮件”)中的一个。

您可能想知道逻辑回归模型如何确保输出值始终落在 0 和 1 之间。巧合的是,S 型函数生成的输出值正好具有这些特性,其定义如下:

$$y = \frac{1}{1 + e^{-z}}$$

S 型函数会产生以下曲线图:

S 型函数

图7 S 型函数

如果 z 表示使用逻辑回归训练的模型的线性层的输出,则 S 型(z) 函数会生成一个介于 0 和 1 之间的值(概率)。用数学方法表示为:

$$y’ = \frac{1}{1 + e^{-(z)}}$$

其中:

  • y’ 是逻辑回归模型针对特定样本的输出。
  • z 是 b + w1x1 + w2x2 + … wNxN
    • “w”值是该模型学习的权重和偏差。
    • “x”值是特定样本的特征值。

请注意,z 也称为对数几率,因为 S 型函数的反函数表明,z 可定义为标签“1”(例如“狗叫”)的概率除以标签“0”(例如“狗不叫”)的概率得出的值的对数:
$$z = log(\frac{y}{1-y})$$

逻辑回归模型训练

线性回归的损失函数是平方损失。逻辑回归的损失函数是对数损失函数,定义如下:
$$Log Loss = \sum_{(x,y)\in D} -ylog(y’) - (1 - y)log(1 - y’)$$

其中:

  • (xy)ϵD 是包含很多有标签样本 (x,y) 的数据集。
  • “y”是有标签样本中的标签。由于这是逻辑回归,因此“y”的每个值必须是 0 或 1。
  • “y’”是对于特征集“x”的预测值(介于 0 和 1 之间)。

对数损失函数的方程式与 Shannon 信息论中的熵测量密切相关。它也是似然函数的负对数(假设“y”属于伯努利分布)。实际上,最大限度地降低损失函数的值会生成最大的似然估计值。

正则化在逻辑回归建模中极其重要。如果没有正则化,逻辑回归的渐近性会不断促使损失在高维度空间内达到 0。因此,大多数逻辑回归模型会使用以下两个策略之一来降低模型复杂性:

  • L2 正则化。
  • 早停法,即,限制训练步数或学习速率。

分类

指定阈值

为了将逻辑回归值映射到二元类别,您必须指定分类阈值(也称为判定阈值)。如果值高于该阈值,则表示“垃圾邮件”;如果值低于该阈值,则表示“非垃圾邮件”。人们往往会认为分类阈值应始终为 0.5,但阈值取决于具体问题,因此您必须对其进行调整。

真与假以及正类别与负类别

  • 真正例是指模型将正类别样本正确地预测为正类别。
  • 真负例是指模型将负类别样本正确地预测为负类别。
  • 假正例是指模型将负类别样本错误地预测为正类别
  • 假负例是指模型将正类别样本错误地预测为负类别。

准确率

准确率是一个用于评估分类模型的指标。通俗来说,准确率是指我们的模型预测正确的结果所占的比例。正式点说,准确率的定义如下:

$$\text{Accuracy} = \frac{\text{Number of correct predictions}}{\text{Total number of predictions}}$$

对于二元分类,也可以根据正类别和负类别按如下方式计算准确率:

$$\text{Accuracy} = \frac{TP+TN}{TP+TN+FP+FN}$$

其中,TP = 真正例,TN = 真负例,FP = 假正例,FN = 假负例。

精确率和召回率

当使用分类不平衡的数据集(比如正类别标签和负类别标签的数量之间存在明显差异)时,单单准确率一项并不能反映全面情况。这时需要能够更好地评估分类不平衡问题的指标:精确率和召回率。

精确率的定义如下:
$$\text{Precision} = \frac{TP}{TP+FP}$$

从数学上讲,召回率的定义如下:
$$\text{Recall} = \frac{TP}{TP+FN}$$

要全面评估模型的有效性,必须同时检查精确率和召回率。遗憾的是,精确率和召回率往往是此消彼长的情况。

ROC 和曲线下面积

ROC 曲线(接收者操作特征曲线)是一种显示分类模型在所有分类阈值下的效果的图表。该曲线绘制了以下两个参数:

  • 真正例率
  • 假正例率

真正例率 (TPR) 是召回率的同义词,因此定义如下:

$$TPR = \frac{TP} {TP + FN}$$

假正例率 (FPR) 的定义如下:

$$FPR = \frac{FP} {FP + TN}$$

ROC 曲线用于绘制采用不同分类阈值时的 TPR 与 FPR。降低分类阈值会导致将更多样本归为正类别,从而增加假正例和真正例的个数。下图显示了一个典型的 ROC 曲线。

不同分类阈值下的 TP 率与 FP 率

图8 不同分类阈值下的 TP 率与 FP 率

曲线下面积表示“ROC 曲线下面积”。也就是说,曲线下面积测量的是从 (0,0) 到 (1,1) 之间整个 ROC 曲线以下的整个二维面积(参考积分学)。

曲线下面积(ROC 曲线下面积)

图9 曲线下面积(ROC 曲线下面积)

曲线下面积对所有可能的分类阈值的效果进行综合衡量。曲线下面积的一种解读方式是看作模型将某个随机正类别样本排列在某个随机负类别样本之上的概率。

曲线下面积的取值范围为 0-1。预测结果 100% 错误的模型的曲线下面积为 0.0;而预测结果 100% 正确的模型的曲线下面积为 1.0。

曲线下面积因以下两个原因而比较实用:

  • 曲线下面积的尺度不变。它测量预测的排名情况,而不是测量其绝对值。
  • 曲线下面积的分类阈值不变。它测量模型预测的质量,而不考虑所选的分类阈值。

不过,这两个原因都有各自的局限性,这可能会导致曲线下面积在某些用例中不太实用:

  • 并非总是希望尺度不变。 例如,有时我们非常需要被良好校准的概率输出,而曲线下面积无法告诉我们这一结果。

  • 并非总是希望分类阈值不变。 在假负例与假正例的代价存在较大差异的情况下,尽量减少一种类型的分类错误可能至关重要。例如,在进行垃圾邮件检测时,您可能希望优先考虑尽量减少假正例(即使这会导致假负例大幅增加)。对于此类优化,曲线下面积并非一个实用的指标。

预测偏差

逻辑回归预测应当无偏差。即:
$$ \text{预测平均值}\approx\text{观察平均值}$$
预测偏差指的是这两个平均值之间的差值。即:
$$\text{预测偏差} = \text{预测平均值} - \text{数据集中相应标签的平均值}$$

造成预测偏差的可能原因包括:

  • 特征集不完整
  • 数据集混乱
  • 模型实现流水线中有错误?
  • 训练样本有偏差
  • 正则化过强

端到端(end to end)

传统机器学习算法在应用过程中需要经历特征工程这一步骤,从研究对象中提取特征信息,便于后续的训练和测试。在传统机器学习中,特征工程非常重要,它提取特征的好坏关系到机器学习的最终效果。

本质上,特征工程是将研究对象信息降维的过程。而深度学习则无需这一手工提取特征的过程。以深度学习在图像分类中的应用为例,它直接输入高维的原始图像,输出即是图像分类。这个过程即叫做端到端。

参考链接

  1. 机器学习,by wikipedia.
  2. 机器学习速成课程,by google.
  3. MathJax使用LaTeX语法编写数学公式教程
  4. 什么是 end-to-end 神经网络?,by zhihu.
  5. 数学基础——浅谈似然,by shidata.
  6. 【机器学习】解构概率,重构世界:贝叶斯定理与智能世界的暗语,by 半截诗​.

计算机视觉之相机成像原理与坐标系转换

发表于 2018-11-19 | 更新于 2021-06-27

计算机视觉是一门研究用摄影机和计算机代替人眼对目标进行识别、跟踪和测量的学科。为了解该门学科,首先应掌握投影原理和世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换关系。

三维投影

计算机3D图形学中,三维投影是将三维空间中的点映射到二维平面上的方法。常用三维投影有正交投影和透视投影。正交投影通常用于对现实物品的三维建模,而透视投影与人的视觉系统类似,常用于在二维平面呈现三维世界。

正交投影原理

正交投影是一系列用于显示三维物体的轮廓、细节或精确测量结果的变换方法。通常又称作截面图、鸟瞰图或立面图。

当视平面的法向(即摄像机的朝向)平行于笛卡尔坐标系三根坐标轴中的一根,数学变换定义如下: 若使用一个平行于y轴(侧视图)的正交投影将三维点 $a_{x}$, $a_{y}$,$a_{z}$投影到二维平面上得到二维点 $b_{x}$,$b_{y}$,可以使用如下公式

$$b_x=s_xa_x+c_x$$
$$b_y=s_za_z+c_z$$

其中向量s是一个任意的缩放因子,而c是一个任意的偏移量。这些常量可自由选择,通常用于将视口调整到一个合适的位置。该投影变换同样可以使用矩阵表示(为清晰起见引入临时向量d)

$$
\begin{bmatrix}
d_x \
d_y \
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 & 0 \
0 & 0 & 1 \
\end{bmatrix}
\begin{bmatrix}
a_x \
a_y \
a_z \
\end{bmatrix}
$$
$$
\begin{bmatrix}
b_x\
b_y\
\end{bmatrix}
=
\begin{bmatrix}
s_x & 0 \
0 & s_z \
\end{bmatrix}
\begin{bmatrix}
d_x\
d_y\
\end{bmatrix}
+
\begin{bmatrix}
c_x\
c_z\
\end{bmatrix}
$$
虽然正交投影产生的图像在一定程度上反映了物体的三维特性,但此类投影图像和实际观测到的并不相同。特别是对于相同长度的平行线段,无论离虚拟观察者(摄像机)远近与否,它们都会在正交投影中显示为相同长度。这会导致较近的线段看起来被缩短了。

透视投影原理

透视投影是为了获得接近真实三维物体的视觉效果而在二维的纸或者画布平面上绘图或者渲染的一种方法,它也称为透视图。透视投影的绘制必须根据已有的几何规则进行。

常用的透视投影视椎体模型如图1所示。设视点E位于原点,视平面P垂直于Z轴,且四边分别平行于x轴和y轴,视椎体的近截面离视点的距离为n,远截面离视点的距离为f,且一般取近截面为视平面。

透视投影的标准视椎体模型

图1 透视投影的标准视椎体模型

坐标系之间的转换

计算机视觉通常涉及到四个坐标系:像素平面坐标系(u,v)、像平面坐标系(图像物理坐标第(x,y)、相机坐标系(Xc,Yc,Zc)和世界坐标系(Xw,Yw,Zw),如图2所示。

四个坐标系

图2 四个坐标系

1 : 世界坐标系:根据情况而定,可以表示任何物体。单位m。

2:相机坐标系:以摄像机光心为原点(在针孔模型中也就是针孔为光心),z轴与光轴重合也就是z轴指向相机的前方(也就是与成像平面垂直),x轴与y轴的正方向与物体坐标系平行,其中上图中的f为摄像机的焦距。单位m

3:图像物理坐标系(也叫平面坐标系):用物理单位表示像素的位置,坐标原点为摄像机光轴与图像物理坐标系的交点位置。坐标系为图上o-xy。单位是mm。单位毫米的原因是此时由于相机内部的CCD传感器是很小的,比如8mm x 6mm。但是最后图像照片是也像素为单位比如640x480.这就涉及到了图像物理坐标系与像素坐标系的变换了。下面的像素坐标系将会讲到。

4:像素坐标系:以像素为单位,坐标原点在左上角。这也是一些opencv,OpenGL等库的坐标原点选在左上角的原因。当然明显看出CCD传感器以mm单位到像素中间有转换的。举个例子,CCD传感上上面的8mm x 6mm,转换到像素大小是640x480. 假如dx表示像素坐标系中每个像素的物理大小就是1/80. 也就是说毫米与像素点的之间关系是piexl/mm.

世界坐标系到相机坐标系的转换

物体之间的坐标系变换都可以表示坐标系的旋转变换加上平移变换,则世界坐标系到相机坐标系的转换关系也是如此,他们之间的变换如图3所示。

世界坐标系到相机坐标系的转换

图3 世界坐标系到相机坐标系的转换

可以得到P点在相机坐标系下的坐标:
$$
\begin{bmatrix}
X_c\
Y_c\
Z_c\
\end{bmatrix}
=
R
\begin{bmatrix}
X_w\
Y_w\
Z_w\
\end{bmatrix}
+T
\Rightarrow
\begin{bmatrix}
X_c\
Y_c\
Z_c\
1\
\end{bmatrix}
=
\begin{bmatrix}
R & T\
\vec{0} & 1\
\end{bmatrix}
\begin{bmatrix}
X_w\
Y_w\
Z_w\
1\
\end{bmatrix}
,
R:33,T:31
$$

相机坐标系到图像物理坐标系的转换

从相机坐标系到图像坐标系,属于透视投影关系,从3D转换到2D。 也可以看成是针孔模型的变种。该转换满足三角形的相似定理,如图4所示。

相机坐标系到图像物理坐标系的转换

图4 相机坐标系到图像物理坐标系的转换

图像物理坐标系到像素坐标系的转换

图像物理坐标系到像素坐标系的转换不涉及旋转变换,但是坐标原点位置不一致,大小不一致,涉及伸缩变换及平移变换,如图5所示。

图像物理坐标系到像素坐标系的转换

图5 图像物理坐标系到像素坐标系的转换

小结

四个坐标系之间存在着下述关系 ( 矩阵依次左乘 ),如图6所示:

世界坐标系到像素坐标系的转换

图6 世界坐标系到像素坐标系的转换

其中相机的内参和外参可以通过张正友标定获取。通过最终的转换关系来看,一个三维中的坐标点,的确可以在图像中找到一个对应的像素点,但是反过来,通过图像中的一个点找到它在三维中对应的点就很成了一个问题,因为我们并不知道等式左边的Zc的值。

参考链接

  1. 三维投影,by wikipedia.
  2. 透视投影的原理和实现,by Goncely.
  3. 计算机视觉:相机成像原理:世界坐标系、相机坐标系、图像坐标系、像素坐标系之间的转换,by 生活没有if-else
  4. 【相机标定】四个坐标系之间的变换关系
  5. SLAM相机位姿估计(1),by Zhao xuhui.
  6. 单目SLAM理论基础,by Zhao xuhui.
  7. 透视投影详解,by zdd.
  8. 旋转变换(一)旋转矩阵,by csxiaoshui.
  9. 第十二课 透视投影,by 极客学院.
  10. 光栅化渲染-6-透视投影矩阵,by Aincrad.

生活中的统计学之购买车展黄牛票

发表于 2018-11-17 | 更新于 2019-06-23

前段时间去看车展,一出地铁口,就有黄牛兜售车展门票,只需30元一张,而从车展正规窗口购买需要50元一张,那么买黄牛票还是买正规车展门票呢?

黄牛票有可能是真的,这样我就只需30元就可以参观车展,也有可能是假的,这样我就得花80元才能参观车展。假设黄牛票为真的概率是p1,根据概率论的知识,我参观车展花费的期望是:

$$E = 30p_1+80(1-p_1)$$

在没有任何先验知识的前提下,假设黄牛票为真的概率是0.5,于是每次买黄牛票参观车展的花费期望是55,而每次买正规门票参观车展的花费期望是50,因此不建议买黄牛票,而应该去买正规车展门票。

参考链接

  1. Cmd Markdown 公式指导手册
  2. Markdown中写数学公式

中国电信光猫华为HG8245C开启IPV6的方法

发表于 2018-11-11 | 更新于 2019-04-04

据报道,目前中国电信已成功创建了IP骨干网全面支持IPv6,并且在4G网络开启了IPv6服务,在100多个城域网提供了IPv6服务[1]。那么如何使家里宽带用上IPv6服务呢?这个问题最关键是设置入户光猫使其支持IPv6。以如何光猫华为HG8245C为例,说明设置过程。

基础知识

IPv6简介

网际协议第6版(英文:Internet Protocol version 6,缩写:IPv6)是网际协议(IP)的最新版本,用作互联网的网上层协议,用它来取代IPv4主要是为了解决IPv4地址枯竭问题,不过它也在其他很多方面对IPv4有所改进。

IPv6格式

IPv6二进位制下为128位长度,以16位为一组,每组以冒号“:”隔开,可以分为8组,每组以4位十六进制方式表示。例如:2001:0db8:85a3:08d3:1319:8a2e:0370:7344 是一个合法的IPv6地址。

同时IPv6在某些条件下可以省略:

  1. 每项数字前导的0可以省略,省略后前导数字仍是0则继续,例如下组IPv6是等价的。
    1
    2
    3
    4
    5
    2001:0DB8:02de:0000:0000:0000:0000:0e13
    2001:DB8:2de:0000:0000:0000:0000:e13
    2001:DB8:2de:000:000:000:000:e13
    2001:DB8:2de:00:00:00:00:e13
    2001:DB8:2de:0:0:0:0:e13
  2. 可以用双冒号“::”表示一组0或多组连续的0,但只能出现一次。
    1
    2
    3
    4
    5
    6
    7
    * 2001:DB8:2de:0:0:0:0:e13
    2001:DB8:2de::e13
    * 2001:0DB8:0000:0000:0000:0000:1428:57ab
    2001:0DB8:0000:0000:0000::1428:57ab
    2001:0DB8:0:0:0:0:1428:57ab
    2001:0DB8:0::0:1428:57ab
    2001:0DB8::1428:57ab
  3. 如果这个地址实际上是IPv4的地址,后32位可以用10进制数表示;因此::ffff:192.168.89.9 相等于::ffff:c0a8:5909。

IPv6地址分类

常用地址

IPv6地址可分为三种:

  • 单播(unicast)地址:单播地址标示一个网上接口。协议会把送往地址的数据包送往给其接口。
  • 任播(anycast)地址: Anycast是IPv6特有的数据发送方式,它像是IPv4的Unicast(单点传播)与Broadcast(多点广播)的综合。
  • 多播(multicast)地址: 多播地址也称组播地址。多播地址也被指定到一群不同的接口,送到多播地址的数据包会被发送到所有的地址。

特殊地址

未指定地址

  • ::/128-所有比特皆为零的地址称作未指定地址。

链路本地地址

  • ::1/128-是一种单播绕回地址。如果一个应用程序将数据包送到此地址,IPv6堆栈会转送这些数据包绕回到同样的虚拟接口(相当于IPv4中的127.0.0.1/8)。
  • fe80::/10-这些链路本地地址指明,这些地址只在区域连线中是合法的,这有点类似于IPv4中的169.254.0.0/16。

唯一区域位域

  • fc00::/7-唯一区域地址(ULA,unique local address)只可在一群网站中绕送。

多播地址

  • ff00::/8-这个前置表明定义在”IP Version 6 Addressing Architecture”(RFC 4291)中的多播地址[

IPv4转译地址

  • ::ffff:x.x.x.x/96-用于IPv4映射地址。
  • 2001::/32-用于Teredo隧道。
  • 2002::/16-用于6to4。

IPv6优势

  • 巨大的地址空间
  • 新的协议头格式,加快路由速度
  • 有效地、分级的寻址和路由结构
  • 有状态和无状态的地址配置
  • 内置的安全性
  • 更好的支持Qos
  • 用新协议处理邻节点的交互
  • 可扩展性

设置华为光猫HG8245C开启IPv6步骤

在设置华为光猫HG8245C开启IPv6之前,一是要确保所在电信已支持IPv6服务。二是获取华为HG8245C隐藏管理员帐号telecomadmin的密码,通常为nE7jA%5m。

以隐藏管理员帐号telecomadmin登录华为HG8245C的Web管理控制台后,选择“网络->宽带设置”,选择连接”2_INTERNET_R_VID_”,设置协议类型为“IPv4/IPv6”,设置前缀获取方式为“DHCPv6-PD”,再点应用即可。

开启IPv6

在Debian Linux中打开终端,输入如下命令或者浏览网站http://test-ipv6.com/验证IPv6。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
link/ether e4:70:b8:30:f1:5b brd ff:ff:ff:ff:ff:ff
inet 192.168.1.100/24 brd 192.168.1.255 scope global dynamic noprefixroute wlan0
valid_lft 251566sec preferred_lft 251566sec
inet6 240e:bc:e60:3d00:ea5b:f704:6b65:fab1/64 scope global dynamic noprefixroute
valid_lft 258984sec preferred_lft 172584sec
inet6 fe80::4e2c:4397:f016:3eb4/64 scope link noprefixroute
valid_lft forever preferred_lft forever

参考链接

  1. 中国电信:IPv6在线用户已超千万 年底将完成端到端服务能力.2018-07-16
  2. 维基百科.IPv6
  3. 掌握IPv6网络协议的优势,2010-06-10

树莓派学习之SFTP管理文件

发表于 2018-11-09

当需要向树莓派发送文件时,可使用SFTP上传下载文件。下面介绍如何使用SFTP向树莓派发送下载文件。

基础知识

SFTP是Secure File Transfer Protocol的缩写,安全文件传送协议。可以为传输文件提供一种安全的网络的加密方法。sftp 与 ftp 有着几乎一样的语法和功能。

SFTP 为 SSH的其中一部分,是一种传输文件至服务器的安全方式。在SSH软件包中,已经包含了一个叫作SFTP(Secure File Transfer Protocol)的安全文件信息传输子系统,SFTP本身没有单独的守护进程,它必须使用sshd守护进程(端口号默认是22)来完成相应的连接和答复操作。

SFTP传输使用了加密/解密技术,所以传输效率比普通的FTP要低得多,如果您对网络安全性要求更高时,可以使用SFTP代替FTP。

SFTP客户端程序

在Windows平台,常用的SFTP客户端程序有:

  • FileZilla
  • WinSCP
  • Xftp
  • Core FTP

在Linux平台,可直接使用sftp命令进行连接服务器。

基本操作

下面介绍在Debian平台使用sftp命令连接树莓派,在此之前应配置树莓派开启ssh服务。

  1. 建立连接

使用如下命令连接树莓派:

1
sftp pi@192.168.0.103
  1. 查看帮助

通过help查看在sftp连接下能使用的命令。从帮助中可知,在命令前加前缀“l”或者“!”即可在本地操作系统shell执行命令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
sftp> help
Available commands:
bye Quit sftp
cd path Change remote directory to 'path'
chgrp grp path Change group of file 'path' to 'grp'
chmod mode path Change permissions of file 'path' to 'mode'
chown own path Change owner of file 'path' to 'own'
df [-hi] [path] Display statistics for current directory or
filesystem containing 'path'
exit Quit sftp
get [-afPpRr] remote [local] Download file
reget [-fPpRr] remote [local] Resume download file
reput [-fPpRr] [local] remote Resume upload file
help Display this help text
lcd path Change local directory to 'path'
lls [ls-options [path]] Display local directory listing
lmkdir path Create local directory
ln [-s] oldpath newpath Link remote file (-s for symlink)
lpwd Print local working directory
ls [-1afhlnrSt] [path] Display remote directory listing
lumask umask Set local umask to 'umask'
mkdir path Create remote directory
progress Toggle display of progress meter
put [-afPpRr] local [remote] Upload file
pwd Display remote working directory
quit Quit sftp
rename oldpath newpath Rename remote file
rm path Delete remote file
rmdir path Remove remote directory
symlink oldpath newpath Symlink remote file
version Show SFTP version
!command Execute 'command' in local shell
! Escape to local shell
? Synonym for help
  1. 下载远程文件到本地主机
    使用get命令下载远程文件到本地主机:

    1
    2
    3
    sftp> get README.TXT 
    Fetching /home/pi/wiringPi/README.TXT to README.TXT
    /home/pi/wiringPi/README.TXT 100% 606 39.1KB/s 00:00

    get命令还有一些有用参数,如递归选项“ -r ”来递归的复制一个文件夹里面的内容,“ -P ”或者“ -p ”参数来告诉 SFTP 保持文件的权限访问位的设置和访问时间。

  2. 上传本地文件到远程主机
    使用“ put ”命令将文件上传到远程主机:

    1
    2
    3
    sftp> put README.TXT 
    Uploading README.TXT to /home/pi/README.TXT
    README.TXT 100% 606 33.6KB/s 00:00

    ” put “具有类似“ get ”的参数。例如,递归选项“ -r ”可以上传整个文件夹。

参考文献

  1. 华华. 手把手教你使用 SFTP 安全地传输文件.2015-12-5.

Cesium摄像头跟踪飞机实体时晃动问题分析

发表于 2018-11-08 | 更新于 2020-01-13

在3D场景下使用Cesium跟踪飞机时会出现摄像头晃动问题,导致地图背景不断晃动,影响观看。下面以最新的Cesium1.51源码为例,解析Cesium 渲染过程原理,分析跟踪实体时摄像头晃动的原因,找出可能的解决方法。

Cesium渲染过程分析

使用Cesium最简单示例代码如下:

1
var viewer = new Cesium.Viewer('cesiumContainer');

Viewer是Cesium构建应用的最基础的组件。它又是其他组件的容器,包括:

  • animation:控制时间前进、倒退、暂停以及前进和倒退速度的组件
  • baseLayerPicker:图层选择组件
  • fullscreenButton:控制是否全屏的组件
  • vrButton:控制是否VR显示的组件
  • geocoder:地理位置搜索组件
  • homeButton:返回摄像头默认位置按钮组建
  • infoBox:信息框组件
  • sceneModePicker:场景模式选择组件
  • selectionIndicator:选择指示组件
  • timeline:时间线组件
  • navigationHelpButton:导航帮助按钮,告诉使用者如何使用鼠标和触摸屏操纵虚拟地球
  • CesiumWidget:虚拟地球组件

其中,虚拟地球组件CesiumWidget是Viewer包含核心组件,在Viewer中创建CesiumWidget对象时,将设置其useDefaultRenderLoop属性。设置该属性将启动渲染函数startRenderLoop。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//from Source/Widgets/CesiumWidget/CesiumWidget.js
useDefaultRenderLoop : {
get : function() {
return this._useDefaultRenderLoop;
},
set : function(value) {
if (this._useDefaultRenderLoop !== value) {
this._useDefaultRenderLoop = value;
if (value && !this._renderLoopRunning) {
startRenderLoop(this);
}
}
}
},

函数startRenderLoop是Cesium渲染的开始,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
function startRenderLoop(widget) {
widget._renderLoopRunning = true;

var lastFrameTime = 0;
function render(frameTime) {
if (widget.isDestroyed()) {
return;
}

if (widget._useDefaultRenderLoop) {
try {
var targetFrameRate = widget._targetFrameRate;
if (!defined(targetFrameRate)) {
widget.resize();
widget.render();
requestAnimationFrame(render);
} else {
var interval = 1000.0 / targetFrameRate;
var delta = frameTime - lastFrameTime;

if (delta > interval) {
widget.resize();
widget.render();
lastFrameTime = frameTime - (delta % interval);
}
requestAnimationFrame(render);
}
} catch (error) {
...
}
} else {
widget._renderLoopRunning = false;
}
}

requestAnimationFrame(render);
}

CesiumWidget组件的render方法随后调用Scene的render方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
Scene.prototype.render = function(time) {
if (!defined(time)) {
time = JulianDate.now();
}

var frameState = this._frameState;
this._jobScheduler.resetBudgets();

var cameraChanged = this._view.checkForCameraUpdates(this);
var shouldRender = !this.requestRenderMode || this._renderRequested || cameraChanged || this._logDepthBufferDirty || (this.mode === SceneMode.MORPHING);
if (!shouldRender && defined(this.maximumRenderTimeChange) && defined(this._lastRenderTime)) {
var difference = Math.abs(JulianDate.secondsDifference(this._lastRenderTime, time));
shouldRender = shouldRender || difference > this.maximumRenderTimeChange;
}

if (shouldRender) {
this._lastRenderTime = JulianDate.clone(time, this._lastRenderTime);
this._renderRequested = false;
this._logDepthBufferDirty = false;
var frameNumber = CesiumMath.incrementWrap(frameState.frameNumber, 15000000.0, 1.0);
updateFrameNumber(this, frameNumber, time);
}

// Update
this._preUpdate.raiseEvent(this, time);
tryAndCatchError(this, update);
this._postUpdate.raiseEvent(this, time);

if (shouldRender) {
// Render
this._preRender.raiseEvent(this, time);
tryAndCatchError(this, render);

RequestScheduler.update();
}

updateDebugShowFramesPerSecond(this, shouldRender);
callAfterRenderFunctions(this);

if (shouldRender) {
this._postRender.raiseEvent(this, time);
}
};

Scene的render方法中tryAndCatchError函数将调用render函数。在该render函数中,地球的主要要素(地形&影像)的渲染,将在Globe的beginFrame和endFrame之间完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function render(scene) {
...

if (defined(scene.globe)) {
scene.globe.beginFrame(frameState);
}

updateEnvironment(scene);
updateAndExecuteCommands(scene, passState, backgroundColor);
resolveFramebuffers(scene, passState);

passState.framebuffer = undefined;
executeOverlayCommands(scene, passState);

if (defined(scene.globe)) {
scene.globe.endFrame(frameState);

if (!scene.globe.tilesLoaded) {
scene._renderRequested = true;
}
}

...
}

其中updateAndExecuteCommands负责数据的调度,比如哪些Tile需要创建,这些Tile相关的地形数据,以及涉及到的影像数据之间的调度,都是在该函数中维护。而scene.globe.endFrame中,会对该帧所涉及的GlobeTile的下载,解析等进行处理。

Cesium跟踪实体

在Viewer组件构造函数内,Viewer订阅了场景组件Scene的渲染后事件postRender,以执行Viewer自己的_postRender函数。

1
eventHelper.add(scene.postRender, Viewer.prototype._postRender, this);

Viewer的_postRender函数代码如下,其中updateTrackedEntity函数将更新被跟踪实体的摄像头位置:

1
2
3
4
Viewer.prototype._postRender = function() {
updateZoomTarget(this);
updateTrackedEntity(this);
};

updateTrackedEntity函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
function updateTrackedEntity(viewer) {
if (!viewer._needTrackedEntityUpdate) {
return;
}

var trackedEntity = viewer._trackedEntity;
var currentTime = viewer.clock.currentTime;

//Verify we have a current position at this time. This is only triggered if a position
//has become undefined after trackedEntity is set but before the boundingSphere has been
//computed. In this case, we will track the entity once it comes back into existence.
var currentPosition = Property.getValueOrUndefined(trackedEntity.position, currentTime);

if (!defined(currentPosition)) {
return;
}

var scene = viewer.scene;

var state = viewer._dataSourceDisplay.getBoundingSphere(trackedEntity, false, boundingSphereScratch);
if (state === BoundingSphereState.PENDING) {
return;
}

var sceneMode = scene.mode;
if (sceneMode === SceneMode.COLUMBUS_VIEW || sceneMode === SceneMode.SCENE2D) {
scene.screenSpaceCameraController.enableTranslate = false;
}

if (sceneMode === SceneMode.COLUMBUS_VIEW || sceneMode === SceneMode.SCENE3D) {
scene.screenSpaceCameraController.enableTilt = false;
}

var bs = state !== BoundingSphereState.FAILED ? boundingSphereScratch : undefined;
viewer._entityView = new EntityView(trackedEntity, scene, scene.mapProjection.ellipsoid);
viewer._entityView.update(currentTime, bs);
viewer._needTrackedEntityUpdate = false;
}

除此之外,Viewer组件订阅了Clock组建的onTick事件,以执行其自身的_onTick事件处理函数:

1
eventHelper.add(clock.onTick, Viewer.prototype._onTick, this);

在Viewer组件的_onTick事件处理函数中,同样会更新被跟踪实体的摄像头位置。而Cesium摄像头跟踪飞机实体时产生晃动的根源即在此处。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Viewer.prototype._onTick = function(clock) {
var time = clock.currentTime;

var isUpdated = this._dataSourceDisplay.update(time);
if (this._allowDataSourcesToSuspendAnimation) {
this._clockViewModel.canAnimate = isUpdated;
}

var entityView = this._entityView;
if (defined(entityView)) {
var trackedEntity = this._trackedEntity;
var trackedState = this._dataSourceDisplay.getBoundingSphere(trackedEntity, false, boundingSphereScratch);
if (trackedState === BoundingSphereState.DONE) {
entityView.update(time, boundingSphereScratch);
}
}

...
};

可行的解决方案

在Viewer组件的_onTick函数做如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Viewer.prototype._onTick = function(clock) {
var time = clock.currentTime;

var isUpdated = this._dataSourceDisplay.update(time);
if (this._allowDataSourcesToSuspendAnimation) {
this._clockViewModel.canAnimate = isUpdated;
}

var entityView = this._entityView;
if (defined(entityView)) {
var trackedEntity = this._trackedEntity;
var trackedState = this._dataSourceDisplay.getBoundingSphere(trackedEntity, false, boundingSphereScratch);
if (trackedState === BoundingSphereState.DONE) {
//entityView.update(time, boundingSphereScratch);
var range=this.camera.distanceToBoundingSphere(boundingSphereScratch);
var targetRange=range>boundingSphereScratch.radius*10?range:boundingSphereScratch.radius*10;
var offset=new HeadingPitchRange(0.0,-Math.toRadians(45.0),targetRange);
this.camera.viewBoundingSphere(boundingSphereScratch,offset)
}
}

...
};

参考文献

  1. Cesium原理篇:1最长的一帧之渲染调度, by 法克鸡丝

树莓派学习之远程登录

发表于 2018-11-07 | 更新于 2023-02-06

每次通过USB转串口登录树莓派比较麻烦,可以设置树莓派开启ssh和vnc服务,以便通过ssh或vnc远程登录树莓派。下面介绍在Debian中通过ssh或vnc远程登录树莓派的过程。

基础知识

SSH

Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH通过在网络中创建安全隧道来实现SSH客户端与服务器之间的连接。SSH最常见的用途是远程登录系统,人们通常利用SSH来传输命令行界面和远程执行命令。

SSH基本用法

1
2
3
4
# 以用户名user,登录远程主机host
$ ssh user@host
# SSH的默认端口是22。使用p参数,可以修改这个端口。
$ ssh -p 2222 user@host

SSH通信过程及风险

SSH采用了公钥加密保证安全。

整个过程是这样的:(1)远程主机收到用户的登录请求,把自己的公钥发给用户。(2)用户使用这个公钥,将登录密码加密后,发送回来。(3)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。

这个过程本身是安全的,但是实施的时候存在”中间人攻击”风险:如果有人截获了登录请求,然后冒充远程主机,将伪造的公钥发给用户,那么用户很难辨别真伪。因为不像https协议,SSH协议的公钥是没有证书中心(CA)公证的,也就是说,都是自己签发的。

VNC

VNC(Virtual Network Computing),为一种使用RFB协议的显示屏画面分享及远程操作软件。此软件借由网上,可发送键盘与鼠标的动作及即时的显示屏画面。

VNC与操作系统无关,因此可跨平台使用,例如可用Windows连线到某Linux的计算机,反之亦同。甚至在没有安装客户端程序的计算机中,只要有支持JAVA的浏览器,也可使用。

原理

VNC系统由客户端,服务端和一个协议组成

VNC的服务端目的是分享其所运行机器的屏幕,服务端被动的允许客户端控制它。VNC客户端(或Viewer)观察控制服务端,与服务端交互。VNC协议Protocol(RFB)是一个简单的协议,传送服务端的原始图像到客户端(一个X,Y位置上的正方形的点阵数据),客户端传送事件消息到服务端。

服务器发送小方块的帧缓存给客户端,在最简单的情况,VNC协议使用大量的带宽,因此各种各样的方法被发明出来减少通讯的开支,举例来说,有各种各样的编码方法来决定最有效率的方法来传送这些点阵方块。

VNC默认使用TCP端口5900至5906,而JAVA的VNC客户端使用5800至5806。一个服务端可以在5900端口用“监听模式”连接一个客户端,使用监听模式的一个好处是服务端不需要设置防火墙。

安全性

VNC并非是安全的协议,虽然VNC伺服程序需设置密码才可接受外来连线,且VNC客户端与VNC伺服程序之间的密码传输经过加密,但仍可被轻易的拦截到并使用暴力破解法破解。不过VNC可设计以SSH或VPN传输,以增加安全性。

VNC软件

由于VNC以GPL授权,派生出了几个VNC软件:

  • RealVNC:由VNC团队部分成员开发,分为全功能商业版及免费版。
  • TightVNC:强调节省带宽使用。
  • UltraVNC:加入了TightVNC的部分程序及加强性能的图型映射驱动程序,并结合Active Directory及NTLM的账号密码认证,但仅有Windows版本。
  • Vine Viewer:MacOSX的VNC客户端。

配置树莓派支持SSH和VNC

通过USB转串口登录树莓派后,按如下步骤开启SSH和VNC:

  1. 运行命令raspi-config
    1
    sudo raspi-config
  2. 选择第五项Interfacing Options
  3. 选择enable SSH
  4. 启用SSH,重复步骤一和二,再选择enable VNC。

通过SSH或VNC连接树莓派

debian主机与树莓派在同一个局域网内,则按如下步骤通过SSH或VNC连接树莓派。

通过nmap命令获取树莓派ip地址

1
2
3
4
5
6
7
8
9
10
11
$ nmap 192.168.0.1/24
Starting Nmap 7.70 ( https://nmap.org ) at 2018-11-08 13:08 CST

Nmap scan report for 192.168.0.103
Host is up (0.041s latency).
Not shown: 998 closed ports
PORT STATE SERVICE
22/tcp open ssh
5900/tcp open vnc

Nmap done: 256 IP addresses (6 hosts up) scanned in 35.77 seconds

开启22和5900端口的主机即是树莓派。

通过ssh远程登录树莓派

1
2
# 首次登录会给出主机认证不能建立的提示,输入yes可继续,再输入用户密码即可远程登录树莓派
$ ssh pi@192.168.0.103

通过vnc远程登录树莓派

  1. 通过ssh登录树莓派后查看其使用的vnc程序

    1
    2
    3
    $ apt list --installed | grep vnc
    realvnc-vnc-server/now 6.3.1.36657 armhf [已安装,可升级至:6.3.2.39069]
    # 可知,树莓派上使用realvnc程序
  2. 在Debian上安装realvnc客户端程序realvnc-vnc-viewer

  3. 在终端输入vncviewer,输入树莓派ip、用户名、密码即可登录树莓派

参考文献

  1. SSH原理与运用(一):远程登录,by 阮一峰.
  2. Secure Shell,by wikipedia.
  3. VNC,by wikipedia.
  4. VPN原理与简单应用,by 新盟-胡巴.

树莓派学习之USB转串口

发表于 2018-11-05 | 更新于 2022-05-11

最近研究树莓派,需要通过usb转串口去连接树莓派,然后设置其wifi连接。于是将用到计算机硬件知识整理一下,并记录通过usb转串口设置树莓派wifi连接的过程。

硬件知识

计算机硬件常用接口有并口和串口,对应串行通信和并行通信。串行通信(英语:Serial communication)是指在计算机总线或其他数据信道上,每次传输一个比特数据,并连续进行以上单次过程的通信方式。与之对应的是并行通信,它在串行端口上通过一次同时传输若干比特数据的方式进行通信。

串行通信被用于长距离通信以及大多数计算机网络,在这些应用场合里,电缆和同步化使并行通信实际应用面临困难。凭借着其改善的信号完整性和传播速度,串行通信总线正在变得越来越普遍,甚至在短程距离的应用中,其优越性已经开始超越并行总线不需要串行化组件(serializer),并解决了诸如时钟偏移(Clock skew)、互联密度(interconnect density)等缺点。PCI到PCI Express的升级就一个例子。

并口

并行接口,简称并口。并口采用的是25针D形接头。所谓“并行”,是指8位数据同时通过并行线进行传送,这样数据传送速度大大提高,但并行传送的线路长度受到限制,因为长度增加,干扰就会增加,数据也就容易出错,目前,并行接口主要作为打印机端口等。

串口

串口叫做串行接口,也称串行通信接口,即COM口。按电气标准及协议来分包括RS-232-C、RS-422、RS485、USB等。 RS-232-C、RS-422与RS-485标准只对接口的电气特性做出规定,不涉及接插件、电缆或协议。

串行端口可以用于连接外置调制解调器、绘图仪或串行打印机。它也可以控制台连接的方式连接网络设备,例如路由器和交换机,主要用来配置它们。

  • RS-232-C

也称标准接口,是目前最常用的一种串行通讯接口。它是在1970年由美国电子工业协会(EIA)联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。

传统的RS-232-C接口标准有22根线,采用标准25芯D型插头座。自IBM PC/AT开始使用简化了的9芯D型插座。计算机一般有两个串行口:COM1和COM2,9针D形接口通常在计算机后面能看到。现在有很多手机数据线或者物流接收器都采用COM口与计算机相连。

  • RS-422

为改进RS-232通信距离短、速率低的缺点,RS-422定义了一种平衡通信接口,将传输速率提高到10Mb/s,传输距离延长到4000英尺(速率低于100kb/s时),并允许在一条平衡总线上连接最多10个接收器。

  • RS-485

为扩展应用范围,EIA又于1983年在RS-422基础上制定了RS-485 标准,增加了多点、双向通信能力,即允许多个发送器连接到同一条总线上,同时增加了发送器的驱动能力和冲突保护特性,扩展了总线共模范围,后命名为 TIA/EIA-485-A标准。

  • Universal Serial Bus(通用串行总线)

简称USB,是目前计算机上应用较广泛的接口规范,由Intel、Microsoft、Compaq、IBM、NEC、Northern Telcom等几家大厂商发起的新型外设接口标准。USB接口是计算机主板上的一种四针接口,其中中间两个针传输数据,两边两个针给外设供电。USB接口速度快、连接简单、不需要外接电源,传输速度12Mbps,新的USB 2.0可达480Mbps;电缆最大长度5米,USB电缆有4条线:2条信号线,2条电源线,可提供5伏特电源,USB电缆还分屏蔽和非屏蔽两种,屏蔽电缆传输速度可达12Mbps,价格较贵,非屏蔽电缆速度为1.5Mbps,但价格便宜;USB通过串联方式最多可串接127个设备;支持热插拔。最新的规格是USB 3.1。

  • RJ-45接口

是以太网最为常用的接口,RJ45是一个常用名称,指的是由IEC(60)603-7标准化,使用由国际性的接插件标准定义的8个位置(8针)的模块化插孔或者插头。

USB转串口原理

硬件接口电气特性

  • TTL电平:一般用作数字芯片的电平,例如芯片的供电电压是5V,那么高电平就是5V,低电平就是0V,这里所说的电平,就是TTL电平。

  • 232电平:232电平特制电脑串口的电平,-12V左右为正电平,+12V左右为低电平。我们刚才所见到的“USB转串口线”和电脑原生的串口,就是232电平。

USB转串口方法

PC的串口电气特性是232电平,单片机的串口电气特性是TTL电平,这两个就不一样,肯定需要某个芯片或者电路来进行转换匹配才可以通信。这个时候我们就需要TTL转232芯片了,常见的是MAX232,MAX3232等。连接方式如下:

232转TTL

但是随着USB接口的普及,当前计算机已经取消了串口。为实现232到TTL的转换,又需要USB转232。连接方式如下:

USB转232

为简化,可将USB转232和232转TTL集成到一个芯片上。这样的芯片常见的有CH340、PL2303。连接方式如下:

USB转TTL

常见的CH340芯片如下图所示:

USB转TTL

通过USB转串口连接树莓派

下面介绍在Debian Linux主机通过USB转串口连接树莓派的过程。

配置树莓派开启串口通信

将树莓派操作系统镜像烧录到SD卡后,打开boot分区,编辑其config.txt,在其末尾添加如下代码,以开启串口通信权限。

1
enable_uart=1

通过USB转串口物理连接树莓派

通过将USB转串口将Debian主机和树莓派物理连接好之后,给树莓派加电启动。

查看Debian系统能否识别ch340芯片

在官方Linux内核版本中自Kernel2.6以后就默认包含了对CH340/CH341芯片的驱动支持。在系统的默认驱动目录/lib/modules/$(uname -r)/kernel/drivers内可找到ch340芯片的驱动文件ch341.ko。

使用命令lsusb或dmesg查看linux系统是否识别USB转串口硬件。

1
2
3
4
5
6
7
8
~$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 005: ID 0bda:0129 Realtek Semiconductor Corp. RTS5129 Card Reader Controller
Bus 001 Device 004: ID 8087:0a2a Intel Corp.
Bus 001 Device 003: ID 1bcf:2b8a Sunplus Innovation Technology Inc.
Bus 001 Device 006: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter
Bus 001 Device 002: ID 046d:c062 Logitech, Inc. M-UAS144 [LS1 Laser Mouse]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Bus 001 Device 006: ID 1a86:7523 QinHeng Electronics HL-340 USB-Serial adapter表明Linux系统识别了usb转串口线缆,芯片类型为HL-340。

‘dmesg’命令显示linux内核的环形缓冲区信息,我们可以从中获得诸如系统架构、cpu、挂载的硬件,RAM等多个运行级别的大量的系统信息。当计算机启动时,系统内核(操作系统的核心部分)将会被加载到内存中。在加载的过程中会显示很多的信息,在这些信息中我们可以看到内核检测硬件设备。运行dmesg,输出如下:

1
2
3
4
5
6
7
8
9
10
11
~$ sudo dmesg | tail
[ 4248.441104] usbcore: registered new interface driver usbserial_generic
[ 4248.441112] usbserial: USB Serial support registered for generic
[ 4248.456079] usbcore: registered new interface driver ch341
[ 4248.456088] usbserial: USB Serial support registered for ch341-uart
[ 4248.456097] ch341 1-3:1.0: ch341-uart converter detected
[ 4248.456464] usb 1-3: ch341-uart converter now attached to ttyUSB0
[ 4284.405593] i2c_hid i2c-ELAN1010:00: i2c_hid_get_input: incomplete report (14/65535)
[ 4284.407844] i2c_hid i2c-ELAN1010:00: i2c_hid_get_input: incomplete report (14/65535)
[ 4657.772761] i2c_hid i2c-ELAN1010:00: i2c_hid_get_input: incomplete report (14/65535)
[ 4657.774969] i2c_hid i2c-ELAN1010:00: i2c_hid_get_input: incomplete report (14/65535)

[ 4248.456097] ch341 1-3:1.0: ch341-uart converter detected;
[ 4248.456464] usb 1-3: ch341-uart converter now attached to ttyUSB0说明linux系统识别了usb转串口适配器,并附加到ttyUSB0文件上。

使用minicom连接树莓派

minicom是linux平台的串行通信程序,类似于windows的超级终端程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
$ usermod -a -G dialout $USER
#首次运行minicom使用-s选项,用于设置串行通信参数
$ minicom -s
+-----[configuration]------+
| Filenames and paths |
| File transfer protocols |
| Serial port setup |
| Modem and dialing |
| Screen and keyboard |
| Save setup as dfl |
| Save setup as.. |
| Exit |
| Exit from Minicom |
+--------------------------+
#通过上下键选择Serial port setup
+----------------------------------- +
| A - Serial Device : /dev/ttyUSB0 |
| B - Lockfile Location : /var/lock |
| C - Callin Program : |
| D - Callout Program : |
| E - Bps/Par/Bits : 115200 8N1 |
| F - Hardware Flow Control : Yes |
| G - Software Flow Control : No |
| |
| Change which setting? |
+----------------------------------+
# 设置Serial Device为/dev/ttyUSB0,然后推出到上一界面,使用enter键Save setup as dfl,保存配置。下次即可直接运行minicom使用之前保存的配置进行串口通信
# 这时候要选择单独Exit(退出),不要选择Exit from Minicom(退出Minicom),不然你就把minicom关了。
# 这时即可连接树莓派。

连接上树莓派后,输入用户名pi和密码raspberry,即可进入系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Raspbian GNU/Linux 9 raspberrypi ttyS0                                       
raspberrypi login: pi
Password:
Last login: Tue Oct 9 13:12:40 UTC 2018 on tty1
Linux raspberrypi 4.14.71-v7+ #1145 SMP Fri Sep 21 15:38:35 BST 2018 armv7l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.

Wi-Fi is disabled because the country is not set.
Use raspi-config to set the country before use.

pi@raspberrypi:~$

退出minicom,按Ctrl+A,再按下X键,会提示你是否退出,yes就可以了。

配置树莓派wifi连接

查看周围wifi热点信息

1
pi@raspberrypi:~$ iwlist scan

配置连接到wifi热点

1
2
3
4
5
6
7
8
9
10
11
12
# 编辑wifi文件
pi@raspberrypi:~$ sudo vi /etc/wpa_supplicant/wpa_supplicant.conf
# 在该文件最后添加下面的话
network={
  ssid="WIFINAME"
  psk="password"
}
# 引号部分分别为wifi的名字和密码
# 重启系统
pi@raspberrypi:~$ sudo init 6
# 登录后查看是否连接成功
pi@raspberrypi:~$ ip addr

参考文献

  1. https://www.cnblogs.com/zcshan/archive/2010/12/03/com.html ,by 水寒
  2. https://zh.wikipedia.org/wiki串行端口 , by wikipedia
  3. https://blog.csdn.net/he_wen_jie/article/details/50983076. by hwj666
  4. https://linux.cn/article-3587-1.html , by linux中国
  5. https://blog.csdn.net/JAZZSOLDIER/article/details/70170466 , by SoldierJazz2018
  6. http://blog.51cto.com/irinilu/289622, by feng9422
  7. Pipci. Linux 串口终端调试工具minicom[EB/OL].https://blog.csdn.net/Pipcie/article/details/79379451, 2018-02-26.
  8. https://blog.csdn.net/huayucong/article/details/51376279, by huayucong
  9. USB转串口CH340接线方法,2015-11-03.

树莓派操作系统镜像烧录方法指南

发表于 2018-11-03 | 更新于 2018-11-04

树莓派是一个微型计算机硬件平台,为使其正常工作还需要安装操作系统。通常将树莓派操作系统烧录到SD卡上,再将SD卡插到树莓派上。加电启动后,树莓派就会从SD卡引导启动操作系统,开始工作。这里SD卡就相当于普通计算机的硬盘。下面记录在Debian Linux下烧录树莓派操作系统到SD卡的方法。

镜像烧录工具

Etcher

Etcher是树莓派官方推荐的镜像烧录工具。它采用图形界面,支持Windows、Linux、Mac,使用简单方便,推荐普通用户使用。

dd

Linux的dd命令用于复制文件并对原文件的内容进行转换和格式化处理。dd命令功能很强大的,对于一些比较底层的问题,使用dd命令往往可以得到出人意料的效果。用的比较多的还是用dd来备份裸设备。同样也可用dd命令烧录系统镜像。

使用dd命令必须非常小心,如果输出指定了错误分区,将摧毁该分区的所有数据。

Linux下镜像烧录过程

使用Etcher烧录镜像比较简单,重点介绍使用dd命令烧录镜像的方法。

查找sd卡设备

可使用lsblk或者fdisk命令查找sd卡存储设备。需要注意的是:

  • 块设备以/dev/sdX命名,其中X是小写字母,例如/dev/sda。
  • 测试时发现当计算机只有usb3.0接口时,将不识别usb2.0的sd读卡器。

烧录镜像到sd卡

将镜像烧录到sd卡

使用如下dd命令将树莓派镜像少量到sd卡:

1
dd bs=4M if=2018-10-09-raspbian-stretch.img of=/dev/sdX conv=fsync

其中:

  • if=文件名:输入文件名,缺省为标准输入。即指定源文件。
  • of=文件名:输出文件名,缺省为标准输出。即指定目的文件。
  • bs=bytes:同时设置读入/输出的块大小为bytes个字节。
  • conv=conversion:用指定的参数转换文件。

将压缩镜像烧录到sd卡

当文件系统不支持大于4GB的文件时,可使用利用管道技术烧录镜像,命令如下:

1
unzip -p 2018-10-09-raspbian-stretch.zip | sudo dd of=/dev/sdX bs=4M conv=fsync

检查烧录进度

默认情况下dd命令不给出烧录进度信息,最新的dd提供status=progress选项给出进度信息,命令如下:

1
dd bs=4M if=2018-10-09-raspbian-stretch.img of=/dev/sdX status=progress conv=fsync

或者可使用dcfldd命令替换dd进行烧录操作。

检查镜像是否正确的烧录到sd卡

先使用dd命令将sd卡中内容复制到硬盘,在使用diff或者md5sum检查镜像文件与sd卡内容镜像之间的一致性。

之前dd命令烧录镜像到sd卡完成后会在shell中输出如下结果:

1
2
3
xxx+0 records in
yyy+0 records out
yyyyyyyyyy bytes (yyy kB, yyy KiB) copied, 0.00144744 s, 283 MB/s

xxx是我们需要的,表示读取xxx块记录烧录到sd卡,xxx乘以bs=4M应该等于原始镜像的大小。

使用如下命令复制sd卡内容到硬盘上:

1
2
# 经测试count命令没有其作用,该命令将整个sd内容复制到了硬盘,但我们期望只复制之前烧录到sd卡内容
dd bs=4M if=/dev/sdX of=from-sd-card.img count=xxx

如果from-sd-card.img文件大于原始镜像文件,那么先使用truncate命令将from-sd-card.img缩小到原始镜像文件的大小,命令如下:

1
truncate --reference 2018-10-09-raspbian-stretch.img from-sd-card.img

使用diff命令比较两者直接的一致性,如果不一致,可能烧录过程中有错误。

1
diff -s from-sd-card.img 2018-10-09-raspbian-stretch.img

使用sync命令强制将缓存写入硬盘,然后umount sd卡文件系统,最后移除sd卡。

参考文献

  1. https://www.raspberrypi.org/documentation/installation/installing-images/linux.md, by raspberrypi.
  2. https://blog.csdn.net/liumang_D/article/details/3899462, by liumang_d.
  3. https://linux.cn/article-8024-1.html, by linux中国
  4. https://www.jianshu.com/p/ff09ceffa816, by 二石兄.

树莓派学习笔记

发表于 2018-11-03 | 更新于 2018-12-20

WiringPi简介

WiringPi是应用于树莓派平台的GPIO控制库函数,WiringPi遵守GUN Lv3。wiringPi使用C或者C++开发并且可以被其他语言包转,例如python、ruby或者PHP等。WiringPi中的函数类似于Arduino的wiring系统,这使得熟悉arduino的用户使用wringPi更为方便。

树莓派具有26个普通输入和输出引脚。在这26个引脚中具有8个普通输入和输出管脚,这8个引脚既可以作为输入管脚也可以作为输出管脚。除此之外,树莓派还有一个2线形式的I2C、一个4线形式的SPI和一个UART接口。树莓派上的I2C和SPI接口也可以作为普通端口使用。如果串口控制台被关闭便可以使用树莓派上的UART功能。如果不使用I2C,SPI和UART等复用接口,那么树莓派总共具有8+2+5+2 =17个普通IO。

wiringPi包括一套gpio控制命令,使用gpio命令可以控制树莓派GPIO管脚。用户可以利用gpio命令通过shell脚本控制或查询GPIO管脚。wiringPi是可以扩展的,可以利用wiringPi的内部模块扩展模拟量输入芯片,可以使用MCP23x17/MCP23x08(I2C 或者SPI)扩展GPIO接口。另外可通过树莓派上的串口和Atmega(例如arduino等)扩展更多的GPIO功能。另外,用户可以自己编写扩展模块并把自定义的扩展模块集成到wiringPi中。WiringPi支持模拟量的读取和设置功能,不过在树莓派上并没有模拟量设备。但是使用WiringPi中的软件模块却可以轻松地应用AD或DA芯片。

I2C

I²C(Inter-Integrated Circuit)字面上的意思是集成电路之间,它其实是I²C Bus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I²C的正确读法为“I平方C”(”I-squared-C”),而“I二C”(”I-two-C”)则是另一种错误但被广泛使用的读法。自2006年10月1日起,使用I²C协议已经不需要支付专利费,但制造商仍然需要付费以获取I²C从属设备地址。

设计说明

I²C只使用两条双向漏极开路(Open Drain)(串行数据(SDA)及串行时钟频率(SCL))并利用电阻将电位上拉。I²C允许相当大的工作电压范围,但典型的电压准位为+3.3V或+5v。

I²C的参考设计使用一个7比特长度的地址空间但保留了16个地址,所以在一组总线最多可和112个节点通信[a]。常见的I²C总线依传输速率的不同而有不同的模式:标准模式(100 Kbit/s)、低速模式(10 Kbit/s),但时钟频率可被允许下降至零,这代表可以暂停通信。而新一代的I²C总线可以和更多的节点(支持10比特长度的地址空间)以更快的速率通信:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。

虽然最大的节点数目是被地址空间所限制住,但实际上也会被总线上的总电容所限制住,一般而言为400 pF。

I²C通信

如上所述,参考设计为使用串行数据线(SDA)和串行时钟线(SCL)、拥有7bit寻址空间的总线。 总线上有两种类型角色的节点:

  • 主节点 - 产生时钟并发起与从节点的通信
  • 从节点 - 接收时钟并响应主节点的寻址

该总线是一种多主控总线,即可以在总线上放置任意多主节点。此外,在停止位(STOP)发出后,一个主节点也可以成为从节点,反之亦然。

总线上有四种不同的操作模式,虽然大部分设备只作为一种角色和使用其中两种操作模式:

  • 主节点发送 - 主节点发送数据给从节点
  • 主节点接收 - 主节点接收从节点数据
  • 从节点发送 - 从节点发送数据给主节点
  • 从节点接收 - 从节点接收主节点数据

一开始,主节点处于主节点发送模式,发送起始位(START),跟着发送希望与之通信的从节点的7bit位地址,最后再发送一个bit读写位,该数据位表示主节点想要与从节点进行读(1)还是写(0)操作。

如果从节点在总线上,它将以ACK字符比特位应答(低有效)该地址。主节点收到应答后,根据它发送的读写位,处于发送模式或者接收模式,从节点则处于对应的相反模式(接收或发送)。

地址和数据首先发送最高有效位。 起始位在SCL位高时,由SDA上电平从高变低表示;停止位在SCL为高时,由SDA上电平从低变高表示。其他SDA上的电平变化在SCL为低时发生。

如果主节点想要向从节点写数据,它将发送一个字节,然后从节点以ACK位应答,如此重复。此时,主节点处于主节点发送模式,从节点处于从节点接收模式。

如果主节点想要读取从节点数据,它将不断接收从节点发送的一个个字节,在收到每个字节后发送ACK进行应答,除了接收到的最后一个字节。此时,主节点处于主节点接收模式,从节点处于从节点发送模式。

此后,主节点要么发送停止位终止传输,要么发送另一个START比特以发起另一次传输(即“组合消息”)。

应用

I²C被应用在简单且其制造成本较传输速度更为重要的外设上。一些常见的应用如下:

  • 为了保存用户的设置而访问NVRAM芯片。
  • 访问低速的数字模拟转换器(DAC)。
  • 访问低速的模拟数字转换器(ADC)。
  • 改变监视器的对比度、色调及色彩平衡设置(视频数据通道)。
  • 改变音量大小。
  • 获取硬件监视及诊断数据,例如中央处理器的温度及风扇转速。
  • 读取实时时钟(Real-time clock)。
  • 在系统设备中用来打开或关闭电源供应。

I²C的另一个强大用途在于微控制器的应用,利用两根通用的输入输出接脚及软件的规划,可以让微控制器控制一个小型网络。

外设可以在系统仍然在运作的同时加入或移出总线,这代表对于有热插拔需求的设备而言是个理想的总线。

像I²C这样的总线之所以流行起来,是因为计算机工程师发现到对于集成电路设计而言,许多的制造成本源自于封装尺寸及接脚数量。更小的包装通常能够减少重量及电源的消耗,这对于移动电话及手持式计算机而言格外重要。

UART

在通信和计算机科学中,Serial communication是一个通用概念,泛指所有的串行的通信协议,如RS232、USB、I2C、SPI、1-Wire、Ethernet等。这里的串行(serial),是相对并行通信(parallel communication)来说的,如下图:

串行与并行通信

理解串行通信的概念之后,大家可能会有疑问:接收方接收到一长串的、表示0/1电平跳变的信号之后,怎么还原出有效的信息呢?有两种方法:

  • 发送端在发送串行数据的同时,提供一个时钟信号,并按照一定的约定(例如在时钟信号的上升沿的时候,将数据发送出去)发送数据,接收端根据发送端提供的时钟信号,以及大家的约定,接收数据。这就是常说的同步串行通信(Synchronous serial communication),I2C、SPI等有时钟信号的协议,都属于这种通信方式。本文不再详述。

  • 发送端在数据发送之前和之后,通过特定形式的信号(例如START信号和STOP信号),告诉接收端,可以开始(或者停止)接收数据了。与此同时,收发两方会约定一个数据发送的速度(就是大名鼎鼎的波特率),发送端在发送START信号之后,就按照固定的节奏发送串行数据,与此同时,接收端在收到START信号之后,也按照固定的节奏接收串行数据。这就是常说的异步串行通信(Asynchronous serial communication),我们本节的主角—-串口通信,就是这种通信方式。

UART(Universal Asynchronous Receiver/Transmitter) 即是规定编码格式、bit rate,产生通信所需的bit流的标准。

SPI

串行外设接口(Serial Peripheral Interface Bus,SPI),是一种用于短程通信的同步串行通信接口规范,主要应用于单片机系统中。类似I²C。 这种接口首先被Motorola(摩托罗拉)公司开发,然后发展成了一种行业规范。典型应用包含SD卡和液晶显示器。 SPI设备之间使用全双工模式通信,包含一个主机和一个或多个从机。主机产生待读或待写的帧数据,多个从机通过一个片选线路 决定哪个来响应主机的请求。 有时SPI接口被称作四线程接口,SPI准确来讲称为同步串行接口,但是与同步串行接口协议(SSI)不同,SSI是一个四线程 同步通信协议,但是使用差分信号输入同时仅提供一个单工通信信道。

接口

SPI总线规定了4个保留逻辑信号接口:

  • SCLK(Serial Clock):串列时脉,由主机发出
  • MOSI(Master Output,Slave Input):主机输出从机输入信号,由主机发出
  • MISO(Master Input,Slave Output):主机输入从机输出信号,由从机发出
  • SS(Slave Selected):选择信号,由主机发出,一般是低电位有效

尽管上面的引脚名称是最常用的,但在过去,有时会使用其他引脚命名约定,因此旧IC产品的SPI端口引脚名称可能有所不同。

1-Wire

1-Wire是Maxim子公司达拉斯半导体的专利技术,仅用单一信号线就可像I²C、SPI一样,传输时钟(clock)又传输数据(data),并且数据传输是双向的。1-Wire使用较低的数据传输速率,通常是用来沟通小型设备,如数字温度计。1-Wire有两种速率:标准模式16kbps,驱动模式142kbps。

单总线只有一根数据线。设备主机或从机通过一个漏极开路或三态端口连接至该数据线,这样允许设备在不发送数据时释放数据总线,以便总线被其它设备所使用。单总线端口为漏极开路其内部等效电路如下图所示。

单总线硬件接口示意图

参考文献

  1. 树莓派学习笔记——wiringPi简介、安装和管脚说明 , by xukai871105.
  2. I²C, by wikipedia.
  3. UART、RS232、TTL关系浅析, by 老狼.
  4. 串行外设接口,by wikipedia.
  5. 1-Wire,by wikipedia.
  6. 1-Wire单总线的基本原理,by ce123.
  7. 解析单总线协议(1-wire),by zhengqijun_.
上一页1…505152…56下一页

Jack Huang

552 日志
70 标签
© 2026 Jack Huang
由 Hexo 强力驱动
|
主题 — NexT.Muse