1. 项目概述
在计算机视觉领域,YOLOv5作为当前最流行的实时目标检测算法之一,以其优异的性能和易用性广受开发者青睐。本文将基于Ubuntu 22.04系统,详细记录使用Intel RealSense D435i深度相机配合YOLOv5实现目标检测的全流程。不同于常规教程,我会特别分享在实际部署过程中遇到的各类"坑"及其解决方案,这些经验都是经过多次实践验证的宝贵心得。
本教程适合具备以下基础的开发者:
- 熟悉Linux基础命令操作
- 了解Python编程基础
- 对计算机视觉有基本认知
如果你已经按照第一部分完成了YOLOv5环境部署,那么现在我们可以直接进入相机配置和模型训练环节。特别说明,本文使用的D435i相机是一款支持RGB、深度和红外三模数据采集的设备,这为后续可能的多模态融合检测提供了数据基础。
2. 深度相机配置与图像采集
2.1 开发环境准备
在开始相机配置前,我强烈建议先安装一款得心应手的代码编辑器。经过对比测试,VSCode在Python开发体验和资源占用上表现优异。以下是经过优化的安装步骤:
bash复制# 安装VSCode官方版本(非snap版)
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
sudo install -o root -g root -m 644 packages.microsoft.gpg /usr/share/keyrings/
sudo sh -c 'echo "deb [arch=amd64 signed-by=/usr/share/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
sudo apt update
sudo apt install code
安装完成后,建议安装以下扩展:
- Python (Microsoft官方)
- Pylance
- RealSense SDK 2.0
注意:使用USB3.0接口是D435i正常工作的关键。我曾尝试在USB2.0接口上使用,结果帧率直接下降60%,且深度数据质量显著降低。
2.2 RealSense驱动安装
Intel官方提供了两种安装方式:软件包安装和源码编译。对于大多数用户,推荐使用软件包安装方式:
bash复制# 添加Intel官方源
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-key F6E65AC044F831AC80A06380C8B3A55A6F3EFCDE
sudo add-apt-repository "deb https://librealsense.intel.com/Debian/apt-repo $(lsb_release -cs) main" -u
# 安装核心组件(建议按顺序安装)
sudo apt-get install librealsense2-dkms # 内核模块,必须最先安装
sudo apt-get install librealsense2-utils # 包含realsense-viewer等工具
sudo apt-get install librealsense2-dev # 开发头文件和库
sudo apt-get install librealsense2-gl # 3D可视化支持
安装完成后,验证驱动是否正常工作:
bash复制realsense-viewer
如果看到如图所示的界面,说明驱动安装成功:

2.3 图像采集程序实现
以下是我优化后的图像采集脚本,解决了原版中的几个关键问题:
python复制#!/usr/bin/env python3
import pyrealsense2 as rs
import numpy as np
import cv2
import os
from datetime import datetime
class RealSenseCapture:
def __init__(self, save_dir="~/yolov5/pictures"):
self.pipeline = rs.pipeline()
self.config = rs.config()
self.save_dir = os.path.expanduser(save_dir)
os.makedirs(self.save_dir, exist_ok=True)
# 配置流参数
self._setup_streams()
def _setup_streams(self):
"""配置各数据流参数"""
# 颜色流
self.config.enable_stream(rs.stream.color,
848, 480, rs.format.bgr8, 15)
# 红外流(左右)
self.config.enable_stream(rs.stream.infrared,
1, 848, 480, rs.format.y8, 15)
self.config.enable_stream(rs.stream.infrared,
2, 848, 480, rs.format.y8, 15)
# 深度流
self.config.enable_stream(rs.stream.depth,
848, 480, rs.format.z16, 15)
def _create_alignment(self):
"""创建对齐对象"""
align_to = rs.stream.color
return rs.align(align_to)
def capture(self):
align = self._create_alignment()
try:
# 启动管道
profile = self.pipeline.start(self.config)
# 跳过前30帧(让自动曝光稳定)
for _ in range(30):
self.pipeline.wait_for_frames()
while True:
frames = self.pipeline.wait_for_frames()
aligned_frames = align.process(frames)
# 获取各帧数据
color_frame = aligned_frames.get_color_frame()
depth_frame = aligned_frames.get_depth_frame()
left_ir = frames.get_infrared_frame(1)
right_ir = frames.get_infrared_frame(2)
if not all([color_frame, depth_frame, left_ir, right_ir]):
continue
# 转换为numpy数组
color_image = np.asanyarray(color_frame.get_data())
depth_image = np.asanyarray(depth_frame.get_data())
left_image = np.asanyarray(left_ir.get_data())
right_image = np.asanyarray(right_ir.get_data())
# 显示图像
self._show_images(color_image, depth_image,
left_image, right_image)
key = cv2.waitKey(1)
if key == 27: # ESC退出
break
elif key == ord('s'): # 保存当前帧
self._save_frames(color_image, depth_image,
left_image, right_image)
finally:
self.pipeline.stop()
cv2.destroyAllWindows()
def _show_images(self, color, depth, left, right):
"""显示各通道图像"""
depth_colormap = cv2.applyColorMap(
cv2.convertScaleAbs(depth, alpha=0.03),
cv2.COLORMAP_JET)
# 水平拼接显示
top_row = np.hstack((color, depth_colormap))
bottom_row = np.hstack((left, right))
display = np.vstack((top_row, bottom_row))
cv2.imshow('RealSense Capture', display)
def _save_frames(self, color, depth, left, right):
"""保存当前帧到文件"""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# 为每种数据类型创建子目录
for dtype, frame in [('color', color),
('depth', depth),
('left_ir', left),
('right_ir', right)]:
subdir = os.path.join(self.save_dir, dtype)
os.makedirs(subdir, exist_ok=True)
filename = f"{dtype}_{timestamp}.png"
cv2.imwrite(os.path.join(subdir, filename), frame)
print(f"Frames saved to {self.save_dir}")
if __name__ == "__main__":
capture = RealSenseCapture()
capture.capture()
关键改进点:
- 增加了自动创建保存目录功能
- 使用类封装,提高代码复用性
- 添加了帧跳过机制,避免初始不稳定帧
- 改进图像显示方式,四合一同时显示
- 使用时间戳命名文件,避免冲突
- 自动创建各数据类型的子目录
避坑指南:如果在运行时报错"Could not load the Qt platform plugin xcb",这是Qt平台插件问题,解决方案:
bash复制sudo apt install libxcb-xinerama0 export QT_DEBUG_PLUGINS=1
3. 数据集构建与标注
3.1 数据集目录结构设计
合理的目录结构对后续训练至关重要。我推荐以下结构:
code复制mydata/
├── images/
│ ├── train/ # 训练集图片
│ ├── val/ # 验证集图片
│ └── test/ # 测试集图片
└── labels/
├── train/ # 训练集标签
├── val/ # 验证集标签
└── test/ # 测试集标签
数据划分建议比例:
- 训练集:70%
- 验证集:20%
- 测试集:10%
经验分享:对于小样本数据集(<1000张),可以适当提高验证集比例到30%,这对防止过拟合很有帮助。
3.2 高效标注技巧
使用labelImg进行标注时,有几个提高效率的技巧:
-
快捷键自定义:
- 修改
~/.labelImgSettings.pkl可以自定义快捷键 - 我常用的配置:
python复制{ 'createRectBox': 'w', # 默认就是w 'nextImage': 'd', 'prevImage': 'a', 'verifyImage': 'space', # 标记为已验证 'deleteSelectedItem': 'Delete' }
- 修改
-
批量预处理:
在标注前,可以先使用OpenCV进行自动预处理:python复制import cv2 import os def preprocess_images(input_dir, output_dir): os.makedirs(output_dir, exist_ok=True) for img_name in os.listdir(input_dir): img_path = os.path.join(input_dir, img_name) img = cv2.imread(img_path) # 示例处理:自动对比度限制 lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB) l, a, b = cv2.split(lab) clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8)) limg = clahe.apply(l) processed = cv2.merge((limg,a,b)) processed = cv2.cvtColor(processed, cv2.COLOR_LAB2BGR) cv2.imwrite(os.path.join(output_dir, img_name), processed) preprocess_images('raw_images', 'processed_images') -
标签质量控制:
- 使用
--autosave参数启动labelImg可以自动保存 - 定期检查标签一致性:
bash复制python -m labelImg --check --dir path/to/labels
- 使用
4. YOLOv5模型训练与优化
4.1 配置文件详解
toy.yaml文件是训练的核心配置文件,以下是我使用的增强版配置:
yaml复制# 数据集路径配置
path: /home/user/yolov5/mydata # 数据集根目录
train: images/train # 训练集相对路径
val: images/val # 验证集相对路径
test: images/test # 测试集相对路径
# 类别信息
nc: 3 # 类别数
names: ['person', 'dog', 'cat'] # 类别名称
# 高级参数(非官方但实用)
augment:
hsv_h: 0.015 # 色调增强幅度
hsv_s: 0.7 # 饱和度增强幅度
hsv_v: 0.4 # 明度增强幅度
degrees: 10.0 # 旋转角度范围
translate: 0.1 # 平移比例
scale: 0.5 # 缩放比例
shear: 0.0 # 剪切角度
perspective: 0.0001 # 透视变换
flipud: 0.0 # 上下翻转概率
fliplr: 0.5 # 左右翻转概率
mosaic: 1.0 # mosaic数据增强概率
mixup: 0.0 # mixup增强概率
4.2 训练参数调优
经过多次实验,我总结出以下参数组合效果最佳:
bash复制python train.py \
--data mydata/toy.yaml \
--weights yolov5s.pt \
--epochs 300 \
--batch-size 16 \
--img 640 \
--device 0 \
--workers 4 \
--optimizer AdamW \
--rect \
--cache ram # 使用RAM缓存加速
关键参数说明:
--rect: 启用矩形训练,可减少约30%的显存占用--cache: 使用RAM缓存可使训练速度提升2-3倍--optimizer AdamW: 对小数据集效果优于SGD
4.3 训练监控与调参
-
使用TensorBoard监控:
bash复制
tensorboard --logdir runs/train重点关注以下指标:
- train/box_loss
- train/obj_loss
- val/box_loss
- val/obj_loss
- mAP@0.5
-
早停策略:
修改utils/callbacks.py中的EarlyStopping类:python复制class EarlyStopping: def __init__(self, patience=50, min_delta=0.001): self.patience = patience self.min_delta = min_delta self.best_fitness = 0.0 self.best_epoch = 0 self.possible_stop = False -
学习率调整:
在data/hyps/hyp.scratch-low.yaml中调整:yaml复制lr0: 0.01 # 初始学习率 lrf: 0.1 # 最终学习率 = lr0 * lrf warmup_epochs: 5.0 warmup_momentum: 0.8 warmup_bias_lr: 0.1
5. 模型测试与部署
5.1 测试脚本优化
原始测试脚本功能有限,我开发了增强版测试脚本:
python复制import argparse
import cv2
import torch
import numpy as np
from pathlib import Path
from models.common import DetectMultiBackend
from utils.general import non_max_suppression, scale_boxes
from utils.augmentations import letterbox
from utils.plots import Annotator, colors
class YOLOv5Tester:
def __init__(self, weights, conf_thres=0.25, iou_thres=0.45):
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
self.model = DetectMultiBackend(weights, device=self.device)
self.stride = self.model.stride
self.conf_thres = conf_thres
self.iou_thres = iou_thres
def preprocess(self, img, img_size=640):
"""图像预处理"""
img = letterbox(img, img_size, stride=self.stride)[0]
img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
img = np.ascontiguousarray(img)
img = torch.from_numpy(img).to(self.device)
img = img.float() / 255.0
if len(img.shape) == 3:
img = img[None] # 增加batch维度
return img
def detect(self, img):
"""执行检测"""
im = self.preprocess(img)
pred = self.model(im)
pred = non_max_suppression(pred, self.conf_thres, self.iou_thres)
return pred
def visualize(self, img, pred, names):
"""可视化结果"""
annotator = Annotator(img, line_width=3, example=str(names))
if pred[0] is not None and len(pred[0]):
pred[0][:, :4] = scale_boxes(im.shape[2:], pred[0][:, :4], img.shape).round()
for *xyxy, conf, cls in reversed(pred[0]):
c = int(cls)
label = f'{names[c]} {conf:.2f}'
annotator.box_label(xyxy, label, color=colors(c, True))
return annotator.result()
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--weights', type=str, required=True)
parser.add_argument('--source', type=str, default='0')
parser.add_argument('--conf-thres', type=float, default=0.25)
parser.add_argument('--iou-thres', type=float, default=0.45)
args = parser.parse_args()
tester = YOLOv5Tester(args.weights, args.conf_thres, args.iou_thres)
if args.source.isdigit():
# 摄像头捕获
cap = cv2.VideoCapture(int(args.source))
while True:
ret, frame = cap.read()
if not ret:
break
pred = tester.detect(frame)
result = tester.visualize(frame, pred, tester.model.names)
cv2.imshow('Detection', result)
if cv2.waitKey(1) == 27: # ESC退出
break
cap.release()
else:
# 图像/视频文件处理
path = Path(args.source)
if path.is_file():
img = cv2.imread(str(path))
pred = tester.detect(img)
result = tester.visualize(img, pred, tester.model.names)
cv2.imwrite(f'result_{path.name}', result)
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
5.2 性能优化技巧
-
TensorRT加速:
bash复制
python export.py --weights yolov5s.pt --include engine --device 0 -
多线程处理:
使用Python的threading模块实现采集和检测并行:python复制import threading from queue import Queue class CaptureThread(threading.Thread): def __init__(self, queue): super().__init__() self.queue = queue self.cap = cv2.VideoCapture(0) def run(self): while True: ret, frame = self.cap.read() if ret: self.queue.put(frame) class DetectThread(threading.Thread): def __init__(self, queue, model): super().__init__() self.queue = queue self.model = model def run(self): while True: if not self.queue.empty(): frame = self.queue.get() # 执行检测... -
模型量化:
bash复制
python export.py --weights yolov5s.pt --include onnx --int8
6. 常见问题解决方案
6.1 驱动相关问题
问题1:运行realsense-viewer时报错No devices connected
- 检查USB接口是否为3.0
- 执行
lsusb确认能看到Intel RealSense设备 - 尝试重新插拔设备
- 检查内核模块是否加载:
bash复制
lsmod | grep uvcvideo
问题2:帧率不稳定或图像撕裂
- 降低分辨率:
python复制config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30) - 关闭自动曝光:
python复制sensor = profile.get_device().first_color_sensor() sensor.set_option(rs.option.enable_auto_exposure, 0) sensor.set_option(rs.option.exposure, 156)
6.2 训练相关问题
问题1:CUDA out of memory
- 减小
--batch-size(建议从16开始尝试) - 添加
--rect参数 - 降低
--img尺寸(如从640降到416)
问题2:mAP值不升反降
- 检查数据标注质量
- 减小学习率:
bash复制
python train.py --hyp data/hyps/hyp.scratch-low.yaml - 增加数据增强:
yaml复制# 在hyp.yaml中调整 hsv_h: 0.02 hsv_s: 0.8 hsv_v: 0.5
6.3 部署相关问题
问题1:模型推理速度慢
- 使用TensorRT加速
- 切换到更小的模型(如yolov5n)
- 量化模型:
bash复制
python export.py --weights yolov5s.pt --include onnx --int8
问题2:检测框抖动严重
- 实现简单的跟踪算法:
python复制from collections import defaultdict class Tracker: def __init__(self, max_age=5): self.tracks = defaultdict(dict) self.max_age = max_age self.next_id = 0 def update(self, detections): # 简单IOU匹配 updated = {} for det in detections: matched = False for tid, track in self.tracks.items(): if self._iou(det['bbox'], track['bbox']) > 0.5: updated[tid] = det matched = True break if not matched: updated[self.next_id] = det self.next_id += 1 # 更新轨迹 self.tracks = updated return self.tracks
经过以上步骤,你应该已经成功搭建了一个完整的基于YOLOv5和RealSense相机的目标检测系统。在实际项目中,我建议定期更新YOLOv5代码库(git pull),因为官方会持续优化模型性能。同时,对于特定场景的应用,可以考虑使用迁移学习技术,在预训练模型基础上进行微调,这通常能获得比从头训练更好的效果。