所有文章

RT-DETR模型的自定义数据训练以及在RK3588上的适配

本文完整的记录了基于Ultralytics项目中的 RT-DETR 预训练模型在自定义数据集上进行的训练、ONNX导出,以及在RK3588硬件平台上的适配流程。 对于 RT-DETR 模型的实现而言,目前主要有两套独立的实现,即 Baidu 的 PaddleDetection 和 Ultralytics (YOLOv8/v11 生态)。因为我自己对于 Ultralytics/YOLO 较为熟悉,所以本文以 Ultralytics/YOLO 的实现进行试验和记录。 !image.png 1.自定义数据集的训练 因为这里使用的是Ultralytics项目下的RT-DETR实现,所以其训练过程、训练所生成的中间文件组织结构以及对训练数据的表格格式、目录结构等都与YOLO模型没有任何区别,只需要把之前在YOLO模型上训练所使用的数据集目录以及其 data.yaml 直接拷贝过来使用即可。所以如果对YOLO模型的训练流程有经验的话,在这个项目中训练RT-DETR就很容易上手了。可参考一文总结YOLOv5模型的自定义训练、测试与模型导出的全过程。 训练流程的代码也与Ultralytics项目下的其他YOLO模型类似,训练过程的代码如下: 训练流程基本上与普通的YOLO没有多大区别,有两点注意事项: - 在Windows环境中进行训练的话,训练代码需要包裹在__main__里面。这是因为在 Linux 系统中,进程的创建通常使用 fork,它会直接克隆当前的内存状态,不需要重新导入模块;而 Windows 不支持 fork,只能 spawn(冷启动一个新的 Python 解释器并导入以上脚本),这样的话,子进程启动时会再次运行一遍整个 train.py,从而又触发了一个新的训练请求,形成死循环。系统为了保护内存不被耗尽,主动抛出 RuntimeError来结束整个程序。而把训练代码包裹在__main__里面以后,只有主进程会执行train_model函数,子进程只负责执行训练过程,这样就不会进入反复新建子进程的死循环。 - batch和workers设置相对小一点,否则训练过程中很容易卡死。我一开始设置了batch为16,结果发现程序很容易卡死并报显存不足,所以如果出现训练过程稳定性问题的时候可以尝试把workers和batch这两个参数设置小一点。 以上的训练执行过程明显比使用相同数据集训练的YOLOv8模型耗时要长一些,应该是RT-DETR模型的计算复杂度要高于 YOLOv8。训练结果同样保存在runs目录下的best.pt文件。 2.Grid Sample算子的梦魇与Monkey Patch 在RK3588硬件上跑AI模型,大致的路径无非是: - 在PC端基于Pytorch等框架完成模型的训练; - 把训练的pt格式模型文件转换为更通用的ONNX格式; - 使用瑞芯微的RKNN-Toolkit2工具进行板端模型转换,把ONNX格式的模型转换为RKNN格式; - 在RK3588板端使用rknn-toolkit-lite2 工具(Python)或者C/C++语言把模型跑起来,在板端完成推理测试得到结果。 所以既然前面的训练过程已经完成,那么下一步自然就是进行ONNX格式的模型转换。但是这一步出现了一个由Grid Sample算子所导致的很大的问题: - Grid Sample算子是在Opset 16中被支持的,所以要能够导出ONNX成功,必须要设置Opset版本为16。设置为低于16的Opset版本,在导出ONNX模型这一步会报错。 - 如果把Opset设置为16的话,使用RKNN-Toolkit2工具(2.3.2版本)确实可以成功导出RKNN模型,但是这个模型放在RK3588板端跑的话,配合2.3.2版本的librknnrt.so文件会出现段错误的问题,原因是2.3.2版本的librknnrt.so文件根本不支持Grid Sample这个算子。也就是说,2.3.2版本的RKNN-Toolkit2工具认为Grid Sample算子是可以支持的,所以模型转换通过;而2.3.2版本的librknnrt.so文件根本就没有实现这个算子,所以板端运行会出现段错误的问题。瑞芯微的算法团队发布版本的时候不需要同步一下吗? 对于以上问题,当然是可以修改模型的结构,使用其他更通用的算子替换掉Grid Sample算子,然后重新训练,就可以从根本上解决这个问题。 但是,如果不考虑算法在板端的运行速度的话,其实是可以用一个简单的办法绕过这个算子:Monkey Patch。 所谓的Monkey Patch就是对于要导出ONNX的模型,在导出之前,先把某个板端不支持的算子用更常用的算子替换掉,这样在ONNX导出的时候,根本就看不到这个Grid Sample算子。具体来讲,就是在导出ONNX模型之前先用以下的代码进行Grid Sample算子(F.grid_sample)的替换: 3.ONNX模型的导出与RKNN转换 解决了以上的Grid Sample算子的问题,后续的ONNX导出就比较简单了。如如何理解ONNX模型导出的Opset版本?一文所总结的:在 Rockchip NPU的2.3.2版本的模型转换工具和板端NPU驱动上最高能够支持的Opset版本就是15。实际的部署过程中,最好的Opset版本设置应该是12或者13,避免版本过新有可能带来的稳定性和兼容性问题。 以上导出的ONNX格式的模型就是能够在RK3588上得到良好支持的OPSet=13的版本,接下来就是使用RKNN-Toolkit2工具进行板端模型转换,把ONNX格式的模型转换为RKNN格式: 4.板端推理运行 最后在RK3588板端使用rknn-toolkit-lite2 工具进行板端推理就可以看到运行的结果了。不过感觉推理执行的速度比较慢,可能跟手动替换了Grid Sample算子有关: !result_fp16_final.jpg

RT-DETR(RealTime DEtection TRansformer)网络架构与计算流程详细总结

本文详细介绍了Baidu提出的基于Transformer架构的图像目标检测模型RT-DETR的网络结构及其数据在网络中的计算流程。 RT-DETR模型简介 之前在DETR(DEtection TRansformer)网络架构与计算流程详细总结整理了基于Transformer的计算机视觉目标检测模型DETR的网络架构。 RT-DETR(Real-Time DEtection Transformer) 由百度团队开发,旨在解决传统 DETR 模型由于计算量巨大而无法满足实时场景需求的问题,RT-DETR 的出现标志着 Transformer 架构正式进入了工业级实时检测的战场,实际上 RT-DETR 是第一个真正实现实时性能的端到端 Transformer 检测器。 !image.png 与 YOLO 系列通过缩放逻辑提供不同规模的模型版本相同,RT-DETR模型也包含有两个版本:RT-DETR Large(32M参数)和RT-DETR Extra-Large(67M参数),前者性能稍差,但对于算力资源的需求也相对较低,适合在嵌入式边缘端部署。 与Facebook的DETR模型相同,RT-DETR模型的整体架构也由三个部分组成: - 由卷积网络组成的主干网络Backbone。 - 混合编码器。 - 解码器与检测头。 下文对上面三个部分的结构以及数据在其中的计算流程进行详细总结和解释。 Backbone 与DETR架构相同,在RT-DETR模型中的Backbone 是整个模型的特征提取引擎,负责从输入的原始图像中提取多尺度(标准的DETR模型只会提取最小分辨率的尺度特征)的特征图。 RT-DETR 官方实现的Backbone支持两种网络结构选项:HGNetv2 和 ResNet 系列。 - DETR的Backbone网络使用的也是相同的ResNet50和ResNet101。与DETR在主干网络只输出一个 1/32 尺度的特征图不同的是,RT-DETR 的主干网络会输出三个不同比例的特征图(如上图标记的 S3, S4, S5),分别对应原图尺寸的 1/8, 1/16, 1/32,这些特征图包含了从局部纹理到全局语义的不同层级信息。 - HGNetv2 (Higher-order Geometric Network)是百度自研的基于卷积神经网络的Backbone网络结构。它的核心思想是利用深度可分离卷积和特定的结构重参数化来提升精度,同时保持低延迟。 无论主干网络是使用 ResNet 还是 HGNetv2 ,输入的图像数据进入主干网络后,都会经历 5 个阶段(Stage 1-5),而每经过一个 Stage,特征图的长宽减半,通道数增加(这一点跟YOLO模型的主干网络也是相同的)。在输入图像分辨率为640x640的情况下,主干网络的输出为: - Stage 3 (S3): 输出特征图尺寸为 (C_3, 80, 80) —— 尺度为 1/8。 - Stage 4 (S4): 输出特征图尺寸为 (C_4, 40, 40) —— 尺度为 1/16。 - Stage 5 (S5): 输出特征图尺寸为 (C_5, 20, 20) —— 尺度为 1/32. 以上的C_3,C_4,C_5分别为三个阶段的通道数。 位置编码 在特征图的位置编码方面,RT-DETR 与 DETR的计算公式与流程相同,两者默认都使用正余弦位置编码 (Sine-Cosine Positional Encoding)。 对于特征图上的一个点 (x, y),其编码由不同频率的 sin 和 cos 函数生成,每个x和y各生成128维的向量,最后两个向量合并成一个长度为256的向量。具体的计算公式和流程可以参考DETR(DEtection TRansformer)网络架构与计算流程详细总结的相关部分。 但是需要注意的是,对于 RT-DETR Backbone部分输出的特征图而言,只会将 S5 特征图拉平(Flatten)成一个长序列,对这个最小分辨率的特征图上的所有像素点应用位置编码,并送入 Encoder 进行自注意力计算,因为 RT-DETR 认为只在最低分辨率特征图上做全局注意力计算最划算。S3和S4这两个分辨率较大的特征图会直接进入会直接进入混合编码器中的CCFM模块进行融合计算,不会参与全局注意力运算,如下所述。 混合编码器:AIFI_+CCFM 接下来就是 RT-DETR 的混合编码器部分,这是 RT-DETR 模型最核心的部分,这是这部分的设计能够使得模型兼顾 Transformer 的全局建模能力与实时性。 !image.png 如上图所示,混合编码器主要由两个核心模块组成:AIFI 和 CCFM。其中主干网络输出的 S5 特征图会送入AIFI模块进行处理,输出相同维度的 F5 特征图;而 F5 特征图与主干网络输出的S4,S5 这两个特征图一起送入CCFM模块进行更充分的特征融合。 但无论是在AIFI还是CCFM中进行处理之前,首先要对主干网络输出的 S3, S4, S5 特征图进行维度对齐。 维度对齐 为什么要进行维度对齐?这是因为,主干网络(如前述的 ResNet 或 HGNetv2)的不同阶段输出的通道数( C_3,C_4,C_5 )是不一致的: - S3 层输出 512 通道 - S4 层输出 1024 通道 - S5 层输出 2048 通道 在 RT-DETR 的架构中,为了让不同尺度的特征能够进行在后续的处理过程中相加融合以及送入统一维度的解码器,所有尺度的特征图最终都必须统一到相同的通道数(在 RT-DETR 中默认为 256)。 因此,这一步就是要把不同阶段输出的通道数都统一对齐到256。具体的对齐方式很简单,就是对 S3, S4, S5 的每一层分别应用一个 1 x 1 的卷积层,将其通道数从 512/1024/2048 统一压缩到 256 通道即可。经过这个1 x 1卷积后,就得到了三组通道数完全相同(即数据维度)、但空间分辨率不同的特征图。 AIFI AIFI:基于注意力的内尺度特征交互模块(Attention-based Intra-scale Feature Interaction)。该模块专门负责处理来自主干网络最深层、分辨率最低的特征图(也就是主干网络输出的最小分辨率特征图S5)。 > AIFI的核心本质上就是一个单层的 Transformer 编码器结构,这个编码器的输入只有Backbone的 S5 特征图。 为什么不需要对 S3 和 S4 进行自注意力计算? 这是因为,高分辨率特征图(S3, S4)包含的是局部细节,只有最低分辨率的特征图(S5)才包含了丰富的语义信息和全局上下文,因此全局相互作用只需要在低分辨率特征图中进行提取。最重要的是,编码器部分往往是计算瓶颈,如果对所有尺度的特征图进行全量的自注意力计算,复杂度随像素数量呈平方级增长,只在最低分辨率的 S5 特征图上进行自注意力计算,极大地降低了计算量(FLOPs),同时确保了模型能提取到图像中目标之间的全局关系。 经过以上的维度对齐操作后,输入到AIFI模块的 S5 特征图的维度是(256, 20, 20)。接下来的计算流程与标准的Vision Tranformer Encoder的计算流程相同,首先把这个(256, 20, 20) 特征图展平为长度400,维度为256的向量序列,然后给这个向量序列叠加位置编码信息。在特征图的位置编码方面,RT-DETR 与 DETR的计算公式与流程相同,两者默认都使用正余弦位置编码 (Sine-Cosine Positional Encoding)。具体的计算公式和流程可以参考DETR(DEtection TRansformer)网络架构与计算流程详细总结的相关部分。 然后就是标准化的多头注意力计算(默认为8个头)和前馈网络(FFN)的处理,最终经过 AIFI 处理后的 S5 特征序列会重新 Reshape 回 (256, 20, 20) 的张量形状,这就是上面架构图中的 F5 。 CCFM 在 RT-DETR 中,CCFM 舍弃了传统 DETR 计算复杂的自注意力计算,转而使用基于卷积神经网络的结构来处理不同分辨率特征图之间的融合,这一点与YOLO中使用的PANet的概念非常类似。既然是融合不同尺度的特征,自然也就包含两个融合路径: - Top-down (自上而下):将高层语义特征向下传递。 - Bottom-up (自下而上):将底层定位特征向上传递。 CCFM进行两个方向的特征融合操作的核心,在于其 Fusion 模块,如下图所示。 !image.png 假设输入Fusion模块的两个特征图分别为 F_high(来自高层的特征)和 F_low(低层的原始特征),且它们的通道数都已经对齐为 c,并且高层和低层特征图的分辨率已经通过之前的 Upsample或者Downsample 环节变换成了相同的分辨率。那么Fusion模块的工作流程如下: - 特征拼接 (Concatenate):将两个输入通道数为 c 的特征图在通道维度合并,输出一个通道数为 2c 的特征张量。 - 支路分流(Feature Splitting):拼接后的通道数为 2c 的特征图进入两个并行的支路,这两个支路都分别使用了一个 1 x 1 卷积处理,把通道数由 2c 压回到 c。 - 对于下支路而言,还要额外进行连续N个RepBlock的处理。这个RepBlock本质上就是一个3 x 3的卷积,通过连续的卷积层提取局部空间特征。 - 特征融合 (Element-wise Add):将上支路的基础特征图与下支路经过 RepBlock 处理后的增强特征图逐元素进行相加,通道数和分辨率均保持不变。 因此,对于每个Fusion模块而言,其输入的两个特征图的分辨率与通道数,与其输出的特征图的分辨率和通道数相同。 而对于整个CCFM模块而言,其输入可以分为三个部分:AIFI 模块对S5特征图处理后的输出F5,主干网络 S4 和 S3 特征图降维后的特征图。那么CCFM要实现的功能就是充分融合三个不同尺度的特征图中的信息,融合方式与YOLO的PANet大同小异,都是通过UpSample(下图中的黄色方块)和DownSample(下图中的蓝色方块)把不同尺度的特征图分辨率变换到相同的分辨率,然后再Fusion Block中进行融合操作。 !image.png 经过 CCFM 模块的双向充分融合,混合编码器部分最终从三个Fusion Block上输出了三个融合增强后的特征图: - P3 (80 x 80 x 256):包含丰富的细节,利于检测小目标。 - P4 (40 x 40 x 256):中等尺度。 - P5 (20 x 20 x 256):包含深层语义,利于检测大目标。 最后,再将这三个特征图进行展平操作,得到三个维度为256的序列化向量(6400 x 256,1600 x 256,400 x 256),然后把三个序列拼接(这就是CCFM模块的输出C的作用)起来,最终输出一个 8400 x 256 的长序列。 解码器与检测头 RT-DETR 最后部分负责基于前一阶段(即混合编码器部分的输出)输出的 8400 x 256 长序列(针对640x640分辨率的图像输入)进行目标检测的端到端检测,这部分主要由 IoU 感知查询选择模块 (IoU-aware Query Selection) 和 Transformer Decoder 组成。即混合编码器输出的数据并不会直接送入解码器,而是要先使用一个IoU 感知查询选择的模块对其进行筛选,从Encoder的输出里挑选最有希望的query作为Decoder的输入,以此来降低Decoder的运算量。 !image.png IoU 感知查询选择 IOU-aware Query Selection这个部分的整体工作逻辑就是:利用混合编码器的输出结果(8400 x 256),按预测 IoU 质量筛选出 Top-K 个(默认为前300个)最可能是真实目标的候选,作为 Decoder 的 queries。 首先,为了实现 IoU 的感知,RT-DETR 在 混合编码器的输出端设计了一个轻量级的检测头。这个检测头并不是一个深层次的网络,而是一个极其精简的线性预测层集合,以混合编码器输出的 8400x256(可以认为是三张特征图包含有8400个patch) 的长序列做为输入,通过3个独立的分支,分别使用各自的线性预测层,得到8400个Patch对应的分类预测、边界框预测以及IOU预测结果。 三个分支都把输入的256维特征映射为其输出的目标维度: - 分类分支:输出维度 8400 x num_classes,预测每个点属于各个类别的置信度。 - 回归分支:输出维度 8400 x 4,预测每个点相对于其坐标原点的偏移量 (x, y, w, h)。 - IoU 分支: 输出维度 8400 x 1,预测该点对应的预测边界框与潜在检测目标真值框之间的IoU 质量分。 有了以上三个分支的输出数据后,就可以按照以下公式构建出来8400个质量分数: 前者为当前Patch在分类分支上置信度数值最大的那个类别对应的置信度数值,后者则是这个Patch的IoU质量分。通过这个质量分数来进行评估,只有当模型既确定它是这个类别,又确信自己能框准它时,这个点才会被选入后续的 Decoder。 然后对以上的8400个质量分数进行 Top-K 降序排序,然后选出质量分数最高的300个做为该阶段的输出。因此,通过以上流程,就相当于从8400个候选框中,以质量分数作为标准,选出来其中的300个作为后续进一步筛选的基础,而不是把所有的8400个候选框全部作为备选框,这样就大幅度的降低了Deocder阶段的计算量。 最终 IoU-aware Query Selection 模块的输出不是一个张量,而是一组结构化信息。对于以上的 Tok-k 计算流程中选择出来的每个 query,在这一环节都输出两部分信息: - Content Query:每个特征点对应的 256 维特征,这些向量被用作下一阶段Decoder中自注意力模块的输入,维度为 300 x 256。 - Reference Points:由前述回归分支所输出的坐标位置 (x, y, w, h),维度为 300 x 4。 Decoder Decoder部分的结构仍然是标准的:自注意力模块+交叉注意力模块+前馈模块。只不过为了兼顾计算速度,这里的交叉注意力模块并没有采用标准 Transformer 那种全量的 Cross-Attention(即每个 Query 都要和 8400 个点算一遍),而是采用了Deformable Attention(可变形注意力)。 与标准的Transformer Decoder的架构类似,RT-DETR模型的Decoder部分的网络结构包含有6层相同网络模块的堆叠结构,每一层的包含有独立的自注意力计算模块+跨尺度采样交叉注意力模块+前馈网络FFN模块+由线性层构成的分类头和回归头。 1. 自注意力模块 这一步的作用是让 300 个 Content Query 之间充分地进行信息交换。 这个自注意力模块以 300 x 256 维度的Content Query作为初始输入,以Reference Points作为参考生成对应的位置编码,与Content Query相加后,作为自注意力计算模块的输入,自注意力模块的计算流程可以参考一文彻底搞懂Transformer模型的Encoder结构与计算流程。 自注意力模块的输出为经过充分信息交换后的Content Query,维度仍然为 300 x 256。 1. 跨尺度采样交叉注意力模块 交叉注意力模块的输入数据包含三个部分: - 前面的自注意力模块输出的更新后的Content Query,维度为 300 x 256。 - 300个预测框的位置坐标Reference Points,来自于IOU-aware Query Selection模块的输出(第一个decoder层)或者来自于前一个decoder模块回归头的更新坐标(后面的五个decoder层)。 - Encoder Features,混合编码器部分输出的全局特征序列(包含 S3, S4, S5 三个尺度),其维度为 8400 x 256。 需要注意的是,RT-DETR模型Decoder部分中的交叉注意力模块,不同于标准的Decoder中的交叉注意力运算,它所进行的交叉注意力运算不会对Encoder Features所包含的所有的 8400 x 256 维度的数据做矩阵乘法,而是以 300 个预测框的中心作为参考点,在这个参考点的附近有选择地进行采样,在每个参考点附近的采样点对其进行交叉注意力运算(也就是所谓的Deformable Attention,可变形注意力运算),这样就大大减少了交叉注意力的运算复杂度,从而实现了计算效率与精度之间的平衡。 这个可变形交叉注意力模块的设计,是RT-DETR模型能够实现实时性能的关键之一。 无论如何,经过了以上这个可变形交叉注意力模块的计算之后,其输出数据为融合了多尺度局部图像特征后的新的 Content Query 向量,维度仍然是 300 x 256。 1. 前馈网络FFN模块 该模块的目的是增强特征的非线性表达能力,类似于 CNN 中的全连接层。 与标准的Encoder以及Decoder模块相同,FFN 模块的输入数据为上一步交叉注意力计算输出的 Content Queries (维度为[300, 256])。 FFN模块的计算流程是标准的 Linear -> ReLU -> Linear 结构,通常中间维度会扩大(如 1024 或 2048)。而输出数据则是深度映射后的 Content Queries,维度仍然保持为 [300, 256]。 1. 分类头/回归头 输入数据均为前一层前馈网络模块输出的Content Query,维度为 300 x 256。分布通过独立的线性层映射分类预测值和坐标偏移量。 - 分类头:经过线性层映射到目标类别数量 num_classes。输出数据为分类概率 Pred Logits,维度为 [300, num_classes]。 - 回归头:同样通过线性层预测一个预测框坐标的偏移量 ,然后将这个坐标偏移量更新于本层的输入 Reference Points上,下一个模块使用的 Reference Points就是经过偏移量更新后的值。 模型输出 RT-DETR模型最终的输出,就是Decoder部分最后一个Decoder block的分类头和回归头,这两个独立的检测头分布输出300个检测框的分类判断值和检测框的坐标值。 - 分类头输出分类预测分数 (Pred Logits): 维度为 [300, num_classes]。每一个 Query 都会对所有类别给出一个得分。 - 回归头输出预测坐标 (Pred Boxes): 维度为 [300, 4]。这是经过Decoder部分 6 层Decoder Block的不断修正后的最终坐标 (x, y, w, h)。 接下来如何从这300个检测框得到最终呈现给检测结果呢? 首先,对以上分类头输出的 Pred Logits 进行 Sigmoid 处理,将其转化为 0 到 1 之间的概率值。然后以每个 Query 在所有类别中的最大得分作为该框的置信度。此时,就拥有了 300 个带类标和置信度的预测框。 然后进行阈值过滤 。通常会设置一个置信度阈值 (例如 0.5)。系统会直接丢弃所有得分低于该阈值的框,并输出置信度大于该阈值的检测框。 - 这一部分的关键点在于,由于 Transformer 的自注意力机制已经在内部完成了去重(抑制了指向同一目标的重复 Query),所以理论上剩下的检测框通常就是互不重叠的高质量检测结果。 参考资料 - RT-DETR: Paper Explanation and Inference - lyuwenyu/RT-DETR: [CVPR 2024] Official RT-DETR (RTDETR paddle pytorch), Real-Time DEtection TRansformer, DETRs Beat YOLOs on Real-time Object Detection. 🔥 🔥 🔥

详细总结EddyStone BLE广播包协议规范

EddyStone简介 Eddystone 是专门针对蓝牙低功耗(BLE)广播包定义的数据格式。 它是由 Google 在 2015 年推出的开源 Beacon 协议,旨在提供比苹果 iBeacon 更开放、功能更丰富的广播标准。 !image.png 在蓝牙的协议栈中,Eddystone 运行在 GAP(Generic Access Profile) 层之上。它利用了BLE 广播包中 31 字节的载荷(Payload)进行通信,实际上也就是基于这个31字节的数据长度所规定的一套标准化的数据结构 。 - 因为是工作在 BLE 的广播模式之下,因此进行通信的设备双方不需要建立配对连接。在通信过程中,广播设备只是单向地定时向外发射信号,任何在其覆盖范围内的扫描设备负责监听这些信息 。 - 不同于 iBeacon 在广播通信的过程中只发出一组 ID,Eddystone 允许设备轮流广播不同类型的广播帧(Frames),这是 EddyStone的一大特色。 EddyStone的四种主要的帧类型 UID(身份与状态帧) 这种帧类型主要用于标识设备的身份 。其帧结构如下: - Namespace ID (10 Bytes):类似于设备的姓名,用于标识某个特定的公司或者产品类型/系列 。所以一般情况下某个公司生产的同一型号的设备的Namespace ID都是相同的。 - Instance ID (6 Bytes):类似于设备的名称,用于标识具体的某台设备。即使是同一型号的设备,其Instance ID也是不同的,用于区分不同的设备。 - 自定义空间:这部分空间可自定义其具体的含义 。 TLM(遥测帧) 这个帧一般用于广播设备的状态,如电量、温度、开机时间等,是设备的状态帧。 URL(网页跳转帧) 这个帧类型是 Eddystone 最著名的功能。它用于直接广播一个压缩后的 URL 地址(如 https://goo.gl/...)。 其实这也是Beacon类型的蓝牙设备的典型应用场景: - 手机靠近某个公交站,会自动弹出附近的路线信息网页,无需安装 App。 - 手机靠近超市的某个区域,会收到一个网页URL,其中包含有这个区域对应商品的促销信息。 EID(安全帧) 这个帧类型主要用于对 ID 进行加密处理,每隔一段时间变换一次 ID,防止设备被他人非法追踪。 EddyStone的包结构 首先总结一下典型的BLE广播包的整体包结构。在空气中传输的原始数据包长度通常为 8 到 47 字节: !affce66d-09d8-4bec-b2a5-72a72a1fe8d1.png EddyStone的所有数据结构就包含在其中的Payload中。Payload部分的长度为6-37字节,其中包含了设备的 MAC 地址和实际的广播数据 (ADV Data) ,除去 6 字节的设备地址以外,剩下的 31 字节 就是用户可定义的广播数据区。它由多个 AD Structure 组成(如上图的AD 0到AD N),每个AD Structure结构的格式为:[长度] [类型] [数据]。 对于 Eddystone 协议而言,其通常包含以下三个标准的 AD Structure: - Flags :对于所有的EddyStone设备而言,这部分是相同的 用于告知扫描者该设备的物理特性(如:仅限发现模式、不支持经典蓝牙)。 • 字节流:02 01 06 ◦ 02: 后面数据的长度。 ◦ 01: 类型(Flags)。 ◦ 06: 数值(代表 LE General Discoverable Mode + BR/EDR Not Supported)。 - 16-bit Service UUID:对于所有的EddyStone设备而言,这部分是相同的 用于告知扫描者这是一个符合 Eddystone 标准的设备。 • 字节流:03 03 AA FE ◦ 03: 后面数据的长度。 ◦ 03: 类型(16-bit Service UUID)。 ◦ AA FE: Eddystone 的官方注册 UUID (0xFEAA),采用小端序存储,所有的EddyStone设备都必须包含这个UUID。 - Service Data (核心载荷):这是广播协议存放实际数据的地方。 • 字节流示例 (UID Frame):17 16 AA FE 00 xx xx xx ◦ 0-17: 长度可根据包内容长度变换,最长23 字节。 ◦ 16: 类型为Service Data,即0x16,固定不变。 ◦ AA FE: UUID 0xFEAA,固定不变。 ◦ 00/10/20/30: Eddystone Frame Type。 ◦ 后续为不同的帧数据 因此,可以简单认为:对于所有标准的 Eddystone 设备包和广播包,前 7 个字节(02 01 06 03 03 AA FE)是完全相同且固定的。各个设备真正的差异化竞争和业务逻辑的区分,全部集中在类型为 16 之后的 Service Data 载荷中。 EddyStone的帧类型与Service Data结构的对应 以上提到,对于所有的EddyStone设备的广播包而言,其广播包的Payload部分包含有三个AD Structure,其中前两个AD Structure的数据内容完全固定并且相同,只有最后一个AD Structure即Service Data部分真正用于承载差异化的数据。 Service Data部分的第一个字节为Length,其对应了这个部分的长度有多长;第二个字节固定为0x16,即代表这个部分为Service Data类型,因此是固定不变的;第3-4个字节同样为固定的UUID 0xFEAA。所以 EddyStone 广播包真正的应用层差异主要体现在Service Data第五个字节及其以后的部分。 以下安装不同的EddyStone帧类型对其帧结构进行总结,后续的解释以Service Data的第五个字节位置(即EddyStone Frame Time)为起始点。 UID帧(身份与状态帧) - Byte 0,Frame Type,1 Byte,固定为 0x00 - Byte 1,TX Power,1 Byte,0米处的校准接收信号强度 (RSSI),范围为 -100 到 +20 dBm - Byte 2-11,Namespace ID,10 Bytes,用于区分不同的范围/组织/厂商/产品类型 - Byte 12-17,Instance ID,6 Bytes,用于标识具体的设备即设备ID,每个设备都不同 - Byte 18-19,Reserved,2 Bytes,官方规范预留,通常填充0 URL帧(网页跳转帧) - Byte 0,Frame Type,1 Byte,固定为 0x10 - Byte 1,TX Power,1 Byte,0米处的校准 RSSI 值 - Byte 2,URL Scheme,1 Byte,前缀编码(如0x00代表http://www. ,0x03代表https:// 。 - Byte 3-19,Encoded URL,1-17 Bytes,经过压缩后的网址内容(非 ASCII,有专门的后缀压缩码) TLM (遥测帧) - Byte 0,Frame Type,1 Byte,固定为 0x20 - Byte 1,TLM Version,1 Byte,协议版本(目前通常为0) - Byte 2-3,VBATT,2 Bytes,电池电压,单位为毫伏 (mV) - Byte 4-5,Temperature,2 Bytes,设备温度(8.8 固定小数点格式,单位摄氏度) - Byte 6-9,ADV_CNT,4 Bytes,自重启以来发送的广播包总数 - Byte 10-13,SEC_CNT,4 Bytes,自重启以来的运行时间(单位 0.1 秒) EID(安全帧) - Byte 0,Frame Type,1 Byte,固定为 0x30 - Byte 1,TX Power,1 Byte0米处的校准 RSSI 值 - Byte 2-9,Ephemeral ID,8 Bytes,随时间变化的 8 字节加密标识符 补充1:以上提到的8.8固定小数点格式是什么意思? 8.8 固定小数点格式是一种用 2 个字节(16 位)来表示带小数点的数值的高效方法。它将 16 位划分为两个相等的部分:高 8 位表示整数,低 8 位表示小数。 一个 16 位的字段被拆分为: • 高 8 位 (Most Significant Byte, MSB):有符号整数部分。采用补码形式(即大于0x80为负数,小于0x80为正数),范围是 -128 到 +127。 • 低 8 位 (Least Significant Byte, LSB):无符号分数部分。它代表 256 分之几(即 n/256)。 补充2:以上提到的Encoded URL的压缩流程是什么? 在 Eddystone-URL 规范中,为了在蓝牙广播有限的 31 字节 空间内塞入尽可能长的网址,Google 并没有使用复杂的通用压缩算法,而是采用了一种基于查表法的字节替换技术。这种方法分为两部分:前缀压缩 和 后缀/常用词压缩。 前缀压缩。用 1 个字节代表网址的开头,常见的映射关系如下: - 0x00:http://www. - 0x01: https://www. - 0x02: http:// - 0x03: https:// 后缀压缩。其他普通的 ASCII 字符(如 a-z, 0-9, -)直接按原样存储。但对于顶级域名(TLD)和常用路径符号,规范定义了一套特殊的单字节替换码(通常在 0x00 到 0x0D 范围内): - 0x00:.com/ - 0x01:.org/ - 0x02:.edu/ - 0x03:.net/ - 0x07:.com - 0x08:.org - 0x0B:.net

如何理解ONNX模型导出的Opset版本?

在把PC或服务器上基于Pytorch训练好的模型导出为ONNX格式的时候,需要在导出的脚本中设置opset的版本,不同的opset版本设置会对后续的NPU适配过程造成很大的影响,那么这个opset的版本就是是个什么概念?不同的opset版本究竟有哪些差异,以及这些差异对后续的NPU适配过程如何造成影响?本文尝试回答以上问题。 从总的概念上来讲,在嵌入式AI模型的部署流程中,Opset(即Operator Set,算子集) 是 ONNX(Open Neural Network Exchange)所定义的算子集合的版本标准。在将PC上训练好的 PyTorch(.pt/.pth)模型导出为 ONNX 格式时,设置正确的 opset 版本至关重要,因为它直接决定了导出的模型能否在后续的NPU板端模型转换工具成功转换,并高效运行在 NPU 上 。 !image.png 要理解不同的Opset对后续的NPU模型适配和部署流程会造成什么样的影响,首先要深入的理解究竟什么是算子和什么是算子集。 什么是算子(Operator/Op)和算子集(Operator Set)? 在 AI 模型的语境之中,算子(Operator)是模型里的最小执行单元。一个典型的深度学习模型(例如 YOLO 模型),其本质就是一个巨大的有向无环计算图(DAG)。这个图由无数个节点组成,而每个节点就是一个算子。 这就有点类似于我们在写软件程序的时候,如果要实现某项功能,通常会把这个功能封装为一个函数来进行调用。那么_算子就类似于一个可被应用程序调用的标准库函数,对这个算子(函数)的调用,就相当于是在NPU上执行一次最小粒度的标准化操作。_ 常见的算子包括: - Conv (卷积):提取特征,相当于嵌入式里的滤波算法。 - ReLU (激活函数):实际上就是一个简单的 if(x

PANNs音频分类模型在RK3588平台上的适配全记录

之前在PANNs音频分类模型的预研信息汇总 一文中,对PANNs这个音频分类模型的信息进行了汇总,在PC端做了一些简单的测试。 在基于ECS50数据集对PANNs语音分类模型进行的训练流程 一文中,记录了基于PANNs模型的CNN14架构对ECS50数据集进行训练的完整流程。 本文进一步尝试把前一阶段基于ECS50数据集训练好的模型移植到瑞芯微RK3588平台上,并且使用瑞芯微的rknn-toolkit-lite2工具使用python脚本实现板端推理。 模型导出为onnx文件 经过基于ECS50数据集对PANNs语音分类模型进行的训练流程 一文所描述的模型训练流程,经过训练后就得到了使用ECS50数据集训练后的模型文件 cnn14_esc50_best.pth 。 下一步调用以下脚本把这个模型文件导出为onnx格式,导出文件为cnn14_esc50_rk3588.onnx。 > 💡 > PANNs这个模型实际上是把音频数据先转换为频谱图,然后在基于标注的CNN网络做分类的训练。那么对于这种模型,前面的音频数据转频谱图部分的运算是不适合在NPU上运算的。所以合理的做法是:音频数据采集完成后,在CPU上完成音频数据转频谱图的计算,然后再把频谱图在NPU上进行CNN分类识别。在这种情况下,要适配到NPU上的模型应该就只包含CNN网络部分,不包含前面的频谱图计算部分。每次送入CNN网络部分进行识别的频谱图应该是1s的音频数据(对应101个音频帧)所生成的频谱图,频谱图的mel刻度范围为64,因此频谱图的分辨率为101x64。 模型转换校准文件的准备 在执行模型转换的过程中,需要从之前的训练集(也就是ECS50数据集)中抽取出来20个音频文件,作为模型转换过程中的校准数据集。 从ECS50数据集中随机抽取其中的20个音频文件,放入dataset_ecs50子目录中,然后使用一个文本文件dataset_ecs50.txt记录所有校准音频文件的路径名称: 使用以下脚本把其中的校准音频文件转换为npy格式保存在dataset_ecs50_npy目录中,并自动修改dataset_ecs50.txt为生成的npy文件的路径名称:因为后续模型转换过程中只能使用npy格式的音频文件进行校准: 此时模型转换的校准文件就准备好了: - dataset_ecs50_npy :其中包含有所有20个音频校准文件的npy格式文件。 - dataset_ecs50.txt :其中包含有所有npy校准文件的路径名称。 模型转换 执行以下代码把PC端导出的ONNX模型文件转换为可以在RK3588上运行的rknn模型,转换完成后得到的端侧模型文件cnn14_esc50.rknn。 板端rknn-toolkit-lite2环境的安装 我的RK3588上的系统是debian,其中已经包含了基本的python 3.12环境。要能够使用rknn-toolkit-lite2在板端直接使用python执行推理代码,还需要安装板端的rknn-toolkit-lite2环境。 先查询RK3588板端的Python版本,然后根据版本信息在rknn-toolkit2-master/rknn-toolkit-lite2/packages 路径中找到与之对应的rknn-toolkit-lite2板端安装包。因为我的板端Python环境是3.12.4,所以此处应该选择的安装包是rknn_toolkit_lite2-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl 。 使用以下命令安装rknn-toolkit-lite2的板端环境支持包以及推理代码所需要的python包numpy和librosa。 为什么要增加--break-system-packages选项? 因为板子上运行的Debian系统引入了PEP 668规范,系统为了防止直接使用 pip 安装的包与系统自带的 apt 包管理工具发生冲突导致系统崩溃,锁定了全局 Python 环境,否则会报以下错误,系统推荐在独立的虚拟环境中安装python包。 但是因为这里只是做一个测试,所以就不用费劲去折腾虚拟环境了,直接使用--break-system-packages选项忽略以上错误即可。 板端推理 至此板端的rknn-toolkit-lite2环境以及板端的模型(cnn14_esc50.rknn)都已经准备好了,接下来就是写一段基于rknn-toolkit-lite2的板端推理代码来进行测试: > 💡 此处需要注意,因为ESC50数据集中的wav文件的长度都是5s,而默认情况下模型的输入为1s音频文件的频谱图,所以在板端对ESC50数据集中的wav文件进行推理的时候,就需要使用滑动窗口(滑动重叠的时间长度为0.5s)的方式把5s的wav文件分割为多段1s的音频,然后针对每个1s音频计算频谱图、推理得到结果,再把所有的结果合并在一起得到最后的识别结果。 以上代码对1-977-A-39.wav 这个音频文件的推理结果如下: 检测结果的类别ID是39,查询ECS50的ID对应表,39对应于Clock Tick,与音频文件中所包含的声音相吻合。 !image.png

在Android Flutter项目中flutter_litert插件运行TFLite模型

在之前的一篇笔记(总结Android Flutter项目中YOLO模型的执行流程)中,我详细总结了基于flutter_vision插件在一个Android Flutter项目中把Ultralytics项目中导出的YOLOv8模型运行起来的完整流程。 但是Flutter_Vision插件的使用有诸多限制,例如只能运行YOLO的几个模型,并且只能配合Ultralytics的export接口导出的TFLite模型文件来使用。那么如果想要在Android上跑YOLO之外的模型,就没有办法是用Flutter_Vision插件了。 因此,要把一个自定义的模型转换为TFLite格式并且能够在Android APP中跑起来,就需要一个更灵活的应对方式。而这个过程分为两个步骤: - 把自定义模型的pt文件转换为TFLite格式。这部分在如何把PT格式的模型转换为TFLite/LiteRT模型?已有详细总结。 - 把以上转换的TFLite格式的模型文件在Android Flutter项目中跑起来,避免受到FlutterVision插件所带来的限制。这一点就是本文要解决的问题。 flutter_litert vs tflite_flutter 在Android Flutter项目中跑 TFLite模型,目前比较常用的是flutter_litert和tflite_flutter这两个插件。 - tflite_flutter:这个插件是 Flutter 社区中最经典、最老牌的 TFLite 插件,目前搜到的信息大部分都是使用这个插件在Android上跑AI模型。它通过 Dart FFI 直接封装了 C 语言的 TFLite 解释器。目前仍有社区维护版(如tflite_flutter_plus),但配置分散,社区维护分散,在新的项目中不建议使用。 - flutter_litert:这个插件是为了响应 Google 对 TensorFlow Lite 品牌的正式更名和升级 (LiteRT) 而推出的现代继任者。它也是目前推荐的、更符合 Google 最新人工智能路线图的插件,其本质上是 tflite_flutter 的演进版本,旨在解决旧插件的维护痛点。因此强烈建议在新的项目中使用flutter_litert这个插件。 !image.png flutter_litert 这个插件本质上就是一个工作在 Flutter(Dart 语言)层级的硬件抽象层 (HAL) Wrapper。这个插件总的特点是: - 底层机制: 该插件并不是用 Dart 重写模型的推理逻辑,而是通过 Dart FFI (Foreign Function Interface) 接口极其轻量地绑定了 Google 已经预编译好的 C++ LiteRT Native 二进制库(.so 文件),这样的好处当然是可以利用底层的C/C++语言的执行达到最高的效率。 - 零拷贝理念: 其核心思想是在内存里开辟一段连续的 Buffer(如 Float32List),使用Dart 语言往里填数据,C++ 引擎则会直接读取这段物理内存进行模型的矩阵运算,避免了跨语言传值所带来的性能损耗。 flutter_litert 插件运行的整体架构与数据流程 下图是在Android Flutter项目中使用flutter_litert 插件运行AI模型的整体架构与数据流程图: !litert_architecture_high_res.jpg 从以上的整体流程图中,模型的前向推理过程中的数据流动可以大体分为四个层次和阶段。 1.采集与调度层:Flutter Dart Zone (User Space) 这部分运行在 Flutter 的 Dart 虚拟机中,相当于系统的用户态应用程序。它的首要任务是协调前端 UI 显示与模型检测结果的时序、图像预处理这两部分工作。 当然在模型能够对图像进行检测和识别之前,首先要把模型加载到内存中,模型及其label文件一般整合在app的assets中,因此采用assets对应的API加载模型: 对于手机的摄像头而言,在打开的情况下,其以 30/60fps 的帧率向主线程推送高分辨率的 YUV420 裸流数据并显示在屏幕上。而一般情况下,手机所具备的算力无法支持以这么高的算力实现对每一帧图像的检测,可能1秒钟只能完成10帧图像的处理,这样的情况下,Camera采集图像与Model处理图像这两部分的工作就不是同步的。因此需要分开两个线程同时进行处理,主线程按照Camera的采集帧率刷新显示实时图像,而针对模型对图像的处理而言,就必须开辟一个独立的 Isolate(类似后台高优先级线程),执行 YUV 转 RGB、图像缩放等预处理后送入litert_flutter插件进行识别和检测 。 众所周知,图像在送入YOLO等模型中进行前向推理之前,还必须要进行一系列的预处理流程,以符合模型对输入图像数据在各个维度上的要求。因此,对于每一帧从摄像头中取出的YUV420数据,其分辨率可能是1280x720,那么要进行的预处理过程就至少要包括:分辨率缩放到640x640、YUV转RGB、图像归一化到0-1.0之间、图像数据排列格式调整为NCHW(Pytorch模型的数据排列格式默认为NHWC)等。 以上经过预处理以后,输入图像就准备好了,下一步就是调用flutter_litert插件的API接口进行处理了: 2.跨语言函数接口与内存零拷贝机制 在 Interpreter 对输入图像数据所进行的前向推理的执行流程中,图像数据要从APP应用层的Dart语言传递到模型推理代码的C++语言,而且为了在两种语言之间高效的传递大片的图像内存数据,最好能够直接通过类似内存指针的方式来实现零拷贝的图像内存传递机制,这就是 Flutter 框架为了提升应用程序性能的 Dart FFI (Foreign Function Interface)的用武之地了,它可以允许 Dart 直接与底层 C/C++ 共享同一块物理内存的指针,从而避免在调用者和被调用者之间反复拷贝大片的内存。 如上图所示,无论是在 Dart 语言所写的 interpreter 的函数调用,还是最终 interpreter 把模型的运行结果从底层的C++语言返回给用户态的Dart语言层,都采用了 Dart FFI所提供的内存零拷贝机制。 3.底层算力的执行:Native C++ Zone 这部分是模型使用C/C++语言真正执行前向推理的部分。 在如何理解TFLite/LiteRT的核心概念和框架流程? 一文中,已经详细总结了在Android的TFLite/LiteRT框架下运行AI模型的关键概念和执行流程,Google 使用 C++ 语言写了一个 LiteRT Runtime运行时环境(实际上也就是所谓的interpreter),负责执行和调用 TFLite\LiteRT 格式模型在Android系统中的运行和算子的调度。 具体的算子调度执行过程中,Interpreter 进一步 通过Delegates 来决定把具体的运算算子派发给谁: - GpuDelegate (Adreno GPU): 适合处理模型中大规模并行的浮点矩阵乘法。 - NnApiDelegate (Hexagon NPU): 针对 Android Neural Networks API 设计的专用 NPU。如果模型完全量化为 INT8,交给 NPU 跑不仅极快,而且功耗极低。 - XNNPACK (CPU SIMD): 最底层的兜底方案。如果某个算子 GPU/NPU 不支持,就会退回到 CPU 用向量指令硬算,当然这通常是模型运算速度慢导致卡顿的罪魁祸首。 具体的执行和调度逻辑可以参考如何理解TFLite/LiteRT的核心概念和框架流程?以及TFLite如何处理算子版本和兼容性的问题?这两篇文章。 4.模型返回与渲染 通过以上 LiteRT Runtime 所调度的模型前向推理的执行后,该部分的 C++ 引擎更新内存状态,通过Dart FFI机制,应用层的 Dart 语言层捕获到模型执行完成的信号。此时用户层应用程序就可以按照模型输出的数据结构和类型,从连续的内存块中拆解出 YOLO/RT-DETRv2等模型输出的张量信息(例如类别、边框、置信度等),对这些张量信息进行后处理(如YOLO还需要进行一步NMS处理),然后交回给 Flutter 的 GPU 渲染管道,根据屏幕比例映射坐标,在画布上轻量级地画出最终的目标检测框。 模型推理的GPU/NPU加速设置 如上所述,在LiteRT Runtime运行时环境对模型所进行的前向推理的计算过程中,interpreter 会根据具体算子的版本情况选择对应的delegate执行选项,主要是GPU、NPU和CPU SIMD等选项。那么在flutter框架下使用litert_flutter插件运行AI模型的时候,如何设置使用GPU/NPU来实现这些算子的硬件加速呢? 需要通过 InterpreterOptions 来进行设置,具体代码如下: 以上代码的配置逻辑是,在Interpreter进行算子调度的过程中,优先使用GpuDelegateV2,如果算子在GPU上不支持的话,则会回退到CPU上用4线程来进行计算。 在当前(2026年4月)最新的2.0.11版本的 flutter_litert 插件中,InterpreterOptions 能够支持的加速模式包括: - GPU Delegates (图形处理器):适用于大多数模型,特别是 CNN 类网络。优点是速度快,功耗相对较低。缺点是部分算子不支持,可能回退到 CPU。 - GpuDelegateV2():针对android平台,其底层API是OpenGL ES 3.1 / Vulkan。 - GpuDelegate():针对iOS平台,其底层API是iOS的Metal。 - Core ML Delegate (iOS):针对iOS设备,iOS 11+,A12 芯片以上的设备。优点是在Apple 设备上性能最佳;缺点是仅支持 iOS。 - iOS 上应优先尝试 CoreMLDelegate() ,如果不支持再回退到 GpuDelegate() 。 - XNNPACK Delegate (CPU 优化):无 GPU 或 GPU 不支持时。优点是兼容性比较好,速度快于普通的CPU;缺点是仍比 GPU 慢很多。 需要注意的是:通过options.addDelegate只能设置一个加速选项,如果多次调用addDelegate接口,只有最后一个会生效。因此实践中应使用try…catch的方式选择最合适的加速选项:

基于ECS50数据集对PANNs语音分类模型进行的训练流程

本文使用ECS50数据集对语音检测和分类模型PANNs进行自定义训练的完整流程进行了记录。 ECS50数据集简介 在PANNs音频分类模型预研信息总结一文中,详细介绍了在语音分类应用中非常流行的模型PANNs,该模型使用Google的AudioSet数据集上进行了充分的训练,从而在不同种类的语音检测、辨识和分类应用上取得了非常好的效果。本文基于该模型(CNN14结构)的预训练文件Cnn14_mAP=0.431.pth,使用ECS-50语音数据集对预训练模型进行微调,使之适应ECS-50数据集的检测和分类需求。 ESC-50数据集包含有2000个环境录音文件,该数据集适用于环境声音分类的基准方法。数据集中的每个音频文件由5秒长的记录组成,可分为50个语义类,每个类型有40个示例,松散地分为5个主要类别: !image.png ECS-50数据集的下载地址:karolpiczak/ESC-50: ESC-50: Dataset for Environmental Sound Classification > 需要注意的是,ESC-50 数据集中的音频文件原始采样率是 44.1kHz,而 PANNs Cnn14 的预训练权重是基于 32kHz 的。 因此在读取这些音频文件送入模型训练时,必须强制重采样,从44.1Khz转换为32KHz。 ECS-50数据集的训练流程记录 客观的讲,PANNs模型对于自定义数据集的训练支持并不是很友好,所以需要自行设计和处理整个训练过程(数据集准备,模型检测头修改,训练流程)的各个阶段的处理流程。 训练数据集的准备 首先从以上ECS-50数据集的下载地址下载到这个数据集的压缩包,整个数据集有600多MB的样子。 然后基于Pytorch的Dataset类派生专门的ECS50数据集的Dataset类,在其中实现len和getitem接口。此处值得一提的是,因为PANNs模型是基于32KHz的音频文件进行训练的,所以此处在加载训练数据之前,需要把ECS50数据集的44.1KHz的音频文件转换为32KHz。 此外为了进一步规范化训练数据集中的音频数据长度,统一把训练数据集中的音频长度固定为5s,过长的音频文件直接截断,过短的因为文件则在后面补零。 CNN14检测头的修改 PANNs模型是在Google的AudioSet数据集的基础上进行设计和训练的,预训练文件Cnn14_mAP=0.431.pth对应的模型检测头支持527种音频分类。而ECS50数据集支持50种音频分类,因此要在训练之前对检测头部分进行修改,以适应ECS50数据集的要求。 整个过程分为以下步骤: - 首先基于PANNs模型源代码实例化一个Cnn14结构的模型,并加载预训练模型文件。 - 然后修改以上Cnn14模型的检测头部分,最后一层使用一个50个神经元的线性层替代之前默认的527神经元的线性层。 - 最后对新修改的检测头的线性层的权重参数进行初始化。 至此,预训练数据集和待训练的模型结构就准备好了。下一步就开始进行训练。 模型的训练 模型的训练流程的执行与普通的卷积神经网络模型的训练流程没有太大区别,需要注意的是:因为是分类模型,所以训练过程使用标准的交叉熵损失函数,优化器使用Adam。 训练过程中的log信息如下: 可以看到,经过30个epoch的训练以后,在验证集上的识别准确率达到了93.5%。训练完成后会自动把训练好的模型参数保存为cnn14_esc50_best.pth文件。 推理 最后基于以下代码对训练出来的模型进行推理测试: 参考资料 - karolpiczak/ESC-50: ESC-50: Dataset for Environmental Sound Classification

音频FFT计算流程总结与频谱图解读

本文总结了语音信号序列进行FFT计算其对应频谱的概率、参数总结以及计算流程,并提供了一个Python例子代码对一个wav文件进行频谱图的计算并进行解读。 语音信号FFT计算简介及其参数总结 众所周知,在时域中,音频信号是随着时间波动的压力值(即声压,表现为振幅的大小)。傅里叶变换原理证明了:任何周期性信号都可以分解为一系列不同振幅、频率和相位的正弦波/余弦波之和。因此,音频信号在时域中所表现出来的波动,也可以看做是由无数个不同频率、振幅和相位的正弦波叠加而成的。而对数字音频信号序列所进行的 FFT(快速傅里叶变换)计算,本质上就是从这个数字音频信号序列中提取出来与之对应的各种正弦波的组合,也就是所谓的频谱。 !image.png 对数字域中音频信号的离散时间序列所进行的FFT计算,包含的特性如下: - 对音频信号序列进行 FFT 计算的结果并不是连续的频率分析,而是将总频率范围(从 到音频采样率 )均匀地切割成 个小格子,每个格子被称为一个 Bin, 每个 Bin 的宽度为 。 - FFT 计算输出的结果是 复数。为了得到最终的频谱图,还需要对复数求取其幅度值(Magnitude):,这个幅度值代表了该频率分量在信号中的能量大小。 - 对于实数的音频信号序列,其 FFT 计算结果是对称的。也就是说,如果输入 1024 个音频采样点进行FFT计算,其输出的 1024 个复数中,前 512 个涵盖了 0 到 (奈奎斯特频率)的信息,后 512 个点只是前半部分的镜像。因此,在绘制频谱图时,通常只取前 个数据。 FFT计算结果与频域频点之间的对应关系 如上所述,对一个长度为 的音频序列所进行的FFT计算,其计算结果长度为 的复数序列,这个复数序列前半部分与后半部分呈现出镜像对称。FFT计算结果对应的整个频段为 ,因为整个计算结果序列前后镜像对称,因此这个频率范围均匀地切成了 份。 对于FFT计算的每个输出点(称为一个Bin)而言,其对应的频点计算公式为: 其中 是该复数在数组中的索引(从 0 开始)。如果采样率 ,FFT 长度 N = 1024: - Index 0: (直流分量 DC) - Index 1: - Index 2: - ... - Index 512: (奈奎斯特频率,即最高分析频率) 每个频点的幅度,对这个频点对应的复数求其幅度值:,这个幅度值代表了该频率分量在信号中的能量大小。 FFT运算的重点参数 音频序列进行FFT运算的重点参数如下:(可结合下面使用FFT进行频谱计算的流程来理解相关的参数) - FFT Size / Window Length / ():每一个音频采样帧参与计算的采样点数,通常取 2 的指数(如 512, 1024, 2048, 4096)。 - 采样率 (): 每秒钟对模拟信号采集的样本数,这个采集频率直接决定了频谱的带宽。根据奈奎斯特采样定律,采样样本序列能够分析的最高频率与采样频率之间的关系为 ,因此如果想要分析 的高频,采样率就必须大于 。 - Window:为了减少频谱泄露,必须在进行 FFT 计算之前给信号加窗。加窗的类型主要有:Hann,Hamming,Blackman。 - Hop Size:相邻两个 FFT 窗口之间滑动的距离。连续的两个音频帧重叠越多(Hop size越小),生成的频谱图在时间轴上越平滑,丢失的信息越少(因为窗函数会压低边缘能量,通过重叠可以补偿这一点)。但Hop 越小,同一个音频序列需要计算的帧数越多,计算成本线性增加。 音频采样帧Window Size、滑动窗口HopSize与连续两帧之间的Overlap Size的示意图如下所示: !image.png 语音信号频谱计算流程 对于一个语音文件,要生成与其对应的频谱图,通常需要包含以下步骤和流程。 语音信号的采集与分帧 音频采样设备(MIC)以固定的采样率 (如16K,32K,44.1K)进行模拟语音信号的采集,根据奈奎斯特采样定理,后续通过FFT能够计算和分析的最大频率对应于 。 此外,一段语音信号可能有几分钟长,直接对整个文件做 FFT 是没有意义的,因为语音信号的频率始终在随时间变化。因此,在进行FFT计算之前,要先将完整的语音信号序列切成许多小段,每一段称为一个音频帧 (Frame)。通常一个音频帧的长度为 N(实际上也就是进行一次FFT计算的点数,即FFT Size),取 2 的幂次方,如512,1024 或 2048。 当然,在分帧方面,连续两个音频帧会有一部分的重叠的,例如,第一个音频帧对应于0-1023区间的音频采样,第二个音频帧则对应于320-1352区间的音频采样,以此类推,直到整个音频文件的末尾。 加窗 经过分帧后的每一个音频帧,在进行FFT计算之前,还需要加窗。在对音频序列进行FFT计算时,如果不加窗,默认就是矩形窗,会导致频谱泄漏和混叠。加窗的目的是让一帧信号的幅度在两端渐变到 0,这样可以让频谱上的各个峰更细,不容易糊在一起,从而减轻频谱泄漏和混叠的影响。当然这样的话,一帧信号两端的部分会被削弱,没有像中央的部分那样得到重视。而弥补的办法就是两个连续的音频帧相互重叠。相邻两帧的起始位置的时间差叫做帧移(也就是Hop Size)。 下图为在语音信号处理过程中常用的窗函数: !image.png 所谓的加窗,就是把音频帧 与相同长度的窗函数帧 逐点相乘: 其结果就是,这一帧信号的首尾被平滑地压低到了 0,这样就强制实现了首尾衔接,消除了断层,避免信号序列起始点和结束点的幅值不一致导致的跳变。 FFT计算与频谱图 接下来就是对加窗后的数字信号序列进行FFT计算,在计算的过程中通过高效的蝶形运算(Butterfly Computation)方式,利用奇偶分解将原本 的计算量降低到 。 对于一个长度为 N 的音频数字序列,FFT 计算结果是相同长度 N 的复数序列。如前所述,因为音频数字序列中全部都是实数,这个长度为 N 的复数序列呈现出镜像对称。 然后计算每个频点的幅度值: ,这代表了该频率分量的强度。因为音频在各个频段上分布的能量跨度极大,必须通过对数映射(dB)的方式转换成分贝值来进行表示:。 最后,将每一帧计算出的频率分贝能量分布(纵轴,使用颜色表示能量大小)按时间顺序(横轴)排列起来,就得到了线性频谱图。 梅尔刻度频谱图 在数字音频领域中,如果是为了进行语音识别或音乐分析,通常会将普通的线性频率转换为梅尔刻度 (Mel Scale)。这是因为人类耳朵对低频的变化比高频更敏感,梅尔刻度能更好地模拟人类的听觉感知。 线性频谱图频率轴上的频率分布是均匀的,而梅尔刻度频谱图的频率轴是前疏后密的,其低频部分非常敏感,而在高频部分则非常压缩。人类的听觉是非线性的,对低频信号更为敏感,我们能轻易分辨出 和 的音高区别,但如果两个频率分别是 和 ,尽管它们同样相差 ,人耳几乎听不出任何区别。 > 梅尔刻度的定义就是为了让等距的梅尔频率变化在人类听觉上感受也是等距的。 从线性频谱图到梅尔频谱图的转换,并不是简单的坐标轴拉伸,而是通过一组梅尔滤波器组(Mel Filter Banks)对线性频率能量进行重新加权聚合。其大致工作流程是: 1. 首先在频谱上放置一系列三角形滤波器:在低频区,这些三角形很窄,排列很密;在高频区,这些三角形变得很宽,排列很稀疏。 2. 将一个音频帧整个频段的幅度数据与每一个三角形滤波器相乘并求和:第一个滤波器会收集低频区的能量,输出一个数值;最后一个很宽的滤波器会收集高频区一大片范围的能量,求和后输出一个数值。就得到了这个音频帧的梅尔刻度的能量分布。 3. 对所有的音频帧均作第二步处理,得到所有音频帧的在不同频段上的梅尔刻度能量分布。 最终,梅尔频谱图上的频点数量取决于梅尔滤波器组的个数,如果之前的线性频谱图中有 1024 个线性频点,通过 40 个梅尔滤波器后,只剩下 40 个梅尔频点。这样的话,梅尔频谱图的横坐标与线性频谱图相同,都等同于音频帧的个数;其纵坐标就变成了由低频到高频的梅尔滤波器组的数量40个。 语音信号频谱的计算示例 下面以语音分类模型(PANNs)代码自带的一个打电话语音文件R9_ZSCveAHg_7s.wav为例,使用torchlibrosa库中的Spectrogram和LogmelFilterBank分别进行线性频谱图和梅尔刻度频谱图的生成流程。 通过以上代码绘制的频谱图如下所示: !image.png 从上面的频谱图可以看到,无论是上方的线性频谱图,还是下方的梅尔刻度频谱图,其横坐标都是音频帧的索引。这个音频文件的长度是7s,采样率32KHz,单通道,总共包含有224000个采样(7s * 32000=224000)。FFT计算频谱的窗口大小为1024个音频采样点,音频帧间隔(Hop size)的样本数为320,因此最终整个wav文件的224000个音频采样被切分为701个(224000 / 320 +1=701)重叠的音频帧。因此,两个频谱图的横坐标都是连续701个音频帧。 线性频谱图的纵坐标方面,因为FFT计算频谱的窗口大小为1024个音频采样点,FFT计算得到的1024个频域结果,因为FFT计算结果是镜像对称的,所以线性频谱图的频点数量就是 . 采样率为32K,每个频点对应的频率就是: - Index 0: (直流分量 DC) - Index 1: - Index 2: - ... - Index 512: 梅尔频谱图是在线性频谱图的基础上生成,在代码中通过mel_bins参数设置梅尔滤波器组的数量为64,按照低频密集、高频稀疏的方式对线性频谱图各个频段的能量进行过滤和汇总,最终在梅尔刻度频谱图的纵坐标上形成64个从低频和高频的能量密度分布。 参考资料 - 语音信号处理中的“窗函数” - 凌逆战 - 博客园

PANNs音频分类模型的预研信息汇总

本文整理了经典的音频分类模型PANNs的基本情况,模型架构及其预训练参数文件的分类,开发环境的搭建和基本的推理测试,以及对该模型主推的CNN14模型的网络架构进行了说明。 PANNs模型简介 PANNs (Large-scale Pre-trained Audio Neural Networks),大规模预训练音频神经网络。该模型由Qiuqiang Kong等学者在 2019-2020 年间提出,其核心意义在于:它打破了音频领域“小样本、难训练”的僵局,为各种音频任务提供了一套强大的通用特征提取器。从整体工作流程上讲,其工作原理就相当于是先对音频文件生成与之对应的梅尔刻度频谱图,然后再使用标准的卷积神经网络基于这个频谱图进行分类操作。该模型在Github上的仓库地址:qiuqiangkong/audioset_tagging_cnn PANNs模型之所以强大,是因为它在Google的AudioSet数据集上进行了极其充分的训练,学会了分辨世界上几乎所有常见声音的频谱特征。该模型的预训练参数文件下载地址:PANNs: Large-Scale Pretrained Audio Neural Networks for Audio Pattern Recognition (Pretrained Models)。从以上的预训练参数下载文件列表中可以看到,该模型提供了多个不同网络模型和参数的预训练参数文件: !image.png 这些预训练参数文件的命令格式大致是:模型架构_采样率_嵌入向量长度_性能指标.pth。 - 模型架构:支持的模型架构版本包括CNN14(推荐模型架构),CNN6/CNN10(轻量级版本),Resnet/MobileNet,Wavegram-Logmel-Cnn14(性能强大,但计算量大),DecisionLevelMax(适合用于Sound Event Detection推理模式)。 - 采样率:PANNs的官方实现中预置了对 8kHz、16kHz 和 32kHz 这三种采样率的支持,默认为32KHz采样率。 - 嵌入向量长度:进入检测头部分的嵌入向量的维度,默认为2048,官方的权重文件库中还提供了对 32、128、512这几种维度的支持。 - 性能指标:在Google的AudioSet数据验证集上测试到的mAP指标。 环境安装与基本推理测试 环境与依赖安装: 从源代码目录的scripts/0_inference.sh脚本中可以看到,该模型的推理模式可以分为两种模式:Audio Tagging和Sound Event Detection。 - Audio Tagging (音频打标): - 解决的问题是:这段录音的内容是什么。 - 模型输出结果为整个音频片段的总体分类概率,对应于Google音频分类数据集AudioSet中的527个音频分类。 - 其技术判断逻辑是对音频文件在整个时间维度上进行全局池化,只给出一个总的分类概率判断。 - Sound Event Detection (SED, 声学事件检测): - 解决的问题是:什么时候发生了什么音频事件。 - 模型的输出结果为一条时间轴,在这个时间轴上标注出异常声音出现的起始和结束时间。 - 其技术实现逻辑是在Audio Tagging的基础上,保留了时间分辨率,然后像扫描仪一样逐帧判断整个时间周期上的音频事件。 audio_tagging推理模式测试: CNN14模型结构 如上所述,PANNs 并不是单一的一个模型,而是一系列不同网络架构的全家桶。可以在其模型预训练权重文件下载地址 Zenodo 页面看到的各种模型的预训练权重文件,对应了不同的网络深度和结构: - CNN 系列 (CNN6, CNN10, CNN14):这是PANNs最常用的系列。其中的 CNN14 是性能最强的旗舰型号,也是该项目主推的网络结构。 - ResNet 系列 (ResNet22, ResNet38, ResNet54):借鉴了残差网络的结构设计,适合更深层次的特征提取。 - 轻量化系列 (MobileNetV1, MobileNetV2):专为手机或嵌入式设备设计,牺牲少量精度换取极快的速度。 - 混合系列 (Wavegram-Logmel-CNN):该网络架构不仅关注频谱图,还直接参考原始波形,捕捉更精细的时间信息。 以下是CNN系列的三种网络架构组成: !image.png 以下是CNN系列不同架构配合不同的采样率、梅尔频谱刻度数量、数据增强策略的情况下的验证指标: !image.png 对于CNN14的网络结构而言 - 首先是基于torchlibrosa库中的Spectrogram和LogmelFilterBank生成与音频文件相对应的梅尔刻度频谱图。 - 接着是连续6个卷积块: - 前5个卷积块的组成都是:3x3卷积+BN层+Relu激活函数+3x3卷积+BN层+Relu激活函数+2x2池化层。 - 最后一个卷积块的组成是:3x3卷积+BN层+Relu激活函数+3x3卷积+BN层+Relu激活函数+1x1池化层。 - 第一个卷积块输出64通道,第二个卷积块输出128通道,以此类推,最后一个卷积块输出2048通道。 - 最后一个卷积块的输出维度数据为[batch_size, channels, time_steps, freq_bins]。默认情况下,channels为2048;time-steps为时间维度的步长,经过前面的5轮2x2池化以后就是音频采样帧数的1/32;freq_bins为频率特征,经过前面的5轮2x2池化后已经很小,甚至为1。此时,每个时间段(连续32个音频采样帧)就对应一个 2048 维的特征向量,它就代表了该时刻声音的指纹信息。 - 然后是在时间维度上,对以上连续卷积块提取的特征向量信息同时进行全局的最大池化和平均池化操作,把两者加起来,得到一个2048维的特征向量。 - 最后是一个从2048到num_classes的全连接层,用于基于2048维特征向量执行最后的分类操作,通过sigmoid给出分类判断的概率。 参考资料 - 【音频分类与检测】PANNs:用于音频模式识别的大规模预训练音频神经网络-CSDN博客

详细解释空洞卷积和可变形卷积的计算流程

有关卷积神经网络中的标准卷积核的计算流程,在详细解释卷积神经网络中的卷积与池化计算一文中已有非常详细的总结,本文主要整理了两种相对比较特殊的卷积操作:空洞卷积和可变形卷积。 空洞卷积 简单来说,空洞卷积相比于标注的卷积计算,可以在不增加参数量的情况下,强行拉大卷积计算的感受野(Receptive Field)。 在标准卷积的计算中,卷积核的元素是紧挨着的: !image.png 而在进行空洞卷积的计算时,会在卷积核的各个权重元素之间插入了空洞(即填充 0,但实际计算时直接跳过)。如以下的动图所示,3x3的空洞卷积,在计算的过程中,每个元素在计算时会都被插入空洞元素,这样的话相当于之前3x3卷积的感受野变成了5x5,但是参数量和计算量仍然保持为3x3。 !%E7%A9%BA%E6%B4%9E%E5%8D%B7%E7%A7%AF%E4%B8%8E%E5%8F%AF%E5%8F%98%E5%BD%A2%E5%8D%B7%E7%A7%AF-%E7%A9%BA%E6%B4%9E%E5%8D%B7%E7%A7%AF%E7%9A%84%E8%AE%A1%E7%AE%97.gif 上图中进行空洞卷积计算的核心参数是膨胀率(Dilation Rate),也就是说在进行卷积计算的过程中,在两个连续的权重元素进行计算的过程中要插入的空洞个数,也就是元素之间的间距: - d=1:标准卷积。 - d=2:每两个权重元素之间跳过一个像素,依次类推。 在上图所展示的空洞卷积的计算流程中,采样点之间进行计算时会跳过一定数量的像素,即膨胀率参数。 假设输入特征图为 ,二维卷积核为 ,大小为。对于输出特征图上位置为的值 ,其进行空洞卷积的计算公式为: - 其中的 和 就是空洞的体现,它改变了读取输入 时的索引步长。当 时以上公式也就是普通的卷积计算公式。 - 需要注意的是,卷积核 的参数量依然是 ,并没有因为空洞而增加额外的权重参数。 在 PyTorch 中,nn.Conv2d 内部已经集成了 dilation 参数用于指定卷积计算过程中的膨胀率。 可变形卷积Deformable Convolution 在传统的卷积(包括空洞卷积)计算的过程中,无论物体的形状轮廓是什么,以及无论物体如何旋转、缩放或变形,执行卷积计算的卷积核的采样形状永远是方形(如3x3,5x5等)。 而可变形卷积的理论则认为:卷积核应该具备形状感知和自适应的能力,也就是说,卷积核的采样点不应该被限制在固定的方形里面,而应该根据物体的实际轮廓和大小去偏移,通过让网络自己学习这个偏移量(Offset),来实现对感受野的精准定制。 可变形卷积的计算公式如下: 下图是可变形卷积的计算流程示意图: !image.png 从总的流程上讲,可变形卷积的计算分为两个阶段:第一个阶段负责偏移量的生成,第二个阶段基于偏移量来进行可变形卷积的计算。 以下基于一个实际的例子来解释上面两个阶段的的计算流程。假设输入的特征图是以下5x5分辨率的特征图,要执行的是3x3的可变形卷积计算。计算过程中关注的是下面输入特征图的中心点(2,2)坐标位置,即值为13的这个坐标点: !image.png 步骤1:偏移量的生成 阶段1的计算比较简单,实际上就是一个标准的3x3卷积,在这种情况下,输出的通道数必须因为18,因为这个阶段1的目的是要为输入特征图的每个像素点生成两个偏移量(dx,dy),3x3卷积的情况下对应的输出通道数就应该是3x3x2=18。 - 注意,在这个偏移量的计算过程中,涉及到一个独立的offset卷积核。 因此偏移量的计算过程,最终的输出结果为与输入特征图分辨率相同,通道数为18的偏移量张量。对于位置(2,2)这个特征点而言,这个偏移量张量包含了18个偏移量: !image.png 这样,针对中心点(2,2)这个位置的卷积计算,其周围9个像素(包含它自己)的位置就对应了9组(dx,dy)偏移量。 步骤2:可变形采样 下面以(2,2)位置的3x3区域的左下角点(-1,-1)来解释可变形采样的计算流程。按照以上可变形卷积的计算公式: 左下角点对应的,那么上面的公式实际上就是。这是一个非整数的坐标(如下图中的蓝点)。从输入的特征图上,找到其周围的四个像素点(如下图中的红框): !image.png 接下来就是进行双线性插值计算,距离越近,权重越高,插值权重为 (即去掉整数部分,仅保留小数)。双线性插值的计算如下: 这个 20.476 只是左下角点(-1,-1)的可变形卷积计算结果。接下来按照步骤2完全相同的流程,对所有的9个位置全部进行以上的计算,得到9个变形采样值: !image.png 步骤3:主卷积核加权求和 最后针对(2,2)这个位置最终的变形卷积的计算结果,还要对以上9个值,与主卷积核的权重参数逐元素相乘,得到最终的结果: - 是以上步骤2得到的9个变形采样值, 是主卷积核的权重参数。 以上的步骤1到步骤3只算出来了输入特征图中心位置(2,2)所对应的可变形卷积的计算结果,对于其他位置的所有像素同样要按照步骤1到步骤3的完整流程计算后,才能得到整张输入特征图的可变形卷积计算结果。由此可见,可变形卷积的计算结果,相比普通的卷积计算要多了很多。 在 PyTorch 中并没有提供原生的可变形卷积的实现,但是在 torchvision.ops 中提供了DeformConv2d可以用于实现可变形卷积的计算。 参考资料 - Deformable Convolution - Dilated Convolution

Android Studio+Flutter全栈开发环境的搭建及其注意事项

本文完整的记录了在Windows环境中基于Android Studio+Flutter的全栈开发环境的搭建过程,整个过程其实并不复杂,但因为概念众多,再加上中国国内网络访问Google服务器不方便等问题,在首次搭建并测试环境中存在不少值得注意和记录的问题,在此做完整的记录,方便后续开发过程中参考。 Flutter SDK的下载和环境变量设置 从中国国内的镜像源下载Flutter SDK,参考Flutter SDK 归档列表,选择Windows系统的Stable Channel选择下载即可,下载速度要比官方默认源下载快很多: !d99ea2d7-2389-4625-b608-a1796c881a53.png 下载后无需安装,只需要把 Flutter SDK 压缩包解压缩到一个固定的路径中,并把该SDK包中Bin目录的路径设置到Path环境变量中即可。 此外针对国内的网络环境,还需要通过增加环境变量的方式修改 Flutter 社区的地址,在环境变量的用户环境变量中增加两个环境变量:PUB_HOSTED_URL 和 FLUTTER_STORAGE_BASE_URL。 !7024f394-d4b1-4a8b-8e8e-9fee7ad6de79.png 此时,在Windows终端中应该可以通过flutter --version查看当前flutter sdk版本: Android SDK的安装和配置 从Android Studio中文官网下载最新版本的Android Studio并安装:下载 Android Studio 和应用工具 - Android 开发者  |  Android Developers。 Android Studio安装完成以后,首次打开还需要在线安装 SDK和NDK等工具,这些工具包也动辄数GB的大小,因此在首次运行Android Studio安装SDK的过程中选择Cutom方式,首先设置HTTP Proxy后再继续后面的SDK安装环节,否则SDK的下载环节会很让人崩溃,可设置的常用Proxy列表如下: !b5eda9af-5302-4c0f-bcb3-c7c5fa3727d0.png 默认情况下 Android Studio 在安装SDK的过程中不会安装命令行工具(Android SDK Command-line Tools),但 Flutter 必须通过它来操作镜像,所以还需要额外安装命令行工具。此外,因为后续我计划要基于Flutter来开发需要调用C/C++语言的音视频和AI模型方面的包和库,还需要安装Android NDK包。 因此,在SDK安装完成后还需要进入 Settings 页面额外安装以下SDK包: !766a7d54-6774-4cef-a456-16a5faf546bf.png 此外,还要在Android Studio中安装下一步使用Flutter进行开发的两个插件:Flutter和Dart。 !4d4dfa01-4df5-4623-b865-6f1a1174c997.png 至此,Flutter SDK、Android Studio及其相关的SDK、Flutter 插件等均安装完毕。 环境测试与验证 下一步在基于Android Studio中使用 Flutter 进行开发之前,可以在终端中使用 flutter doctor 命令检查当前环境的正确性。而在此之前首先通过 flutter doctor --android-licenses 命令并通过按下多次 y 按键来确认一些 Android 的license。最终的 flutter doctor命令的输入大致类似于:因为我不关注web开发,所以不关注此处的Chrome浏览器的报错信息,如果需要web开发的话,额外安装一下Chrome浏览器就可以了。 Gradle 换源 到此还没有结束,在Android Studio中编译和打包Android APK的时候还需要用Gradle,这同样牵扯到一大堆工具的下载。 在 Android APP的构建过程中,Android 的构建脚本 gradle 默认会去从 Google 和 MavenCentral 下载各种依赖包,中国国内网络很容易在这个包下载的过程中卡住,所以需要更改依赖包下载服务器的源地址。 在网上大概搜了一下,大部分都提到通过手动修改 gradle/wrapper/gradle-wrapper.properties 文件修改默认的gradle.org镜像,以及在 android/build.gradle文件中修改其中的 allprojects 等解决方案。这种方式很直观简单,但问题是:以后每次新建一个项目,都需要自行手动去修改这两个文件才行,如果忘记的话就会到外网去下载依赖文件,耗时会非常长。 而我希望做到的是直接把这些修改放到整个环境中,这样每次新建的flutter项目在 build android app的时候自动把下载的服务器地址链接到国内的镜像,而不是访问外网的中心服务器。具体的方式就是在 Gradle 的安装目录(例如我的gradle的安装路径是D:\Android\.gradle,默认的安装路径应该是C盘用户目录下的.gradle目录)下创建一个init.d目录,在其中放一个mirror.init.gradle.kts文件,内容为: 这样在 Gradle 每次加载的时候,都会自动替换与其相关的下载服务器的URL,替换为国内的镜像服务器地址,这样下载速度会快很多。 调试 至此,以上的完整过程就总算是完成了。其实整个过程并不复杂,关键是国内网络访问外网不方便,从外网服务器上的下载速度会非常让人抓狂,而Android Studio/SDK/NDK/Flutter/Gradle等多个环节又需要下载很多包才能完成环境的搭建。好在有国内众多的镜像服务器可以中转,让这个过程和环境一旦完成搭建之后,后续的开发就比较顺畅了。 接下来就是经典的调试过程了,直接在终端中使用Gemini Cli这类终端开发工具或者使用Trae这类 AI IDE 进行代码的修改,然后通过flutter devices + flutter run + r这样的黄金组合进行代码的调试和运行了。正常开发的过程中不需要再打开Android Studio了,只需要使用Android Studio管理和更新自己的SDK就好了。 参考资料 - 在中国网络环境下使用 Flutter - Android Studio快速配置国内镜像源和HTTP代理_android studio 国内代理-CSDN博客

Android Studio+Flutter全栈开发环境的搭建及其注意事项

本文完整的记录了在Windows环境中基于Android Studio+Flutter的全栈开发环境的搭建过程,整个过程其实并不复杂,但因为概念众多,再加上中国国内网络访问Google服务器不方便等问题,在首次搭建并测试环境中存在不少值得注意和记录的问题,在此做完整的记录,方便后续开发过程中参考。 Flutter SDK的下载和环境变量设置 从中国国内的镜像源下载Flutter SDK,参考Flutter SDK 归档列表,选择Windows系统的Stable Channel选择下载即可,下载速度要比官方默认源下载快很多: !d99ea2d7-2389-4625-b608-a1796c881a53.png 下载后无需安装,只需要把 Flutter SDK 压缩包解压缩到一个固定的路径中,并把该SDK包中Bin目录的路径设置到Path环境变量中即可。 此外针对国内的网络环境,还需要通过增加环境变量的方式修改 Flutter 社区的地址,在环境变量的用户环境变量中增加两个环境变量:PUB_HOSTED_URL 和 FLUTTER_STORAGE_BASE_URL。 !7024f394-d4b1-4a8b-8e8e-9fee7ad6de79.png 此时,在Windows终端中应该可以通过flutter --version查看当前flutter sdk版本: Android SDK的安装和配置 从Android Studio中文官网下载最新版本的Android Studio并安装:下载 Android Studio 和应用工具 - Android 开发者  |  Android Developers。 Android Studio安装完成以后,首次打开还需要在线安装 SDK和NDK等工具,这些工具包也动辄数GB的大小,因此在首次运行Android Studio安装SDK的过程中选择Cutom方式,首先设置HTTP Proxy后再继续后面的SDK安装环节,否则SDK的下载环节会很让人崩溃,可设置的常用Proxy列表如下: !b5eda9af-5302-4c0f-bcb3-c7c5fa3727d0.png 默认情况下 Android Studio 在安装SDK的过程中不会安装命令行工具(Android SDK Command-line Tools),但 Flutter 必须通过它来操作镜像,所以还需要额外安装命令行工具。此外,因为后续我计划要基于Flutter来开发需要调用C/C++语言的音视频和AI模型方面的包和库,还需要安装Android NDK包。 因此,在SDK安装完成后还需要进入 Settings 页面额外安装以下SDK包: !766a7d54-6774-4cef-a456-16a5faf546bf.png 此外,还要在Android Studio中安装下一步使用Flutter进行开发的两个插件:Flutter和Dart。 !4d4dfa01-4df5-4623-b865-6f1a1174c997.png 至此,Flutter SDK、Android Studio及其相关的SDK、Flutter 插件等均安装完毕。 环境测试与验证 下一步在基于Android Studio中使用 Flutter 进行开发之前,可以在终端中使用 flutter doctor 命令检查当前环境的正确性。而在此之前首先通过 flutter doctor --android-licenses 命令并通过按下多次 y 按键来确认一些 Android 的license。最终的 flutter doctor命令的输入大致类似于:因为我不关注web开发,所以不关注此处的Chrome浏览器的报错信息,如果需要web开发的话,额外安装一下Chrome浏览器就可以了。 Gradle 换源 到此还没有结束,在Android Studio中编译和打包Android APK的时候还需要用Gradle,这同样牵扯到一大堆工具的下载。 在 Android APP的构建过程中,Android 的构建脚本 gradle 默认会去从 Google 和 MavenCentral 下载各种依赖包,中国国内网络很容易在这个包下载的过程中卡住,所以需要更改依赖包下载服务器的源地址。 在网上大概搜了一下,大部分都提到通过手动修改 gradle/wrapper/gradle-wrapper.properties 文件修改默认的gradle.org镜像,以及在 android/build.gradle文件中修改其中的 allprojects 等解决方案。这种方式很直观简单,但问题是:以后每次新建一个项目,都需要自行手动去修改这两个文件才行,如果忘记的话就会到外网去下载依赖文件,耗时会非常长。 而我希望做到的是直接把这些修改放到整个环境中,这样每次新建的flutter项目在 build android app的时候自动把下载的服务器地址链接到国内的镜像,而不是访问外网的中心服务器。具体的方式就是在 Gradle 的安装目录(例如我的gradle的安装路径是D:\Android\.gradle,默认的安装路径应该是C盘用户目录下的.gradle目录)下创建一个init.d目录,在其中放一个mirror.init.gradle.kts文件,内容为: 这样在 Gradle 每次加载的时候,都会自动替换与其相关的下载服务器的URL,替换为国内的镜像服务器地址,这样下载速度会快很多。 调试 至此,以上的完整过程就总算是完成了。其实整个过程并不复杂,关键是国内网络访问外网不方便,从外网服务器上的下载速度会非常让人抓狂,而Android Studio/SDK/NDK/Flutter/Gradle等多个环节又需要下载很多包才能完成环境的搭建。好在有国内众多的镜像服务器可以中转,让这个过程和环境一旦完成搭建之后,后续的开发就比较顺畅了。 接下来就是经典的调试过程了,直接在终端中使用Gemini Cli这类终端开发工具或者使用Trae这类 AI IDE 进行代码的修改,然后通过flutter devices + flutter run + r这样的黄金组合进行代码的调试和运行了。正常开发的过程中不需要再打开Android Studio了,只需要使用Android Studio管理和更新自己的SDK就好了。 参考资料 - 在中国网络环境下使用 Flutter - Android Studio快速配置国内镜像源和HTTP代理_android studio 国内代理-CSDN博客

QWen3-VL-4B多模态大模型在RK3588上的适配流程记录

本文详细记录了在瑞芯微RK3588上进行Qwen3-VL-4B-Instruct多模态大模型的完整适配记录,以及在适配过程中容易出现问题的重点事项。 QWen3-VL-4B模型的下载与测试 阿里在 2025 年 9 月推出了Qwen3-VL 系列视觉语言模型。相较于其前代产品(如 Qwen2-VL),Qwen3-VL 的发布标志着多模态大模型从简单的视觉感知正式跨越到了深层的逻辑推理与自主 Agent 交互的新阶段。Qwen3-VL 延续了阿里的开源精神,推出了涵盖从端侧到云端的多种规模模型。 !image.png 要进行Qwen3-VL-4B-Instruct这个模型在瑞芯微平台上的适配,首先需要下载模型文件,并在PC端进行基本的推理测试,验证下载到的模型文件的正确性,这部分工作在详解QWen3-VL模型的测试以及vLLM生产环境部署流程一文中已有详细介绍。 Qwen3-VL-4B-Instruct模型在HuggingFace中的链接路径如下,按照上文使用transformers库从huggingface上下载并执行本地的推理验证: !image.png > 注:下文中我已经下载好的本地模型文件的路径为/mnt/d/HuggingFace/hub/models--Qwen--Qwen3-VL-4B-Instruct/snapshots/ebb281ec70b05090aa6165b016eac8ec08e71b17/ 环境的搭建 多模态大模型在瑞芯微平台NPU上的适配,需要同时用到瑞芯微的rknn-llm和rknn-toolkit2工具包。截至2026年1月份,这两个工具包在github上的最新版本是: - rknn-llm:1.2.3 - rknn-toolkit2:2.3.2 以下工作就基于这两个版本展开。比较奇葩的是,这两个工具包的最新版本,对于python环境的版本要求是相互冲突的。所以最好基于上面的两个工具包分别搭建各自的开发环境rknn和rkllm,在后续的不同步骤切换到不同的环境中执行。 rknn-toolkit2的环境搭建可以参考瑞芯微RK3588 NPU开发环境搭建笔记,rkllm的环境搭建流程可以参考轻量级LLM模型QWen2.5-0.5B在RK3588上的适配流程记录。 _整体来讲,QWen3-VL-4B-Instruct这个模型在RK3588开发板上的适配,主要是基于rknn-llm这个仓库中的__rknn-llm-main/examples/multimodal_model_demo__目录中所包含的文件展开。_ - 该目录下有一个README.md可以作为参考,但是其中缺少一些细节。 模型转换 针对QWen3-VL-4B-Instruct这类多模态模型的转换,实际上分为两个步骤,分别对应rknn模型和rkllm模型的转换。最终上板运行的demo,既需要同时加载独立的rknn模型文件和rkllm模型文件,也需要依赖于各自的运行时库:librkllmrt.so和librknnrt.so。 rknn模型的转换 这部分转换操作在rknn环境(即安装rknn-toolkit2工具包的环境中)执行。 这部分分为两个子步骤:模型的Vision部分转换为onnx文件,在从onnx格式的模型文件转换为上板运行的rknn模型文件。 在rknn-llm-main/examples/multimodal_model_demo目录下的export子目录中执行export_vision.py脚本转换模型的Vision部分到onnx文件中: 以上执行完成后会在当前export目录下生成一个onnx子目录,其中包含有转换好的模型Vision部分的onnx文件: 接下来,继续使用相同目录下的export_vision_rknn.py脚本把这个onnx文件转换为板端可运行的rknn模型文件: 以上执行完成后会在当前export目录下生成一个rknn子目录,其中包含有转换好的模型Vision部分的板端运行的rknn模型文件: 至此模型Vision部分的rknn上板运行的模型文件qwen3-vl_vision_rk3588.rknn就已经准备好了。 rkllm模型的转换 这部分转换操作在rkllm环境(即安装rknn-llm工具包的环境中)执行。 这部分同样可以分为两个子步骤:校准数据的准备,rkllm板端模型的转换。 校准数据准备的这部分工作主要在data子目录中进行,该目录下包含一个make_input_embeds_for_quantize.py脚本用于准备校准数据,但是这个脚本是针对Qwen2-VL的,直接在Qwen3-VL模型上使用会报错,所以我基于上面的脚本文件修改了一个make_input_embeds_for_quantize_qwen3-vl.py专门用于生成Qwen3-VL模型的校准数据,然后在export下执行: 以上执行完成后会在data/inputs.json和data/inputs_embeds/生成校准数据,后续rkllm板端模型的转换过程要用到这个校准数据。 接着就是模型转换的最后一步,也是最耗时的一步(主要原因是我的显卡型号比较新,与rkllm环境中的torch和cuda版本有冲突,所以只能使用CPU来执行模型转换):rkllm板端模型文件的转换。主要用到export下的export_rkllm.py脚本: 注意,以上脚本执行的过程中对内存需求巨大,如果在WSL中执行的话会在运行在出现Killed的问题,原因就是内存OOM了,此时需要修改WSL的内存和虚拟内存的配置:在自己的用户目录C:\Users[用户名]下创建一个.wslconfig文件,写入以下内容然后重启WSL,然后重新执行以上转换命令即可: 以上执行完成后会在当前目录下生成rkllm的板端执行文件_w8a8_rk3588.rkllm: 至此,板端推理demo运行所需要的两个板端模型文件qwen3-vl_vision_rk3588.rknn和_w8a8_rk3588.rkllm就准备好了。 板端推理应用程序编译 板端通用的推理应用程序代码在deploy目录下,需要注意RKLLM v1.2.3版本要求交叉编译的gcc版本在10.2以上,需要先下载工具链gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu: !image.png 在deploy目录下,修改其中的build-linux.sh文件中的交叉编译工具链路径GCC_COMPILER,然后执行build-linux.sh脚本编译板端执行文件即可。这个编译过程完成后会把所有上板运行的文件(除以上两个板端模型文件以外)放入一个install目录下。 上板运行测试 上板运行测试的过程,把以上的install目录、两个板端模型文件qwen3-vl_vision_rk3588.rknn和_w8a8_rk3588.rkllm上传到板子的models目录下上。然后执行: 最终demo在rk3588上运行的效果如下图所示: !image.png 参考资料 - airockchip/rknn-llm - airockchip/rknn-toolkit2 - 【硬核部署】Qwen3-VL 在 RK3588 上的部署-CSDN博客 --- 尝试从底层原理的角度去理解和解释技术问题:音视频/摄像头/智能家居/蓝牙/WiFi/无线通信/AI。 敬请关注微信公众号:Pavel Han。

DETR(DEtection TRansformer)网络架构与计算流程详细总结

本文详细介绍了Facebook在2020年提出的基于Transformer架构的图像目标检测模型DETR的网络结构及其数据在网络中的计算流程。 DETR简介 DETR 是由 Facebook AI Research (FAIR) 在 2020 年提出的针对计算机视觉领域中目标检测方向的AI模型,首次把NLP领域中广泛应用的 Transformer 架构引入到了计算机视觉领域。它彻底改变了目标检测领域的传统设计范式,将目标检测任务从繁琐的各种组件和处理流程的堆砌简化为了一个真正的端到端的集合预测问题。 在 DETR 模型出现之前,在目标检测领域的主流检测器(如 Faster R-CNN, YOLO, SSD)都高度依赖各种设计组件: - Anchor Boxes (锚框):基于训练数据集的总体情况预设检测目标的不同尺度和比例。 - NMS (非极大值抑制):用于在模型输出数据的后处理过程中消除重复的检测框。 - 极其复杂的正负样本匹配策略。 而DETR 的核心创新则在于:通过利用 Transformer 结构的全局建模能力,直接输出一个固定数量的预测集合(通常是 100 个),然后通过二分图匹配 (Bipartite Matching) 算法与真实标签(Ground Truth)进行一对一匹配。 从架构上讲,DETR 网络主要由三个部分组成:CNN Backbone,Tranformer编解码器,检测头。 !image.png CNN Backbone DETR网络架构以卷积神经网络CNN结构的BackBone部分开始,Backbone网络采用标准的Resnet网络(原始论文中,作者使用的 Backbone 网络是Resnet50和Resnet101)。该Bonebone网络作为输入图像的特征提取器,用于处理原始输入图像以产生丰富的视觉特征向量。 - 有关 Resnet 的网络结构可参考:CNN经典网络模型架构学习之Resnet。 在图像处理的处理维度上,假设 Backbone 的输入图像尺寸为 (3, H, W),那么该部分经过多阶段 Resnet 进行的特征提取后会输出一个降维后的特征图。在标准的 DETR 实现中,特征图下采样的倍数通常是 32 倍,因此输出特征图的维度是 (C, H/32, W/32)。而通道数 C 通常使用一个1x1卷积投影到256,用以匹配 Transformer 的输入维度。 - 如果输入图像分辨率为 800 x 800,输出特征图的分辨率就是 25 x 25。 参考ViT架构中Transformer在计算机视觉领域中的应用(一文入门Vision in Transformer(ViT)模型的架构),Transformer 接收的是序列数据(Sequence),而不是矩阵形式的向量。因此,Backbone 中Resnet网络所输出的 (25,25,256) 的特征图会被展平拉直成一个长度为 625 的向量序列,其中的每个元素为长度256的向量。 !image.png 位置编码 同样的,卷积神经网络本身由于平移不变性和局部感知,能隐式的感知位置信息。但 Transformer 处理的序列数据需要额外在展平的向量序列上增加位置编码信息,这一点跟自然语言处理中对文本信息增加位置编码信息是一致的。 DETR 对BackBack输出特征图序列的位置信息采用了固定正余弦二维位置编码(Fixed 2D Sinusoidal Positional Encodings)。大致的做法是:将特征图每个像素位置的编码信息分为 x 方向和 y 方向,分别进行编码,最后将它们拼接(Concatenate)起来。如上所述,特征图经过展平拉直后每个像素的向量长度为256,那么就分配 128 维来编码 x 坐标,另外的 128 维来编码 y 坐标,将两者拼接后形成最终的 256 维位置特征向量。 对于特征图上每个像素的 (x,y) 坐标,每个 x 对应一个 128 维的向量,这个向量由 64 组正余弦对组成,其正余弦对的计算公式分别为: 同样是由64组正余弦对组成的128维向量,采用相同的计算公式计算。两个128维向量再拼接得到一个256维的位置向量,这样得到的向量维度正好与经过 1 x 1 卷积后的特征向量维度对齐,位置编码信息的维度同样是长度为 625 的向量序列,其中的每个元素为长度256的向量。。 Transformer Encoder-Decoder 如上所述,经过Backbone对图像的特征提取、特征图展平拉直以后,送入Transformer Encoder的数据维度就是长度为 625 的序列向量,序列中每个元素的维度维 256。需要注意,BackBone 输出的数据与并不会与位置编码向量直接相加,而是在计算注意力时将位置向量注入到 Query 和 Key 的计算中,这一点是与标注的自然语言处理的Transformer计算流程不同的地方。 下图是 DETR 的Transformer Encoder与Decoder的处理流程架构: !image.png Encoder Encoder的多头自注意力模块采用了 8 个 Head,因此每个 Head 的维度为 。每一个 Head 的Q、K、V矩阵的计算如下: - Query (Q): → 维度: 625 x 32 - Key (K): → 维度: 625 x 32 - Value (V): → 维度: 625 x 32 > 注意:在计算Query和Key的时候,把 Backbone 提取出来的特征向量 Src 与其位置编码 Pos 相加进行计算,Value 的计算不需要加入位置编码,因为 Value 负责提供内容信息,而 Q 和 K 负责定位和匹配。 接下来对于多头注意力模块的计算仍然遵循标准的计算流程,在如何理解Transformer架构中的多头注意力机制?一文中已经非常详细的总结了多头注意力模块的计算步骤。 总之,从输入和输出数据的维度上讲,每个 Encoder 模块(上面架构图左侧虚线框部分)输入的特征图展平向量和位置编码向量的维度是 625 x 256 , 而其输出数据的维度同样是 625 x 256 。DETR 模型默认包含有连续的6个Encoder模块,最终 Encoder 部分输出的数据维度同样是 625 x 256 。 Encoder 的工作逻辑就是:利用全局注意力机制,对前面的Backbone卷积网络提取的局部特征进行上下文建模,最终输出一个与输入尺寸相同的增强特征序列,其中每个点都包含了全局的上下文信息和语义特征。 Decoder 对于 Decoder 部分架构的理解,首先定义Decoder的输入。Decoder 的输入包含两个部分: - Encoder的输出,这一部分比较好理解,就是前面最后一个 Encoder 模块输出的维度为 625 x 256 的张量。 - Object Queries (对象查询向量):这是一个维度为 100 x 256 的张量。在训练阶段,它是可学习的参数(类似于 Embedding),训练完成后这个100 x 256维度的张量就作为固定的参数用于后续图片的推理流程。这 100 个向量就代表着 100 个候选框。 如下图所示的Decoder Block的架构,每个Decoder Block从下到上依次包含有三个部分:多头自注意力模块(Multi-Head Self-Attention),多头交叉注意力模块(Multi-Head Attention),前馈模块(FFN)。 !image.png 首先是自注意力模块(上图中的Multi-Head Self-Attention模块)。这个自注意力模块的计算流程与标准的Transformer自注意力的计算流程稍有差异:标准的Transformer自注意力模块的输入是增加了位置编码信息的输入向量,然后基于这个输入向量分别与 这三个参数矩阵运算得到 Q,K,V 向量;而以上自注意力模块的计算逻辑则是: 以上自注意力计算的逻辑,实际上把输入的Object Queries同时作为输入数据和其位置编码向量来使用,这就是为什么在自注意力计算Q和K的过程中要对Object Queries进行自加操作的原因所在。 > 为什么是对相同的输入Object Queries进行自加,而不是对这个输入直接乘以2呢?这是因为,实际上Decoder部分有6个连续叠加的Decoder block,而只有第一层的decoder在计算Q和K的时候用的是Object Queries的自加作为输入,后续的decoder在进行自注意力模块计算的时候是使用Object Queries(作为位置向量信息)和前一层Decoder的输出相加来作为其输入的。 经过以上自注意力模块的计算后,其输出数据的维度为 100 x 256。 接下来是交叉注意力模块Multi-head Attention部分的计算。在交叉注意力模块中,Decoder 自注意力模块的输出扮演的是主动提问者的角色,而 Encoder 输出的特征图则是知识库。 交叉注意力模块的输入数据有三个部分:Decoder 自注意力模块输出的object_queries(维度为(100,256)),Encoder输出的特征图信息encoder_features(维度为(625,256)),以及与Encoder部分相同的空间位置编码信息(维度为(625,256))。在这种情况下,Q,K,V 三个张量的计算逻辑及其数据维度如下: - Q (Query): → (100, 256) - K (Key): → (625, 256) - V (Value): → (625, 256) 交叉注意力模块后续的计算逻辑以及前馈网络模块FFN仍然与标准的Decoder Block的计算流程相同,具体可以参考一文彻底搞懂Transformer模型的Decoder结构与计算流程,最终以上的整个Decoder Block的输出维度与其输入维度相同,都是(100,256)。DETR网络结构右侧的Decoder部分总共包含有6个连续叠加的Decoder Block,每个block的输出作为下一个block的object queries输入,最终整个Decoder部分的输出数据维度仍然是 (100,256)。 检测头 如上所述,经过整个 Decoder 部分的连续6个Decoder Block的叠加处理后,最终Decoder部分输出的数据维度是(100,256)。那么DETR模型最后的检测头部分的任务,就是将这 100 个高维向量映射为人类可读的类别标签和边界框坐标。 在检测头结构的设计上,DETR 采用了两个并行的 FFN(前馈神经网络)分支,它们共享 Decoder 的输出,但负责不同的任务。 !image.png 分类头 (Classification Head)的结构是一个简单的线性层(Linear Layer)。该线性层的输入数据维度为 100 x 256,输出数据为 100 x (K+1)。其中 K 是目标数据集的类别数(如 COCO 的 80 类),+1 代表背景类(no object)。最后的激活函数使用Softmax,它会为每个 Query 分配一个概率分布,指示该 Query 对应的物体属于哪一类。 回归头 (Bounding Box Regression Head)的结构是一个具有 3 个隐藏层的多层感知机(MLP),每层之间带有 ReLU 激活函数。整个回归头的输入数据维度为100 x 256,输出数据维度为 100 x 4。其数值的含义是归一化后的中心坐标及宽高。 在以上输出头结构的情况下,对于每张图片,DETR模型会输出100个检测框的坐标信息(回归头),每个检测框对应一个长度为 K+1 的分类信息。在进行背景框过滤时,只需要设定一个阈值(例如 0.5),过滤掉那些分类结果为背景类别(no object)或得分过低的预测框即可,不再需要YOLO模型非常复杂的NMS计算流程了。 参考资料 - facebookresearch/detr: End-to-End Object Detection with Transformers - Introduction to DETR - Part 1 | DigitalOcean

轻量级LLM模型QWen2.5-0.5B在RK3588上的适配流程记录

本文详细学习和总结基于瑞芯微的RKLLM工具及其官方的相关指导文档,把轻量级大语言模型Qwen2.5-0.5B适配到RK3588上的完整过程及其注意事项记录。 总体而言,在瑞芯微NPU上部署 LLM 和部署 YOLO 的核心逻辑与流程是一致的,都是:PC端转换模型 -> 推送模型和库到板端 -> 调用 API 运行。 但是,对于大语言模型的适配而言,不再使用 RKNN-Toolkit2 工具链,而是使用专门为大语言模型优化的 RKLLM-Toolkit 工具链。 - RKNN-Toolkit2: 处理卷积神经网络(CNN)和简单的 Transformer(Vision Transformer)。 - RKLLM-Toolkit: 专门处理大语言模型,支持权重量化(W4A16/W8A8)和 KV Cache 优化。 下图是瑞芯微RKLLM Toolkit及其软硬应用生态的框架图,与RKNN类似: !image.png - RKLLM-Toolkit:用于在PC上进行模型转换和量化。 - RKLLM Runtime:为瑞芯微NPU平台提供C/C++编程接口,帮助用户在NPU上部署RKLLM模型,加速LLM应用的运行。 - RKNPU内核驱动:负责与NPU硬件交互,可以在Rockchip的内核代码中找到。 环境搭建 从rkllm的Github仓库(airockchip/rknn-llm)上下载rkllm代码。 PC端环境 创建一个独立的rkllm环境,并进入rknn-llm仓库代码中的rkllm-toolkit/packages目录下,安装rkllm-toolkit及其依赖包: 板端环境 对于LLM大模型在瑞芯微NPU上的运行,在板端主要需要NPU驱动以及librkllmrt.so链接库(在rkllm-runtime/Linux/librkllm_api/aarch64下)的支持。 NPU驱动要求在v0.9.8版本以上,可以通过以下命令检查自己板端的驱动是否符合要求,否则就需要重新编译内核: 下载模型文件并在PC上推理测试 使用transformers从Huggingface上下载Qwen2.5-0.5B模型的模型文件,并在PC本机上执行推理测试,验证下载文件的正确性: HuggingFace下载速度慢的问题可以通过设置国内镜像来解决,在执行以上下载脚本之前设置环境变量: 下载后的模型文件列表如下图所示: !image.png 模型转换 基于环境搭建部分安装的rkllm-toolkit包,调用以下代码完成模型的转换: > 注意:在量化类型的支持上,RK3588不支持w4a16, 可支持w8a8, w8a8_g128, w8a8_g256, w8a8_g512这四种。 以上模型转换结束后会在当前目录下生成一个qwen2_5_0_5b_w8a8.rkllm文件,大小800M的样子。 板端编译与推理 以上的板端模型转换完成以后,接下来就是准备板端的推理代码: 对于板端推理代码的交叉编译,可以按照常规的开发方法,在PC上编译好以后上传到板子上执行。但是我使用的1.2.3版本的RKLLM-Toolkit,在文档中明确推荐的交叉编译工具版本是GCC10.2,而我PC上的瑞芯微平台的编译工具链是gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu。所幸我用的瑞芯微RK3588板子上运行的Debian系统有满足条件的编译工具: 所以这样就比较简单了,可以直接把板端推理代码的源文件(main.cpp)、头文件(rkllm.h)、链接库(librkllmrt.so)以及前面转换好的板端模型上传到板子上,直接在板子的debian系统中使用g++进行编译: 执行后的打印信息如下: 可以看到,这个模型在RK3588上已经跑起来了。但是0.5B规模的大语言模型所生成的内容还是存在比较大的问题,不仅轻松的解决了西咸一体化这个困扰西安发展30年的痼疾,而且明显把榆林的资料张冠李戴到了咸阳头上。 推理过程中的内存占用上,这个模型只用到了680MB的内存,而实际上光是板端转换后的模型文件就已经800MB了,这就说明瑞芯微RKLLM在执行时并没有把完整的模型文件全部加载到内存中,而是按需加载,这样的话对于嵌入式设备这类内存非常紧张的应用还是非常实用的。 参考资料 - airockchip/rknn-llm - RKLLM SDK User Guide v1.2.3