一、为什么选树莓派 5 做边缘 AI1.1 树莓派 5 硬件规格规格Raspberry Pi 5Raspberry Pi 4SoCBCM2712 (16nm)BCM2711 (28nm)CPUCortex-A76 × 4 2.4GHzCortex-A72 × 4 1.8GHzGPUVideoCore VIIVideoCore VI内存4/8/16 GB LPDDR4X2/4/8 GB LPDDR4AI 算力INT8~5 TOPSCPUGPU~2 TOPS功耗5-12W3-7W价格~$80 (8GB)~$55 (8GB)Pi 5 的 Cortex-A76 比 Pi 4 的 A72 单核性能提升约 60%对 TFLite 的 NEON 指令集优化支持更好是边缘 AI 部署的性价比之选。1.2 为什么不用 Coral TPU / Jetson Nano方案价格算力易用性适用场景Raspberry Pi 5$80~5 TOPS高通用边缘计算Coral USB TPU$80 Pi4 TOPS中专用 AI 加速Jetson Nano$150472 GFLOPS中GPU 推理Orange Pi 5 (RK3588)$806 TOPS (NPU)中NPU 推理选择 Pi 5 的理由生态最成熟、文档最全、TFLite 支持最好、不需要额外硬件。对于 YOLOv8n 级别的模型Pi 5 CPU 推理已足够实时。二、环境准备2.1 硬件清单注意Pi 5 必须用 27W 电源5V/5A普通 15W 手机充电器会导致 CPU 降频推理性能打 7 折。2.2 系统安装# 1. 使用 Raspberry Pi Imager 烧录 64-bit Raspberry Pi OS # 选择Raspberry Pi OS (64-bit) with desktop # 版本Bookworm (Debian 12) # 2. 首次启动后更新系统 sudo apt update sudo apt full-upgrade -y sudo rpi-eeprom-update -a sudo reboot # 3. 确认 CPU 频率验证未降频 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq # 应输出 2400000 (2.4GHz) # 4. 安装散热风扇控制Pi 5 官方 Cooler sudo rpi-config # Performance Options → Fan → Enable # 5. 验证温度推理时不应超过 75°C vcgencmd measure_temp2.3 Python 环境# Pi 5 自带 Python 3.11 python3 --version # Python 3.11.2 # 创建虚拟环境 python3 -m venv ~/yolo-env source ~/yolo-env/bin/activate # 安装核心依赖 pip install --upgrade pip pip install tflite-runtime2.14.0 # TFLite 推理引擎 pip install numpy1.24.3 pip install opencv-python4.8.1.78 # 图像处理 摄像头 pip install pillow10.0.0 pip install ultralytics8.1.0 # YOLOv8 训练和导出可选在PC上用2.4 验证 TFLite 安装import tflite_runtime.interpreter as tflite import numpy as np # 创建一个简单模型验证 TFLite interpreter tflite.Interpreter(model_path/dev/null) # 预期报错OpenCV... no such file → 说明 tflite 库本身加载正常 # 检查支持的 delegate print(Available delegates:) try: interpreter tflite.Interpreter( model_pathtest.tflite, experimental_delegates[tflite.load_delegate(libXNNPACKDelegate.so)] ) print( - XNNPACK: ✓) except: print( - XNNPACK: not available) try: interpreter tflite.Interpreter( model_pathtest.tflite, experimental_delegates[tflite.load_delegate(libethosu_delegate.so)] ) print( - Ethos-U: ✓) except: print( - Ethos-U: not available (Pi 5 无 NPU正常))三、模型转换全流程3.1 转换链路3.2 Step 1: 准备 YOLOv8 模型PC 上操作# PC 端非 Pi使用 ultralytics 导出 from ultralytics import YOLO # 加载预训练模型 model YOLO(yolov8n.pt) # nano 版本适合边缘部署 # 在自定义数据集上微调可选 # model.train(datacoco128.yaml, epochs50, imgsz640) # 导出为 ONNX 格式 model.export( formatonnx, imgsz640, opset12, # TFLite 兼容性最好的 opset simplifyTrue, # 简化模型图 dynamicFalse, # 固定输入尺寸有利于量化优化 halfFalse # ONNX 导出不用 FP16 ) # 生成 yolov8n.onnx3.3 Step 2: ONNX → TFLite (FP32)import onnx import tensorflow as tf from onnx_tf.backend import prepare import onnx_tf # 1. 加载 ONNX 模型 onnx_model onnx.load(yolov8n.onnx) # 2. 转换为 TensorFlow 格式 tf_rep prepare(onnx_model) tf_rep.export_graph(yolov8n_tf) # 3. 转换为 TFLite (FP32) converter tf.lite.TFLiteConverter.from_saved_model(yolov8n_tf) converter.target_spec.supported_ops [ tf.lite.OpsSet.TFLITE_BUILTINS, tf.lite.OpsSet.SELECT_TF_OPS # 允许部分 TF ops 回退 ] tflite_model converter.convert() with open(yolov8n_fp32.tflite, wb) as f: f.write(tflite_model) print(fFP32 模型大小: {len(tflite_model) / 1024 / 1024:.1f} MB)3.4 Step 3: INT8 量化关键步骤INT8 量化需要一批校准数据calibration data让量化器了解权重和激活值的分布import numpy as np import cv2 import os import tensorflow as tf # 1. 准备校准数据集用 COCO val 或自定义数据集100-500 张即可 calibration_dir calibration_images/ image_paths [os.path.join(calibration_dir, f) for f in os.listdir(calibration_dir) if f.endswith((.jpg, .png))] def representative_dataset(): 生成校准数据生成器 for img_path in image_paths[:200]: # 读取并预处理图像 img cv2.imread(img_path) img cv2.resize(img, (640, 640)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 # 归一化到 [0, 1] img np.expand_dims(img, axis0) # 添加 batch 维度 yield [img] # 2. 配置量化转换器 converter tf.lite.TFLiteConverter.from_saved_model(yolov8n_tf) converter.optimizations [tf.lite.Optimize.DEFAULT] converter.representative_dataset representative_dataset # 3. 确保 fully INT8 量化而非混合精度 converter.target_spec.supported_ops [tf.lite.OpsSet.TFLITE_BUILTINS_INT8] converter.inference_input_type tf.int8 # 输入也为 INT8 converter.inference_output_type tf.int32 # 输出保持 INT32YOLOv8 输出结构需要 # 4. 执行量化 tflite_quantized converter.convert() with open(yolov8n_int8.tflite, wb) as f: f.write(tflite_quantized) print(fINT8 模型大小: {len(tflite_quantized) / 1024 / 1024:.1f} MB) print(f压缩比: {len(tflite_model) / len(tflite_quantized):.1f}x)3.5 模型大小对比模型格式文件大小说明yolov8n.pt (PyTorch FP32)6.3 MB原始训练权重yolov8n.onnx (ONNX)6.2 MB中间格式yolov8n_fp32.tflite6.5 MBTFLite FP32yolov8n_int8.tflite1.9 MBINT8 量化压缩 3.4x四、Pi 5 推理部署4.1 推理脚本#!/usr/bin/env python3 YOLOv8 TFLite 推理脚本 - Raspberry Pi 5 支持摄像头实时检测和图片检测 import tflite_runtime.interpreter as tflite import numpy as np import cv2 import time import argparse class YOLOv8TFLite: def __init__(self, model_path, num_threads4): 初始化 TFLite 推理器 # 配置 XNNPACK delegate 多线程 self.interpreter tflite.Interpreter( model_pathmodel_path, num_threadsnum_threads, experimental_delegates[ tflite.load_delegate(libXNNPACKDelegate.so) ] ) self.interpreter.allocate_tensors() # 获取输入输出信息 self.input_details self.interpreter.get_input_details() self.output_details self.interpreter.get_output_details() self.input_shape self.input_details[0][shape] # [1, 640, 640, 3] self.input_dtype self.input_details[0][dtype] # int8 or float32 # COCO 类别标签 (80 类) self.labels self._load_labels() def _load_labels(self): 加载 COCO 标签 return [ person, bicycle, car, motorcycle, airplane, bus, train, truck, boat, traffic light, fire hydrant, stop sign, parking meter, bench, bird, cat, dog, horse, sheep, cow, elephant, bear, zebra, giraffe, backpack, umbrella, handbag, tie, suitcase, frisbee, skis, snowboard, sports ball, kite, baseball bat, baseball glove, skateboard, surfboard, tennis racket, bottle, wine glass, cup, fork, knife, spoon, bowl, banana, apple, sandwich, orange, broccoli, carrot, hot dog, pizza, donut, cake, chair, couch, potted plant, bed, dining table, toilet, tv, laptop, mouse, remote, keyboard, cell phone, microwave, oven, toaster, sink, refrigerator, book, clock, vase, scissors, teddy bear, hair drier, toothbrush ] def preprocess(self, img): 预处理图像 # 1. Resize 到 640x640letterbox 保持比例 h, w img.shape[:2] scale min(640 / h, 640 / w) new_h, new_w int(h * scale), int(w * scale) resized cv2.resize(img, (new_w, new_h)) # 2. 创建 640x640 画布居中放置 canvas np.full((640, 640, 3), 114, dtypenp.uint8) y_offset (640 - new_h) // 2 x_offset (640 - new_w) // 2 canvas[y_offset:y_offsetnew_h, x_offset:x_offsetnew_w] resized # 3. BGR → RGB canvas cv2.cvtColor(canvas, cv2.COLOR_BGR2RGB) # 4. 归一化 if self.input_dtype np.int8: # INT8 模型量化到 [-128, 127] canvas canvas.astype(np.float32) / 255.0 canvas (canvas - 0.5) / 0.5 # 归一化到 [-1, 1] scale, zero_point self.input_details[0][quantization] canvas (canvas / scale zero_point).astype(np.int8) else: # FP32 模型 canvas canvas.astype(np.float32) / 255.0 # 5. 添加 batch 维度 canvas np.expand_dims(canvas, axis0) return canvas, scale, x_offset, y_offset def postprocess(self, output, scale, x_offset, y_offset, conf_thres0.5, iou_thres0.45): 后处理NMS 坐标还原 # YOLOv8 输出格式: [1, 84, 8400] → 转为 [8400, 84] # 84 4 (x,y,w,h) 80 (类别置信度) predictions output[0].T # [8400, 84] # 过滤低置信度 scores predictions[:, 4:].max(axis1) mask scores conf_thres predictions predictions[mask] scores scores[mask] if len(predictions) 0: return [], [], [] # 获取类别 class_ids predictions[:, 4:].argmax(axis1) # 坐标转换 (cx, cy, w, h) → (x1, y1, x2, y2) boxes predictions[:, :4].copy() boxes[:, 0] - boxes[:, 2] / 2 # x1 boxes[:, 1] - boxes[:, 3] / 2 # y1 boxes[:, 2] boxes[:, 0] boxes[:, 2] # x2 boxes[:, 3] boxes[:, 1] boxes[:, 3] # y2 # NMS indices cv2.dnn.NMSBoxes( boxes.tolist(), scores.tolist(), conf_thres, iou_thres ) if len(indices) 0: return [], [], [] indices indices.flatten() boxes boxes[indices] scores scores[indices] class_ids class_ids[indices] # 坐标还原到原图 boxes[:, [0, 2]] (boxes[:, [0, 2]] - x_offset) / scale boxes[:, [1, 3]] (boxes[:, [1, 3]] - y_offset) / scale return boxes.astype(int), scores, class_ids def detect(self, img): 单帧检测 # 预处理 input_data, scale, x_offset, y_offset self.preprocess(img) # 推理 self.interpreter.set_tensor(self.input_details[0][index], input_data) self.interpreter.invoke() output self.interpreter.get_tensor(self.output_details[0][index]) # 后处理 boxes, scores, class_ids self.postprocess( output, scale, x_offset, y_offset ) return boxes, scores, class_ids def draw_results(self, img, boxes, scores, class_ids): 绘制检测结果 for box, score, cls_id in zip(boxes, scores, class_ids): x1, y1, x2, y2 box label f{self.labels[cls_id]}: {score:.2f} # 画框 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) # 画标签 cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) return img def run_camera(model_path, num_threads4): 摄像头实时检测 detector YOLOv8TFLite(model_path, num_threads) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) cap.set(cv2.CAP_PROP_FPS, 30) fps_counter 0 fps_timer time.time() current_fps 0 print(按 q 退出s 保存截图) while True: ret, frame cap.read() if not ret: break # 检测 start time.time() boxes, scores, class_ids detector.detect(frame) inference_time (time.time() - start) * 1000 # 绘制 frame detector.draw_results(frame, boxes, scores, class_ids) # FPS 计算 fps_counter 1 if time.time() - fps_timer 1.0: current_fps fps_counter fps_counter 0 fps_timer time.time() # 显示信息 info fFPS: {current_fps} | Inference: {inference_time:.1f}ms | Threads: {num_threads} cv2.putText(frame, info, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imshow(YOLOv8 Detection, frame) key cv2.waitKey(1) 0xFF if key ord(q): break elif key ord(s): cv2.imwrite(fcapture_{int(time.time())}.jpg, frame) print(截图已保存) cap.release() cv2.destroyAllWindows() if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--model, defaultyolov8n_int8.tflite, helpTFLite 模型路径) parser.add_argument(--threads, typeint, default4, help推理线程数) args parser.parse_args() run_camera(args.model, args.threads)4.2 运行推理# INT8 模型 4 线程 python3 yolo_tflite.py --model yolov8n_int8.tflite --threads 4 # FP32 模型对比 python3 yolo_tflite.py --model yolov8n_fp32.tflite --threads 4五、性能优化与压测5.1 不同配置下的推理性能测试环境Pi 5 (8GB) Raspberry Pi OS 64-bit 主动散热配置模型单帧推理 (ms)FPSCPU 占用FP32, 1 线程yolov8n_fp32.tflite85.211.725%FP32, 4 线程yolov8n_fp32.tflite62.815.985%INT8, 1 线程yolov8n_int8.tflite52.319.125%INT8, 4 线程yolov8n_int8.tflite33.230.188%INT8, 4 线程, XNNPACKyolov8n_int8.tflite32.830.590%5.2 优化手段逐一拆解优化 1INT8 量化INT8 量化对 YOLOv8n 的精度影响很小mAP 仅降 2.1%但速度提升近 2 倍。优化 2多线程# TFLite 多线程通过 num_threads 参数控制 interpreter tflite.Interpreter( model_pathyolov8n_int8.tflite, num_threads4 # Pi 5 有 4 个 A76 核心 )注意超过 4 线程没有额外收益Pi 5 只有 4 核反而因线程切换开销略微变慢。优化 3XNNPACK Delegate# XNNPACK 是 TFLite 的高性能算子库利用 ARM NEON 指令集 interpreter tflite.Interpreter( model_pathyolov8n_int8.tflite, num_threads4, experimental_delegates[ tflite.load_delegate(libXNNPACKDelegate.so) ] )无 XNNPACK → 有 XNNPACK 效果INT8, 4线程 33.2 ms → 32.8 ms加速 1.2%XNNPACK 对 INT8 模型提升不大因为 INT8 量化本身已使用了优化的算子但对 FP32 模型提升约 8-12%。优化 4输入分辨率输入分辨率推理时间 (ms)FPSmAP0.5640×64033.230.10.364480×48022.544.40.328320×32012.878.10.271如果场景对精度要求不高如简单的行人检测降低分辨率到 480×480 可以跑到 44 FPS。优化 5Governor 频率锁定# 默认 governor 是 ondemand推理时可能降频 # 锁定最高频率可获得更稳定的性能 echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor # 验证 cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq # 应输出 2400000Governor平均推理 (ms)最差推理 (ms)FPS 稳定性ondemand (默认)33.252.8差performance32.834.1好schedutil33.038.5中5.3 温度与功耗30 分钟连续推理测试时间CPU 温度功耗FPS0 min48°C6.2W30.55 min62°C7.1W30.310 min68°C7.5W30.115 min71°C7.8W29.820 min72°C7.8W29.930 min73°C7.9W29.8主动散热下Pi 5 稳定在 73°C没有热降频FPS 稳定在 29-30。六、完整踩坑记录坑 1ONNX → TFLite 转换报错 Unsupported op: NonMaxSuppression原因YOLOv8 的 ONNX 导出包含 NMS 算子但 TFLite 不支持。解决导出 ONNX 时不带 NMS在推理后处理中用 OpenCV 的cv2.dnn.NMSBoxes替代。# 导出时排除 NMS model.export(formatonnx, nmsFalse)坑 2INT8 量化后输出全为 0原因校准数据预处理与推理预处理不一致。量化器在校准时看到的数据分布与实际推理时不同导致量化参数错误。解决确保representative_dataset()中的预处理逻辑与推理时的preprocess()完全一致。# 校准时的预处理必须与推理时完全一致 def representative_dataset(): for img_path in image_paths: img cv2.imread(img_path) img cv2.resize(img, (640, 640)) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img.astype(np.float32) / 255.0 # 必须和推理时的归一化一致 img (img - 0.5) / 0.5 yield [np.expand_dims(img, axis0)]坑 3cv2.VideoCapture 打开摄像头很慢3-5 秒原因V4L2 后端初始化慢。解决指定 CAP_V4L2 后端并设置缓冲区为 1减少延迟。 cap cv2.VideoCapture(0, cv2.CAP_V4L2) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # 最小缓冲降低延迟坑 4TFLite 运行一段时间后 OOM原因interpreter.invoke()每次调用不会自动释放中间张量持续运行导致内存增长。解决每帧推理后手动调用interpreter.allocate_tensors()重置或复用输入输出 tensor。# 方案1定期重置每 1000 帧 if frame_count % 1000 0: interpreter.reset_all_variables() interpreter.allocate_tensors() # 方案2不在循环中重复创建 interpreter在外部初始化一次 # ✅ 正确interpreter 在 __init__ 中创建一次 # ❌ 错误每次 detect() 都新建 interpreter坑 5Pi 5 USB 3.0 摄像头不识别原因Pi 5 的 USB 3.0 控制器与部分 UVC 摄像头兼容性问题。解决# 方案1插 USB 2.0 口 # 方案2在 /boot/firmware/config.txt 中添加 dtoverlayusb3-disconnect-quirk # 方案3使用 CSI 摄像头Pi Camera Module 3 # 需要安装 libcamera sudo apt install -y python3-libcamera坑 6INT8 模型精度严重下降mAP 从 0.37 降到 0.18原因YOLOv8 的输出层包含大量分支部分分支对量化敏感。全 INT8 量化会导致检测框回归精度下降。解决使用混合精度量化——输出层保持 FP32其余层 INT8。# 混合精度量化允许部分 op 保持 FP32 converter.target_spec.supported_ops [ tf.lite.OpsSet.TFLITE_BUILTINS_INT8, tf.lite.OpsSet.TFLITE_BUILTINS # 允许 FP32 回退 ] # 不设置 inference_output_type让量化器自动决定 # converter.inference_output_type tf.int32 ← 删除这行 # 精度对比 # 全 INT8: mAP0.5 0.18太差 # 混合精度 INT8: mAP0.5 0.364可接受 # FP32: mAP0.5 0.372七、性能优化检查清单八、从 30fps 到更高下一步优化方向方向预期 FPS难度说明降低分辨率 480×480~44低已验证精度降 10%降低分辨率 320×320~78低精度降 27%仅简单场景可用Coral USB TPU 加速~100中需要额外硬件 Edge TPU 编译器RKNN NPUOrange Pi 5~80中换平台使用 RK3588 NPU模型剪枝channel prune~40高需要 PyTorch 剪枝 重训练TensorRT on Jetson~120中换平台Jetson Nano/Orin九、总结本文完整记录了从 PyTorch 到 TFLite INT8 的端到端部署流程在树莓派 5 上实现 YOLOv8n 30.5 FPS 实时检测。核心经验INT8 量化是边缘部署的必选项模型缩小 3.4x速度提升 1.9x精度仅降 2.1%混合精度量化保护输出层全 INT8 会 mAP 暴跌混合精度是安全方案4 线程 XNNPACK performance governor三件套缺一不可校准数据预处理必须与推理一致这是 INT8 量化最常见的坑27W 电源 主动散热Pi 5 推理时功耗约 8W15W 电源会导致降频下一篇预告本专栏下一篇文章将深入ESP32-S3 TinyML 实战在 512KB 内存上跑通关键词检测全流程——如何在资源极度受限的微控制器上做机器学习。如果觉得有帮助点个赞和收藏关注专栏「AI大模型大数据硬件编程」不错过后续更新。