在嵌入式设备上部署计算机视觉模型一直是AI落地的重要挑战之一。树莓派5作为一款性能强劲的单板计算机,搭配轻量化的YOLOv5模型,能够实现实时目标检测任务。本文将详细介绍如何将自定义训练的YOLOv5模型转换为ONNX格式,并在树莓派5上完成部署的全过程。
这个方案特别适合需要低成本、便携式计算机视觉解决方案的场景,比如智能交通监控、工业质检、安防巡检等。相比云端方案,本地部署避免了网络延迟和隐私问题;相比传统PC方案,树莓派5的功耗和体积优势明显。
在开始转换前,确保你的YOLOv5模型已经完成训练并达到预期效果。训练时需要注意:
提示:训练时可使用--rect参数进行矩形训练,能提升推理速度但可能略微降低精度
转换命令看似简单,但有几个关键参数需要特别注意:
bash复制python export.py --weights your.pt --include onnx --opset 12 --simplify --dynamic
参数说明:
转换完成后,建议使用Netron工具检查模型结构,确认:
为提升树莓派上的推理速度,可以考虑以下优化手段:
bash复制python -m onnxruntime.tools.convert_onnx_models_to_ort --input best.onnx --output best.ort --float16
python复制from onnxruntime.quantization import quantize_dynamic
quantize_dynamic("best.onnx", "best_quant.onnx")
实测在树莓派5上,FP16量化可使推理速度提升15-20%,而INT8量化能提升30-40%但精度损失较大。
推荐使用Raspberry Pi OS 64-bit (Bullseye)系统,相比32位系统能更好发挥树莓派5的性能:
bash复制# 更新系统
sudo apt update && sudo apt upgrade -y
# 安装基础依赖
sudo apt install -y python3-pip python3-venv libopenblas-dev libatlas-base-dev
创建专用虚拟环境能避免依赖冲突,以下是更完善的配置流程:
bash复制# 创建并激活虚拟环境
python3 -m venv ~/yolo/venv --system-site-packages
source ~/yolo/venv/bin/activate
# 安装优化版的NumPy
pip install numpy==1.24.3 --no-binary numpy
# 核心依赖
pip install opencv-python==4.5.5.64 \
onnxruntime==1.16.0 \
pillow==9.5.0
注意:使用--no-binary numpy会从源码编译,能更好地适配树莓派硬件
针对树莓派5的ARM架构,可以启用特定优化:
python复制# 在代码中添加以下配置
so = ort.SessionOptions()
so.intra_op_num_threads = 4 # 使用4个CPU核心
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
session = ort.InferenceSession("best.onnx", sess_options=so)
原始代码可以扩展为支持更多配置选项:
python复制def create_session(model_path, provider='cpu'):
providers_map = {
'cpu': ['CPUExecutionProvider'],
'gpu': ['CUDAExecutionProvider', 'CPUExecutionProvider'],
'tensorrt': ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
}
so = ort.SessionOptions()
so.intra_op_num_threads = 4
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
try:
return ort.InferenceSession(model_path, providers=providers_map[provider], sess_options=so)
except Exception as e:
print(f"Error loading model: {e}")
return None
原始预处理方法可以优化为保持长宽比的resize:
python复制def preprocess(img, target_size=640):
h, w = img.shape[:2]
scale = min(target_size/h, target_size/w)
new_h, new_w = int(h * scale), int(w * scale)
resized = cv2.resize(img, (new_w, new_h))
padded = np.full((target_size, target_size, 3), 114, dtype=np.uint8)
padded[:new_h, :new_w] = resized
# 归一化并转换格式
padded = padded.astype(np.float32) / 255.0
padded = padded.transpose(2, 0, 1)
return np.expand_dims(padded, axis=0), scale
改进后的后处理考虑了原始图像比例:
python复制def postprocess(outputs, original_shape, scale):
predictions = outputs[0][0]
conf_threshold = 0.5
detections = []
for det in predictions:
x, y, w, h = det[0:4]
conf = det[4]
class_id = np.argmax(det[5:])
if conf > conf_threshold:
# 还原到原始图像坐标
x1 = int((x - w/2) / scale)
y1 = int((y - h/2) / scale)
x2 = int((x + w/2) / scale)
y2 = int((y + h/2) / scale)
detections.append({
'class_id': int(class_id),
'confidence': float(conf),
'bbox': [x1, y1, x2, y2]
})
return detections
对于视频流处理,可以使用生产者-消费者模式:
python复制from threading import Thread
from queue import Queue
class VideoProcessor:
def __init__(self, model_path):
self.frame_queue = Queue(maxsize=10)
self.session = create_session(model_path)
def capture_frames(self, camera_id=0):
cap = cv2.VideoCapture(camera_id)
while True:
ret, frame = cap.read()
if not ret: break
if self.frame_queue.full():
self.frame_queue.get()
self.frame_queue.put(frame)
cap.release()
def process_frames(self):
while True:
frame = self.frame_queue.get()
# 处理帧...
树莓派内存有限,需要注意:
python复制def optimized_detect(frame):
# 使用内存视图
frame_view = frame.view()
input_data = preprocess(frame_view)
# 复用输出缓冲区
if not hasattr(optimized_detect, 'output_buffer'):
optimized_detect.output_buffer = np.zeros((1, 25200, 85), dtype=np.float32)
session.run(None, {'images': input_data}, {'output0': optimized_detect.output_buffer})
return postprocess([optimized_detect.output_buffer], frame.shape)
将部署好的模型用于实时交通标志检测:
python复制def traffic_sign_detection():
processor = VideoProcessor("traffic_sign.onnx")
capture_thread = Thread(target=processor.capture_frames, args=(0,))
process_thread = Thread(target=processor.process_frames)
capture_thread.start()
process_thread.start()
while True:
result = processor.get_latest_result()
display_result(result)
if cv2.waitKey(1) == ord('q'):
break
capture_thread.join()
process_thread.join()
调整模型用于工业零件检测:
错误现象:
code复制[ONNXRuntimeError] : 1 : FAIL : Load model from best.onnx failed
解决方案:
python -c "import onnx; onnx.load('best.onnx')"优化建议:
raspi-config超频CPUsudo systemctl set-default multi-user.target调试步骤:
虽然树莓派5的GPU不支持CUDA,但可以通过以下方式进一步优化:
实现更复杂的应用场景:
python复制class MultiModelSystem:
def __init__(self):
self.detector = create_session("yolov5s.onnx")
self.classifier = create_session("resnet18.onnx")
def pipeline(self, img):
detections = detect_objects(img, self.detector)
for det in detections:
roi = crop_roi(img, det['bbox'])
cls_result = classify(roi, self.classifier)
det.update(cls_result)
return detections
对于需要更高精度的场景:
这套部署方案在树莓派5上实测可以达到8-12FPS的推理速度(YOLOv5s模型),足够应对大多数实时检测场景。关键在于根据具体应用需求平衡精度和速度,必要时可以通过模型剪枝、知识蒸馏等技术进一步优化模型。