识别香烟目标检测
使用 YOLOv8 实现香烟品牌和规格(条装/单盒装)识别是一个典型的 多标签目标检测任务。下面我将从整体思路、数据准备、标注方法、训练策略等方面系统地为你梳理整个流程。
一、总体思路
你希望实现的目标是:
- 在图像中检测出香烟包装;
- 同时识别其 品牌(如中华、玉溪、黄鹤楼等) 和 规格(条装 / 单盒装)。
这可以看作一个 多分类属性联合检测任务,即每个检测框输出两个标签信息:brand 和 packaging_type。
YOLOv8 支持多类别检测,但默认只支持单一类别标签。因此我们需要通过以下方式扩展:
✅ 解决方案:将“品牌+规格”组合成复合类别(例如:“中华_条装”、“中华_单盒装”),或者使用 多输出头 的方式分别预测品牌和规格。
我们推荐采用 复合类别 + YOLOv8 分类能力增强 的方式,兼顾精度与工程实现简便性。
二、训练模型的整体过程
1. 数据准备阶段
(1)需要多少张标注图片?
全屏复制
| 类别数 | 建议每类样本数量 |
| 约30种品牌 × 2种规格 = 最多60个复合类别 | 每类建议 50~100张带标注的图片 |
✅ 总体建议:
- 总图像数量 ≥ 3000 张(理想为 4000~6000)
- 每张图可包含多个香烟实例(提高效率)
- 图像来源应多样化:不同角度、光照、遮挡、背景复杂度、清晰度等
- 包括真实场景拍摄(超市货架、便利店柜台、桌面摆放等)
🔍 小贴士:
- 若某些品牌数据较少,可通过数据增强(旋转、模糊、亮度调整、仿射变换)补充。
- 可加入网络爬虫获取电商图片(注意版权问题,仅用于训练且脱敏处理)。
2. 是否需要设置反例(Negative Examples)?
❌ 不需要专门标注“反例”图像(空标签图像)
原因如下:
- YOLO 训练机制本身对负样本(无物体区域)已有处理(通过置信度损失自动学习背景);
- 加入少量不含香烟的图像有助于防止误检,但不宜过多;
- ✅ 建议:在训练集中混入 约5%~10%的无香烟图像(即标签为空),帮助模型更好地区分背景。
👉 这些图像无需标注边界框,对应 .txt 标签文件为空即可。
三、标注策略:如何同时标注品牌和规格?
方法一:【推荐】合并为复合类别(Compound Class)
将品牌和规格组合成一个新的类别名称:
text
类别列表示例: 0: 中华_条装 1: 中华_单盒装 2: 玉溪_条装 3: 玉溪_单盒装 ... 58: 黄鹤楼_条装 59: 黄鹤楼_单盒装
✅ 优点:
- 兼容标准 YOLOv8 训练流程;
- 不需修改模型结构;
- 易于部署和推理。
⚠️ 缺点:
- 如果品牌或规格变化频繁,类别数增长快;
- 推理后需解析字符串提取品牌和规格。
📌 示例标注文件(.txt)格式:
0 0.5 0.6 0.2 0.3 # 表示类别0(中华_条装),中心x=0.5, y=0.6, 宽=0.2, 高=0.3 2 0.7 0.4 0.15 0.25 # 玉溪_条装
方法二:双标签输出(高级用法,需自定义模型)
修改 YOLOv8 的 Head,使其输出两个独立的分类结果:
- 一个分支预测品牌(30类)
- 一个分支预测规格(2类:条装/单盒装)
✅ 优点:
- 更灵活,便于后续扩展;
- 减少类别爆炸(从最多60类 → 30+2=32类);
- 可共享特征提取器,提升泛化能力。
⚠️ 缺点:
- 需要修改模型结构(如添加额外分类头);
- 训练代码更复杂(通常基于 Ultralytics 源码开发);
- 推理时需同步解码两个输出。
🔧 实现路径(进阶):
- 使用
Ultralytics的 Python API 自定义DetectionModel; - 修改
head输出结构; - 修改损失函数,支持多任务学习。
👉 初期建议使用 方法一(复合类别),后期再考虑升级为方法二。
四、训练策略:一起训练 or 独立训练?
全屏复制
| 方案 | 是否推荐 | 说明 |
| 联合训练(一起训练) | ✅ 强烈推荐 | 使用复合类别,一次性检测并识别品牌+规格,端到端训练,效果最好 |
| 分步训练(先检测位置,再分类) | ⚠️ 不推荐 | 两阶段流程复杂,误差累积,速度慢 |
| 独立训练品牌和规格模型 | ❌ 不推荐 | 规格依赖品牌上下文,分开训练难以保证一致性 |
🎯 结论:统一进行联合训练(end-to-end)是最优选择。
五、完整训练流程步骤
Step 1: 数据收集
- 收集香烟实物照片(实拍为主,辅以电商平台图);
- 覆盖各种品牌、包装风格、角度、光照条件;
- 注意避免侵权图片(可自行拍摄或使用授权数据集);
Step 2: 数据标注
- 工具推荐:LabelImg、CVAT、Roboflow、MakeSense.ai;
- 每张图标注所有可见香烟对象;
- 使用复合类别命名规则(品牌_规格);
- 导出为 YOLO 格式(
.txt+classes.txt);
Step 3: 数据划分
- 划分训练集(70%)、验证集(20%)、测试集(10%);
- 确保各类别分布均衡(尤其小众品牌不要集中在某一子集);
Step 4: 数据增强(可选)
- 使用 Albumentations 或 YOLO 内置增强:
- 随机翻转、色彩抖动、缩放、裁剪、Mosaic、MixUp;
- 提高模型鲁棒性;
Step 5: 模型选择与训练
# 使用 ultralytics 库 pip install ultralytics
# 开始训练(假设共60个复合类别) yolo detect train data=smoke_config.yaml model=yolov8m.pt epochs=100 imgsz=640 batch=16
smoke_config.yaml 示例:
train: ./datasets/train/images val: ./datasets/val/images nc: 60 names: - "中华_条装" - "中华_单盒装" - "玉溪_条装" - ...
Step 6: 模型评估与调优
- 查看 mAP@0.5、Precision、Recall;
- 分析混淆矩阵,查看易混淆品牌(如颜色相近的软包 vs 硬包);
- 对低召回率类别补充数据或增强;
- 可尝试更大的模型(yolov8l / yolov8x)提升精度;
Step 7: 推理与后处理
from ultralytics import YOLO model = YOLO("runs/detect/train/weights/best.pt") results = model("test.jpg") for r in results: boxes = r.boxes for box in boxes: cls_id = int(box.cls) label = model.names[cls_id] # 如 "中华_条装" brand, packaging = label.split("_") # 解析出品牌和规格 print(f"检测到:{brand}, {packaging}")
六、优化建议
- 关注细节特征区分:
- 条装 vs 单盒装:通常条装更大、有透明膜包裹、排列整齐;
- 品牌差异:主色调、Logo、字体、防伪标识等;
- 增加困难样本:
- 部分遮挡、反光、倾斜视角;
- 相似品牌对比训练(如芙蓉王系列);
- 部署时考虑实时性:
- 若需边缘设备运行(如 Jetson),可用
yolov8s或量化模型(ONNX/TensorRT);
- 持续迭代更新:
- 新增品牌时,增量训练(fine-tune)已有模型;
七、总结
全屏复制
| 项目 | 建议方案 |
| 数据量 | 每类50~100张,总数≥3000张 |
| 是否加反例 | 可加入5%~10%无香烟图像 |
| 标注方式 | 使用“品牌_规格”作为复合类别 |
| 模型训练 | 联合训练,不拆分任务 |
| 模型选择 | YOLOv8s/m(平衡速度与精度) |
| 后续扩展 | 可升级为多头输出架构 |
空间结构理解-识别香烟的摆放
群体行为理解(Group Behavior Detection),即识别“盒装香烟”的摆放是 U型 还是 T型。
这类问题属于 场景结构理解(Scene Layout Understanding) 或 空间模式识别(Spatial Pattern Recognition),不依赖单个物体,而是分析多个同类物体之间的 空间分布关系。
目标
给定一组 YOLO 检测出的“单盒装香烟”边界框(bounding boxes),
自动判断它们是否组成一个 U型排列 或 T型排列。
一、核心思路(三步走)
我们将整个过程分为以下三个阶段:
全屏复制
| 阶段 | 任务 | 方法 |
| 1️⃣ 分组 | 按品牌和位置聚类 | 过滤条装 + 按品牌分组 + 空间聚类 |
| 2️⃣ 特征提取 | 提取几何结构特征 | 中心点分布、方向性、密度、空缺区域 |
| 3️⃣ 形状分类 | 判断是否为 U型 / T型 | 使用规则引擎 或 轻量级分类器 |
二、详细实现步骤
Step 1:预处理 —— 筛选目标检测框
只保留你要分析的“单盒装香烟”,并提取其位置信息。
python
# 假设 results 是 YOLOv8 的输出 target_boxes = [] for box in results[0].boxes: cls_id = int(box.cls) label = model.names[cls_id] # 只保留 单盒装 if "单盒装" in label: x1, y1, x2, y2 = box.xyxy[0].cpu().numpy() cx = (x1 + x2) / 2 # 中心x cy = (y1 + y2) / 2 # 中心y w = x2 - x1 h = y2 - y1 brand = label.split("_")[0] target_boxes.append({ "center": (cx, cy), "bbox": (x1, y1, x2, y2), "size": (w, h), "brand": brand })
Step 2:按品牌 + 空间位置分组
同一个品牌的香烟可能在柜台不同区域有多组陈列(比如左边一组U型,右边一组T型),所以需要先分组。
方法:DBSCAN 聚类(推荐)
python
from sklearn.cluster import DBSCAN import numpy as np def cluster_by_position(boxes, eps=80): points = np.array([b["center"] for b in boxes]) if len(points) == 0: return [] clustering = DBSCAN(eps=eps, min_samples=3).fit(points) labels = clustering.labels_ clusters = [] for cluster_id in set(labels): if cluster_id == -1: # 噪声点 continue cluster_boxes = [boxes[i] for i, lbl in enumerate(labels) if lbl == cluster_id] clusters.append(cluster_boxes) return clusters
eps是最大距离阈值(单位像素),可根据图像分辨率调整(如图像宽1920,则80px ≈ 4cm)
Step 3:对每组进行“U型 / T型”判断
我们现在有一组“同品牌、邻近摆放”的香烟中心点,接下来判断它们的排列形状。
🔍 三、U型 与 T型 的几何特征分析
全屏复制
| 类型 | 几何特征 |
| U型 | 三个边有排列,中间空缺(像字母U),两端对称,底部连通 |
| T型 | 横向一排 + 垂直向下延伸一列,交点居中 |
| 直线型 | 所有点基本在一条直线上 |
| 散乱型 | 无明显规律 |
我们可以基于以下特征来区分:
特征1:点云主成分分析(PCA)
判断整体方向性和延展性。
python
def get_orientation_score(points): points = np.array(points) mean = np.mean(points, axis=0) centered = points - mean cov_matrix = np.cov(centered.T) eigenvals, eigenvecs = np.linalg.eigh(cov_matrix) # 主轴方向 major_axis = eigenvecs[:, np.argmax(eigenvals)] aspect_ratio = eigenvals[1] / (eigenvals[0] + 1e-6) return aspect_ratio, major_axis
- 高长宽比 → 可能是线性或U/T的主干
特征2:密度分布 + 空洞检测(U型关键)
U型的特点是:中间有空缺。
方法:网格化统计 + 找空洞
python
def has_hole_in_middle(points, grid_size=5): points = np.array(points) # 归一化到局部坐标 min_x, max_x = points[:,0].min(), points[:,0].max() min_y, max_y = points[:,1].min(), points[:,1].max() if max_x - min_x < 1 or max_y - min_y < 1: return False # 划分网格 x_bins = np.linspace(min_x, max_x, grid_size) y_bins = np.linspace(min_y, max_y, grid_size) H, _, _ = np.histogram2d(points[:,0], points[:,1], bins=[x_bins, y_bins]) # 检查中心区域是否有空 center_region = H[1:-1, 1:-1] # 去掉边缘 hole_ratio = np.sum(center_region == 0) / center_region.size return hole_ratio > 0.5 # 中心一半以上为空
U型通常满足:有主轴 + 中间空洞
特征3:T型 —— 分支结构检测
T型 = 一行横排 + 一列竖排,且垂直相交于中点。
方法:投影分析(Projection Profile)
python
def is_t_shape(points, tolerance=0.3): points = np.array(points) # X轴投影(垂直方向有多少点) x_proj = np.histogram(points[:,0], bins=10)[0] > 0 y_proj = np.histogram(points[:,1], bins=10)[0] > 0 # 主轴判断 dx = np.std(points[:,0]) dy = np.std(points[:,1]) is_horizontal_main = dx > dy # 找最长线(主干) if is_horizontal_main: main_dir = points[:,0] cross_dir = points[:,1] else: main_dir = points[:,1] cross_dir = points[:,0] # 主干上点密集,分支上也有集中 main_hist, _ = np.histogram(main_dir, bins=10) cross_hist, _ = np.histogram(cross_dir, bins=10) # 主干连续性强 consecutive_main = np.sum(np.diff(main_hist > 0) == 1) <= 2 # 最多两次断开 # 分支在中间有一个高峰 mid_idx = len(cross_hist) // 2 peak_in_center = cross_hist[mid_idx-1:mid_idx+2].max() > 0 and \ np.argmax(cross_hist) in [mid_idx-1, mid_idx, mid_idx+1] return consecutive_main and peak_in_center
Step 4:综合判断逻辑(规则引擎)
python
def classify_arrangement(cluster_boxes): points = np.array([b["center"] for b in cluster_boxes]) n = len(points) if n < 4: return "散乱型" # 数量太少 # 特征提取 aspect_ratio, _ = get_orientation_score(points) has_hole = has_hole_in_middle(points) is_t = is_t_shape(points) if aspect_ratio < 0.4: return "密集堆叠" # 几乎是方形分布 if has_hole and aspect_ratio > 0.6: return "U型" elif is_t: return "T型" elif aspect_ratio > 0.8: return "直线型" else: return "散乱型"
四、最终输出示例
python
results = model("counter.jpg") target_boxes = [b for b in parse_boxes(results) if "单盒装" in b["label"]] clusters = cluster_by_position(target_boxes) arrangements = [] for cluster in clusters: shape = classify_arrangement(cluster) brand = cluster[0]["brand"] # 同一组品牌一致 # 计算包围整个陈列区域的bbox xs = [b["bbox"][0] for b in cluster] + [b["bbox"][2] for b in cluster] ys = [b["bbox"][1] for b in cluster] + [b["bbox"][3] for b in cluster] area_bbox = [min(xs), min(ys), max(xs), max(ys)] arrangements.append({ "brand": brand, "count": len(cluster), "arrangement": shape, "bbox": area_bbox }) # 输出 import json print(json.dumps(arrangements, indent=2, ensure_ascii=False))
五、进阶优化方向(可选)
全屏复制
| 方法 | 说明 |
| 用Hough Line检测辅助 | 检测是否存在多条平行线或垂直交叉线 |
| 使用GNN(图神经网络)建模关系 | 把每个盒子当节点,空间邻近当边,学习拓扑结构 |
| 训练轻量分类器(SVM/MLP) | 提取特征后用带标签数据训练分类模型 |
| 引入深度信息(RGB-D相机) | 区分前后层,避免重叠误判 |
| 模板匹配 + 形变模型 | 预定义U/T型模板,做弹性匹配 |
总结:U/T型识别完整流程
附加建议
- 数据标注时可手动标记“U/T型区域”,用于后期验证算法准确性;
- 保存典型样本图像 + 聚类结果,用于调试和演示;
- 可视化工具推荐:用 OpenCV 画出聚类结果、主轴方向、网格密度图;
如果你希望我提供:
- 完整 Python 可运行代码;
- OpenCV 可视化示例;
- 使用 ONNX 部署 + 实时视频流处理;
- Web API 接口封装;
欢迎继续提问,我可以一步步帮你实现!