【树莓派 Zero 2W】物体识别
本文介绍了树莓派 Zero 2W 开发板实现物体识别的项目设计,包括准备工作、环境搭建、ONNX 模型、流程图、工程代码以及效果演示等。
参与活动:e络盟——惊喜盲盒等您开:e络盟全站新客福利活动等您参与 .
项目介绍
准备工作:包括所需 Python 环境以及软件包的安装部署等;
ONNX 模型:使用 YOLOv5n 等轻量化 ONNX 模型实现物体识别;
工程代码:包括流程图、关键推理及执行代码;
效果演示:展示不同场景的物体识别效果。
ONNX
Open Neural Network Exchange (ONNX) 是一个开放的生态系统,为 AI 开发人员提供支持 随着项目的发展选择正确的工具。


ONNX 为 AI 模型(包括深度学习和传统 ML)提供开源格式。
定义了一个可扩展的计算图模型,以及内置运算符和标准的定义数据类型。
详见:onnx/onnx: Open standard for machine learning interoperability .
YOLO
YOLO(You Only Look Once)是一种流行的目标检测算法,它以其快速和准确性而闻名。YOLO算法的核心思想是将目标检测问题转化为一个回归问题,通过一个单一的卷积神经网络(CNN)直接从输入图像预测边界框和类别概率。


YOLO 的主要优点是速度快,能够实时处理视频流。它还具有较强的通用性,能够迁移到新的领域。
YOLOv5n
YOLOv5n 代表了目标检测方法的进步,源于 Ultralytics 开发的 YOLOv5 模型的基础架构,调整优化了模型架构,从而在目标检测任务中实现了更高的精度-速度权衡。
YOLOv5 为那些在研究和实际应用中寻求稳健解决方案的人们提供了高效的解决方案。


详见:YOLOv5 SOTA Realtime Instance Segmentation · ultralytics .
准备工作
系统安装及环境搭建详见:【树莓派Zero2W】介绍、系统安装、人脸检测 .
硬件连接
若采用 SSH 远程登录操作,则仅需连接电源供电即可;
若采用本地登录,则需连接 HDMI 视频流传输线、USB 键盘连接线等;

库安装
执行指令 sudo apt install python3-opencv 安装 OpenCV
安装解析 ONNX 模型所需的 onnxruntime 库,终端执行
sudo apt install python3-pip sudo pip3 install onnxruntime --break-system-packages
若遇到问题,可根据 Linux 系统版本和 Python 版本,下载 对应的 *.whl 文件并安装
wget https://pypi.tuna.tsinghua.edu.cn/packages/1c/a1/428ee29c6eaf09a6f6be56f836213f104618fb35ac6cc586ff0f477263eb/onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl sudo pip install onnxruntime-1.23.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl --break-system-packages
通过 uname -a 查询Linux系统信息,python --version 指令查询Python版本。
模型下载
下载 模型文件至本地 ./model 文件;
mkdir model cd model wget https://github.com/ultralytics/yolov5/releases/download/v7.0/yolov5n.onnx
流程图
采用 YOLOv5n 轻量化的 onnx 模型实现物体识别的本地板端推理,并弹窗显示识别结果。

工程代码
执行 touch or_onnx.py 指令新建文件,并使用 nano 文本编辑器添加如下代码
#!/usr/bin/env python3
import cv2, numpy as np, onnxruntime as ort
from pathlib import Path
# ---------- 命令行参数 ----------
parser = argparse.ArgumentParser(description='YOLOv5n-ONNX 物体识别')
parser.add_argument('image', nargs='?', default='./img/road.jpg',
help='待检测图片路径,缺省为 ./img/road.jpg')
args = parser.parse_args()
IMG_PATH = args.image
# ---------- 物体类别 ------------
COCO_NAMES = ["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 generate_distinct_colors(num_colors):
colors = []
# 使用预定义的明显不同的颜色
base_colors = [
(255, 0, 0), # 红色
(0, 255, 0), # 绿色
(0, 0, 255), # 蓝色
(255, 255, 0), # 黄色
(255, 0, 255), # 紫色
(0, 255, 255), # 青色
(255, 128, 0), # 橙色
(128, 0, 255), # 深紫色
(0, 255, 128), # 青绿色
(128, 255, 0), # 黄绿色
(255, 0, 128), # 粉红色
(0, 128, 255), # 天蓝色
]
# 为常见类别设置固定颜色
fixed_colors = {
'person': (255, 0, 0), # 红色
'car': (0, 255, 0), # 绿色
'truck': (255, 255, 0), # 黄色
'bus': (255, 128, 0), # 橙色
'bicycle': (255, 0, 255), # 紫色
'motorcycle': (128, 0, 255), # 深紫色
'traffic light': (0, 0, 255), # 蓝色
'stop sign': (0, 0, 255), # 蓝色
'parking meter': (0, 255, 255), # 青色
}
# 生成所有类别的颜色
for i, name in enumerate(COCO_NAMES):
if name in fixed_colors:
colors.append(fixed_colors[name])
else:
color_idx = (i - len(fixed_colors)) % len(base_colors)
colors.append(base_colors[color_idx])
return colors
# --------- 文字颜色 ------------
def get_contrast_text_color(background_color):
"""
根据背景色返回对比度最高的文字颜色(黑色或白色)
使用相对亮度公式计算
"""
r, g, b = background_color
# 计算相对亮度(ITU-R BT.709标准)
luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
# 如果亮度大于128,使用黑色文字,否则使用白色文字
return (0, 0, 0) if luminance > 128 else (255, 255, 255)
CLASS_COLORS = generate_distinct_colors(len(COCO_NAMES))
MODEL_PATH = "model/yolov5n.onnx"
#IMG_PATH = "img/road.jpg"
# ---------- 1. 加载模型 ----------
sess = ort.InferenceSession(MODEL_PATH, providers=['CPUExecutionProvider'])
in_name = sess.get_inputs()[0].name
out_name = sess.get_outputs()[0].name
print(f"模型输入类型: {sess.get_inputs()[0].type}")
print(f"模型输出类型: {sess.get_outputs()[0].type}")
# ---------- 2. 读图 ----------
img0 = cv2.imread(IMG_PATH)
if img0 is None:
raise FileNotFoundError(f"无法找到图片: {IMG_PATH}")
# ---------- 3. 预处理 ----------
blob = cv2.resize(img0, (640, 640))
blob = blob.astype(np.float16) / 255.0
blob = blob.transpose(2, 0, 1)[None]
print(f"输入数据形状: {blob.shape}, 数据类型: {blob.dtype}")
# ---------- 4. 推理 ----------
outputs = sess.run([out_name], {in_name: blob})[0]
outputs = np.squeeze(outputs)
print(f"输出形状: {outputs.shape}")
print(f"输出范围: min={np.min(outputs):.6f}, max={np.max(outputs):.6f}")
if outputs.dtype == np.float16:
outputs = outputs.astype(np.float32)
# ---------- 5. 后处理 ----------
boxes, scores, class_ids = [], [], []
valid_detections = 0
for pred in outputs:
pred = pred.astype(np.float32)
x, y, w, h, conf = pred[:5]
if conf < 0.25:
continue
if not all(np.isfinite([x, y, w, h, conf])):
continue
if w <= 1 or h <= 1:
continue
class_probs = pred[5:]
cls_id = np.argmax(class_probs)
cls_conf = class_probs[cls_id]
final_conf = conf * cls_conf
# 坐标转换
if x > 1.0 or y > 1.0:
x_rel, y_rel, w_rel, h_rel = x/640.0, y/640.0, w/640.0, h/640.0
else:
x_rel, y_rel, w_rel, h_rel = x, y, w, h
valid_detections += 1
scores.append(float(final_conf))
class_ids.append(int(cls_id))
# 映射回原图坐标
x_scale = img0.shape[1] / 640
y_scale = img0.shape[0] / 640
x1 = int((x_rel - w_rel / 2) * img0.shape[1])
y1 = int((y_rel - h_rel / 2) * img0.shape[0])
x2 = int((x_rel + w_rel / 2) * img0.shape[1])
y2 = int((y_rel + h_rel / 2) * img0.shape[0])
x1 = max(0, min(x1, img0.shape[1]))
y1 = max(0, min(y1, img0.shape[0]))
x2 = max(0, min(x2, img0.shape[1]))
y2 = max(0, min(y2, img0.shape[0]))
width = x2 - x1
height = y2 - y1
if width > 0 and height > 0:
boxes.append([x1, y1, width, height])
print(f"总检测数: {len(outputs)}, 有效检测: {valid_detections}, 最终边界框: {len(boxes)}")
# ---------- 6. NMS和绘制结果 ----------
if boxes:
indices = cv2.dnn.NMSBoxes(boxes, scores, score_threshold=0.25, nms_threshold=0.45)
if indices is not None and len(indices) > 0:
print(f"NMS后保留 {len(indices)} 个检测结果")
# 存储检测结果用于排序输出
detection_results = []
for i in indices.flatten():
x, y, w, h = boxes[i]
cls_id = class_ids[i]
score = scores[i]
class_name = COCO_NAMES[cls_id]
detection_results.append((class_name, score, [x, y, w, h]))
# 获取类别颜色
bg_color = CLASS_COLORS[cls_id]
# 计算对比文字颜色
text_color = get_contrast_text_color(bg_color)
# 绘制边界框(细线)
cv2.rectangle(img0, (x, y), (x + w, y + h), bg_color, 1)
# 创建标签文本
label = f"{class_name} {score:.2f}"
# 计算文本尺寸
font = cv2.FONT_HERSHEY_SIMPLEX
font_scale = 0.5
thickness = 1
(text_width, text_height), baseline = cv2.getTextSize(label, font, font_scale, thickness)
# 标签背景位置
text_x = x
text_y = y - 5 if y - 5 > text_height else y + text_height + 5
# 确保标签不超出图像上边界
if text_y - text_height - baseline < 0:
text_y = y + text_height + 5
# 绘制标签背景(填充矩形)
cv2.rectangle(img0,
(text_x, text_y - text_height - baseline),
(text_x + text_width, text_y + baseline),
bg_color, -1)
# 绘制标签文本(自适应颜色)
cv2.putText(img0, label, (text_x, text_y), font, font_scale,
text_color, thickness, cv2.LINE_AA)
# 按置信度排序并输出
detection_results.sort(key=lambda x: x[1], reverse=True)
for class_name, score, bbox in detection_results:
x, y, w, h = bbox
print(f"检测到: {class_name} ({score:.3f}) at [{x}, {y}, {w}, {h}]")
else:
print("NMS后没有保留任何检测结果")
else:
print("没有有效的检测边界框")
# ---------- 7. 显示结果 ----------
cv2.imshow("YOLOv5n_ONNX", img0)
cv2.waitKey(0)
cv2.destroyAllWindows()保存代码。
效果演示
终端执行 python or_onnx.py ./img/road.jpg 弹窗显示物体识别结果;


同时终端打印信息


更多场景
办公


道路


户外


运动


交通


总结
本文介绍了树莓派 Zero 2W 开发板实现物体识别的板端推理的项目设计,包括准备工作、环境搭建、ONNX 模型、流程图、工程代码以及效果演示等,为相关产品在 AI 视觉领域的开发设计和快速应用提供了参考。
我要赚赏金
