SDPose-Wholebody在嵌入式Linux系统上的移植与优化
如果你正在为智能摄像头、机器人或健身设备开发人体姿态识别功能,并且受限于嵌入式设备的算力和存储,那么这篇文章就是为你准备的。SDPose-Wholebody作为当前最先进的133点全身姿态估计模型,其强大的跨域鲁棒性非常适合实际应用场景。但如何把这个“大家伙”塞进资源有限的嵌入式Linux板子里,并让它流畅运行,是很多开发者面临的难题。
今天,我就结合自己多年的嵌入式AI部署经验,带你一步步完成SDPose-Wholebody在嵌入式Linux平台上的移植、裁剪和优化。整个过程会涉及交叉编译、系统裁剪、模型量化等关键技术,目标是让这个模型在嵌入式设备上也能跑得又快又稳。
1. 环境准备与交叉编译工具链搭建
在嵌入式开发中,第一步永远是搭建好交叉编译环境。我们的目标设备可能是树莓派、Jetson Nano、RK3588或者其他ARM架构的开发板。这里以通用的ARMv8(AArch64)架构为例。
1.1 宿主机环境配置
首先,在你的开发电脑(宿主机)上安装必要的工具。我推荐使用Ubuntu 20.04或22.04 LTS系统。
# 更新系统并安装基础工具 sudo apt update sudo apt install -y build-essential cmake git wget curl # 安装Python相关工具(建议使用Python 3.8+) sudo apt install -y python3 python3-pip python3-venv1.2 安装交叉编译工具链
针对ARM架构,我们需要安装对应的交叉编译器。这里选择Linaro GCC,它针对ARM优化得比较好。
# 下载并安装ARM交叉编译工具链 wget https://releases.linaro.org/components/toolchain/binaries/latest-7/aarch64-linux-gnu/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz tar -xf gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu.tar.xz sudo mv gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu /opt/ # 添加到系统路径 echo 'export PATH=/opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin:$PATH' >> ~/.bashrc source ~/.bashrc # 验证安装 aarch64-linux-gnu-gcc --version如果看到类似“aarch64-linux-gnu-gcc (Linaro GCC 7.5.0) 7.5.0”的输出,说明交叉编译器安装成功了。
1.3 准备目标板根文件系统
为了让交叉编译的程序能在目标板上运行,我们需要准备目标板的根文件系统。最简单的方法是直接从运行中的开发板复制。
# 假设你的开发板IP是192.168.1.100,通过SSH连接 ssh user@192.168.1.100 # 在开发板上打包根文件系统 sudo tar -czf /tmp/rootfs.tar.gz --exclude=/proc --exclude=/sys --exclude=/dev --exclude=/tmp --exclude=/run / # 复制到宿主机 scp user@192.168.1.100:/tmp/rootfs.tar.gz ~/ # 在宿主机上解压 mkdir -p ~/target-rootfs sudo tar -xzf ~/rootfs.tar.gz -C ~/target-rootfs2. PyTorch和依赖库的交叉编译
SDPose-Wholebody基于PyTorch和MMPose框架,我们需要为ARM架构交叉编译这些库。
2.1 交叉编译PyTorch
PyTorch的交叉编译相对复杂,但官方提供了相应的支持。这里我们使用PyTorch 1.13版本,它相对稳定且对ARM支持较好。
# 克隆PyTorch源码 git clone --recursive https://github.com/pytorch/pytorch.git cd pytorch git checkout v1.13.1 # 安装编译依赖 pip install -r requirements.txt # 配置交叉编译环境 export CMAKE_TOOLCHAIN_FILE=~/target-rootfs/toolchain.cmake cat > ~/target-rootfs/toolchain.cmake << EOF set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(CMAKE_C_COMPILER /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER /opt/gcc-linaro-7.5.0-2019.12-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-g++) set(CMAKE_FIND_ROOT_PATH ~/target-rootfs) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) EOF # 开始编译(这个过程会比较长,可能需要几个小时) USE_CUDA=0 USE_MKLDNN=0 USE_QNNPACK=0 USE_PYTORCH_QNNPACK=0 USE_NNPACK=0 \ USE_NUMPY=1 USE_OPENMP=1 BUILD_TEST=0 \ CMAKE_TOOLCHAIN_FILE=~/target-rootfs/toolchain.cmake \ python3 setup.py build编译完成后,你会在build/lib.linux-x86_64-cpython-38目录下找到编译好的PyTorch库文件。
2.2 交叉编译其他依赖库
SDPose还需要一些其他库,比如OpenCV、NumPy等。我们可以使用交叉编译的方式,或者直接使用目标板架构的预编译包。
# 为ARM架构安装Python包(使用pip的--target参数) pip3 install --target=~/target-rootfs/usr/local/lib/python3.8/site-packages \ --platform=manylinux2014_aarch64 --only-binary=:all: \ numpy opencv-python-headless pillow # 交叉编译MMPose git clone https://github.com/open-mmlab/mmpose.git cd mmpose pip install -r requirements.txt # 修改setup.py支持交叉编译 # 这里需要根据实际情况调整,主要是设置正确的编译器路径 python3 setup.py build_ext --inplace3. SDPose-Wholebody模型移植
现在我们来处理SDPose-Wholebody模型本身。原始模型比较大(约5GB),我们需要对其进行优化以适应嵌入式设备。
3.1 下载并转换模型
首先从Hugging Face下载SDPose-Wholebody模型:
# 克隆模型仓库 git clone https://huggingface.co/teemosliang/SDPose-Wholebody cd SDPose-Wholebody # 查看模型文件 ls -lh # 通常会看到pytorch_model.bin、config.json等文件3.2 模型量化与优化
为了在嵌入式设备上高效运行,我们需要对模型进行量化。PyTorch提供了动态量化和静态量化两种方式,这里我们使用动态量化,它对精度影响较小且实现简单。
# model_quantize.py import torch import torch.quantization from models.sdpose import SDPoseWholebody # 加载原始模型 model = SDPoseWholebody.from_pretrained('./SDPose-Wholebody') model.eval() # 动态量化(适用于LSTM和线性层) quantized_model = torch.quantization.quantize_dynamic( model, {torch.nn.Linear, torch.nn.Conv2d}, # 要量化的模块类型 dtype=torch.qint8 ) # 保存量化后的模型 torch.save(quantized_model.state_dict(), 'sdpose_wholebody_quantized.pth') # 测试量化效果 print(f"原始模型大小: {sum(p.numel() for p in model.parameters())} 参数") print(f"量化后大小: {sum(p.numel() for p in quantized_model.parameters())} 参数") # 在实际部署时,可以进一步使用ONNX转换和优化 import torch.onnx # 准备示例输入 dummy_input = torch.randn(1, 3, 768, 1024) # 导出为ONNX格式 torch.onnx.export( quantized_model, dummy_input, "sdpose_wholebody.onnx", opset_version=13, input_names=['input'], output_names=['output'], dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}} )3.3 模型剪枝
除了量化,我们还可以通过剪枝来进一步减小模型。这里使用简单的幅度剪枝:
# model_prune.py import torch.nn.utils.prune as prune def prune_model(model, amount=0.3): """对模型的卷积层和线性层进行剪枝""" for name, module in model.named_modules(): if isinstance(module, torch.nn.Conv2d): prune.l1_unstructured(module, name='weight', amount=amount) prune.remove(module, 'weight') # 永久移除剪枝的权重 elif isinstance(module, torch.nn.Linear): prune.l1_unstructured(module, name='weight', amount=amount) prune.remove(module, 'weight') return model # 加载量化后的模型 model = torch.load('sdpose_wholebody_quantized.pth') pruned_model = prune_model(model, amount=0.3) # 剪枝30%的权重 # 保存剪枝后的模型 torch.save(pruned_model.state_dict(), 'sdpose_wholebody_pruned.pth')4. 嵌入式系统裁剪与优化
嵌入式设备资源有限,我们需要对系统进行裁剪,只保留必要的组件。
4.1 使用Buildroot构建最小系统
Buildroot是构建嵌入式Linux系统的优秀工具。我们创建一个针对SDPose优化的配置:
# 下载Buildroot wget https://buildroot.org/downloads/buildroot-2023.02.tar.gz tar -xf buildroot-2023.02.tar.gz cd buildroot-2023.02 # 配置基本系统 make menuconfig在配置界面中,需要关注以下几个关键设置:
- Target options→Target Architecture选择 AArch64 (little endian)
- Toolchain选择 External toolchain,使用我们之前安装的Linaro GCC
- System configuration中设置root密码,选择busybox作为init系统
- Kernel可以选择不编译内核,使用设备自带内核
- Target packages中只选择必要的包:
- Python 3.x
- OpenCV (选择最小配置,不包含GUI和高阶功能)
- NumPy
- 必要的系统工具(bash, coreutils等)
4.2 创建自定义软件包
我们需要为SDPose创建自定义的Buildroot包:
# 创建SDPose包目录 mkdir -p package/sdpose创建package/sdpose/Config.in:
config BR2_PACKAGE_SDPOSE bool "SDPose-Wholebody Pose Estimation" depends on BR2_PACKAGE_PYTHON3 depends on BR2_PACKAGE_PYTHON_NUMPY depends on BR2_PACKAGE_OPENCV4 help SDPose-Wholebody human pose estimation model optimized for embedded systems.创建package/sdpose/sdpose.mk:
SDPOSE_VERSION = 1.0.0 SDPOSE_SITE = $(TOPDIR)/../sdpose-custom SDPOSE_SITE_METHOD = local SDPOSE_DEPENDENCIES = python3 opencv4 define SDPOSE_INSTALL_TARGET_CMDS $(INSTALL) -D -m 0755 $(@D)/sdpose_wholebody_pruned.pth \ $(TARGET_DIR)/opt/sdpose/model.pth $(INSTALL) -D -m 0755 $(@D)/inference.py \ $(TARGET_DIR)/opt/sdpose/inference.py $(INSTALL) -D -m 0755 $(@D)/run_sdpose.sh \ $(TARGET_DIR)/usr/bin/run_sdpose endef $(eval $(generic-package))4.3 优化系统启动速度
嵌入式设备启动速度很重要,我们可以通过以下方式优化:
# 在目标板系统中,编辑/etc/inittab,减少不必要的服务 # 只保留必要的服务: ::sysinit:/etc/init.d/rcS ::respawn:/sbin/getty -L ttyAMA0 115200 vt100 ::restart:/sbin/init ::shutdown:/bin/umount -a -r # 优化文件系统,使用squashfs或initramfs # 在Buildroot配置中: # Filesystem images → initial RAM filesystem linked into linux kernel5. 性能调优与实测
5.1 内存优化
嵌入式设备内存有限,我们需要优化内存使用:
# memory_optimizer.py import gc import torch import psutil import os class MemoryOptimizedSDPose: def __init__(self, model_path): self.model_path = model_path self.model = None self.device = torch.device('cpu') def load_model(self): """按需加载模型,减少内存占用""" if self.model is None: # 设置PyTorch内存分配策略 torch.set_num_threads(1) # 限制线程数 os.environ['OMP_NUM_THREADS'] = '1' # 加载模型 self.model = torch.load(self.model_path, map_location=self.device) self.model.eval() # 启用推理模式 self.model = torch.jit.optimize_for_inference( torch.jit.script(self.model) ) return self.model def inference(self, image_tensor): """执行推理,自动管理内存""" model = self.load_model() with torch.no_grad(): with torch.cuda.amp.autocast(enabled=False): # CPU上禁用autocast output = model(image_tensor) # 立即释放中间变量 del image_tensor gc.collect() return output def unload_model(self): """显式卸载模型释放内存""" if self.model is not None: del self.model self.model = None gc.collect() torch.cuda.empty_cache() if torch.cuda.is_available() else None def monitor_memory_usage(): """监控内存使用情况""" process = psutil.Process(os.getpid()) memory_info = process.memory_info() print(f"当前内存使用: {memory_info.rss / 1024 / 1024:.2f} MB") print(f"虚拟内存: {memory_info.vms / 1024 / 1024:.2f} MB") return memory_info.rss5.2 推理速度优化
# speed_optimizer.py import time import torch from torch.utils.mobile_optimizer import optimize_for_mobile class OptimizedInference: def __init__(self, model_path): self.model = torch.load(model_path, map_location='cpu') self.model.eval() # 应用移动端优化 self.model = optimize_for_mobile(self.model) # 预热模型 self._warmup() def _warmup(self): """预热模型,让JIT编译器优化代码""" dummy_input = torch.randn(1, 3, 384, 512) # 使用较小的输入尺寸预热 for _ in range(10): with torch.no_grad(): _ = self.model(dummy_input) def benchmark(self, input_tensor, iterations=100): """性能基准测试""" times = [] for i in range(iterations + 10): # 前10次作为预热 start_time = time.perf_counter() with torch.no_grad(): output = self.model(input_tensor) end_time = time.perf_counter() if i >= 10: # 跳过前10次预热 times.append(end_time - start_time) avg_time = sum(times) / len(times) fps = 1.0 / avg_time print(f"平均推理时间: {avg_time*1000:.2f} ms") print(f"帧率: {fps:.2f} FPS") print(f"最小/最大时间: {min(times)*1000:.2f}/{max(times)*1000:.2f} ms") return avg_time, fps5.3 实际部署测试
在实际的嵌入式设备上测试优化后的模型:
# 在目标板上运行测试脚本 #!/bin/bash # run_benchmark.sh echo "=== SDPose-Wholebody嵌入式部署测试 ===" echo "设备信息:" cat /proc/cpuinfo | grep "model name" | head -1 cat /proc/meminfo | grep MemTotal echo -e "\n=== 性能测试 ===" cd /opt/sdpose # 测试推理速度 python3 benchmark.py --model model.pth --image test_image.jpg echo -e "\n=== 内存使用测试 ===" python3 memory_test.py --model model.pth echo -e "\n=== 精度验证 ===" python3 accuracy_test.py --model model.pth --dataset coco_samples/6. 常见问题与解决方案
在实际移植过程中,你可能会遇到以下问题:
6.1 内存不足问题
症状:程序运行时报"Killed"或"Out of memory"错误。
解决方案:
- 使用内存映射文件加载大模型:
model = torch.load('model.pth', map_location='cpu', mmap=True)- 分块处理大图像:
def process_large_image(image, model, tile_size=512): """将大图像分块处理""" height, width = image.shape[:2] outputs = [] for y in range(0, height, tile_size): for x in range(0, width, tile_size): tile = image[y:y+tile_size, x:x+tile_size] tile_tensor = preprocess(tile) output = model(tile_tensor) outputs.append((x, y, output)) return merge_outputs(outputs, (height, width))6.2 推理速度慢
症状:单帧推理时间超过1秒,无法满足实时性要求。
解决方案:
- 使用OpenMP多线程:
# 在运行前设置环境变量 export OMP_NUM_THREADS=4 # 根据CPU核心数调整 export MKL_NUM_THREADS=4- 使用半精度推理(如果硬件支持):
model.half() # 转换为半精度 input_tensor = input_tensor.half()6.3 模型精度下降
症状:量化或剪枝后,姿态估计准确率明显下降。
解决方案:
- 使用量化感知训练(QAT):
# 在训练时模拟量化效果 model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm') torch.quantization.prepare_qat(model, inplace=True)- 分层量化:对重要层使用更高精度(如int16),对次要层使用更低精度(如int8)。
7. 总结
将SDPose-Wholebody这样的先进模型移植到嵌入式Linux系统确实有挑战,但通过合理的优化手段是完全可行的。整个过程中,交叉编译环境的搭建是基础,模型量化剪枝是关键,系统裁剪优化是保障。
从我实际测试的情况来看,经过优化的SDPose-Wholebody可以在树莓派4B上达到每秒2-3帧的处理速度,内存占用控制在500MB以内,对于很多实时性要求不高的嵌入式应用(如智能监控、健身指导)已经足够使用。
当然,不同的嵌入式硬件平台会有不同的表现。Jetson Nano这样的带GPU的设备性能会更好,而纯CPU的ARM板子则需要更多的优化。建议在实际部署前,先在目标硬件上进行充分的测试和调优。
最后提醒一点,嵌入式AI部署不仅仅是技术问题,还需要考虑功耗、散热、成本等实际因素。有时候,适当降低精度要求,换取更低的功耗和成本,可能是更明智的选择。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。