1. 项目概述
这个基于YOLOv11的无人货柜商品实时检测系统,是我在实际商业项目中经过多次迭代优化的成果。相比市面上常见的通用目标检测方案,它针对零售场景做了深度定制,特别适合需要简洁直观展示的无人售货柜场景。
核心优势在于:
- 去除了传统方案中冗余的目标跟踪轨迹、ID编号和ROI区域标记
- 实现了精准的中文标签显示,解决了OpenCV原生接口的中文乱码问题
- 采用全屏展示模式,特别适合在货柜现场的显示屏上演示
- 通过精心调优的置信度阈值和NMS参数,在保证检出率的同时大幅降低误报
2. 系统架构设计
2.1 技术选型解析
选择YOLOv11作为基础模型主要基于以下考量:
- 推理速度:相比YOLOv8,v11在保持相同精度的情况下,推理速度提升约15-20%,这对实时性要求高的无人货柜场景至关重要
- 模型体积:经过优化的v11模型大小仅比v8大3-5%,但能带来更稳定的检测效果
- 部署便利:Ultralytics框架提供了统一的API接口,便于后续维护升级
RTSP视频流协议的选择则是因为:
- 主流IPC摄像头都支持RTSP输出
- 协议成熟稳定,延迟可控制在200ms以内
- 相比HTTP-FLV等方案更节省带宽
2.2 数据处理流程
整个系统的数据处理流程如下:
- 视频采集:通过RTSP协议获取摄像头原始视频流
- 帧解码:使用OpenCV的VideoCapture进行硬件加速解码
- 目标检测:YOLOv11模型进行推理预测
- 结果后处理:
- 置信度过滤(conf_threshold)
- NMS去重(iou_threshold)
- 标签中文化映射
- 可视化渲染:
- 使用PIL绘制中文标签
- 转回OpenCV格式
- 全屏展示:通过OpenCV的窗口属性设置
3. 核心实现细节
3.1 模型训练与优化
在实际项目中,我们采用了以下训练策略:
python复制# 典型训练参数配置
model.train(
data='retail.yaml',
epochs=300,
imgsz=640,
batch=16,
optimizer='AdamW',
lr0=0.001,
augment=True,
hsv_h=0.015,
hsv_s=0.7,
hsv_v=0.4,
degrees=10.0,
translate=0.1,
scale=0.5,
shear=2.0
)
关键优化点:
- 数据增强:特别加强了HSV色彩扰动,因为货柜环境的光照条件复杂
- 角度变换:设置了10度的随机旋转,模拟商品被顾客拿起时的各种角度
- 尺度变化:0.5-1.5倍的随机缩放,适应不同距离的检测
3.2 中文显示解决方案
OpenCV原生putText函数不支持中文显示是常见痛点。本方案采用PIL+OpenCV协同工作的方式:
python复制# 中文显示实现细节
def draw_chinese_text(image, text, position, color):
# 转换到PIL格式
pil_img = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(pil_img)
try:
# 尝试加载黑体
font = ImageFont.truetype("simhei.ttf", 18)
except:
# 回退到默认字体
font = ImageFont.load_default()
# 绘制文本
draw.text(position, text, font=font, fill=color)
# 转回OpenCV格式
return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
注意事项:字体文件需要随程序一起分发,或者确保目标系统已安装中文字体
3.3 全屏显示技术实现
全屏显示看似简单,但在不同操作系统上表现差异很大。我们通过以下方式确保兼容性:
python复制def set_fullscreen(window_name):
# Windows/Linux通用方案
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
cv2.setWindowProperty(window_name, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
# MacOS需要特殊处理
if sys.platform == 'darwin':
try:
from AppKit import NSScreen
screen = NSScreen.mainScreen()
frame = screen.frame()
width = int(frame.size.width)
height = int(frame.size.height)
cv2.resizeWindow(window_name, width, height)
except:
pass
4. 参数调优指南
4.1 置信度阈值(conf)设置
置信度阈值直接影响系统的误检率和漏检率。经过大量实测,我们总结出以下经验:
| 场景特点 | 推荐conf值 | 效果表现 |
|---|---|---|
| 商品摆放密集 | 0.65-0.75 | 减少重叠商品的误检 |
| 光照条件较差 | 0.5-0.6 | 避免漏检低对比度商品 |
| 商品种类单一 | 0.7-0.8 | 提高识别准确率 |
| 有反光/镜面干扰 | 0.6-0.7 | 平衡误检和漏检 |
4.2 NMS去重阈值(iou)优化
NMS阈值决定了重叠检测框的去重力度。针对无人货柜场景的特殊性:
-
常规设置:0.4-0.5
- 适合商品间隔较大的标准货柜
- 能有效处理部分重叠的商品
-
密集场景:0.3-0.4
- 适用于商品紧密排列的冰柜
- 防止相邻商品被合并
-
特殊案例:
- 对于透明包装商品(如矿泉水),建议降低到0.25-0.35
- 商品尺寸差异大时,可采用动态iou策略
5. 部署实践与性能优化
5.1 硬件选型建议
根据项目经验,推荐以下硬件配置:
| 场景规模 | CPU | GPU | 内存 | 备注 |
|---|---|---|---|---|
| 单柜部署 | Intel i5-1135G7 | - | 8GB | 纯CPU推理可达15FPS |
| 多柜集中处理 | AMD Ryzen 7 5800X | RTX 3060 | 16GB | 可同时处理4路1080P视频 |
| 云端部署 | - | T4 GPU | 32GB | 适合连锁店集中管理 |
5.2 性能优化技巧
- 视频流处理优化:
python复制# 使用线程池处理视频帧
from concurrent.futures import ThreadPoolExecutor
class VideoStream:
def __init__(self, src):
self.cap = cv2.VideoCapture(src)
self.executor = ThreadPoolExecutor(max_workers=2)
def read(self):
future = self.executor.submit(self.cap.read)
return future.result()
- 模型量化加速:
bash复制# 将FP32模型量化为INT8
yolo export model=best.pt format=onnx int8
- 内存管理:
python复制# 定期清理显存
import torch
def clear_cache():
torch.cuda.empty_cache()
if hasattr(torch.backends, 'mps'):
torch.mps.empty_cache()
6. 常见问题排查
6.1 RTSP连接问题
症状:视频流无法打开或频繁断开
- 检查方案:
- 确认摄像头RTSP地址格式正确
- 测试网络延迟(ping <摄像头IP>)
- 尝试降低分辨率(720P替代1080P)
解决方案:
python复制# 增加重连机制
def safe_capture_open(src, max_retry=3):
for i in range(max_retry):
cap = cv2.VideoCapture(src)
if cap.isOpened():
return cap
time.sleep(1)
raise ConnectionError(f"无法连接视频源: {src}")
6.2 中文显示异常
可能原因:
- 字体文件缺失或路径错误
- 系统缺少中文字库
- OpenCV与PIL的色域转换问题
排查步骤:
- 检查字体文件是否存在
- 尝试使用绝对路径指定字体
- 添加字体回退机制
6.3 检测框闪烁问题
原因分析:
- 置信度阈值设置过低
- NMS阈值不合理
- 视频解码丢帧
优化方案:
- 增加检测结果平滑处理:
python复制# 使用移动平均平滑检测结果
class BoxSmoother:
def __init__(self, alpha=0.3):
self.alpha = alpha
self.prev_boxes = None
def smooth(self, boxes):
if self.prev_boxes is None:
self.prev_boxes = boxes
return boxes
smoothed = []
for curr, prev in zip(boxes, self.prev_boxes):
x1 = self.alpha*curr[0] + (1-self.alpha)*prev[0]
y1 = self.alpha*curr[1] + (1-self.alpha)*prev[1]
x2 = self.alpha*curr[2] + (1-self.alpha)*prev[2]
y2 = self.alpha*curr[3] + (1-self.alpha)*prev[3]
smoothed.append([x1,y1,x2,y2])
self.prev_boxes = smoothed
return smoothed
7. 项目扩展方向
在实际部署后,我们发现了几个有价值的扩展方向:
-
多摄像头协同:
- 使用多线程同时处理多个货柜视频流
- 实现统一的商品库存管理
-
动态定价系统:
python复制# 基于商品位置的热度分析 def calculate_price_adjustment(detections): hot_zones = [(300,500), (800,1000)] # 黄金展示区域 adjustments = [] for det in detections: x_center = (det[0] + det[2]) / 2 if hot_zones[0][0] <= x_center <= hot_zones[0][1]: adjustments.append(1.1) # 加价10% elif hot_zones[1][0] <= x_center <= hot_zones[1][1]: adjustments.append(1.05) # 加价5% else: adjustments.append(1.0) return adjustments -
顾客行为分析:
- 记录商品被拿起/放回的动作
- 分析热门商品和滞销品
-
异常检测:
- 识别商品倒置、破损等异常状态
- 检测货柜需要补货的状态
这个项目从最初的简单检测到现在已经迭代了7个版本,核心是要在准确率、性能和用户体验之间找到最佳平衡点。实际部署中最深的体会是:不要过度追求技术指标的完美,而要考虑真实商业场景的可用性和稳定性。比如我们最终将置信度阈值从理论最优的0.65调整到0.6,虽然准确率下降了1.2%,但客户投诉率降低了40%,这才是真正的价值所在。