所有文章

详细总结图像ISP处理流程之Gamma校正

本文详细总结了图像ISP处理流程中的Gamma校正模块,解释了在ISP处理流程中要包含Gamma校正模块的原因,以及主流ISP在进行Gamma校正的处理过程中的主要思路和工作流程。 Gamma校正存在的意义 在ISP对于Sensor输出RAW图像的处理流程中,Gamma校正模块是其中一个非常重要的图像亮度非线性变换模块。Gamma模块存在的意义,主要是解决图像传感器感光性能的线性,与人眼/显示设备感知和显示图像的非线性之间的匹配问题。 具体而言,图像传感器在曝光的过程中,对于光线强度和曝光时间长短的响应基本上是线性的,也就是说,光照强度增加一倍,图像传感器所输出的电信号也就增加一倍。 显示设备的输出电压与亮度效果的呈现之间存在的非线性关系,这一点要从古老的CRT显示器说起。对于CRT显示器而言,其显示亮度与输入电压之间成 γ≈2.2 的幂律关系,如果把图像传感器采集到的线性亮度信号直接输入到CRT显示器上显示的话,显示出来的效果会呈现出图像的暗部细节损失严重,整体画面偏暗,而亮部的对比度压缩,高光部分的层次感减弱。所以,为了能够在CRT显示器上把图像传感器采集的线性亮度图像以线性的方式呈现出来,就需要在把图像在CRT上进行显示之前,先进行一个Gamma Encode的运算,即对图像传感器的线性亮度图像进行一个 γ=0.45 的Gamma映射,然后把这个处理过的图像送入CRT显示器,CRT显示器显示出来的图像就可以把线性亮度还原回来。整体的流程如下图所示: !image.png CRT显示器早已经被淘汰了,而目前主流的LCD/OLCD液晶显示器的亮度响应实际上是接近于线性的,所以理论上讲,直接把图像传感器采集的原始亮度图像,直接显示在液晶显示器上,就可以免去上面的Gamma映射的繁琐流程了。但问题是,这样的话,之前CRT时代所积累的大量图像文件(以前在进行图像压缩之前提前进行了Gamma Encode的运算处理),显示在LCD/OLCD显示器上就会有问题了。所以为了兼容CRT时代所积累的历史内容,现代的LCD显示器都会额外内置一个 的校正,这样就可以继续保持之前的线性图像-编码gamma(0.45)处理-显示gamma(2.2)-线性图像的闭环。 人眼对于光照亮度的感知与以上逻辑类似,也是非线性的,其亮度感知规律符合韦伯-费希纳定律,具体来讲就是,人眼对于光照的暗区更加敏感,因此在低照度的情况下,人眼可以分辨极其微小的亮度变化;而对于光照的亮区就没有那么敏感了,在高亮度的情况下,人眼对光照亮度的变化往往很难察觉。 !image.png Gamma校正的流程 基于以上所描述的在ISP端进行的Gamma Encode处理和显示器端自动进行的Gamma Decode处理流程可知,在ISP处理流程中所执行的gamma encode需要对RGB三通道的亮度进行以下处理: 在ISP实际进行Gamma处理的计算流程中,因为直接计算各个像素的幂函数耗时高,所以实际工程中采用预计算LUT查找表实现来实时Gamma操作的幂映射。 Gamma校正模块的输入数据为经过Demosaic和CCM处理后的RGB域数据,每个像素都包含了RGB三个通道,但为了保证三个通道的色彩平衡,所以一般情况下对三个通道数据使用统一的Gamma映射表来进行Gamma转换操作。 - 以上提到的这种三个颜色通道共用同一组LUT表的处理方式也成为1D LUT,实际上就是Gamma亮度变换。在部分ISP或者图像处理流程中,还有一个3D LUT的概念,RGB三个通道使用各自的LUT表,一般用于实现一些色彩风格个性化的功能。 为了应对各种复杂的光照场景(如逆光、夜景),以及更好的适应人眼感知特性的非线性,对图像素质要求较高的主流ISP往往还会采用分段式可编程LUT表来实现更为精细化的控制。例如,分段式Gamma LUT将输入亮度范围(如0~255)划分为暗部(0~20%)、中灰(20%~70%)、高光(70%~100%)三个亮度区间,然后为每个区间独立配置不同的Gamma映射函数: - 暗部:采用低 值,斜率平缓,拉伸暗区细节 - 中灰:采用标准 值,维持自然的对比度 - 高光:采用高 值或对数压缩,斜率陡峭,抑制过曝 !image.png 这个分段LUT的设计可以很好的适应人眼的亮度响应,在暗部拉伸图像的亮度响应以保留更多的暗部细节,在亮部则压缩响应范围抑制过曝。 参考资料 - ISP算法——Gamma校正、Degamma校正 - 【基础】一文搞懂伽玛(Gamma)校正

计算机视觉类AI应用领域及其主流模型总结

本文总结了目前的人工智能领域中,针对计算机视觉方面的主要应用方向(图像分类,图像定位,目标检测,图像分割,语义分割,实例分割)的差异以及各个应用方向所存在的主流模型。 在当前的计算机视觉领域,图像分类、物体检测以及图像分割是最基础,也是目前发展最为迅速的三大领域。 图像分类 Image Classification 计算机视觉领域的图像分类任务,要解决的问题是:判断图像中所包含图像的类别是什么? 例如,对于下图而言,图像识别任务给出的识别结果是这幅图中包含了一只狗: !image.png 所以对于传统的图像分类任务而言,该任务识别结果对应的就是这幅图中最显著的物体所对应的分类(例如上图中的狗),并把这张图片直接按照这个最显著的物体来进行分类。因此,对于图像分类任务的图片样本而言,一般总是存在一个非常突出、最容易用于进行分类的显著物体存在,如下图的各个子图中均可以一目了然的判断该图片对应的分类(实际上也就是这个图片中所包含的最显著的物体): !image.png 当然,很有可能,我们提交给模型进行分类的图片中包含了多个较为显著的待识别物体,如下图中同时包含了一只狗和一只猫: !image.png 针对这种图像分类任务就需要多目标分类(Multi Label Classfier)。实际上从分类的角度上看,机器学习的分类算法可以分为三种类型: - Binary classification:有/无,是/非 - Multi-class Classification:从多个类别中返回一个 - Multi-Label Classification:从多个类别中返回多个 !image.png 针对Multi-Class和Multi-Label类型的分类算法,模型进行分类识别的判断结果区别就是,Multi-Class的输出结果是从多个类别中的1个,而Multi-Label则是多个类别中的多个: !image.png 无论如何,图像分类任务始终只关注要进行分类的图像中存在的物体类别,并不关注这些物体在图像中的位置和数量。 用于图像分类领域的模型训练数据集有:ImageNet,MNIST,CIFAR等。 图像分类领域的经典模型包括: - VGG,Visual Geometry Group at the University of Oxford - GoogLeNet (Inception),Google - ResNet,Microsoft Research - EfficientNet,Google - DenseNet,Cornell University - MobileNet,Google 目标检测 Object Detection 如果不光要关注图片中物体的类别识别,还需要准确的找到这些物体在图片中的位置,那么就需要用到计算机视觉中的目标检测任务。 如上所述,图像分类应用只是通过识别给图像一个单独的标签,而与之相对的目标检测应用,则需要在图像和视频中对检测到的每个对象提供空间坐标(边界框)和分类标签,这使得在更详细的层面上分析和处理可视化数据成为可能。 - 图像分类应用的输出结果,就只是图像中所包含对象的标签/标签。 - 目标检测应用的输出结果,则需要包含图像中包含对象的标签类别,以及以矩形框标记该对象在图像中所在的位置。 如果是图像中只包含一个最显著的目标,对该目标进行识别并定位的应用,就是Image Localization;而如果图像中有很多目标,要对所有的这些目标进行识别和定位的应用,就是Object Detection: !image.png 目标检测领域的经典模型包括: !image.png 图像分割 Image Segmentation 图像分割是把图像中以像素为单位分割为不同的对象,并使用不同的色彩涂色进行区分。 图像分割应用(Image Segmentation)又可以细分为语义分割(Semantic Segmentation)和实例分割(Instance Segmentation)。这两者之间的区别是: - 语义分割:图像分割的处理中,同一个类别的所有物体构成一个分类,因此,同一类别的所有物体都使用同一种颜色着色。 - 实例分割:在进行图像分割的处理过程中,同一个类别的多个不同实例被认为是单独的段,即同一类的每个对象被视为不同的。因此,即使属于同一类,每个独立的物体也会被涂上不同的颜色。 下图是语义分割的典型案例,整个图像中的不同类别的对象被涂以不同的颜色,但是同一类别的对象的颜色相同。 !image.png 下图是实例分割的典型案例,可以看到在该图片中识别到的三只猫被分割为不同的对象: !image.png 图像分割领域的主流模型: - Mask R-CNN - U-Net - SegNet - Deeplab - PSPNet - Vision Transformers (ViTs) 参考资料 - 人工智能中图像分类、目标检测、语义分割和实例分割等任务是什么? - 知乎 - Image Classification vs. Object Detection vs. Image Segmentation | by Pulkit Sharma | Analytics Vidhya | Medium - Object Detection Models - GeeksforGeeks - Image Segmentation Models - GeeksforGeeks

Pytorch模型的ONNX导出和部署入门

本文详细总结了基于Pytorch框架开发和训练好的模型导出为通用的onnx文件,并且通过onnxruntime进行部署和Python/C++访问的流程和步骤。 本文要解决的问题是:在Pytorch中开发和训练好的网络框架及其参数,如何导出为可以部署的onnx文件?如何在其他的环境中部署这个onnx文件? 本文以[[基于Pytorch实现手写数字识别的卷积神经网络]]一文中所训练的卷积神经网络为例,来说明这个完整的过程。 Pytorch模型导出为pt/pth文件 在Pytorch框架中,已经训练完成或者训练中的模型及其参数可以保存pt或者pth文件,这样后续就可以直接通过加载这些文件快速恢复出来模型及其训练后的参数状态,继续进行训练或者直接用于新数据的推理。 一般而言,使用pt后缀名的文件用于仅保存模型的参数,而pth后缀名文件则同时保存了模型的结构和参数。因此前者保存的文件更小,但在下次加载前需要提前构建模型的框架结构,因为pt文件里面并没有包含模型结构;而后者在使用上更加方便,内部同时包含了模型结构和参数,使用时直接加载即可,但是文件体积更大。 > 当然,在 PyTorch 中, pt和pth这两种文件后缀没有官方严格的区分和定义,它们更多是开发者社区约定俗成的用法。也就是说,你完全可以用 .pt 保存完整模型,或用 .pth 仅保存参数。 以下是基于Pytorch保存训练模型及其参数,并且重新加载模型参数到新的模型对象的代码示例: 模型的ONNX导出 相比于Pytorch的模型参数导出的pt/pth文件格式,ONNX文件格式是模型部署以及在不同模型开发框架之间共享和转换支持上更为普遍的文件格式。因此也可以把训练的模型及其参数导出为ONNX文件,用于进行模型部署以及转换到其他的框架下。 Pytorch模型导出onnx文件并通过ONNXRuntime类似进行新样本推理的前提条件,是需要安装几个依赖库: 把已经训练好的模型及其参数导出为Onnx文件的代码流程如下: 如以上代码中input_sample变量所表明的,在进行ONNX文件导出时需要明确的指定后续进行推理的数据输入格式。以上代码执行后,在当前目录下就会生成一个model.onnx文件,后续就可以基于这个文件进行Onnx部署以及在不同框架下转换和使用。 ONNX文件检查 以上所导出的ONNX文件,可以使用以下代码检测该文件的合法性: onnx.checker.check_model接口用于验证ONNX模型文件的有效性,确保它符合ONNX规范,可以被ONNX兼容的推理引擎正确加载和执行。这是在使用ONNX模型进行推理前的一个常见检查步骤,有助于提高代码的健壮性。 此外,也可以直接使用netron.app工具来对以上onnx文件进行检查,该工具可以以图形的方式输出非常详细的模型及其参数信息,可以非常直观的看到该文件中所描述的模型结构及其当前各层的训练参数信息: !image.png 通过ONNXRuntime部署模型并测试 Onnx文件中所保存的模型结构和参数是模型的静态表示,不能够直接调用PT格式导致后的eval()和predict()方法。当基于Onnx文件加载模型的结构和参数以后,需要通过ONNXRuntime引擎来执行新样本数据的推理。因此要首先安装ONNXRuntime包才能支持对ONNX文件的部署和推理。 > 如前所述,在Pytorch框架下使用ONNXRuntime导入Onnx文件并进行新样本的推理,需要通过pip安装onnxruntime和protobuf两个包。 以前文所导出的模型Onnx文件为例,从MNIST数据集中的某个样本进行OnnxRuntime的推理测试代码如下: 可以看到,除了使用torchvision.datasets和torchvision.transforms来实现对数据集的访问以外(其实完全可以采用其他方式来准备输入该模型进行推理的数据样本,只要数据格式符合ONNX input sample的要求即可),以上代码对于新样本数据的推理仅仅依赖于numpy和onnxruntime,完全不依赖于开发该模型的pytorch框架。 参考资料 - 第一章:模型部署简介 — mmdeploy 1.3.1 文档 - Netron - ONNX Runtime | onnxruntime

基于Pytorch实现手写数字识别的卷积神经网络

本文详细介绍和总结了基于Pytorch框架实现一个卷积神经网络CNN,用于对手写数字数据集MNIST进行识别的代码流程,以此作为对Pytorch框架以及神经网络编程实践的入门基础。 > 本文所解析的MNIST数据集手写数字识别的代码重点参考了《深度学习原理与PyTorch实战(第二版)》一书第五章的相关代码实现,实际上是对这部分代码结合我自身理解的更为详尽的解读。该书对于深度学习和神经网络在各个领域中的工作原理以及基于Pytorch框架的实现进行了非常详尽和精彩的描述,是对机器学习和神经网络领域入门的不可多得的好书,强烈推荐。 手写数字数据集MNIST及其准备 MNIST是一个手写数字的数据集,可以说是进入机器学习和深度学习领域的一个hello world资料库。基于该数据集,无论是通过经典的全连接神经网络,还是针对图像领域的卷积神经网络,都可以非常方便的进行网络结构的设计、测试、训练和验证,从而在代码实践的过程中获得对深度学习和神经网络领域相关理论的更深入理解。 MNIST数据集中的每个样本包括一张手写数字灰度图片(分辨率为28x28),及其对应的数字标签。整个数据集包含6万个训练样本和1万个测试样本,所以从总体上可以分为训练数据集和测试数据集。 !image.png Pytorch中内置了对各种常用开源训练数据集包括MNIST在内的支持,通过执行以下代码即可实现自动下载MNIST数据集到本地当前目录下的data子目录中。首次执行以下代码的时候,Pytorch会自动创建data子目录,并下载数据集到该子目录下。以后再调用这段代码就会直接从data子目录下加载MNIST数据集。 可以看到,以上的代码以train=True/False区分,分别用于下载/加载MNIST数据集的训练数据集train_set和测试数据集test_dataset。此外,因为数据集中的原始数据加载后是普通的数组形式,在该数据集加载的过程中要通过transform=transforms.ToTensor()选项转换为Pytorch的张量Tensor,这是Pytorch模型能够处理的标准数据格式。 Pytorch的数据加载器 Pytorch中专门提供了dataloader和sampler用于对训练数据的加载和调用进行管理,以方便后续在网络的批量训练和测试验证过程中高效的管理和传递训练数据。进行神经网络的训练和测试,一般需要把完整的数据集分为三个完全独立的部分,避免相互干扰:训练集、验证集、测试集。 > 为什么数据集在使用的过程中需要被分割为独立的训练集、验证集和测试集?训练集比较好理解,就是把训练数据送入网络,对比输出结果和该训练样本标签之间的差异,以差异为基础对网络参数进行修正,直到训练数据输出的结果和标签之间的差异最小。测试集也相对比较好理解,就是神经网络中的参数训练好以后,需要使用完全与训练数据不相关的独立数据集对训练好的网络性能进行测试验证,这个用于对训练好的网络进行测试评估的独立数据集就是测试数据集。验证集,则用于在网络参数训练的过程中,定期的使用这个独立的数据集对当前处于训练过程中的网络参数进行测试,这样就可以在训练的过程中实时的了解到网络性能的动态改善状况。 以下代码基于Pytorch的DataLoader创建了三个独立的数据加载器,分别对应于训练数据集(完整的MNIST训练数据集,共6万个样本),验证数据集(MNIST测试数据集的前5000个样本)和测试数据集(MNIST测试数据集的后5000个样本)。后续在进行模拟参数训练和测试的时候,就可以直接调用这三个数据加载器对象来访问对应的数据集了。 batch_size与Mini Batch 以上的三个dataloader变量在定义的过程中,都指定了一个batch_size参数。这个batch_size参数是一个常量,用于指定每次从数据集中取出的样本个数。在这种情况下,后续无论是训练、验证还是测试,每次从数据集中读取到的样本数就是batch_size。 例如设置batch_size=100的情况,每次通过dataloader的接口访问数据集可读取到100个样本,这100个样本一次性送入到网络中通过前向网络运算得到输出,对比这100个样本的标签就得到输出与标签的差异,然后基于差异再通过梯度下降法和反向传播法更新网络中的参数。也就是说,对网络参数的训练是以这个batch为单位进行的,而不是每个样本计算后都更新网络参数,这就是神经网络训练的mini batch概念。对于MNIST训练数据集的6万个样本而言,batch_size为100的情况下,基于整个训练数据集进行一轮训练,会分为600个Mini batch,也就是网络的参数会更新600次。当然,一般情况下,对于网络的训练,通常会基于相同的训练数据集进行很多轮训练,才会得到满意的效果。 基于Pytorch的CNN网络结构设计 用于训练和测试验证的数据集准备好以后,接下来就要开始进行网络设计了。 Pytorch框架中提供了一个torch.nn.Module类作为神经网络模块的基类,我们基于Pytorch框架所构建的网络就应以这个基类派生。该类主要的派生函数包含__init__和forward,前者用于声明网络结构所需要的框架,即每个层的规格定义;后者则实现训练样本从输入到输入的完整过程,也就是对各个层计算的定义以及多个层之间的连接。 以下代码基于torch.nn.Module构建了一个简单的卷积神经网络。通过以下代码可以看到这个神经网络包含两组卷积+池化层,以及两个全连接层,最后以log_softmax激活函数输出对10个数字的识别结果。 - 第一个卷积层包含4个卷积核,卷积核尺寸5x5,通过padding=2确保卷积计算不会缩小尺寸。 - 第二个卷积层包含8个卷积核,卷积核尺寸5x5,通道数为4(这是因为上一个卷积+池化的结果是4张特征图),同样通过padding=2确保卷积计算不会缩小尺寸。 - 两个卷积层使用的池化层规格是一样的,而且池化层的计算不涉及参数训练,所以__init__中只包含了一个池化层,采用2x2 Max池化方式,每次步进幅度为2。 - 卷积+池化层后面跟着两个全连接层,最后一个全连接层通过log_softmax激活函数输出识别结果。 - 最后一个池化层与第一个全连接层之间要通过Flatten操作实现多维特征图向全连接层所需要的一维向量的转换,这个转换通过forward函数中的x.view操作来实现。 - 第一个全连接层的神经元个数为512个,第二个全连接层个数为10个,对应于0-9共10个数字。 - 两个卷积层和第一个全连接层的激活函数都是ReLU,最后一个全连接输出层的激活函数为log_softmax。 除了torch.nn.Module基类中的__init__和forward函数以外,以上卷积神经网络的实现中还包含了一个自定义的retrieve_features,对于该函数的调用,以一个样本数据作为输入,可以读出该样本在当前网络参数状态下的第一层和第二层特征图,方便用户跟踪图像数据在卷积层的计算操作结果。 基于Pytorch的网络参数训练流程 以上的卷积神经网络定义好以后,接下来创建这个网络的示例,并启动和执行完整的参数训练流程。 首先基于之前声明的卷积网络类ConvNet定义一个变量,然后指定在后续训练过程中需要使用的损失函数(交叉熵函数,CrossEntropyLoss)和梯度下降算法参数。 在网络参数的训练过程中,一个epoch就对应于对整个训练数据集6万个样本的一次完整训练,可以多训练几次以提升网络的识别性能,例如设置num_epochs=20或者30。 在训练的过程中,基于以上介绍的mini batch的训练思路,每次对训练数据集的访问,会得到一个batch_size的训练样本(例如一次性得到100个样本),把这个mini batch的样本一次性送入网络进行计算,得到输出结果,然后利用前面指定的损失函数评估输出结果与训练样本标签的差异,在基于差异利用梯度下降法和反向传播法更新网络参数。一个mini batch的样本训练,对应于一次网络参数的更新。 此外,在进行参数训练过程中,还可以定期(例如每100个Mini batch后)基于当前训练的参数状态,利用独立的验证集数据测试当前网络参数的识别性能打印出来,这样就可以实时的了解到在训练过程中网络性能的改善进展。 从以上代码中可以看到,完整的这个过程对于网络而言,可以分为参数训练过程和数据测试/验证过程。分别对应于以下代码: 此外,在以上代码的训练和验证测试过程中,为了了解当前样本batch的正确率,还定义了一个rightness函数,用于比较样本预测输出与样本标签,返回正确的样本数量和样本总数: 模型的测试验证 最后的一步的测试阶段就比较简单了,基本上跟上面的在训练过程中定期使用验证数据集对网络性能的测试相同。 与上面参数训练过程中的验证环节类似,网络进入评估状态,然后从测试数据集中按照batch_size读出样本,送入网络进行推理输出,统计每个batch样本预测的准确率。同时,把训练过程中训练数据的误差率和验证数据的误差率打印出来,可以了解到训练过程中识别正确率的收敛情况。 在num_epochs=20,batch_size=50的情况下,对该网络进行训练,最终再对测试数据集进行测试得到的识别正确率高达98%以上: !image.png 参考资料 - 《深度学习原理与PyTorch实战(第二版)》第五章 - [資料分析&機器學習] 第5.1講: 卷積神經網絡介紹(Convolutional Neural Network) - JamesLearningNote - Medium

卷积神经网络CNN的架构解析入门

本文基于对网络资料的学习,对一个简单的卷积神经网络CNN的架构、数据计算和传递流程进行详细的解析和总结,作为理解卷积神经网络架构的基础。 卷积神经网络CNN架构各个网络层的设计中,最典型的特征就是在经典的神经网络结构中增加了卷积层和池化层。关于CNN的卷积和池化层的运算逻辑与流程,在详细解释卷积神经网络中的卷积与池化计算一文中有详细总结。 卷积神经网络CNN的整体架构设计 对于计算机视觉、图像识别、图像目标检测等领域的人工智能开发而言,卷积神经网络CNN几乎是一个绕不开的话题。因此,要了解这些领域的AI模型算法及参数训练,首先需要对卷积神经网络的整体架构设计、各个层次的作用以及数据计算与传递的流程有清楚的理解。 下图是一个典型的基于卷积神经网络的图像识别模型:图像从左侧的输入层输入,通过一系列处理以后,在输出层以识别概率的方式输出结果。 !cnn_banner.png 从整体的网络结构上可以看到,以上的整个网络可以分为两个部分:特征提取部分和图像分类部分。 特征提取部分的结构非常有规律,二维图像从输入层输入以后,进行卷积计算(Convolution),然后把卷积计算的结构再用激活函数ReLU进行处理,然后进行池化操作(Pooling);池化后反复进行多次卷积-ReLU激活-池化...直到最后通过Flatten操作把多维向量转换为一维向量进行后续的分类操作。 图像分类部分基于特征提取部分提取并经过Flatten操作转换后的一维特征向量,以多个全连接层的方式实现图像分类任务,最终以Softmax作为最后输出层的激活函数,输出图像分类的概率识别结果。 > 相比于普通的全连接层的神经网络结构而言,对图像数据使用卷积神经网络具有先天性的优势。因为全连接神经网络对于数据的输入和处理均不会考虑图像数据的空间结构,例如它同等对待相距很远和彼此接近的相邻像素,这样就只能从后续的训练数据中来推理和学习像素本身应该具有的空间结构。而卷积神经网络直接以二维图像的方式对数据输入和处理,天然考虑了不同像素位置之间的空间结构,因此非常适合用于图像这类二维数据结构,能够更快的进行训练和推理。 以下按照完整网络结构执行的先后顺序对各个层次的处理流程进行详细总结。 卷积层 卷积层的作用主要是用于提取图像中的特征数据,后续的分类层就依据卷积层所提取出来的特征数据对不同的图像进行区分并分类。 卷积层的运算如以下动图所示。以卷积核为单位对原始图像或者上一级的特征图进行扫描,根据进行卷积计算的输入是单通道图像数据还是多通道图像/特征图,分别采用平面卷积核和立体卷积核对多维数据进行卷积计算。在详细解释卷积神经网络中的卷积与池化计算一文中对如何进行平面和立体卷积计算做了非常详细的总结,可进一步参考。 !image.png 通过一个卷积核对输入图像/特征图进行一次完整的扫描(卷积计算),就可以得到一张新的特征图。有多少个卷积核就执行多少次扫描,并输出多少张新的特征图,用于后续的池化运算。例如下图使用16个独立的卷积核对输入的图像数据执行卷积运算,产生16张特征图: !image.png 如上所述,执行卷积计算的结果还需要使用ReLU激活函数处理后,才会传递给下一步进行池化操作。对卷积计算结果的每个像素通过使用ReLE激活函数过滤掉负值,保留图像中物体的形状。 !image.png 池化层 卷积神经网络的池化操作用于对卷积层提取的特征图数据进行降维,目的是保留显著特征的情况下降低数据量,因此就可以降低卷积神经网络的参数数据量和训练以及推理的计算量。 池化所进行的计算非常简单,只需要以 n*n 为单位对前一级卷积层所输出的特征图进行最大化(Max Pooling,选择最大值输出)以及平均化(Average Pooling,对所有值求平均)扫描,每次扫描后步进为 n ,不会与上次扫描相重叠(这一点与卷积操作明显不同),因此最终扫描的结果就是一个在水平和垂直方向上均缩小 n 倍的缩版特征图。 以下图为例,上级卷积层输出的特征图为4x4分辨率,池化操作以2x2对特征图进行扫描,最终池化操作的结果就是一个2x2的缩版特征图。 !image.png 池化操作以单张特征图为单位进行以上的池化计算,所以前一级卷积层输出多少张特征图,池化层就会输出多少张缩版特征图。 详细的池化操作可参考详细解释卷积神经网络中的卷积与池化计算一文。 Flatten 如上所述,无论是卷积层还是池化层,其输出都可以认为是多张二维特征图。经过卷积和池化处理以后,CNN接下来就要把前一阶段经过卷积和池化提取出来的特征图转换为一维数据,方便在后续的环节中进行处理,这个特征图转换为一维数据的动作就是Flatten。 Flatten环节的处理非常简单,就是把二维的特征图从上到下,从左到右展开即可: !image.png 当然,一般而言,最后一个池化层的输出一般会包含多张特征图,那么针对这种多张特征图的Flatten操作,就是先对第一张特征图进行一维转换,然后把后续的特征图按照相同的方式继续展开到这个一维数据后面。无论如何,多张特征图通过Flatten操作最终转换为一个一维的数据序列进行后续的分类操作。 !image.png 识别层 如上所述,经过以上的Flatten操作以后,在卷积层和池化层中所检测出来的多维特征向量就会转换为一维向量进行图像分类任务的处理。 与普通的人工神经网络结构相比,这个识别层的网络没有什么特殊之处,就是通过多个全连接层最后再叠加一个Softmax的输出层: !image.png 卷积神经网络架构中的全连接层网络部分以前一阶段的卷积和池化所提取出来的特征向量作为输入,利用多个全连接层所构成的网络来实现图像分类的功能,在全连接层的最后一层使用Softmax激活函数以概率的形式输出图像对于各个不同类别的识别结果。 从以上CNN网络的整体架构来看,整个网络中所包含的主要训练参数就是特征提取部分各个卷积层的卷积核权重参数以及图像分类部分全连接层各层之间的权重和偏置参数。那么在使用CNN网络结构对图像识别功能进行训练的过程中,基于网络最后的Softmax激活函数的输出,与训练样本标签之间,使用交叉熵函数作为损失函数,对卷积核权重参数以及全连接层权重和偏置参数进行训练,直到网络能够针对输入图像输出稳定、正确的识别判断结果。 - 有关深度学习激活函数和损失函数部分的内容,可参考盘点深度学习神经网络架构中的常用激活函数和盘点深度学习神经网络架构中的常用损失函数。 参考资料 - Convolutional Neural Network | Deep Learning | Developers Breach - Flattening CNN layers for Neural Network and basic concepts | by Muhammad Shoaib Ali | Medium - [資料分析&機器學習] 第5.1講: 卷積神經網絡介紹(Convolutional Neural Network) - JamesLearningNote - Medium

深度学习的数学基础知识总结

本文对学习和理解深度学习所需要的数学基础知识,尤其是微分、偏微分、复合函数等相关的理论知识进行了总结,并基于python做了简单的代码演示。 微分 数学中的微分理论被广泛的引用到机器学习的各种最优化问题的解决方面。 通过基础的微积分计算,我们可以得到一个函数在某个点的斜率,或者说准确的评估函数在某个点的瞬间变化的情况。对于微分概念的理解,以汽车行驶速度为例: - 通过一段时间内,汽车的完整行驶距离,可以得到这段时间里面的平均速度。 - 而通过对这段时间车辆的行驶距离与时间的对应关系求微分,则可以得到车辆在每个时间点上的瞬时速度。 所以,所谓微分的定义,就是函数在某个点上瞬间的变化率,实际上也就是函数在这个点上的斜率。 我们设函数为,设 为一个接近0的数,那么该函数在点 位置的微分(实际上也就是在点 上的斜率)就可以用以下表达式进行计算: 对以上微分计算式定义的解读实际上就是:在输入 上的微小变化,会导致其函数 的值发生多大的变化。 在计算机中使用程序进行函数的微分计算时,往往会给 h 取一个接近于0的固定的数,然后计算函数在某个点上的变化,也就是所谓的数值微分(与之相对应的则是基于数学理论的解析推导): 对于数学微分计算的推导而言,有以下几个常用的特性,在进行深度学习理论的过程中非常重要。 第一个特性是,当 时,对它进行的微分推导可以得到以下表达式: 也就是说,单独常数的微分是0(这是很自然的事情,因为常数是一条水平的直线,其斜率当然是0), 的微分是1 , 的微分是 , 的微分是 ,依次类推。该结论对于负指数也是成立的,例如 的微分是 。 第二个特性是微分的线性化。对于函数 f(x) 和 g(x) ,以及常数 a, 以下表达式成立: 基本上,对于单变量函数的微分计算与推导而言,掌握以上理论知识基本上就已经够用了。 偏微分 以上有关微分计算部分的叙述,针对的都是只有一个变量 x 的单变量函数。但是实际上我们在深度学习理论中所面对的实际问题,绝大多数情况下都是类似以下的这种多变量函数: 针对这种多变量的函数,我们要进行的就是偏微分的计算:在进行偏微分的计算过程中,只需要关注进行微分计算的变量,把其他变量都当成常数来进行处理即可。 例如,设函数 ,对以上函数首先求 变量的偏微分,此时应该把 变量看作是常数(例如把 固定为1),独立常数的微分为0,那么 针对 变量的偏微分(偏微分的符号发生了变化)为: 按照相同的逻辑计算 f(x_1,x_2) 针对 x_1 变量的偏微分: 以下代码示例演示了偏微分的计算逻辑。数值微分的计算过程仍然是相同的,只不过每次计算一个变量的偏微分是要保证只有这个变量是变化的。以下代码演示了函数 在(3,4)位置的偏导数: 而在机器学习理论中反复提到的所谓梯度,实际上就是函数针对所有变量进行偏微分计算的集合: !image.png 复合函数 所谓的复合函数,就是一个函数作为变量出现在另外一个函数中,由多个函数组合而成的函数。例如: 对于复合函数求微分的情况,例如对上式中 f(g(x)) 对变量 x 求微分,可以采用链式法则进行推导:如果某个函数由复合函数表示,那么该复合函数的微分可以用构成该复合函数的各个函数的乘积表示。 上面链式法则的描述比较抽象,可以通过以下对 例子的推导过程来进行理解: 深度神经网络通常都包含很多层,模型训练的目标是要尽可能通过调节各层的参数让最后输出层的损失函数最小,因此从大的概念上讲,可以把各层的权重、偏置等都看做是最后一层损失函数的变量。要让损失函数最小,就是要通过计算各层每个参数的偏微分(也就是导数)来决定参数调节的方向,各层参数偏微分的计算就要依赖于以上的复合函数+链式法则从输出层向上一层层求导得到。 参考资料 - 《白话机器学习的数学》附录 - 《深度学习入门:基于Python的理论与实现》第4章 - 機器/深度學習-基礎數學(二):梯度下降法(gradient descent) | by Tommy Huang | Medium

盘点深度学习神经网络架构中的常用损失函数

本文对深度学习神经网络中常用的损失函数进行详细的学习和总结,对各种损失函数的特性及其适用性进行整理,方便以后理解各种深度神经网络的架构设计。 神经网络损失函数的定义 所谓的损失函数,可以看作是表征当前神经网络工作性能的评估指标,也就是说,当前神经网络对于输入其中的数据进行处理的结果,与实际的真实结果相比较在多大程度上不一致,因此,损失函数的值越小越好。神经网络在训练过程中的目标,就是要找到让损失函数最小的参数(神经网络的权重和偏置)。 例如,如下图所示的简单例子,我们希望通过训练神经网络来拟合一条直线 f(x) ,来最大程度上反映出来给定样本数据输入 x 与对应输出 y 之间的对应关系,后续可以基于这条直线与新的输入 x 找出其对应的 y 。那么怎么来评估这条直线对于样本数据的拟合程度有多好呢?答案就是使用损失函数来评估. 在下图中,损失函数就是所有训练样本数据中 y 与拟合直线对应输出 f(x) 之间的差值 (f(x)-y) 之和。针对所有训练样本数据而言,这个累加的差值之和越大,表示损失函数越大,拟合程度越差;否则损失函数越小,拟合程度越差. !image.png > 上图中如何计算各个样本点与拟合直线之间的差值,有两种方法,一种是使用其插值的绝对值 |f(x)-y| 。另外一种是对其距离的平方 (f(x)-y)^2 (也就是以均方误差作为损失函数)。一般情况下使用后者,因为后续进行参数学习的时候,要对损失函数计算微分,比起绝对值,平方的微分计算更为简单。 为什么要有损失函数? 以图像分类任务为例,很多时候我们会有疑惑,既然我们训练神经网络参数的目标是让其能够更准确的对图像进行分类,那么最直接评估神经网络性能的指标当然就是对图像分类判别的成功率,为什么还需要额外引入一个损失函数的指标呢? 最主要的原因在于,实际应用场景中的神经网络结构往往非常复杂,层数和参数非常多,在这种情况下,每次训练对网络权重和参数的微小修改,对于送入神经网络进行学习和训练并最终进行正确分类的图片数量不会产生任何影响,在这种情况下,下一步对于网络的权重等参数要如何调节就失去了指导方向.因此在实际的神经网络参数学习中,就需要一种能够在输出指标评价上更为精细和平滑的损失函数,来评估每次对参数微小调整后所产生的效果,继而决定下一步的参数调整方向。 均方误差MSE 均方误差(Mean Squared Error, MSE)是机器学习和统计学中最核心的损失函数之一,尤其适合用在回归任务(基于历史数据对模型训练,然后预测新的输入数据所对应的输出)中。 在数学计算上,均方误差的定义如下: 其中,n 为样本数量,为第 i 个训练样本对应的真实值, 为当前模型参数对第 i 个训练样本输入所计算出来的预测值。 那么 就是每个样本的预测误差,通过以上公式计算,就可以得到所有样本预测误差平方的算术平均值,从而反映出来模型在整个训练数据集上的平均误差强度。 训练均方误差的Python实现示例代码如下所示: 交叉熵误差Cross-Entropy 交叉熵误差则主要用于评估样本数据提供的真实概率分布 p 与模型预测概率分布 q 之间的差异状态,其计算公式如下所示: 其中: - C :表示模型输出类别判断的总数。例如对于手写字符判别而言,其识别结果为数字0-9,因此模型输出的类别总数 C 就是10个。 - :表示当前的训练样本对应于类别 i 的真实概率,一般使用one-hot编码。 - :表示当前模型所输出的预测数据属于类别 i 的概率,所有类别的概率之和为1,也就是说 。 > 所谓的one-hot编码,就是只有正确答案的标签为1,其他都是0的数组。例如手写数字6训练样本真实概率所对应的one-hot编码就是[0,0,0,0,0,0,1,0,0,0],只有6这个数字对应的索引位置概率为1,其他全部为0。 因此,对于多分类任务而言,如果当前训练样本的真实概率使用one-hot编码的话,那么实际上也就意味着在进行交叉熵误差计算的时候,只有真实类别 的这一项对于计算误差有贡献,其他类别都是0。 例如,对于某个多分类任务的训练而言,某个训练样本的真实标签为 y=[0,0,1] ,模型针对这个样本输入所预测出来的概率是 y^=[0.1,0.2,0.7] ,则计算该样本对应的交叉熵损失为: ,也就是说,只需要对真实标签概率为1的这一项的预测概率求其log值就可以了。 交叉熵误差损失函数的Python实现示例代码如下所示。需要注意,因为log0在数学上没有意义,所有交叉熵实现代码中需要对这种情况做特殊处理,使用一个很小的正数代替log0。 参考资料 - 《深入浅出神经网络与深度学习》 - 神经网络算法——损失函数(Loss Function)_神经网络损失函数-CSDN博客

盘点深度学习神经网络架构中的常用激活函数

本文对深度学习神经网络中常用的激活函数进行详细的学习和总结,对各个激活函数的特性及其适用性进行整理,方便以后理解各种深度神经网络的架构设计。 激活函数是什么? 一个神经网络的典型神经元的架构如下图所示: !image.png - 输入层的输入与其权重参数各自相乘后累加,然后加上该神经元的偏执参数bias,这就是神经元的输入信号。 - 神经元在其内部处理中,把以上输入信号作为参数,执行其激活函数 f(x) ,激活函数的输出即为神经元的输出。 因此,所谓神经元的激活函数,本质上就是神经节对输入信号加权和的非线性映射和变换而已。通过激活函数的映射,神经网络的计算输出可以逼近任意复杂的函数(如曲线边界分割、多目标重叠检测),解决实际应用中的复杂问题。 为什么需要激活函数? 在没有激活函数的情况下,无论神经网络的层数是多少,其输出都是输入的线性组合,这样就等效为单层的感知机,层数(也就是所谓的深度)失去了意义。激活函数要解决的问题,就是从线性空间向非线性空间的映射。 > _如果没有激活函数的话,实际上无论神经网络有多少层,其输出最终都是对输入数据的线性变换,无法处理复杂的非线性问题。而我们现实生活中要解决的问题基本上都是非线性问题。激活函数通过引入非线性的映射,使得神经网络可以逼近任意复杂的非线性函数。_ 阶跃函数 阶跃函数是神经网络发展早期的感知机所使用的激活函数。阶跃函数以阈值(通常为0)为界,输出二元值:当输入大于等于0时,阶跃函数输出为1;否则输出为0。 其图形如下所示: !image.png 阶跃型激活函数的主要特点: - 其输出呈阶梯状跳跃,在x=0处不连续,形成明显的“断点”。 - 其输出范围只有0和1两种状态(对应于全有和全无两种情况),无法表达中间状态和概率信息。因此比较适合二分类任务。 - 因为在x=0位置跳变,形成了不连续点,所以无法进行微分计算(导数计算要求函数在局部区间内平滑变化),因此无法使用梯度下降法(依赖于对激活函数的偏微分计算)和反向传播法训练参数。 > 想象一下,采用阶跃函数作为激活函数的情况下,对于神经网络中的权重和偏执等参数的微小修改,有可能会导致输出完全翻转,进而引起神经网络对于其他输入数据进行处理的行为发生复杂的巨变。因此,这种情况下,就很难通过逐步修改网络的权重和偏执参数来实现神经网络判断准确性的调优。 因为以上的阶跃函数所存在的只有两个值的取值范围、不可微分等特性,目前阶跃函数已经不用于主流网络的隐藏层,仅在理论教学(感知机)、嵌入式系统轻量级二分类系统等领域有少量的应用。 阶跃函数的Python实现如下: Sigmoid函数 Sigmoid函数也被称为Logistic函数,是一种S型曲线函数。Sigmoid函数是神经网络中经典的激活函数之一,尤其在早期深度学习和二分类任务中应用广泛。 Sigmoid函数的数学表达式如下: 其图形如下所示: !image.png > 从图形的对比上可以看到,Sigmoid激活函数和阶跃函数激活函数类似,但是在使用Sigmoid激活函数的情况下,权重和偏置的微小改动只会引起输出发生微小变化,这一点对于使用sigmoid作为激活函数的神经网络正常学习并调整其参数至关重要。 Sigmoid激活函数的主要特性总结如下: - 与阶跃函数类似,输出值范围在0到1之间,且在(0,0.5)点中心对称,因此非常适合用于表示概率。 - 随输入x的增大严格单调递增,输入增大时输出趋近1,减小时趋近0 - 曲线平滑,处处连续可导,可通过后续的梯度下降法逐次计算逼近极值。 Sigmoid激活函数在神经网络的发展早期阶段应用较广,但因为诸多缺点,在当前的神经网络架构中已经较少使用: - 梯度消失问题。从上面Sigmoid的图形上就可以看出,当输入大于5或者小于-5的时候,整个图形几乎呈现水平的趋势,导数趋近于0,这一点会导致在使用梯度下降法寻求损失函数极值的过程中参数无法更新,训练停滞,这一点在网络层数较多时尤其明显。 - 非零中心化问题。Sigmoid的输出范围为0到1之间,随着输入x的增益严格单调递增,所以无论输入如何,其输出始终大于0。这样就会限制后续基于梯度调整参数的方向,导致调整过程中反复横跳调整参数的方向,训练收敛速度会显著低于零中心化激活函数(如Tanh)。 - 计算开销大。因为涉及到指数运算,相比于ReLU等现代激活函数计算成本过高,拖累训练速度。 Sigmoid函数的Python实现如下: Tanh函数 Tanh函数又称为双曲正切函数,可以认为Tanh函数是Sigmoid函数的变形形式(本质上Tanh是Sigmoid的零中心化改进)。与Sigmoid函数最大的不同在于,Tanh函数的取值范围为-1到1之间单调递增,且是0均值。Tanh函数的数学表达式为: 尽管其数学表达式看上去很复杂,但是它的图形跟Sigmoid非常类似。Tanh函数的图形如下所示: !image.png Tanh激活函数的主要特性总结如下: - 输出范围在-1到1之间,并且以原点对称 (tanh(−x)=−tanh(x)) ,这样就避免了Sigmoid的非零中心化问题,使后续层输入分布更均衡(不再是恒正),从而加速梯度下降的收敛。 - 与Sigmoid一样,输出随输入的增大逐渐单调平滑增大,函数处处可导。 - 在梯度变化方面,相比Sigmoid而言,梯度在相同输入下更大,一定程度上缓解了梯度消失的问题。 总之,Tanh函数解决了Sigmoid存在的非零中心化的问题,可以加速基于梯度下降的参数训练过程,也一定程度上缓解了梯度消失的问题(当输入偏离0过大的时候仍然存在),但是因为要进行指数运算,仍然存在计算成本过高的问题。 Tanh函数的Python实现如下(实际上numpy已经有自己的tanh函数实现了): ReLU函数 Sigmoid是神经网络发展早期应用比较广泛的激活函数,而深度学习时期应用最广泛的激活函数则是ReLU(Rectified Linear Unit)。 ReLU函数在输入大于0的时候,直接输出原值;而输出小于0的时候,直接返回0。其输出范围为0到无穷大,数学表达式为: ReLU函数的图形如下所示: !image.png ReLU激活函数的主要特性总结如下: - 分段线性。正区间保持线性,梯度始终为1,负区间强制归零。 - 计算高效。是ReLU最大的优势,不需要任何三角函数和指数运算,计算速度比Sigmoid和Tanh快10倍以上。 - 稀疏激活。在输入为负的情况下输出为0,此时神经元处于不激活状态,相当于只允许部分关键神经元处于激活状态,从而迫使整个网络优先选择最重要的特征。 - 解决梯度消失的问题。在正区间梯度恒定为1,可以用于支持超深层的网络。而且线性正区间可避免饱和,梯度更新幅度较大,收敛速度显著快。 ReLU最大的问题是死亡神经元问题。该问题是指当使用ReLU激活函数时,某些神经元可能会出现永久失效的现象。在训练的过程中,当神经元的权重更新导致其输入恒为负值时,ReLU的输出恒为0,而梯度也为0,就会导致该神经元无法再更新参数,处于实质性的永久失效的状态。当模型中死亡神经元的占比较大时,就会明显的影响整个网络的工作性能和精度。 ReLU函数的Python实现如下: Leaky ReLU Leaky ReLU(Leaky Rectified Linear Unit)是ReLU激活函数的改进版本,旨在解决其以上提到的死亡神经元的问题。Leaky ReLU针对负区间引入了一个微小的正数斜率 a (一般是0.01): 因此,Leaky ReLU在负区间就不再恒定是0,而是以 a 为斜率的直线,取值范围也变成了负无穷大到正无穷大。 !image.png 以上的这个设计可以有效的解决ReLU存在的死亡神经元问题:通过负区间的斜率 α 可保留微小的梯度,确保反向传播时权重仍然可以更新。 此外Leaky ReLU仍然保持了计算效率高的优势。其计算仅需一次乘法比较,计算复杂度与ReLU相近,远低于Sigmoid/Tanh的指数运算。 Softmax函数 Softmax函数是在神经网络中用于处理多分类任务。例如,对MNIST手写数字数据集进行识别的分类任务)的核心激活函数,其核心作用是将神经网络的原始输出转换为概率分布,使每个类别的概率值非负且总和为1。 Softmax函数的主要作用就是进行多分类的概率转换,其输入是一个多类别的向量数组 z=z_1,z_2...z_k ,其对应的数学表达式为: 在以上softmax的表达式中,在分子方面,对第 i 类输入 z_i 取指数,以放大各个分类的差异;在分母方面,取所有类别输入的指数和,实现归一化,以确保输出的总概率之和为1。 !image.png Softmax激活函数的主要特性总结如下: - 概率归一化。所有输入对应的输出值范围在0和1之间,并且所有输出之和为1,符合概率公理,非常适合用于分类决策。 - 可放大差异。在softmax的计算中通过指数函数可以显著放大高输入的输出权重,这样可以使模型对高置信类别在输出重点表现更明确,减少模糊预测。 - Softmax激活函数与交叉熵损失函数是分类任务的标准组合,梯度计算高效且收敛快。 Softmax函数的Python实现如下: 参考资料 - 神经网络中的激活函数及其各自的优缺点、以及如何选择激活函数_神经网络的激活函数有哪些?他们对神经网络的性能有何影响。-CSDN博客 - 激活函数全解析-CSDN博客

详细解释卷积神经网络中的卷积与池化计算

本文基于对参考资料的学习,以一个简单的卷积神经网络结构为例,对卷积神经网络中的卷积与池化操作的完整计算过程进行了详细的总结和解释。 > 本文实际上是《深度学习原理与Pytorch实战(第二版)》第五章的部分读书笔记,基于自己的语言整理出来加深记忆和理解。这本书是讲解深度学习理论和Pytorch实践的一本不可多得的好书,非常适合入门学习,强烈推荐。 下图是一个典型的用于对手写图像目标进行检测的卷积神经网络的结构示例: !image.png 从以上卷积神经网络的结构上看总体可以分为四层: - 输入层:输入数据是一副由二维矩阵表示的手写图像,矩阵中的每个元素对应图像的一个像素,每个像素包含一个0-255的灰度值,值越大越白,否则越黑。 - 卷积和池化层:对应以上架构中的两组交错的卷积层和池化层,把输入图像一步步提取出来其中包含的特征并降维保存,只保留图像中所包含的重要特征并最终输出给特征层执行分类任务。 - 特征层:也称为全连接层,主要就是基于卷积和池化层的输出执行分类任务。把前面池化层的输出的特征拉伸为一维向量,然后整合这些识别出来的全局特征,使用激活函数(如ReLU、Sigmoid等)进行非线性变换,得到一个分类概率或预测值。 - 输出层:最后的输出层包含10个神经元,针对送入神经网络去识别的每一张手写图像,在这10个神经元中输出0-9各个数字的识别概率,所有神经元的识别概率加起来为1。选取最大概率对应的数字作为输出。 本文的目的,是详细解释清楚在以上架构中,图像通过输入层进入卷积神经网络后,在卷积层和池化层所进行的计算操作是如何展开的。 卷积层的计算 与图像卷积操作密切相关的一个概念是卷积核。卷积核可以认为就是一张很小分辨率的图像,在原始图像上所执行的卷积操作,实际上就是使用这个小分辨率的图像(即卷积核)从左到右,从上到下逐个像素进行扫描和匹配,匹配的结果是一张新的图像,即特征图。特征图是一个灰度图像,每个像素的灰度值表示该位置原始图像与卷积核小图的匹配程度。如下图所示: !image.png 以下图为例说明原始图像与卷积核小图之间进行的卷积计算的过程。此处卷积核为一个3x3的小图,其中的每个像素都是一个实数值,代表卷积神经网络的计算权重。 - 卷积核中每个像素对应的权重值就是后续这个卷积神经网络需要训练的参数。 !image.png 如上图所示,首先是使用卷积核对原始图像左上角的卷积计算。此处进行的卷积计算,也就是把原始图像左上角与卷积核相同的3x3区域的每个像素,与卷积核图像对应位置的像素相乘,然后再把这个3x3区域的9个乘积结果相加,就得到了特征图左上角的第一个像素的值。 然后卷积核在原始图像上向右移动一个像素,继续执行相同的操作,得到特征图第一行第二个像素的值。 !image.png 按照以上计算逻辑,依次向右移动卷积核,每次向右移动一个像素,直到移动到原始图像的最右侧。然后再向下移动一个像素,开始做第二行的卷积。如此循环往复,直到卷积核移动到原始图像的右下角,完成整个图像的扫描,得到一副完整的特征图。 !image.png 按照以上的卷积计算逻辑,当原始图像的尺寸为m x n,卷积核的大小为w,那么经过以上卷积计算以后的特征图的大小为(m-w+1)x(n-w+1)。也就是说,特征图会比原始图像稍微小一点。如果希望特征图大小与原始图像保持一致,就需要在进行卷积计算之前,先在原始图四周填补一圈0: !image.png 以上使用一个卷积核,对原始图像所执行的完整卷积操作,可以得到一副与该卷积核相对应的特征图。而实际上,在卷积神经网络的执行中,往往会使用不同的多个卷积核分别对原始图像进行各自独立的卷积操作,从而产生多个特征图。例如,下图表示对125x125分辨率的原始图像进行100个不同卷积核的卷积操作,生成了100个125x125分辨率的特征图: !image.png 这一点,也是卷积神经网络架构中,从输入的原始图像经过第一个卷积层的卷积计算后,二维图像变成一个长方体的原因所在。 池化层的计算 如以下卷积神经网络整体架构的阴影部分所示,每个卷积层后面都有一个池化层,用于对卷积层所提炼出来的特征进行简化,只保留特征图中的粗粒度信息。卷积层C1经过对原始图像使用4个卷积核卷积之后,得到4个28x28的特征图。池化层P1的操作则是对每一个特征图进行池化操作,将其缩小,最后得到4个14x14分辨率的池化后特征图,在保留重要特征的基础上数据量大大减少。 !image.png 池化的操作非常简单。以下图为例,此处进行的3x3池化操作,是把原始特征图中的每个3x3区域基于取值最大的原则,将其最大值像素取出作为整个区域的池化输出。这样原始特征图的每个3x3区域就对应池化后特征图中的一个像素。 !image.png 需要注意的是,池化运算过程中的移动是无重叠的移动,这一点与卷积不同,所以池化后的输出要比原始特征图小很多。因此,上面9x9的原始特征图,经过3x3的池化操作后,其结果是一个3x3的池化后特征图。同样的,上面架构图中卷积层C1的4个原始28x28分辨率特征图,经过2x2的池化操作后,变成了4个14x14分辨率的池化后特征图。 立体卷积计算 池化层的输出是一个三维的立方体(多张池化后的特征图),那么池化后的数据再送入到下一层的卷积层计算时,其卷积计算的流程就跟原始图像送入第一个卷积层所进行卷积计算不同(原始图像是一个二维矩阵)。池化层的输出,送入到下一个卷积层进行计算时,需要进行的卷积计算是立体卷积计算。 下图是一个立体卷积核的示例。左侧是池化层,右侧是下一个卷积层。对于池化层进行的立体卷积计算而言,需要首先选择一个厚度与池化层输出相同的立体卷积核,然后使用这个立体卷积核对池化层所输出的多张池化后特征图进行立体卷积运算,每次运算结果(进行立体卷积运算的整个长方体相应位置相乘后再相加)对应于卷积层的一个像素,使用立体卷积核对池化层输出进行一次完整的扫描,就对应了卷积层的一张特征图。 !image.png 同样的,也可以有多个立体卷积核,使用一个立体卷积核执行一次立体计算得到一张特征图,那么多个立体卷积核的运算结果就对应于卷积层的多张特征图,因此,卷积层的输出也是一个长方体。 因此总体起来,对于一个简单的卷积神经网络而言,其卷积和池化的计算流程就是:二维图像输出-第一层卷积计算(平面卷积计算)-第一层池化运算-第二层卷积计算(立体卷积计算)-第二层池化计算-第二层卷积计算(立体卷积计算)...如此循环往复,直到最后把卷积计算出来的图像特征整理出来传递给特征层进行图像分类任务。 总结起来,对于卷积神经网络CNN而言,其卷积层的参数就是各个卷积层所包含的卷积核中的参数,这就是CNN网络在训练过程中主要训练的参数,而池化层因为只是简单地进行2x2或者3x3的取最大值的操作,所以没有要训练的参数。 参考资料 - 《深度学习原理与Pytorch实战(第二版)》,第5章手写数字识别器-认识卷积神经网络 - 卷积神经网络各个层的作用

镜头成像的Lens Shading问题

本文对镜头成像中普遍存在的Lens Shading问题进行详细的总结,分析该问题出现的原因,以及在相机产品进行图像调试中解决该问题的思路进行整理。 镜头的Lens Shading问题 从定义上讲,Lens Shading(镜头阴影)是光学成像系统中,因为镜头物理特性方面的原因导致图像亮度或色彩不均匀的现象。在图像上的具体表现,Lens Shading又可以分为Luma Shading(亮度阴影问题)和Color Shading(色彩阴影问题)两种类型。 !image.png 左图的Luma Shading,明显可以看到通过镜头成像的图像中心区域较亮,向四周逐渐变暗,形成“渐晕”效果,图像的边缘最暗。 右图的Color Shading,则是图像中心与边缘颜色不一致,例如表现为四角偏红或偏蓝的现象。 Luma Shading问题的成因总结 导致镜头Luma Shading问题的主要原因包含以下几种因素。 自然光学衰减 镜头本质上就是一个凸透镜,其中心的聚光能力要优于边缘部分,因此会导致光线强度从中心向四周衰减。如下图所示,对比其平行光线D1和角度光线D2: !image.png 对于一个没有畸变的摄像头,图像四周的光照度衰减会遵循一个cos4θ的衰减规律。 对于平行光线(D1)而言,从点P1发出的光线因为与光轴平行,所以其入射角θ=0°,此时其光强遵循公式 I * cos4θ = I,也就是无衰减的状态。 而对于角度光线(D2)而言,从点P2发出的光线与光轴成θ角,那么其光强就会衰减为 I cos4θ。例如,当θ=30°时,该角度发射的光强就会仅剩约56%(cos(4 30°)≈0.56),因此就会导致边缘亮度显著低于中心。 镜头机械结构遮挡 镜头本身以及镜头座的解析结构元器件有可能会对斜射入镜头的部分光线遮挡,导致这些光线无法照射到图像的边缘区域,所以造成图像中心区域射入光线更多而偏亮,边缘区域光线更少而偏暗。 如下图所示。 !image.png 镜头与图像传感器之间的CRA匹配问题 有关镜头与图像传感器之间的CRA不匹配导致的Luma Shading方面的问题,在[[图像传感器和镜头的CRA参数解释及其匹配]]一文中已有非常详细的解释。具体而言,当Lens CRA 95%的白色光源进行拍摄,获取其RAW格式图像。 - 按照RAW图像的Bayer排列分解为四个通道:R,Gr,Gb,B。如果一张Raw的size是3648 2736,那么每个通道的size就是1824 1368; - 把各个通道的RAW图像从横向和纵向分割为多个网格,然后分通道计算各个网格的平均亮度。 - 以每个通道图像的中心网格的亮度为基准(增益=1),对其边缘区域的网格按衰减比例计算补偿增益。大致的公式就是:_增益 = 中心亮度 / 当前网格亮度。 - 按照以上公式分通道计算各个通道各个网格的独立增益表(如17×13网格),存储于ISP的LUT(查找表)中,后续就对RAW图像基于这个表进行LSC增益补偿。 在完成ISP Tuning中的LSC标定后,后续对Sensor输出的RAW图像进行LSC校正时,就会对RAW图像的R、Gr、Gb、B各个通道分别应用其对应的增益表进行增益补偿,避免共用参数导致的色彩串扰。在要求较高的情况下,往往还会对不同色温(如3000K、5000K、7500K)情况下进行多次标定,生成多组不同色温下的增益表,在后续进行LSC校正时根据当前色温动态切换以适配环境光源,取得更好的LSC效果。 参考资料 - 【ISP】浅析Lens Shading-CSDN博客 - Shading校正原理浅析 - Image Shading | ISO 17957 | Image Quality Factors | Image Engineering

消费类Audio Codec的音频接口总结

本文基于对市场上常用的Audio Codec TLV320AI3101的数据手册进行总结和学习,从中更深入的总结通用audio codec的输入输出接口及其内部的信号处理流程,以此建立对audio codec及其音频处理流程的更为全面的认识。 全文分为三个部分: - 模拟音频输入接口 - 模拟音频输出接口 - 数字音频接口 模拟音频输入接口简介 以TLV320AI3101为例,该型号Audio Codec可以支持6个单端模拟音频输入引脚,其中包含 2路全差分输入(抗噪声能力强,适合远距离麦克风连接)和2路单端输入(简化电路设计)。可以通过内部寄存器的配置以及外部电路设置为单端麦克风接入,差分麦克风接入以及单端/差分Line In接入的工作模式。 下图为TLV320AIC3101的简化框图。对于模拟音频输入而言,标准的做法就是:如果使用单端麦克风,可以通过L2_L和L2_R实现两路独立的左右声道单端麦克风的接入;而如果是使用差分式麦克风的话,可以分别通过L1_L+,L1_L-以及L1_R+,L1_R-接入两路独立的左右声道差分麦克风。 !image.png 当然,也可以把差分模式的L1_L和L1_R作为单端麦克风的接口,这样最多可以同时接入6路单端麦克风。 但是需要注意的是,TLV320AI3101内部只有2个ADC,可以同时并行处理两路模拟音频数据的接入,因此如果同时有更多路模拟音频数据接入的话,就需要共享同一个ADC在多路音频数据通道上切换。而音频采样的采样率是设置到ADC的,所以这样也就意味着多路音频输入共享同一个ADC会导致实际采样率的降低。例如,一路ADC最大可支持96 kHz采样率,若同时激活3路输入,那么每路模拟音频输入的采样率仅为32kHz。 下图是TLV320AIC3101多个模拟音频接入共享同一个ADC的示意图,三个模拟音频输入引脚MIC1L/LINE1L,MIC2L/LINE2L/MICDET,MIC1R/LINE1R共享同一个Left ADC。 !image.png Mic In Mic In就是接入模拟麦克风信号的工作模式,这也是Audio Codec最常用的模拟音频数据的接入模式。 我们在产品中常用的驻极体麦克风的输入信号的峰峰值标准值一般为10 mV ~ 100 mV,而ADC的输入范围则通常设计为±1 V(全差分)或0~2 V(单端),所以麦克风输出信号在进行ADC转换之前,需要先通过前置放大器提升信号的电平,以最大程度的扩展ADC转换信号电平的动态范围。 此外,为了能够让驻极体麦克风正常工作,Audio Codec还需要通过MICBIAS引脚(TLV320AIC3101的MICBIAS典型供电电压2V-2.5V)给驻极体麦克风供电。MICBIAS给驻极体麦克风供电的典型连接是,MICBIAS电压通过电阻(如2.2 kΩ)连接至麦克风正极,形成电流回路(典型电流约0.5 mA)。 下图是麦克风单端接入的示意图: !image.png 为了进一步提升麦克风采集信号的降噪性能,可以采样差分接入的方式最大程度抑制共模噪声: !image.png Line In Line In接口(以及针对输出的Line Out接口)用于与其他的消费电子设备或专业音频设备进行对接。 TLV320AIC3101提供了两组Line In接口,同样可以支持单端输入或差分输入两者模式,通过寄存器配置为单端或者差分连接模式。在单端输入的情况下,其峰峰值(Vpp)的范围为 0.5 V ~ 2 V(对接需避免超过电源电压);在差分输入的情况下,其峰峰值范围为1 V ~ 4 V(共模电压需在AVDD/2 ± 0.3 V内)。因此,当以Line In模式接入时,其电压的动态范围已经接近了ADC的满量程输入了,因此就不需要对其进行进行前置放大了,只需要在PGA中对其增益进行适当微调就好,这一点是Line In模式接入和MIC In接入不同的地方。 此外,在Line In接入的情况下,也不需要额外的通过MICBIAS给Line In信号的上游设备供电,这是两者迥异的另外一点。 下图是全志H3平台的Audio Codec的Mic In以及Line In以差分方式接入系统的示意图。 !image.png 模拟音频输出接口简介 对于Audio Codec而言,有模拟输入接口(MIC,Line In),自然就有模拟输出接口(Line Out,HPOut)。总的来讲,Audio Codec的模拟输出接口一般用于把其内部DAC转换的模拟音频信号通过音频功放放大后驱动不同功率的扬声器、耳机以及以Line Out方式提供给下游的专业音频设备Line In输入接口。 大部分的Audio Codec所包含的模拟音频输入接口,主要有两种类型: - HPOUT,在Audio Codec的DAC输出后增加了一个小的功放,可以直接用于驱动耳机、小功率扬声器。 - Line Out,实际上就是Audio Codec的DAC输出,没有在其内部进行功率放大,因此可以用于连接后一级的功放来驱动更高功率的扬声器。 Line Out Audio Codec在模拟音频输出接口方面,Line Out接口适合连接高阻抗设备,例如专业的音频调音台和通过功放来驱动更大功率的扬声器。该接口的输出峰峰值一般在1 V ~ 2 V之间,具有输入阻抗高、输出阻抗低、输出电流很小( 10 kΩ)。 以TLV320AIC3101为例,该Audio Codec包含两组可独立设置的全功能差分式Line Out接口,均可用于驱动10 kΩ的差分式负载。从DAC输出的模拟音频信号,经过灵活的音频信号路径控制,在混音模块中实现多路不同来源的语音信号的混音处理,再经过增益调节后,最终输出差分音频信号。以下为TLV320AIC3101的Line Out输出的结构图。 !image.png - 左右声道DAC输出的模拟音频信号分别通过寄存器配置连接到DAC_L1/2/3和DAC_R1/2/3。 - 当需要在模拟音频输出中使用混音功能时,把DAC_L1和DAC_R1(DAC_L2和DAC_R2用于HPOUT的输出),以及模拟音频输入端的ADC输入信号PGA_L/PGA_R(这样输入Audio Codec的模拟音频信号经过PGA放大以后就不需要进入ADC-DAC的处理,直接进入模拟音频输出环节)分别接入两个独立的混音模块Volume Controls Mixing,在其内部执行混音功能。 - 如果不需要使用混音功能,可以把混音模块旁路掉,直接把DAC_L3和DAC_R3作为Line Out的模拟音频输出信号。 - 混音模块的输出,与DAC_L3/DAC_R3经过音频路径控制的选择,再经过0-9dB的增益调节后,最终在Line Out接口经过差分电路的转换后以差分形式输出。 需要注意的是,以上电路模块图中,Line OUT接口输出的模拟语音信号的处理路径中包含了一个0-9dB的增益调节功能,这个功能只用于调节DAC输出到Line Out或混音器的信号电压幅度,匹配后端设备的输入灵敏度(如功放、调音台),避免信号过弱或过载。该功能只能提升电压,不能提升电流驱动能力。因此Line Out的驱动电路能力仅 HPOUT的命名很容易产生理解上的混淆,例如在TLV320AIC3101里面HPOUT是High Power Output,即高功率输出接口。但是实际上HPOUT接口内置的功放功率很小,只能用于驱动耳机或者很小功率的扬声器,所以说不上是高功率。这里的High Power应该是针对Line Out接口的,因为Line Out接口没有内置功放,输出电流很小。其实如果这个接口只是用去驱动耳机负载的话称为Head Phone Output倒是更为合适。 完整理解了上面的Line Out接口的结构图以后,理解HPOUT的结构图就比较容易了。其中DAC输出、混音模块、模拟音频信号的路径管理模块都与Line Out的结构别无二致,两者最大的不同在于,HPOUT接口在输出之前进行了功率放大(最后的三角形),所以HPOUT可以用于驱动负荷其功率限制的小功率负载。 !image.png 除了以上的Line Out接口和HPOUT接口以外,部分Audio Codec还内置了一种SPKOUT的更高输出功率的模拟音频输出接口。SPKOUT接口可以用于驱动4~8Ω扬声器,输出功率达到了100-150mW的样子,可以满足一些小功率扬声器的驱动需求。实际上TLV320AIC3101是把HPOUT和SPKOUT两种接口合并起来提供了一个综合的HPOUT接口,既可以用于驱动耳机负载也可以用于驱动小功率的扬声器。 以下表格总结了Line Out接口和HPOUT接口在应用特性上的差异: !image.png 数字音频接口简介 除了能够支持以上的模拟音频数据的输入和输出以外,一般的Audio Codec也同时提供了数字音频数据传输的接口,至少,这个数字传输的接口可以用于: - 把模拟麦克风采集的音频数据经过AD转换以后得到的数字音频数据,通过数字音频接口传输给DSP进行进一步的处理。 - 通过数字音频接口接收DSP传输过来的数字音频数据,经过DA转换以后在扬声器、耳机等设备上播放出来。 而对于Audio Codec而言,最常用的数字音频接口就是I2S接口。一路I2S接口可以用于传输包含两路独立音频数据流的双声道音频数据。为了利用I2S的硬件接口同时传输更多路的音频数据,部分Audio Codec也会在I2S硬件接口上提供TDM时分复用的方式传输多路音频数据的音频传输协议。 I2S接口 I2S(Inter-IC Sound)是一种同步串行接口,专门用于音频数据的传输,具有简单、高效和抗干扰能力强的特点。I2S接口传输的是数字音频数据,通常以PCM(脉冲编码调制)格式表示,实际上也就是Audio Codec中从麦克风采集语音数据经过AD转换后的数字音频数据,或者从DSP接收到的、需要通过Audio Codec进行DA转换后在扬声器播放出来的数字音频数据。 通过I2S所传输的数字音频数据对应的参数包括采样率(如16KHz、32KHz、44.1kHz、48kHz等)、位深(如16位、24位等)和声道数(如立体声双声道)。具体的传输过程中,I2S接口通过时钟信号来同步音频数据的传输。它使用多个时钟信号来控制数据的发送和接收,确保音频数据的准确传输。 I2S硬件接口的信号线包括: - SDIN/SDOUT(Serial Data):串行数据线,用于对外或者对内传输数字音频数据。 - WCLK(Word Clock):字选择信号,用于指示当前传输的是左声道还是右声道数据。通常WCLK为高电平时传输左声道数据,为低电平时传输右声道数据。 - BCLK(Bit Clock):串行位时钟线,用于控制数据的采样速率。音频数据在BCLK的上升沿或下降沿被采样。 下图是TLV320AIC3101的I2S工作时序图。在I2S标准中,数据在WCLK边沿(上升沿或下降沿)延迟1个时钟周期(BCLK)后开始传输下一个声道的MSB数据位。 !image.png TDM接口 如上所述,一路I2S接口只能以左右声道的方式用于传输两路独立的音频数据,形成双声道立体声的音频效果。但是很多时候我们的产品(例如包含麦克风阵列的功能)想要传输更多路独立音频流的时候,除了使用多路独立的I2S接口以外,还可以使用TDM接口来解决这个问题。 在硬件接口上,TDM接口与I2S接口共享同一组物理引脚(DOUT/DIN、SCK、WS),只不过数据传输协议不同。I2S模式可用于传输双通道(立体声)音频数据;TDM模式则用于使用相同的物理接口同时传输多通道(2~8通道)音频数据,通过时隙划分复用同一数据线。 在音频Codec中,TDM接口可以用于在单条数据线上传输多个独立音频通道的采样数据,每个通道的数据按预先分配的时间槽(Time Slot)依次传输。在具体的工作过程中,TDM接口和协议会将数据传输周期划分为多个时隙,每个时隙分配给一个独立的音频数据通道。例如,若TDM支持8个时隙,则可传输8个通道的音频数据,8个通道轮流使用自己的时隙传输数据。 下图是TLV320AIC3101的TDM数据传输时序图。可以看到,使用TDM模式进行多路音频数据传输使用了与I2S相同的硬件接口和物理引脚,只不过不再使用Word Clock区分左右声道,而是在Data In和Data Out固定设置不同音频流所使用的传输时隙,因此,对于使用TDM接口进行多路音频数据传输的上下游设备而言,需要提前协商清楚并设置好同时传输几路音频流,以及各路音频流在传输时序中的时隙才行。 !image.png 对于TDM接口的使用,需要注意的是,TDM接口仍然没有完全的标准化,针对其通信协议细节(如时隙数、对齐方式、时钟相位)缺乏统一的标准,不同厂商的实现可能存在一定的差异,所以在使用TDM接口进行上下游设备之间的对接调试中,可能会遇到一定的兼容性问题,需要调试部分参数才能解决。 PCM接口 其实除了以上的I2S接口和TDM接口以外,在一些Audio Codec上还有一种用于传输数字音频信号的PCM接口(TLV320AIC3101没有PCM接口),该接口多用于语音通信和多通道数据传输场景。 PCM接口一般用于传输未经压缩的多通道(能够更灵活的支持多通道是PCM接口与I2S之间的最大差别)线性PCM音频数据,可支持语音、音乐等多种场景。 具体的使用过程中,PCM接口的典型应用场景就是与单设备多通道、多设备多通道等形态的音频设备进行连接,通过一组PCM接口可以实现多个独立的音频通道的音频数据的传输。例如: - 1个8通道ADC芯片通过PCM接口连接Host,每个时隙传输1个通道的采样数据。 - Host通过1个PCM接口连接4个单声道麦克风(每个设备占用1个时隙)。 !image.png 从接口的硬件定义上,PCM接口与I2S接口很类似,通常包含以下数据线: - PCM_CLK(位时钟):由主设备生成和控制,频率为采样率 × 位宽 × 通道数。例如在16位、8通道、48 kHz采样率的情况下,PCM_CLK频率 = 48k × 16 × 8 = 6.144 MHz。 - PCM_SYNC(帧同步):由主设备生成和控制,标识音频帧的起始位置,频率等于采样率。 - PCM_DIN(数据输入)与 PCM_DOUT(数据输出):传输多通道音频数据。 从以上PCM接口所包含的数据线可以看到,PCM_CLK、PCM_DIN、PCM_DOUT的定义与I2S没有区别,双方之间的区别主要是在I2S的WCLK和PCM的PCM_SYNC上。 !image.png 要彻底解释清楚这两者的差异,首先要弄清楚PCM接口中用于作为帧同步信号的PCM_SYNC中的音频帧是什么意思。 PCM接口中所传输的音频帧,总是由PCM_SYNC的一次跳变(跳变边沿可编程设置)来启动传输,而一个音频帧中包含了PCM接口所连接的多通道中每一个通道的一个音频采样数据,每个音频采样数据占据一个固定的时隙,各个通道在音频帧中所占据的时隙在Host和Device两端的寄存器中进行设置。在这种PCM接口连接多通道音频设备的情况下,一个音频帧被分为多个时隙,每个时隙包含其中一个通道的音频采样数据,整个音频帧由PCM_SYNC信号触发开始一个完整的数据传输周期(即这个音频帧的周期)。 如下图所示的PCM接口连接的四通道音频传输的示意图,一次PCM_SYNC(FS)的跳变启动一个PCM Frame的传输,一个PCM Frame包含四个独立通道的音频采样数据,每个通道的采样数据占据固定的时隙。 !image.png 所以本质上,PCM接口也是以TDM时分复用的方式在在一根DIN和DOUT线上使用不同的时隙传输多个通道的音频采样数据。 参考资料 - TLV320AI3101 datasheet - I2S/PCM - 知乎

君正IPC平台的Secure Boot流程实现解析

本文对国产的君正Ingenic IPC平台上所使用的Secure Boot功能的运行流程进行完整的总结和说明。 君正IPC平台的启动流程 与绝大多数嵌入式硬件平台的上电启动以及启动镜像加载并执行的流程相同,君正的IPC平台在上电后也存在多个阶段镜像的加载并执行的过程。 参考资料2中提供了一个很好的流程图,用于解释君正IPC平台的启动镜像加载流程。 !image.png 1. 芯片上电后首先运行其内部固化的Rom Loader,在Rom loader中会根据当前的Power On Pin的检测决定启动方式(Flash,SD卡还是USB)。 2. 当Rom loader从Flash启动时,会首先从Flash的头部按照规则读取Flash中保存的第一阶段bootloader,也就是SPL Image。 3. Rom loader把SPL image加载到其内部的SRAM中,然后把控制权交给SPL。SPL负责执行更多的初始化,尤其是其中的DDR,完成初始化以后,再把Flash中的第二阶段bootloader,也就是Uboot加载到DDR中开始运行。 4. Uboot才是主要的bootloader,对系统执行更为全面的初始化设置,并加载Kernel和Rootfs镜像到DDR中,并且启动Linux Kernel的运行。至此系统就完整的启动起来了。 在Romloader和Uboot之间额外增加一个SPL的阶段,主要是因为君正芯片内部的SRAM空间太小,而Uboot image太大,SRAM容纳不下,所以需要一个中间的bootloader把更大空间的DDR初始化配置好,然后把Uboot镜像加载到DDR里面运行。后续的Kernel、Rootfs等镜像都是会被加载到DDR中运行的。 君正IPC Secure Boot流程中所使用的算法 在君正IPC方案所提供的Secure Boot实现中,主要用到了两种算法: - RSA2048:非对称加解密算法。主要用于对镜像内容的SHA256摘要信息进行加解密以后再匹配比较验签。 - SHA256:哈希算法。用于对SPL Image以及RSA2048算法的公钥进行摘要运算。 因为SHA256哈希算法没有密钥信息需要存储,因此实际上存储到君正芯片Efuse内部的密钥相关信息只有RSA2048算法的公钥摘要信息。 Flash和Efuse中信息结构 芯片Efuse中的信息结构 君正芯片内部的EFuse信息(也就是OTP)中,针对Secure Boot实现主要包含两部分内容:otp4的Secure boot enable配置信息,以及otp9的RSA算法公钥的SHA256摘要信息。 在芯片上电启动时,Rom loader会去检查ota4中写入的Secure boot enable配置信息,来决定是以Normal boot方式启动还是以Secure Boot方式启动。 而如上所述,整个Secure Boot实现中因为只用到了RSA2048和SHA256算法,SHA256没有密钥信息需要存储,因此OTP中就只需要保存RSA公钥相关的信息。 !image.png Flash中与Secure Boot相关的信息结构 下图为启用Secure Boot模式的情况下,利用君正提供的PC端脚本所生成的SPL Image对应的Flash结构。其重点在于: - SPL Image本身保存在Flash 0x800开始的位置,SPL Image并没有进行加密。 - Flash 0x300-0x400之间保存有RSA算法的公钥。 - PC端脚本在基于原始的SPL Image生成Secure Boot镜像文件时,对SPL Image内容进行SHA256摘要运算,并把摘要运算的结果再使用RSA算法的私钥进行加密。Flash 0x200-0x300之间就用于保存SPL Image的这个加密摘要,后续用于对SPL Image验签。 !image.png 君正IPC平台的Secure Boot启动流程 基于以上信息,下面总结出君正IPC平台上电启动后,Rom Loader在Secue Boot模式下对SPL Image进行校验的启动流程。下图是其启动的完整流程图: !image.png 以上流程可以分为两个阶段:RSA公钥验证,SPL Image摘要签名验证。 RSA公钥验证 Rom Loader在上电启动时,会检测OTP4中写入的Secure Boot配置信息,在Secure Boot启动模式下,读出OTP9中在生产阶段所烧写的RSA公钥的SHA256摘要信息。 然后Rom Loader再从Flash的0x300-0x400位置读取RSA公钥,对其进行SHA256运算,把这个运算结果与OTP9中读取出来公钥SHA256摘要进行比对。 以上比对一致的情况下,证明OTA烧写和Flash中的公钥是一致的,继续执行启动过程,否则启动失败。 SPL Image摘要签名验证 接下来,Rom Loader会对SPL Image原始镜像的内容进行SHA256签名运算,得到Rom Loader自己的运算结果。 然后再从Flash的0x200-0x300读取经过PC端脚本使用RSA私钥加密后的摘要,使用Flash中读取的RSA公钥对其解密,得到解密后的SHA256摘要。 对以上两个摘要进行匹配比较,如果一致就表示SPL Image的验签通过,这个SPL Image确实是由硬件官方发布的镜像(因为只有官方才有对镜像文件进行加密签名的RSA私钥)。 总结 总体而言,相比于Novatek IPC平台所实现的较为完善和全面的Secure Boot功能,Ingenic平台的Secure Boot功能就要简单很多,要实现完整的Secure Boot需要下游开发者做不少事情。 相比于Novatek平台在其SDK中已经标配了对启动流程中一级bootloader、Uboot、Kernel镜像的全访问保护,君正平台的Secure Boot默认情况下只对SPL Image进行保护。当然,因为Secure Boot模式下,君正芯片内部的Efuse中已经保存了RSA验签密钥公钥相关的信息,所以对于下游开发者而言,完全可以基于相同的逻辑自行实现对Uboot、Kernel镜像的验签保护。只不过这个过程君正方案的SDK本身没有默认提供,需要开发者自行实现罢了。 此外,君正的原始Secure Boot的实现中,Efuse中只保存了RSA公钥,对镜像文件的内容只提供验签的保护,没有提供加密保护。这方面相比于Novatek平台而言,其安全程度也是不如的,Novatek平台会在验签的同时会对镜像文件的内容进行加密。 不过好在君正开放了Efuse中的部分存储空间给下游开发者使用,下游开发者可以利用这部分Efuse的存储空间进行自定义的Secure Boot开发,包括额外针对Uboot、Kernel等镜像增加加密的功能。 参考资料 - 君正Secure Boot使用说明 - 君正Zeratul开发(2)——uboot启动分析_君正t41 sdk包-CSDN博客

音频3A处理简介:AEC,ANS,AGC

本系列文章完整的总结了消费类摄像头领域中针对音频进行处理的3A(AGC,AEC,ANS)概念理论及其工作原理进行总结,为该领域产品中音频效果的调优和问题接近打下基础。 在消费类摄像头领域中,相比与视频图像方面的3A(自动曝光AE Auto Exposure,自动白平衡AWB Auto White Balance以及自动对焦AF Auto Focus)处理,在对音频数据的处理上同样存在3A处理,也就是音频3A:自动增益控制AGC(Auto Gain Control),声学回声消除AEC(Acoustic Echo Cancellation),自动噪声抑制ANS(Auto Noise Suppression)。本文的目的就是尝试对以上音频3A处理的理论技术以及工作原理流程进行总结。 下图是一个典型的音频3A处理并通过服务器与远端进行语音双向通信交互的流程图: - 麦克风采集的数据,首先要结合收到对端的语音数据信息,识别出来其中的回声,利用AEC回声消除算法消除回声。 - 然后把经过回声消除的语音信息再经过ANS降噪处理过滤掉音频噪声,提升音频采集的品质。 - AGC则用于根据采集语音的幅度动态自适应的调整音频增益,把传输到对端的声音信号强度调整到合适的幅度。 - 对原始音频数据进行编码并通过网络和服务器传输到语音通信交互的对端。 - 对端收到语音信息后利用各种缓冲、丢包重传等机制对音频数据进行整理。 - 对端对收到的音频压缩数据进行解码,恢复出来原始音频数据。 - 最后再结合本地混音等的设置,把原始音频数据在扬声器上播放出来。 !image.png 总的来说,对于音频3A在一个全双工的双向音频对讲的应用中,一般的处理顺序是AEC回声消除最先处理,其次是ANS音频噪声抑制,最后是AGC自动增益控制环节。 声学回声消除AEC AEC要解决的问题则是在双向语音通话中(例如电话会议中),通话一端扬声器发出的声音(这个声音来自于通话的另一端)会被同一端的麦克风收集到,并传输到另外一端,这样就会造成双方说话的声音在两端之间反复回传后形成明显的回声的效果。 下图就是在双向全双工语音通话和电话会议中出现回声的示意图: !image.png 从以上的这个双向通话回音生成原理的示意图上可以看出,只有在双向全双工(也就是通话两端的麦克风和喇叭都保持打卡状态)语音通话的时候才会发生回声的问题。如果是半双工的话(类似于对讲机),是不存在回音及其回音消除问题的。因为,在半双工语音通话的情况下,同一时间只有一方的麦克风打开,而且通话过程中总是一方听完对方说的话以后,才会按下通话键向对方发送自己的语音,这样的话,每次某一方的麦克风打开时,他自己的喇叭是不会发出声音的,这样的话自然就不存在回声和回声消除的问题了。当然,这样的通信方式自然是比较低效的,因为同一时间只能有一方说话。 在具体的回声消除的应用场景中,又往往根据回声产生的路径,把回声分为两种类型: - 直接回声:也称为线性回声。是指远端传输过来的声音信号在本地端的喇叭上播放出来以后,没有经过任何反射和折射,直接被本地端的麦克风所采集的情况。这种直接回声不受环境影响,只与本地端的麦克风和喇叭之间的距离和位置有关,也因此回声产生的时间延迟相对比较好估计。 - 间接回声:也称为非线性回声。是指远端传过来的声音信号在本地端的喇叭上播放出来以后,还要经过本地端环境中的各种障碍物、墙面、地面等反射和折射回来以后,才会被本地端的麦克风所采集的情况。因此间接回声的大小与房间环境、布局甚至各种障碍物的声波吸收系数等均有关。当通话设备本身处于移动状态之下,喇叭和麦克风与回声环境各种障碍物的位置也就在始终变化中,回声的延迟也就是动态变化的,因此,这类回声相对不太好处理。 回声消除问题的处理逻辑 从回声所产生的流程上讲,通话双方A和B,A端的语音信号被A端的麦克风采集以后,传输给B端,在B端的喇叭播放出来以后,又重新被B端的麦克风所采集,与B端本身的语音信号叠加在一起,再传输给A端,这样A端就能在自己的喇叭中听到自己刚才所说的话。因此,整个过程中,A端是无辜的,出现回声问题是由于B端的麦克风在采集声音的时候,把自己的语音信号和喇叭播放出来的A端语音信号混合在一起,未作任何处理直接发给A端,导致A端出现回声问题。 !image.png 因此,回声问题的解决,就要从B端着手,在B端麦克风采集到音频数据后,要从其中识别和分离出来自己的语音信号和喇叭中播放的A端的语音信号,然后把麦克风采集的混合音频信号中的A端语音信号处理掉,只保留自己的语音信号,然后再发给A端。这就是针对这个问题需要在B端引入AEC回声消除模块的处理逻辑所在。 当然,因为A端和B端始终是在全双工双向语音通信之中,双方是对等的,那么在实践中就需要在两端同时分别加入各自的AEC回声消除功能和模块,在这个模块中把自己喇叭播放出来的对端语音信号从麦克风采集的混合信号中识别并消除掉。 回音消除的工作原理 如上所述,所谓的回音消除,就是在麦克风端接收到的混合信号中,把本地喇叭播放出来的远端语音信号识别出来,并且消除掉,确保只有本地的原始语音信号会被被传输到另外一端。那么针对这个设计需求,回音消除模块的实现上一般而言就存在以下四个模块。 1. 时延估计模块。 要把喇叭播放出来的远端语音信号消除掉,首先要能够从本地麦克风采集的混合信号中把该信号识别出来,并确定出来喇叭播放出来的远端语音信号和本地麦克风采集的语音信号之间的时间差。有了这个时间差,才能在后续的处理模块中准确的定位需要削弱的远端语言信号在麦克风采集数据中的位置。 那怎么才能找到两个语音信号(收到远端的语音信号数据,本地麦克风采集的语音信号数据)之间的时间差呢?答案是对两个语音信号做切片处理,分别针对分片的音频数据帧做傅里叶变换,得到其频谱数据,然后通过互功率谱等计算来比较二者在频域上的相似性,直到找到双方的音频数据的频谱表现在哪个切片上一致性最佳,就找到了双方的时间差。 2. 基于自适应滤波器的线性回声消除模块。 该模块是整个回声消除模块的核心所在。 在回声消除算法的具体实现中,以本地接收到的远端语音数据作为输入,通过动态迭代和更新自适应滤波的滤波器权重参数,使其输出信号尽可能接近真实的回声信号数据,最终从麦克风采集到的混合信号中减去自适应滤波器输出的预测回声数据,达到保留纯净本地语音数据的目标。 3. 非线性残留回声抑制模块。 以上基于自适应滤波器实现的线性回声消除模块,仅能消除声学路径的线性回声成分,但是无法完全消除非线性失真。因此,在使用自适应滤波器对线性回声进行过滤之后,就需要利用非线性模块,通过频域能量分析、动态增益控制等手段,进一步抑制残留的非线性回声。 4. 双向对讲检测模块。 该模块通过对远端语音数据以及本地麦克风采集的语音数据的动态监测,判断当前是只有一方在说话,还是双方都在说话。 因为如果双方同时对讲的情况下,本地麦克风采集的音频数据中就同时包含了双方的语音数据,在这种情况下,本地的语音信号就会被误判为回声的残留部分,导致滤波器参数错误更新。因此在算法通过双向对讲检测模块识别到双方正在处于双讲状态时,就会冻结滤波器权重的更新,避免算法因噪声干扰而发散。 而如果双向对讲检测模块检测到当前只有一方在说话时,就可以恢复对自适应滤波器的权重更新,并且提升滤波器的回声抑制强度,彻底消除残留回声,从而达到更好的对讲效果。 结合下图可以更好的理解完整的回音消除算法的实现,及其各个子模块在完整的回音消除流程中所起的作用。 !image.png 自动噪声抑制ANS 我们常用的手机、消费类摄像头等产品的麦克风所采集的原始声音信号中往往包含了比较多的背景噪音,不仅影响用户录音和回放的使用体验,而且这些噪声数据还会降低音频编码的压缩效率,因此有必要对音频底噪进行抑制处理,这就是ANS(Auto Noise Suppression)功能的用武之地。 > 音频3A中对于环境噪音的控制,除了以上的自动噪声抑制ANS技术以外,还存在一个主动噪声消除ANC(Active Noise Cancellation)的环节。ANS与ANC的区别主要在于,前者是采用被动降噪的方式,分析环境噪声中频率成分针对性的对其滤波处理,尽可能保留清晰的语音部分,并提高音质;而后者常见于目前市场上非常流行的主动降噪耳机,针对环境中存在的噪音,对噪声声波的频率相位等分析,产生与噪声相位相反的声音信号来实现噪声抵消的效果。因此两者主要的区别就在于,ANS采用被动滤波的方式进行降噪,ANC则采用主动产生与噪声波形相位相反的方式来实现与噪声的相互抵消。一般而言,在消费类摄像头领域中,对于摄像头麦克风所采集声音数据的处理只会用到ANS,因此本文仅关注ANS方式的被动降噪处理机制。 音频背景噪音的来源 一般来说,对于模拟系统来讲,噪声可产生于系统的各个环节之中,因此模拟系统更容易受到干扰而产生噪声,而对于数字设备而言,抗干扰和噪声的性能要好很多,噪声窜入的薄弱环节总是设备中的A/D、D/A转换部分。在消费类电子领域,从成本等角度考虑,目前模拟音频元件和处理电路仍然是应用中的主流,所以该类型产品中因为各种干扰所造成的背景噪声等方面的声音质量劣化往往是产品开发中的难点所在。 总的来讲,消费类Camera产品的音频噪声主要来源于以下几个方面: - 设备电子元器件运行中的固有噪声。例如,电子元器件(电容、电阻等)在高频运行下本身就会产生细微的白噪声,这些噪声很容易被附近灵敏度高的麦克风拾取、放大后变得清晰可闻。 - 电磁干扰。电路在高频开关切换的运行过程中,尤其设备内部空间中有无线通信电路功能运行时,所产生的高频电磁波辐射干扰,通过音频传输线路窜入模拟音频信号之中,从而产生电磁干扰噪声。 - 电源干扰和接地回路噪声。电源电路中的尖峰、脉冲、浪涌等,通过电源线路窜入音频线路,以及不同电路部分的地线接地电位差导致电流环路,这些电源和接地方面的噪声会对音频的小模拟信号产生显著影响。 从以上音频噪声的来源总结可以看到,这些音频噪声大多数来源于硬件设备相关的设计和实现,其中部分噪声来源是可以优化的,例如通过优化电源接地设计,以及调整布局改善音频线路处理模块的电磁干扰状况,但是不可否认的是,消费类电子产品的结构和电子设计方面的局限性,会导致部分干扰因素无法从硬件层面完全解决。此时就要想办法利用Audio Codec中的音频增强功能甚至软件层面上的音频噪声过滤算法来提升音频采集的质量。 噪声的典型分类及其处理 从总体上,音频背景噪声可以分为两类:平稳噪声和瞬时噪声。 平稳噪声的最大特点就是其统计特性((如均值、方差、频谱分布)不随时间变化。 - 平稳噪声在时域上,振幅波动较小,呈现出规律性分布(如高斯分布)。 - 在频域方面,其频谱连续且稳定,例如白噪声会覆盖全频带。 - 比较典型的平稳噪声场景包括:高斯白噪声、电子设备的热噪声、量化噪声、持续的背景环境噪声(如空调声、风扇声等)。 瞬时噪声则是突发性强、持续时间短(毫秒至秒级)的噪声,其统计特性一般会随时间剧烈变化。 - 在时域方面,瞬时噪声一般具有高幅值、短时间冲击的特性。 - 在频域方面,其能量一般集中在高频或者特定的频段。 - 典型的瞬时噪声包括脉冲噪声(敲击键盘声音、敲门声、开关)、机械冲击声、不规则的电磁干扰(如静电放电)等。 总的来讲,对于平稳噪声的处理,因为其统计特性及其频谱分布相对比较稳定,容易识别,因此可以根据噪声的频谱特性和具体的信号特征进行针对性抑制,相对容易处理。但是瞬时噪声在时域上突发性很强,而且频谱基本上总是和正常语音的频谱混叠在一起,很难进行抑制,因此技术上对于瞬时噪声如何有效的抑制缺乏好的解决方案。 典型的音频降噪算法:谱减法,自适应LMS滤波 下图是典型的音频降噪算法的处理流程图。无论是传统的信号处理算法,还是基于AI增强处理的思路,最终都是要对音频噪声的类型进行检测,对噪声模型进行建模,然后基于噪声的模型针对性的进行处理。 !image.png 以下以两个常用的传统音频数字信号处理模式的音频降噪算法,即谱减法和自适应LMS滤波法对音频降噪的流程进行解释和说明。 谱减法 谱减法是一种基于频域的噪声抑制技术,也就是通过对噪声频谱的估计和建模,在频域中从包含噪音的混合信号中减去噪声成分的处理方式。既然是基于频域的处理方式,那么总体的处理流程中,就不可避免地要使用FFT把时域中采集的音频采用数据序列转换成频域的频谱序列 ,在频域中进行处理后,再通过IFFT把处理后的信号转换回时域。这样的处理流程自然就涉及到加大的运算量。 谱减法的整体工作原理是,在语音静默段(也就是仅含噪声的时间里面)估计和建立噪声功率谱模板,然后从包含噪声信号在内的混合信号的功率谱中减去噪声模板的功率谱,从而达到只保留语音成分的目的。 基于谱减法进行音频噪声消除的工作流程大致如下: - 首先对音频在时域中的采样数据序列分片处理,分片的时长固定并与要计算FFT的点数相一致,例如在16KHz音频采样,进行512点FFT计算的情况下,以32ms为单位进行音频数据的分片,一个音频分片单位就是一个音频帧。 - 对这个音频帧进行FFT计算,得到其频谱数据。 - 通过一个独立的VAD检测模块,检测当前是否处于语音静默期。在当前处于语音静默期的情况下,基于当前通过FFT计算得到的频谱序列对噪声功率谱模板进行动态的更新,使之与当前的噪声频谱状态相一致。 - 针对当前音频帧的频谱数据序列,在频域执行谱减运算:也就是把当前音频帧的频谱数据,与当前噪声功率谱模块的同频数据进行相减,在当前音频帧的频谱上减去噪声的功率谱,得到仅保留通话语音成分的频域数据。 - 最后再通过IFFT把处理过的数据从频域转换回到时域,重新合并为音频数据序列,恢复为时域的连续音频采样信号。 自适应LMS滤波算法 不同于在频域工作的谱减法,LMS滤波算法是一种在时域工作的自适应滤波器算法,通过动态比较音频实时采样的数据序列与滤波器的输出,通过其计算误差迭代调整滤波器系数,来实现最小化期望信号与滤波器输出之间的均方误差。 自适应LMS滤波器主要有两个参数: - 滤波器阶数L:用于建模的噪声长度,用于匹配噪声的时间相关性。L越大,意味着对于每个音频采样的过滤会参考越多的历史采样数据,计算量也越大。典型值为64-256之间。 - 补偿μ:主要用于控制噪声变化跟踪的收敛速度和稳定性,典型值为0.001-0.01之间。 自适应LMS滤波算法的执行流程: - 首先把当前要进行过滤的音频采样数据input,以及该采样之前的L-1个历史音频采样数据放在缓冲区History中。 - 基于History历史缓冲区数据以及LMS滤波器的当前参数W,计算LMS滤波器的输出值Y:累加历史采样数据History[i]与滤波器参数W[i]的乘积。 - 计算当前音频采样数据input与以上计算出来的LMS滤波器输出Y的差值E,这个差值E就是经过LMS自适应过滤的值。 - 基于以上差值E、历史缓冲区中的采样数据History[i]、LMS滤波器的μ对滤波器系统的运行参数W[i]进行更新。 - 返回以上计算出来的差值E。 以下是一段进行自适应LMS滤波算法处理的参考代码,可以配合理解以上的执行流程: LMS自适应滤波算法在应用中还经常会配合双麦克风来实现更好的降噪效果。主麦克风靠近人的嘴部用于采集人说话的声音,副麦克风远离嘴部并放在噪声源附近(如耳机外侧)用于采集环境噪声,工作的过程中用副麦克风采集的环境噪声数据训练和更新LMS滤波器的参数,然后对主麦克风的音频采样数据进行过滤。这种方式对对周期性噪声(发动机声、风扇声)的抑制效果极佳。 - 当然,如果要使用主副麦克风的话,以上LMS过滤算法的执行流程和对应的代码,就需要增加一个参考音频信号(来自副麦克风)的采样序列,History和LMS滤波器的输出及其参数更新基于副麦克风的采样数据进行计算,对于主麦克风采样数据的过滤则使用主麦克风的采样数据减去LMS滤波器的输出即可。 从以上的谱减法和自适应LMS滤波算法的实现比较来看,谱减法要涉及到频域和时域之间的两次相互转换,因此数据的计算量是比较大的,但是对于一帧音频数据进行一次运算;而自适应LMS滤波算法是直接在时域中进行运算,但是针对每个采样都需要执行两次与滤波器阶数相同的浮点数运算,分别用于计算滤波器输出和更新滤波器的运行参数,所以如果滤波器阶数比较高的话,计算量也会比较可观。但是总的来说,后者对于计算资源的要求更低,更适合用于资源有限的嵌入式系统。 音频自动增益控制AGC 在音频通话和视频会议中,音频自动增益控制AGC模块的主要作用: - 稳定音频信号的输出电平。无论麦克风采集信号的强弱(如用户离麦克风远近程度不同),尽可能保证音频采集模块的输出音量保持相对一致,不会偏大造成消波,也不会偏小导致听不清楚。 - 动态适应音量采集的变化环境。即可以应对环境中噪音变化、设备差异(如不同麦克风灵敏度)、用户说话习惯差异,动态自动调整语音信号的强度,使得其输出保持在一个合适的水平。 在常见的消费类电子产品的音频编解码器Audio Codec的处理链路之中,AGC模块一般工作中经过ADC转换之后的数字域处理环节,通过DSP算法来实现动态调整语音信号数据增益的功能,这样的处理方式灵活性更好,并且可以与其他DSP算法如降噪、AEC等协同优化。当然,一些低端的Audio Codec(例如TLV320AIC3101)内部缺少丰富的数字音频处理功能,那么AGC模块也可以直接在模拟域对PGA的增益进行调节来实现自动增益控制的效果。如下图是TLV310AIC3101的AGC处理链路。 !image.png 另外,我们只有在音频信号的输入链路的处理中,也就是对麦克风采集语音信号的处理中,才会用到AGC动态调整增益来实现语音信号强度基本保持一致的效果;在音频信号的输出链路,也就是对扬声器播放语音信号的处理链路之中,一般是不需要AGC的,此时对于输出语音信号的大小通常由用户手动设置或系统音量管理,无需自动增益。 AGC音频处理链路与VAD模块 一般而言,从麦克风采集到的声音信号中,除了真正的我们想要保留的本地端语音信号以外,不可避免地还会包含有一定的噪声、双向对接状态下的回声等,而AGC既然是通过调整音频采样数据的增益来维持音量的稳定性,那么其要稳定的对象势必是尽可能去除了噪声和回声的本地端语音信号,这就是在音频3A链路中,应该把AGC放在AEC和ANS之后进行处理的原因所在,因为我们要进行音量自动增益控制的对象并不包含回声和噪声。 !image.png 以上的噪声和回声分别在ANS和AEC环节处理后,在AGC自动增益控制的处理流程中,还包含一个VAD(语音活动检测)模块用于决定何时启动音频采样数据的增益调整。VAD模块的主要目标就是对麦克风连续采集的语音信号区分为语音段与非语音段(如静默或背景噪声),只在语音段才会激活AGC对音频数据的增益进行动态控制,非语音段抑制增益调整或维持固定增益,防止放大噪声,避免AGC的动态增益调整对非语音信号产生负面影响。 VAD模块对于语音段和非语音段的判决,以音频帧为单位,对音频帧内音频采样数据的均方根能量进行统计,与VAD模块维护的动态本地噪声的能量进行比较,若当前帧能量高于动态噪声本底+固定偏移(如+3dB),可判定为语音段候选,再结合状态机控制机制(如连续多帧高于阈值判定为语音段,连续多帧低于阈值判定为非语音段),给出语音段和非语音段的最终判决。 AGC的处理流程 1.音频采样数据的预处理 如上所述,AGC工作在Audio Codec的数字域,也就是对麦克风采集信号经过ADC以及其他音频处理模块处理后的数字采样序列中执行自动增益处理的流程。在该环节,首先对连续的音频采样数据进行切片处理,把连续的音频数据切分为固定采样数量的音频帧,后续的AGC处理是以音频帧为单位。 在AGC的分帧处理上,一般会采用50%重叠的方式进行处理,即当前音频帧的前50%采样与上一个音频帧的后50%采样相同,以平滑音频帧的边界效应,避免两个连续的音频帧增益不同造成的突变。 2.VAD检测 如上所述,VAD模块通过比较当前音频的帧能量与噪声本底的能量,判断当前音频帧处于语音段还是非语音段。 AGC的流程中一般仅在语音段触发AGC增益调整。 3. 自动增益的计算阶段 自动增益的计算阶段要依赖于AGC模块的几个提前预设值: - 目标电平,也就是对于AGC模块的调整而言,期望AGC模块的输出能够达到的目标音量水平。 - 时间常数,表示增益调整的时间参数,用于控制增益调节的平滑度,避免声音音量忽大忽小。 在计算增益时,对当前音频帧的能量和目标电平进行比较,当当前帧能量高于目标电平时,应适当减小增益以避免音频采样数据叠加增益后过载;而如果当前帧能量低于目标电平,则增大增益提高音量的幅度。 针对信号的动态范围比较小(信号幅度的差异不大)的应用场景,一般就可以简单的选择线性增益调整模式:即根据当前音频帧的能量与目标电平的差异确定一个增益的大小,对所有的音频采样进行相同的增益调整。 而针对复杂信号环境(如突发噪声、多频段干扰),则需采用非线性增益的调整策略: - 动态范围压缩(DRC)。DRC是一种通过非线性增益调整信号动态范围的技术,不仅可以用于语音信号的处理,也广泛应用于通信系统和智能设备领域中。DRC的工作大致依赖于三个参数:threshold_low、threshold_high和ratio。其工作逻辑大概为:当音频采样的幅度和能量低于threshold_low阈值的时候,DRC就会设置比较低的增益以抑制噪声;当音频采样的幅度和能量在threshold_low和threshold_high之间的时候,DRC按照前面计算出来的目标增益对该音频采样进行设置;而当音频采样的幅度和能量大于threshold_high时,就按照ratio的设置,对这个音频采样的增益进行适当压缩,以避免出现消顶的问题。 - 多频段增益控制。首先通过FFT运算执行频带分割,把信号分为低频、中频、高频子带;然后对每个子带单独计算并调整增益,这样可以避免整体调整所导致的频谱失衡问题,整个处理方式有点类似于EQ的工作流程了,对于不同频段设置不同的增益,或者抑制,或者增强。 此外,自动增益计算和调整的时间常数则主要包含两种类型的时间参数,用于实现增益和调整后语音幅度的平滑和自然变化: - 启动时间(Attack Time):当音频信号的幅度突增时,快速降低增益(典型值20~100ms),防止出现瞬时过载的问题。 - 释放时间(Release Time):当信号幅度减弱时,则缓慢恢复增益(典型值100~2000ms)。 4.增益的应用阶段及其后处理 AGC处理的最后阶段,就是把计算阶段所计算出来的增益应用到当前音频帧的所有采样数据上。根据前一阶段采用线性或者非线性的增益计算策略,针对性的对音频采样设置一致或者不一致的增益。 此外,经过以上AGC处理以后的音频采样序列数据,还需要限制其幅值(如-1dBFS),防止DAC或扬声器过载;以及与前面阶段的AEC、ANS等音频处理模块反馈联动,实现对音频数据的稳定控制。 参考资料 - 音频自动增益控制 AGC 解决的问题及原理解析 - 知乎 - 回声消除(AEC)原理、算法及实战——AEC背景介绍-CSDN博客 - 音视频处理三剑客之 AEC:回声产生原因及回声消除原理_ZEGO即构科技的技术博客_51CTO博客 - 音频3A算法初步学习 - 令音频工作人员头疼的“噪声”来源及解决办法

WiFi漫游协议详解-IEEE 802.11 kvr

本文对WiFi通信无线漫游的概念和传统的漫游流程进行了解释,并对支持WiFi无线漫游过程的三大协议IEEE 802.11K/V/R进行了详细总结,阐述各自解决的问题。 为了能够支持WiFi设备在不同AP之间快速高效的实现无缝漫游,IEEE 802.11组织专门定义了三个协议:802.11K,802.11V,802.11R协议,简称802.11 KVR协议。这三个协议完整的解决了在WiFi无线漫游过程中“什么时候启动漫游切换”,“漫游到哪个AP”和“怎样快速漫游”的问题。 什么是无线漫游? 既然IEEE 802.11KVR协议主要解决的就是WiFi的无线漫游的问题,那么就需要首先定义什么是无线漫游,以及对于一个wifi STA而言,在无线漫游的整个流程中,这个wifi STA与各个AP之间是如何交互的。 WiFi漫游,是指移动设备(如手机、笔记本电脑等)在不同WiFi接入点(也就是AP)之间自动切换,以保持其连接的无线网络通信业务的连续性,同时要求在这个切换的过程中尽量减少网络中断时间的过程,避免对通信业务造成明显的影响。简单地说,这就好比你在家里拿着手机从客厅走到卧室,客厅的WiFi信号变弱,此时手机就会自动连接到卧室里面WiFi信号更强的路由器上,让你的WiFi网络尽量保持连接在信号最强的WiFi接入点上,同时能在这个过程中持续不中断地、顺畅地使用网络。 众所周知,因为WiFi通信的无线特性,无线信号在空间中传输的衰减非常快,所以一个AP的覆盖范围非常有限,大部分情况下只有几十米。那么对于一个要在较大空间范围内移动的STA而言,要想能够在整个空间中保持与AP的连接,就需要不同的位置部署多个AP,当无线终端STA在移动到两个AP覆盖范围的临界区域时,STA与新的AP进行关联,并且与原有AP断开关联,在整个过程中保持不间断的网络连接。这就是无线漫游的大致执行过程。 !image.png 传统的无线漫游工作流程 传统的无线漫游的完整过程如下:当无线终端逐渐远离当前正在关联的AP时,终端会通过RSSI感知到信号强度逐渐下降,此时终端可通过探测当前环境中多信道的广播报文感知周边AP的存在,并开始与之进行交互。当无线终端的信号达到漫游切换AP的门限值之后,无线终端触发漫游,与新的AP进行关联、并与原有AP断开连接,从而完成AP的切换。 从以上过程中可以看到,传统模式下无线终端的漫游行为完全是由无线终端自己来进行控制的,由终端自己根据漫游切换的门限值,以及自主探测周围环境中所有WiFi热点的信号强度等参数来决定漫游触发机制和漫游判断逻辑。 传统无线漫游模式存在的问题 - 预切换流程中丢包严重。在无线终端测量并且决策进行无线漫游切换的过程中,需要在多信道之间频繁切换监听广播报文、并且同时与多个其他AP进行交互,整个过程从通信的角度看耗时很长,当前连接中所维持的正常业务数据通信中的丢包无法避免。 - 漫游触发不及时。传统无线漫游的触发条件就是其当前连接AP信号的门限值,当信号强度低于门限值,才会进行切换。但是如果尚未达到门限值时,当前环境中有其他通信质量更好的AP时,不会触发切换,这样就会导致终端仍然连接在信号已经较差的AP上继续使用,用户通信的使用体验不佳。 - 无线漫游选择的AP可能不是最佳的AP。按照以上无线漫游工作流程的描述,对于终端而言,选择新AP的比较条件就是其信号强度,但是实际上当前空间中存在的多个AP的工作负荷是不同的,因此选择新AP的时候还应该综合考虑这个AP的当前负荷情况,尽可能选择负荷较少的那个,而不是仅仅依赖于信号强度。 - 关联新AP的时间过长。这一点在AP开启较为复杂的安全认证模式例如802.1x的时候尤其如此。因为无线通信安全协议和流程方面的需求,对STA和AP之间进行完整的重新关联过程需要较长的时间,也就不可避免的导致在这段时间里面应用层的通信无法正常进行,劣化了用户体验。 IEEE针对无线漫游功能,所推出的802.11KVR快速漫游三协议,就是用来解决以上问题的。而能够支持802.11 K/V/R协议的新漫游模式与传统漫游模式的区别,就在于K/V/R漫游的完整流程和执行逻辑是由AC、AP和无线终端一起决定的。 802.11K 802.11K协议的全称是,无线局域网频谱资源测量协议(Radio Resource Measurement of Wireless LANs,简称为 RRM)。 802.11K协议的主要工作目标,就是要通过在AP和STA之间的相互通信,尝试收集并且共享当前无线环境中AP相关的所有信息(信号强度、信道负载、负荷状况、邻居AP列表等),辅助STA找到下次进行无线漫游的最优AP。 802.11K协议的工作流程如下: - 终端主动向AP请求邻居报告信息。当终端STA检测到当前所连接的AP信号质量下降(最典型的就是RSSI低于其预设门限阈值)时,STA向AP发送邻居报告请求(Neighbor Report Request)报文。 - 当AP收到以上请求后,生成邻居报告(Neighbor Report)并发回给终端。这个邻居报告中包含的信息如下:由AP自身所实时维护的邻近AP的静态信息(如SSID、BSSID、信道等),以及动态信息(各个AP的当前负载、信号强度(RSSI)、信道干扰等)。 - 由终端决策与切换。终端收到以上的邻居报告信息以后,根据邻居报告中的综合指标(如信号强度、负载、干扰等)进行评估,选择其中最优的AP作为漫游目标。 从802.11K的以上工作流程可以看到,802.11K协议重点解决的是在漫游过程中尽可能缩短STA的信道扫描时间,由STA当前所连接的AP来提供候选AP列表及其信道方面的信息,最终仍然是由STA为主来决定漫游的切换逻辑。 802.11V 802.11V协议的全称是,无线网络管理协议(Wireless Network Management,简称为WNM)。与802.11K协议类似,802.11V协议也是用于解决传统漫游流程中漫游目标选择不合理的问题,但是两者的区别主要是AP在整个决策机制的被动报告和主动引导上。 在802.11V协议的支持上,AP可以根据网络动态变化状况,主动向终端推荐最优漫游AP,而非仅依赖终端自主选择,通过这种AP主动调整的方式,可以避免网络中的单一AP过载,从而提升网络整体吞吐量。 在具体的工作机制上: - AP在通过802.11K协议获取邻居AP列表信息以后,可进一步利用802.11V的BTM(BSS Transition Management)机制,向终端发送包含各个候选AP的负载、干扰等参数的建议切换列表,以此来引导终端选择最佳AP。 - 此外,在正常的运行中,当AP检测到自身的通信负载过高时,可以通过定向漫游请求(如发送BSS Transition Query帧)将部分终端迁移至低负载的AP。 因此,与802.11K协议相比,802.11V协议在无线漫游流程中的决策主体变成了AP建议或主导,最终的目标是希望能够实现网络中各个AP的负载均衡,优化网络的整体性能。 802.11R 802.11R协议的主要功能是快速BSS切换(FT,Fast Basic Service Set Transition),其核心目标是不降低安全性能的情况下,通过尽量简化AP关联过程中的认证流程,并优化密钥管理机制,实现毫秒级的无缝漫游切换体验。因此,802.11R协议重点解决的问题是传统漫游中,因为STA与AP之间的安全认证流程冗长而导致的业务中断问题。 在支持WPA2/WPA3加密或802.1X认证的网络中,传统的漫游方式需重新执行完整的四次握手(4-Way Handshake)和密钥协商,因此这个完整的关联过程耗时可达数百毫秒甚至秒级。我在另外一篇笔记中详细的整理了WPA/WPA2的完整包交互流程:WiFi WPA/WPA2四步握手认证机制详解 | pavelhan.tech。 而802.11R协议则通过预认证和密钥缓存等机制,可以将漫游过程中STA与AP之间的重新关联切换时间压缩至50毫秒以内,这样就可以满足语音通话(VoWiFi)、AR/VR教学等实时业务需求。 在具体的实现上,802.11R实现快速关联切换时间的核心机制在于快速基本服务集转换(FT): - 预认证机制:STA终端在切换前通过当前AP与目标AP建立安全隧道,提前完成身份验证和密钥协商,这样就可以避免在切换时的重复认证流程。 - 密钥分层管理:802.11R协议引入PMK-R0(主密钥)和PMK-R1(派生密钥)两级密钥架构。PMK-R0由AC统一分发至AP群组,PMK-R1由各个AP基于PMK-R0来动态生成,从而实现跨AP的密钥共享。 - 四步交互优化:将传统漫游流程的8-10次报文交互简化为4次(Reassociation Request/Response + FT Action帧交换),减少了50%以上的信令开销。 总结 IEEE 802.11K,802.11V,802.11R三个协议一起针对WiFi设备在不同AP之间,如何实现快速高效的无缝漫游提供了较为完整的解决方案。其中: - 802.11K侧重于在AP上持续的收集当前无线环境下的静态信息,STA在需要切换AP时向当前正在连接的AP读取这些静态信息,然后决定应该切换到哪个新的AP上。在整个过程中,尽管AP要向STA提供一些漫游切换的相关环境信息,但总体而言,整个过程中仍然是由STA主导,AP处于被动的角色之中。 - 802.11V则更侧重于整体网络的动态负载均衡的管理,更多的是由AP主动发起或者引导STA切换到符合更小的新AP上,实现整体网络负载均衡的情况下,同时优化了整体网络的通信性能。在这个过程中,AP则从网络整体性能和规划的角度上,处于相对更加主动的角色中。 - 802.11R则在无线漫游切换AP过程中,对AP和STA之间的关联时间及其响应的密钥管理和认证机制进行优化,尽可能在保证安全认证的前提下,缩短STA切换到新AP上的关联时间,避免因为频繁切换导致影响用户层面上的通信体验。 参考资料 - 802.11K/V/R协议介绍 - 802.11k/v/r - lsgxeva - 博客园

图像传感器和镜头的CRA参数解释及其匹配

CRA:Chief Ray Angle,也就是主入射光线角度。对于镜头和Image Sensor而言,虽然两者都有CRA参数,但是其实际定义是明显不同的。 镜头的CRA参数 Lens的CRA参数则由镜头的设计本身决定,表示从镜心到成像面的光线角度分布模式。也就是就是镜头各个位置的主光线(过入瞳中心的光线)通过光学系统到达像面以后,与像面交点位置处的法线之间的夹角。 如下图所示,镜头每个位置的主入射光线角度是不同的,因此也就意味着,镜头的CRA参数个固定值,而是根据镜头视场角位置的不同而不同。例如下图从上到下展示了ZEMAX镜头库Double Gauss 28 Degree Field光学系统在14°、10°以及0°视场对应的主光线角度。 !image.png 从以上的镜头光线成像原理图上可以看出,镜头视场角越大,其对应的CRA的角度也越大。镜头中心位置即0度视场角,可以认为其CRA为0,越到镜头的边缘位置,CRA角度越大。我们通常意义上所说的镜头CRA角度,一般指的就是镜头边缘位置的CRA角度。 下图就是某个型号的镜头不同像面高度归一化后对应的CRA,镜头中心的CRA为0,镜头像面上不同高度的主光线角度不同,从而可以得到一条随像面高度变化的CRA曲线。 !image.png 图像传感器的CRA参数 相比于镜头的CRA参数,Lens的CRA参数要稍微复杂一点。 首先,Image Sensor的每个像素上都有一个微小的微透镜(Micro Lens),用于把镜头透入的光线尽可能多的聚焦到其下的光电二极管,实现光电转换。从下图可以看到,在光电二极管的上面增加Micro Lens,可以把更多的光线通过这个微型透镜折射到感光面上,从而提升光电二极管上的光通量,这就是增加Micro Lens的主要作用。 !image.png 按照以上微透镜的结构,如果从镜头透入的光线角度是垂直入射到微透镜上(例如图像传感器和镜头中心区域),那么这些光线就都可以通过微透镜进入像素阵列中的光吸收区域。但是,如果镜头投入的光线是以一定的角度斜射到到微透镜上(例如图像传感器和镜头的边缘位置),经过微透镜折射后,这些光线就会照射到像素内部的金属层上面(如上图的灰色区域),无法照射到中心吸收区域,这样就会使得光吸收区域光照强度下降,从而使边缘像素的灵敏度下降、光学串扰增加。如下图所示。 !image.png 以上问题就引出了Micro Lens的平移设计。 Micro Lens的平移设计 针对以上图像传感器边缘位置的像素感光问题(通过镜头的光线斜射到微透镜上),需要将微透镜平移一定的距离,这样可以使得光线经过折射以后,将未平移透镜之前损失的光线,重新聚焦到光吸收区域。如下图所示,把微透镜的位置向中心位置平移一定距离以后,以一定角度入射的光线会被微透镜折射到该像素的中心吸收区域。 !image.png 从以上镜头CRA的光学理论了解到,通过镜头进入图像传感器感光面的主入射光线角度,与镜头以及Sensor感光面的位置有关。一般的光学应用中,镜头和Sensor的光轴中心是重合的,那么在Sensor的中心位置,从镜头透入的主光线角度基本上是垂直照射在Sensor中心像素的微透镜上,因此大部分光线就可以被微透镜折射到中心吸收区域。 而从中心向四周延展的过程中,镜头透入光线的主光线角度随之越来越大,此时就可以通过对Sensor四周区域像素的微透镜进行以上平移的处理。平移的方向和幅度大小应该跟这个位置上镜头主入射光线的角度相关。这就是镜头与Sensor之间的CRA匹配的问题。 Sensor的CRA同样与像素所在位置相关 同样的逻辑,对于Image Sensor的设计而言,Sensor像素上微透镜的平移方向和幅度与其应接收到的透入光线的主光线角度匹配,这就是Sensor所要求的CRA参数。同样的,以Sensor中心为轴,Sensor中心的CRA为0,向四周延申的不同位置的Sensor区域的CRA不同,越到Sensor的边缘,CRA越大。 以下是GC4653的CRA数据,Sensor中心位置CRA为0,边缘位置接近10度。而一般而言,我们在Sensor数据手册上看到的CRA数据就是其边缘位置最大的CRA角度。 !image.png Sensor和镜头的CRA匹配选型 在进行Image Sensor和镜头的选项流程中,需要特别注意两者CRA的匹配,最理想的情况为镜头的CRA和sensor的CRA产生了完美的匹配,此时光接收效率最高。但实际应用中很难做到两者的完美匹配,此时应确保镜头CRA与Sensor CRA的差异控制在2-3度以内。 !image.png 如果Sensor和镜头的CRA不匹配的话,后期调试的图像效果会存在难以解决的问题: - 当Lens CRA > Sensor CRA时,镜头射入的光线会超出Sensor微透镜的矫正能力,导致后期图像出现相邻像素串扰(Color Shading)的问题,具体表现为图像边缘偏色。 - 当Lens CRA