YOLOv9改进策略【Neck】| 使用CARAFE轻量级通用上采样算子
一、本文介绍
本文记录的是利用CARAFE上采样对YOLOv9的颈部网络进行改进的方法研究。YOLOv9采用传统的最近邻插值的方法,仅考虑子像素邻域,无法捕获密集预测任务所需的丰富语义信息,从而影响模型在密集预测任务中的性能。CARAFE通过在大感受野内聚合信息、能够实时适应实例特定内容且保持计算效率。
文章目录
- 一、本文介绍
- 二、CARAFE介绍
- 2.1、CARAFE原理
- 2.2、优势
- 三、CARAFE的实现代码
- 四、添加步骤
- 4.1 修改common.py
- 4.2 修改yolo.py
- 五、yaml模型文件
- 5.1 模型改进
- 六、成功运行结果
二、CARAFE介绍
CARAFE: 内容感知的特征重新组合
CARAFE(Content - Aware ReAssembly of FEatures)是一种用于特征上采样的操作符,其设计原理和优势如下:
2.1、CARAFE原理
- 总体框架:
CARAFE由两个关键组件组成,即核预测模块和内容感知重组模块。给定大小为 C × H × W C×H×W C×H×W的特征图 x x x和上采样比例 σ \sigma σ(假设 σ \sigma σ是整数),CARAFE将产生大小为 C × σ H × σ W C×\sigma H×\sigma W C×σH×σW的新特征图 X ′ X' X′。 - 核预测模块:负责以内容感知的方式生成重组核。每个源位置 x x x上对应 σ 2 \sigma^{2} σ2个目标位置 X ′ X' X′上,每个目标位置需要一个 k u p × k u p k_{u p}×k_{u p} kup×kup的重组核,因此该模块将输出大小为 C u p × H × W C_{u p}×H×W Cup×H×W的重组核,其中 C u p = σ 2 k u p 2 C_{u p} = \sigma^{2}k_{u p}^{2} Cup=σ2kup2。该模块由三个子模块组成,分别是通道压缩器、内容编码器和核归一化器。
- 通道压缩器:采用 1 × 1 1×1 1×1卷积层将输入特征通道从 C C C压缩到 C m C_{m} Cm,减少特征图的通道数,从而减少后续步骤的参数和计算成本,使CARAFE更高效。
- 内容编码器:使用核大小为 k e n c o d e r k_{encoder} kencoder的卷积层根据输入特征的内容生成重组核,编码器的参数为 k e n c o d e r × k e n c o d e r × C m × C u p k_{encoder}×k_{encoder}×C_{m}×C_{u p} kencoder×kencoder×Cm×Cup。增大 k e n c o d e r k_{encoder} kencoder可以扩大编码器的感受野,利用更大区域内的上下文信息,但计算复杂度也会随之增加。通过研究, k e n c o d e r = k u p − 2 k_{encoder} = k_{u p} - 2 kencoder=kup−2是性能和效率之间的良好平衡。
- 核归一化器:在将每个 k u p × k u p k_{u p}×k_{u p} kup×kup重组核应用于输入特征图之前,使用softmax函数对其进行空间归一化,使核值之和为 1 1 1,这是对局部区域的软选择。
- 内容感知重组模块:对于目标位置 l ′ l' l′和以 l = ( i , j ) l = (i, j) l=(i,j)为中心的相应方形区域 N ( X l , k u p ) N(X_{l}, k_{u p}) N(Xl,kup),重组过程如公式 X l ′ ′ = ∑ n = − r r ∑ m = − r r W l ′ ( n , m ) ⋅ X ( i + n , j + m ) \mathcal{X}_{l'}' = \sum_{n = -r}^{r}\sum_{m = -r}^{r}\mathcal{W}_{l'(n, m)}·\mathcal{X}_{(i + n, j + m)} Xl′′=∑n=−rr∑m=−rrWl′(n,m)⋅X(i+n,j+m)所示,其中 r = ⌊ k u p / 2 ⌋ r = \lfloor k_{u p} / 2\rfloor r=⌊kup/2⌋。通过重组核,区域 N ( X l , k u p ) N(X_{l}, k_{u p}) N(Xl,kup)中的每个像素根据特征的内容而不是位置的距离对上采样像素 l ′ l' l′的贡献不同,使得重组后的特征图的语义比原始特征图更强。

2.2、优势
- 大视野:与之前仅利用子像素邻域的工作不同,
CARAFE可以在大感受野内聚合上下文信息。 - 内容感知处理:
CARAFE能够根据实例特定的内容进行实时处理,为每个样本生成自适应的内核,而不是使用固定的内核。 - 轻量且计算快速:
CARAFE引入的计算开销很小,可以很容易地集成到现代网络架构中。 - 通用性和有效性:在对象检测、实例分割、语义分割、图像修复等广泛的密集预测任务中,CARAFE都能显著提升性能。
论文:https://arxiv.org/abs/1905.02188
源码:https://github.com/tiny-smart/dysample
三、CARAFE的实现代码
CARAFE模块的实现代码如下:
class CARAFE(nn.Module):def __init__(self, c, k_enc=3, k_up=5, c_mid=64, scale=2):""" The unofficial implementation of the CARAFE module.The details are in "https://arxiv.org/abs/1905.02188".Args:c: The channel number of the input and the output.c_mid: The channel number after compression.scale: The expected upsample scale.k_up: The size of the reassembly kernel.k_enc: The kernel size of the encoder.Returns:X: The upsampled feature map."""super(CARAFE, self).__init__()self.scale = scaleself.comp = Conv(c, c_mid)self.enc = Conv(c_mid, (scale * k_up) ** 2, k=k_enc, act=False)self.pix_shf = nn.PixelShuffle(scale)self.upsmp = nn.Upsample(scale_factor=scale, mode='nearest')self.unfold = nn.Unfold(kernel_size=k_up, dilation=scale,padding=k_up // 2 * scale)def forward(self, X):b, c, h, w = X.size()h_, w_ = h * self.scale, w * self.scaleW = self.comp(X) # b * m * h * wW = self.enc(W) # b * 100 * h * wW = self.pix_shf(W) # b * 25 * h_ * w_W = torch.softmax(W, dim=1) # b * 25 * h_ * w_X = self.upsmp(X) # b * c * h_ * w_X = self.unfold(X) # b * 25c * h_ * w_X = X.view(b, c, -1, h_, w_) # b * 25 * c * h_ * w_X = torch.einsum('bkhw,bckhw->bchw', [W, X]) # b * c * h_ * w_return X
四、添加步骤
4.1 修改common.py
此处需要修改的文件是models/common.py
common.py中定义了网络结构的通用模块,我们想要加入新的模块就只需要将模块代码放到这个文件内即可。
CARAFE模块添加后如下:

注意❗:在4.2小节中的yolo.py文件中需要声明的模块名称为:CARAFE。
4.2 修改yolo.py
此处需要修改的文件是models/yolo.py
yolo.py用于函数调用,我们只需要将common.py中定义的新的模块名添加到parse_model函数下即可。
在def parse_model(d, ch)中将CARAFE模块添加后如下:

elif m in [CARAFE]:args = [ch[f], *args[0:]]
五、yaml模型文件
5.1 模型改进
在代码配置完成后,配置模型的YAML文件。
此处以models/detect/yolov9-c.yaml为例,在同目录下创建一个用于自己数据集训练的模型文件yolov9-c-CARAFE.yaml。
将yolov9-c.yaml中的内容复制到yolov9-c-CARAFE.yaml文件下,修改nc数量等于自己数据中目标的数量。
📌 修改方法是将CARAFE模块替换YOLOv9颈部网络中的上采样模块nn.Upsample。
# YOLOv9# parameters
nc: 1 # number of classes
depth_multiple: 1.0 # model depth multiple
width_multiple: 1.0 # layer channel multiple
#activation: nn.LeakyReLU(0.1)
#activation: nn.ReLU()# anchors
anchors: 3# YOLOv9 backbone
backbone:[[-1, 1, Silence, []], # conv down[-1, 1, Conv, [64, 3, 2]], # 1-P1/2# conv down[-1, 1, Conv, [128, 3, 2]], # 2-P2/4# elan-1 block[-1, 1, RepNCSPELAN4, [256, 128, 64, 1]], # 3# avg-conv down[-1, 1, ADown, [256]], # 4-P3/8# elan-2 block[-1, 1, RepNCSPELAN4, [512, 256, 128, 1]], # 5# avg-conv down[-1, 1, ADown, [512]], # 6-P4/16# elan-2 block[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 7(可替换)# avg-conv down[-1, 1, ADown, [512]], # 8-P5/32# elan-2 block[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 9(可替换)]# YOLOv9 head
head:[# elan-spp block[-1, 1, SPPELAN, [512, 256]], # 10# up-concat merge[-1, 1, CARAFE, []],[[-1, 7], 1, Concat, [1]], # cat backbone P4# elan-2 block[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 13# up-concat merge[-1, 1, CARAFE, []],[[-1, 5], 1, Concat, [1]], # cat backbone P3# elan-2 block[-1, 1, RepNCSPELAN4, [256, 256, 128, 1]], # 16 (P3/8-small)# avg-conv-down merge[-1, 1, ADown, [256]],[[-1, 13], 1, Concat, [1]], # cat head P4# elan-2 block[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 19 (P4/16-medium)# avg-conv-down merge[-1, 1, ADown, [512]],[[-1, 10], 1, Concat, [1]], # cat head P5# elan-2 block[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 22 (P5/32-large)# multi-level reversible auxiliary branch# routing[5, 1, CBLinear, [[256]]], # 23[7, 1, CBLinear, [[256, 512]]], # 24[9, 1, CBLinear, [[256, 512, 512]]], # 25# conv down[0, 1, Conv, [64, 3, 2]], # 26-P1/2# conv down[-1, 1, Conv, [128, 3, 2]], # 27-P2/4# elan-1 block[-1, 1, RepNCSPELAN4, [256, 128, 64, 1]], # 28# avg-conv down fuse[-1, 1, ADown, [256]], # 29-P3/8[[23, 24, 25, -1], 1, CBFuse, [[0, 0, 0]]], # 30 # elan-2 block[-1, 1, RepNCSPELAN4, [512, 256, 128, 1]], # 31# avg-conv down fuse[-1, 1, ADown, [512]], # 32-P4/16[[24, 25, -1], 1, CBFuse, [[1, 1]]], # 33 # elan-2 block[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 34# avg-conv down fuse[-1, 1, ADown, [512]], # 35-P5/32[[25, -1], 1, CBFuse, [[2]]], # 36# elan-2 block[-1, 1, RepNCSPELAN4, [512, 512, 256, 1]], # 37# detection head# detect[[31, 34, 37, 16, 19, 22], 1, DualDDetect, [nc]], # DualDDetect(A3, A4, A5, P3, P4, P5)]
六、成功运行结果
分别打印网络模型可以看到CARAFE模块已经加入到模型中,并可以进行训练了。
yolov9-c-CARAFE:
from n params module arguments 0 -1 1 0 models.common.Silence [] 1 -1 1 1856 models.common.Conv [3, 64, 3, 2] 2 -1 1 73984 models.common.Conv [64, 128, 3, 2] 3 -1 1 212864 models.common.RepNCSPELAN4 [128, 256, 128, 64, 1] 4 -1 1 164352 models.common.ADown [256, 256] 5 -1 1 847616 models.common.RepNCSPELAN4 [256, 512, 256, 128, 1] 6 -1 1 656384 models.common.ADown [512, 512] 7 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 8 -1 1 656384 models.common.ADown [512, 512] 9 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 10 -1 1 656896 models.common.SPPELAN [512, 512, 256] 11 -1 1 90696 models.common.CARAFE [512] 12 [-1, 7] 1 0 models.common.Concat [1] 13 -1 1 3119616 models.common.RepNCSPELAN4 [1024, 512, 512, 256, 1] 14 -1 1 90696 models.common.CARAFE [512] 15 [-1, 5] 1 0 models.common.Concat [1] 16 -1 1 912640 models.common.RepNCSPELAN4 [1024, 256, 256, 128, 1] 17 -1 1 164352 models.common.ADown [256, 256] 18 [-1, 13] 1 0 models.common.Concat [1] 19 -1 1 2988544 models.common.RepNCSPELAN4 [768, 512, 512, 256, 1] 20 -1 1 656384 models.common.ADown [512, 512] 21 [-1, 10] 1 0 models.common.Concat [1] 22 -1 1 3119616 models.common.RepNCSPELAN4 [1024, 512, 512, 256, 1] 23 5 1 131328 models.common.CBLinear [512, [256]] 24 7 1 393984 models.common.CBLinear [512, [256, 512]] 25 9 1 656640 models.common.CBLinear [512, [256, 512, 512]] 26 0 1 1856 models.common.Conv [3, 64, 3, 2] 27 -1 1 73984 models.common.Conv [64, 128, 3, 2] 28 -1 1 212864 models.common.RepNCSPELAN4 [128, 256, 128, 64, 1] 29 -1 1 164352 models.common.ADown [256, 256] 30 [23, 24, 25, -1] 1 0 models.common.CBFuse [[0, 0, 0]] 31 -1 1 847616 models.common.RepNCSPELAN4 [256, 512, 256, 128, 1] 32 -1 1 656384 models.common.ADown [512, 512] 33 [24, 25, -1] 1 0 models.common.CBFuse [[1, 1]] 34 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 35 -1 1 656384 models.common.ADown [512, 512] 36 [25, -1] 1 0 models.common.CBFuse [[2]] 37 -1 1 2857472 models.common.RepNCSPELAN4 [512, 512, 512, 256, 1] 38[31, 34, 37, 16, 19, 22] 1 21542822 DualDDetect [1, [512, 512, 512, 256, 512, 512]]
yolov9-c-CARAFE summary: 982 layers, 51180982 parameters, 51180950 gradients, 239.2 GFLOPs
相关文章:
YOLOv9改进策略【Neck】| 使用CARAFE轻量级通用上采样算子
一、本文介绍 本文记录的是利用CARAFE上采样对YOLOv9的颈部网络进行改进的方法研究。YOLOv9采用传统的最近邻插值的方法,仅考虑子像素邻域,无法捕获密集预测任务所需的丰富语义信息,从而影响模型在密集预测任务中的性能。CARAFE通过在大感受…...
SpringMVC上
SpringMVC介绍 MVC模型 MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分: Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用…...
嵌入式软件--51单片机 DAY 2
一、数码管 1.数码管概况 2.设计 (1)硬件设计 我们可以通过阴极控制显示的位置,通过阳极控制显示的内容。两个数码管共有8个阴极引脚和16个阳极引脚,如果所有引脚都直接接入MCU,会造成MCU引脚的极大浪费。 为了节省…...
高精度加法,减法,乘法,除法
加法: 大整数该如何储存? 用数组储存: 把个位放在数下标为0的位置,十位放在数组下标为1的位置(也就是高位放在数组的后面) 因为这样,如果需要增加一位最高位,那我们就可以直接在…...
学习计划(大三上)
第二周 总结Java并发编程的艺术 学习JVM(博客文章) 第三周 学习JVM(博客文章) 图解TCP/IP 4章 第四周 完成简历项目 学习JVM(博客文章) 图解TCP/IP 4章 第五周 完成简历项目 深入学习RocketMQ底层…...
【第0006页 · 数组】寻找重复数
【前言】本文以及之后的一些题解都会陆续整理到目录中,若想了解全部题解整理,请看这里: 第0006页 寻找重复数 今天想讨论的一道题在 LeetCode 上评论也是颇为“不错”。有一说一,是道好题,不过我们还是得先理解了它才…...
移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——10.继承
1.继承的概念及定义 1.1继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保 持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象 程序设计的层…...
uniapp+vue3实现双通道透明MP4播放支持小程序和h5
双通道透明MP4视频播放的截图 以下是合成后结果,二个合并在一起进行播放 下载资源,打开运行直接使用看到效果 https://download.csdn.net/download/qq_40039641/89715780...
汇编:嵌入式软件架构学习资源
成为嵌入式软件架构设计师需要掌握多方面的知识,包括嵌入式系统、实时操作系统、硬件接口、软件设计模式等。 以下是一些推荐的博客和网站,可以帮助你深入学习嵌入式软件架构设计: ### 1. **Embedded.com** - **网址**: [Embedded.com](htt…...
python编程知识(实现数据加密和解密)
👨💻个人主页:开发者-曼亿点 👨💻 hallo 欢迎 点赞👍 收藏⭐ 留言📝 加关注✅! 👨💻 本文由 曼亿点 原创 👨💻 收录于专栏:…...
如何使div居中?CSS居中终极指南
前言 长期以来,如何在父元素中居中对齐一个元素,一直是一个让人头疼的问题,随着 CSS 的发展,越来越多的工具可以用来解决这个难题,五花八门的招式一大堆,这篇博客,旨在帮助你理解不同的居中方法…...
Redis 篇-深入了解分布式锁 Redisson 原理(可重入原理、可重试原理、主从一致性原理、解决超时锁失效)
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 本章目录 1.0 基于 Redis 实现的分布式锁存在的问题 2.0 Redisson 功能概述 3.0 Redisson 具体使用 4.0 Redisson 可重入锁原理 5.0 Redisson 锁重试原理 6.0 Redisson WatchDo…...
PostgreSQL中的多版本并发控制(MVCC)深入解析
引言 PostgreSQL作为一款强大的开源关系数据库管理系统,以其高性能、高可靠性和丰富的功能特性而广受欢迎。在并发控制方面,PostgreSQL采用了多版本并发控制(MVCC)机制,该机制为数据库提供了高效的数据访问和更新能力…...
SpringBoot项目-实现简单的CRUD功能和分页查询
背景 本博文主要是创建了一个新的SpringBoot项目,实现基本的增删改查,分页查询,带条件的分页查询功能。是方便初学者学习后端项目的一个比较清晰明了的实践代码,读者可根据博文,从自己动手创建一个新的SpringBoot项目…...
CCF编程能力等级认证GESP—C++2级—20240907
CCF编程能力等级认证GESP—C2级—20240907 单选题(每题 2 分,共 30 分)判断题(每题 2 分,共 20 分)编程题 (每题 25 分,共 50 分)数位之和小杨的矩阵 单选题(每题 2 分,共…...
C语言手撕实战代码_二叉排序树(二叉搜索树)_构建_删除_插入操作详解
二叉排序树习题1.设计算法构建一棵二叉排序树(又称二叉搜索树BST)2.查找二叉排序树中结点为x的结点所在的层数3.删除二叉排序树T中值为x的结点4.查找二叉排序树中所有小于key的关键字5.编写算法,将一棵二叉树t分解成两棵二叉排序树t1和t2,使得t1中的所有…...
YC教父的创始人模式VS职业经理人模式:AI时代的独立开发者崛起
近年来,由风投资助的创始人模式因其相对较低的入门门槛而在创业圈内广受欢迎。然而,真正的挑战在于独立开发者(一人商业)模式。随着AI技术的飞速发展,一人商业模式有望成为未来的主流。本文将探讨独立开发者的工作范围…...
[SUCTF 2019]Pythonginx
给了源码 app.route(/getUrl, methods[GET, POST]) def getUrl():url request.args.get("url")host parse.urlparse(url).hostnameif host suctf.cc:return "我扌 your problem? 111"parts list(urlsplit(url))host parts[1]if host suctf.cc:retu…...
省市县相关校验sql随笔
1.层级校验 要判断一个给定的省、市、区(县)名字是否符合正确的层级关系,假设你的表结构如下: CREATE TABLE regions (id INT PRIMARY KEY,name VARCHAR(255),parent_id INT, -- 指向上一级区域的id,例如市的parent_id指向省的…...
uniapp ios sticky定位,内部 u-tabs(包含scroll-view)消失问题
uniapp中用sticky定位时,元素内部如果有scroll-view,ios在触发bounce机制时,scroll-view的元素会消失,解决方法是页面上包一层高度为100vh的scroll-view <scroll-view style"height: 100vh;" scroll-y scrolltolowe…...
Cursor Pro破解工具:如何通过开源技术方案实现AI编程助手无限制使用?
Cursor Pro破解工具:如何通过开源技术方案实现AI编程助手无限制使用? 【免费下载链接】cursor-free-vip [Support 0.45](Multi Language 多语言)自动注册 Cursor Ai ,自动重置机器ID , 免费升级使用Pro 功能…...
科研人必备:用浏览器插件给IEEEXplore做个‘小手术’,告别20秒加载
科研效率革命:用浏览器插件精准优化IEEEXplore访问体验 每次打开IEEEXplore文献库,那个转不停的加载图标是否让你焦躁不安?作为每天要与学术数据库打交道的科研工作者,20秒的等待时间足以打断思考流,降低工作效率。这背…...
Android tinyalsa深度解析之pcm_params_get_periods_min调用流程与实战(一百七十三)
简介: CSDN博客专家、《Android系统多媒体进阶实战》作者 博主新书推荐:《Android系统多媒体进阶实战》🚀 Android Audio工程师专栏地址: Audio工程师进阶系列【原创干货持续更新中……】🚀 Android多媒体专栏地址&a…...
抖音音乐高效解决方案:douyin-downloader批量下载与智能管理指南
抖音音乐高效解决方案:douyin-downloader批量下载与智能管理指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fall…...
s2-proGPU部署方案:多模型共存时s2-pro显存隔离与QoS保障策略
s2-proGPU部署方案:多模型共存时s2-pro显存隔离与QoS保障策略 1. 引言 在GPU服务器上同时运行多个AI模型已成为常态,但这也带来了显存资源竞争和性能波动的问题。本文将详细介绍如何在多模型共存环境下,为s2-pro语音合成模型实现显存隔离与…...
【花雕学编程】Arduino BLDC 之使用互补滤波进行姿态控制的机器人
从专业工程视角来看,基于Arduino、使用互补滤波进行姿态控制的BLDC(无刷直流电机)机器人,是一个典型的嵌入式实时闭环控制系统。它集成了传感器数据融合、控制算法和电机驱动,广泛应用于对姿态稳定性有要求的场景。 1、…...
开源字体实用指南:Poppins字体家族的全方位应用策略
开源字体实用指南:Poppins字体家族的全方位应用策略 【免费下载链接】Poppins Poppins, a Devanagari Latin family for Google Fonts. 项目地址: https://gitcode.com/gh_mirrors/po/Poppins 价值定位:如何让开源字体成为项目的视觉资产&#x…...
终极指南:如何用NSC_BUILDER一键搞定Switch游戏文件管理
终极指南:如何用NSC_BUILDER一键搞定Switch游戏文件管理 【免费下载链接】NSC_BUILDER Nintendo Switch Cleaner and Builder. A batchfile, python and html script based in hacbuild and Nuts python libraries. Designed initially to erase titlerights encryp…...
Amlogic S9XXX设备系统改造完全指南:从入门到进阶
Amlogic S9XXX设备系统改造完全指南:从入门到进阶 【免费下载链接】amlogic-s9xxx-armbian Supports running Armbian on Amlogic, Allwinner, and Rockchip devices. Support a311d, s922x, s905x3, s905x2, s912, s905d, s905x, s905w, s905, s905l, rk3588, rk35…...
计算机毕业设计springboot基于Web的健身会员管理系统 基于SpringBoot的健身房智能化运营服务平台 SpringBoot框架下的健身俱乐部会员服务与课程预约系统
计算机毕业设计springboot基于Web的健身会员管理系统e7cr4n62(配套有源码 程序 mysql数据库 论文) 本套源码可以在文本联xi,先看具体系统功能演示视频领取,可分享源码参考。 随着全民健身意识的提升和健康管理需求的日益增长,传统…...
