)
1. 从零搭建超市商品识别系统YOLOv8 数据集标注与 UI 界面全流程超市货架上的商品识别听起来像是大厂才玩得转的项目其实用 YOLOv8 加上一份标注好的数据集个人开发者也能跑出一套能用的 Demo。我这次要做的是一个能识别 295 类商品的检测系统从数据采集、标注、训练到 PyQt 界面集成最后用 TaoToken 的统一 Key 把模型调用链路串起来。整套流程走完你会得到一个可以上传图片、播放视频、调用摄像头实时检测的桌面应用。先说清楚这套系统能做什么。核心是一个基于 YOLOv8 的目标检测模型输入是超市货架或单件商品的图像输出是带边界框和类别标签的检测结果。适合谁想入门深度学习目标检测的学生、需要快速验证零售 AI 方案的开发者、以及想拿一个完整项目练手的 Python 工程师。数据集方面训练集 8336 张、验证集 2163 张覆盖饮料、零食、罐头、乳制品等 295 个类别连“雀巢咖啡丝滑拿铁双包装”和“绿辣椒”这种细分类都能区分。为什么要在流程里引入 TaoToken因为训练完模型只是第一步真正要让系统跑起来还需要一个稳定的模型调用通道来做推理验证和后续的 API 化部署。TaoToken 提供统一的 Key 和 API 入口省去了自己折腾多模型切换的麻烦。你可以把它理解成一个“模型调用的统一插座”不管是本地推理还是云端验证都用同一套鉴权方式。整个项目的技术栈很清晰Python 3.9 PyTorch Ultralytics YOLOv8 PyQt5 OpenCV。训练用 YOLOv8s 预训练权重推理用训练好的 best.pt界面用 PyQt5 手写布局摄像头和视频处理走 OpenCV。下面我会按实际操作的顺序把数据集配置、训练命令、UI 集成、TaoToken 接入验证这几个环节拆开讲每个步骤都给可复制的代码和配置。2. TaoToken 统一 Key 接入模型调用通道的前置准备在开始训练之前先把模型调用的通道准备好。这一步不是必须的但如果你想让训练好的模型能通过 API 被外部调用或者想在训练过程中用统一的 Key 管理多个模型的访问权限TaoToken 的接入就很值得做。它的核心价值在于你不需要为每个模型单独申请 Key也不需要维护多套鉴权逻辑一个 Key 就能覆盖模型对话、Coding Plan、API 调用等多个场景。先访问官网了解整体能力https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。注册后进入控制台在 API Keys 页面创建一个新的 Key。这个 Key 就是你后续所有模型调用的凭证。创建时建议给 Key 起一个能区分用途的名字比如“yolov8-supermarket-demo”方便后续管理。拿到 Key 之后你需要确认两件事Base URL 和 Model ID。TaoToken 的 API 入口是 https://taotoken.net/api 这个地址不加任何 UTM 参数直接用于代码中的请求配置。Model ID 则取决于你要调用的具体模型比如你想用 Claude 做代码辅助就选对应的模型标识如果想用 GPT 系列做推理验证就选对应的 ID。在控制台的模型列表里可以查到所有可用的 Model ID。对于这个超市商品识别项目TaoToken 的接入点主要有两个一是训练过程中用模型对话功能辅助调试代码和排查报错二是训练完成后通过 API 通道做推理结果的二次验证。比如你可以把检测结果发给模型让它判断“这个边界框里的商品类别是否合理”或者用模型生成数据增强的脚本。配置方式很简单在项目根目录创建一个.env文件把 Key 和 Base URL 写进去TAOTOKEN_API_KEYsk-你的实际Key TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_MODEL_ID你的模型ID然后在 Python 代码里用python-dotenv读取import os from dotenv import load_dotenv load_dotenv() api_key os.getenv(TAOTOKEN_API_KEY) base_url os.getenv(TAOTOKEN_BASE_URL) model_id os.getenv(TAOTOKEN_MODEL_ID) print(fAPI Key 已加载: {api_key[:8]}...) print(fBase URL: {base_url}) print(fModel ID: {model_id})如果你用的是 Claude Code 做开发辅助可以在 Claude Code 的配置里填入同样的 Base URL 和 Key。具体路径是 Claude Code 的设置页面找到 Anthropic API 配置项把 Base URL 改成https://taotoken.net/apiKey 填你创建的 Key。这样你在写 YOLOv8 训练脚本时可以直接让 Claude Code 帮你补全数据增强代码或排查 CUDA 报错。注意Key 不要硬编码在代码里也不要把.env文件提交到 Git。建议在.gitignore里加上.env。Coding Plan 适合长期做编码和 Agent 开发的场景。如果你打算在这个项目基础上持续迭代比如加入商品计数、货架缺货预警等功能Coding Plan 的额度会更划算。入口在控制台的 Coding Plan 页面开通后同样用这个 Key 就能调用。3. 可复制配置YOLO 数据集 yaml 与训练参数全解析数据集配置是整个训练流程的地基。YOLOv8 要求一个data.yaml文件来定义训练集、验证集的路径以及类别名称。这个文件写错了训练直接报错所以我把完整的配置模板和每个字段的含义都列出来。先看目录结构。假设你的项目根目录叫supermarket_yolo数据集放在datasets文件夹下supermarket_yolo/ ├── datasets/ │ ├── images/ │ │ ├── train/ # 8336 张训练图像 │ │ └── val/ # 2163 张验证图像 │ ├── labels/ │ │ ├── train/ # 对应的 .txt 标注文件 │ │ └── val/ │ └── data.yaml ├── runs/ │ └── detect/ ├── ui/ │ └── main_window.py ├── train.py └── requirements.txtdata.yaml的内容如下path: ./datasets train: images/train val: images/val nc: 295 names: 0: Nescafe Creamy Latte Twin Pack 1: Green Chili 2: Rebisco Chocolate Biscuit 3: Tomato 4: Coca Cola 5: Pepsi # ... 此处省略中间类别实际使用时需补全 295 个类别名称 294: Alaska Evaporated Milk这里有几个容易踩坑的地方。path字段是数据集的根目录train和val是相对于path的子路径。如果你的图像和标注文件不在同一个父目录下YOLOv8 会找不到标注。标注文件的命名必须和图像文件一致只是扩展名从.jpg变成.txt。比如images/train/001.jpg对应labels/train/001.txt。标注格式是 YOLO 标准的归一化坐标类别索引 x_center y_center width height所有值都在 0 到 1 之间。举个例子一张 640x480 的图里某个商品的边界框左上角在 (100, 200)右下角在 (300, 400)那么标注就是0 0.3125 0.625 0.3125 0.4167计算方式x_center (100300)/2/640 0.3125y_center (200400)/2/480 0.625width (300-100)/640 0.3125height (400-200)/480 0.4167。如果你用 LabelImg 标注选择 YOLO 格式导出它会自动生成这种.txt文件。标注时注意边界框要紧贴商品包装不要留太多空白也不要裁掉商品边缘。对于反光包装或透明包装尽量在多个角度各标一张提高模型鲁棒性。训练脚本train.py的完整内容from ultralytics import YOLO def main(): model_path yolov8s.pt data_path datasets/data.yaml model YOLO(model_path) results model.train( datadata_path, epochs500, batch64, device0, workers0, projectruns/detect, namesupermarket_exp, imgsz640, patience50, lr00.01, lrf0.01, momentum0.937, weight_decay0.0005, warmup_epochs3.0, augmentTrue, cacheFalse, verboseTrue ) print(f训练完成最佳模型保存在: {results.save_dir}) if __name__ __main__: main()参数说明epochs500是最大训练轮数patience50表示如果 50 轮内验证指标没有提升就提前停止。batch64需要根据显存调整8GB 显存建议降到 16 或 32。device0指定第一块 GPU如果没有 GPU 就改成devicecpu但训练速度会慢很多。workers0在 Windows 上比较稳定Linux 可以设成 8 或 16 加速数据加载。模型选择上yolov8n.pt最轻量适合嵌入式设备yolov8s.pt是速度和精度的平衡点我这次用的就是它yolov8m.pt精度更高但推理慢一些yolov8l.pt和yolov8x.pt适合对精度要求极高的场景但需要更强的 GPU。环境配置方面先创建 conda 虚拟环境conda create -n yolov8 python3.9 conda activate yolov8 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics opencv-python PyQt5 python-dotenv如果你没有 NVIDIA GPU把 torch 安装命令改成 CPU 版本pip install torch torchvision torchaudiorequirements.txt可以这样写ultralytics8.0.0 opencv-python4.8.0 PyQt55.15.0 python-dotenv1.0.0 numpy1.24.0训练启动后终端会输出每一轮的损失值和验证指标。重点关注mAP50和mAP50-95前者是 IoU 阈值 0.5 时的平均精度后者是 0.5 到 0.95 多个阈值下的平均值。训练完成后最佳模型会保存在runs/detect/supermarket_exp/weights/best.pt。4. 验证请求与成功结果推理脚本与 UI 界面集成训练完成后先用一个简单的推理脚本验证模型是否正常工作。这个脚本加载best.pt对单张图片做检测并打印检测到的类别和置信度。import cv2 from ultralytics import YOLO def predict_image(image_path, model_pathruns/detect/supermarket_exp/weights/best.pt): model YOLO(model_path) img cv2.imread(image_path) if img is None: print(f无法读取图片: {image_path}) return results model.predict(img, conf0.25, iou0.45) result results[0] print(f检测到 {len(result.boxes)} 个目标) for box in result.boxes: cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() class_name result.names[cls_id] print(f类别: {class_name}, 置信度: {conf:.3f}, 坐标: {xyxy}) annotated result.plot() cv2.imwrite(output_result.jpg, annotated) print(结果已保存到 output_result.jpg) if __name__ __main__: predict_image(test_images/shelf_01.jpg)运行后如果模型训练正常你会看到类似这样的输出检测到 5 个目标 类别: Coca Cola, 置信度: 0.892, 坐标: [120.5, 230.1, 280.3, 450.7] 类别: Pepsi, 置信度: 0.856, 坐标: [300.2, 210.5, 460.8, 440.2] 类别: Oreo, 置信度: 0.781, 坐标: [500.1, 250.3, 620.4, 400.9] ...接下来把推理逻辑集成到 PyQt5 界面里。界面分左右两部分左侧显示原始图像和检测结果右侧是模型设置、参数调节、功能按钮和结果表格。核心代码结构如下from PyQt5 import QtCore, QtWidgets from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QFileDialog, QMessageBox, QTableWidgetItem import cv2 import numpy as np from ultralytics import YOLO import os import datetime class DetectionUI: def __init__(self): self.model None self.cap None self.timer QtCore.QTimer() self.is_camera_running False self.video_writer None self.output_path output if not os.path.exists(self.output_path): os.makedirs(self.output_path) def load_model(self, model_path): try: self.model YOLO(model_path) return True except Exception as e: QMessageBox.critical(None, 错误, f模型加载失败: {str(e)}) return False def detect_image(self, image_path, conf0.25, iou0.45): if self.model is None: QMessageBox.warning(None, 警告, 请先加载模型) return None, None img cv2.imread(image_path) img_rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) results self.model.predict(img_rgb, confconf, iouiou) result results[0] annotated result.plot() return img_rgb, annotated, result def detect_video(self, video_path, conf0.25, iou0.45): if self.model is None: QMessageBox.warning(None, 警告, 请先加载模型) return self.cap cv2.VideoCapture(video_path) if not self.cap.isOpened(): QMessageBox.critical(None, 错误, 无法打开视频文件) return fps self.cap.get(cv2.CAP_PROP_FPS) width int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) timestamp datetime.datetime.now().strftime(%Y%m%d_%H%M%S) output_file os.path.join(self.output_path, foutput_{timestamp}.mp4) fourcc cv2.VideoWriter_fourcc(*mp4v) self.video_writer cv2.VideoWriter(output_file, fourcc, fps, (width, height)) self.timer.start(30) def update_camera_frame(self): if self.cap is None or not self.cap.isOpened(): return ret, frame self.cap.read() if not ret: self.stop_detection() return results self.model.predict(frame, conf0.25, iou0.45) annotated results[0].plot() if self.video_writer is not None: self.video_writer.write(annotated) def stop_detection(self): self.timer.stop() if self.cap is not None: self.cap.release() self.cap None if self.video_writer is not None: self.video_writer.release() self.video_writer None self.is_camera_running False界面布局用QHBoxLayout把左右两侧分开左侧用QVBoxLayout放两个QGroupBox分别显示原始图像和检测结果。右侧放模型选择下拉框、置信度和 IoU 滑块、五个功能按钮图片检测、视频检测、摄像头检测、停止检测、保存结果以及一个QTableWidget显示检测详情。参数调节部分置信度滑块范围 1 到 99默认 25对应 0.25 的阈值。IoU 滑块同样范围默认 45。滑块值变化时实时更新标签显示def update_conf_value(self): conf self.conf_slider.value() / 100 self.conf_value.setText(f{conf:.2f}) def update_iou_value(self): iou self.iou_slider.value() / 100 self.iou_value.setText(f{iou:.2f})检测结果表格有四列类别、置信度、左上坐标、右下坐标。每次检测后清空表格遍历result.boxes填充数据def update_result_table(self, result): self.result_table.setRowCount(0) for box in result.boxes: row self.result_table.rowCount() self.result_table.insertRow(row) cls_id int(box.cls[0]) conf float(box.conf[0]) xyxy box.xyxy[0].tolist() self.result_table.setItem(row, 0, QTableWidgetItem(result.names[cls_id])) self.result_table.setItem(row, 1, QTableWidgetItem(f{conf:.3f})) self.result_table.setItem(row, 2, QTableWidgetItem(f({xyxy[0]:.1f}, {xyxy[1]:.1f}))) self.result_table.setItem(row, 3, QTableWidgetItem(f({xyxy[2]:.1f}, {xyxy[3]:.1f})))摄像头检测用QTimer每 30 毫秒读一帧调用model.predict后把标注结果写入视频文件并显示在界面上。停止检测时释放VideoCapture和VideoWriter重置按钮状态。如果你想把推理结果通过 TaoToken 的 API 做二次验证可以在检测完成后把类别列表和置信度发给模型让它判断是否存在明显误检。调用方式如下import requests import os from dotenv import load_dotenv load_dotenv() def verify_with_taotoken(detections): api_key os.getenv(TAOTOKEN_API_KEY) base_url os.getenv(TAOTOKEN_BASE_URL) model_id os.getenv(TAOTOKEN_MODEL_ID) prompt f以下是一张超市货架图片的检测结果请判断是否存在明显误检\n{detections} headers { Authorization: fBearer {api_key}, Content-Type: application/json } payload { model: model_id, messages: [ {role: user, content: prompt} ] } response requests.post(f{base_url}/v1/chat/completions, headersheaders, jsonpayload) if response.status_code 200: return response.json()[choices][0][message][content] else: return f请求失败: {response.status_code} - {response.text}这个验证步骤不是必须的但在调试阶段很有用。比如模型把“绿辣椒”误判成“青苹果”你可以通过模型对话快速定位是标注问题还是类别混淆。5. 本篇常见错排查401、local proxy failed 与 OAuth 报错对照训练和集成过程中报错是常态。我把几个高频错误和对应的排查路径整理出来你遇到问题时可以对照着看。401 Unauthorized这个错误通常出现在调用 TaoToken API 时。原因一般是 Key 没填对、Key 已过期、或者请求头里的Authorization格式不对。检查.env文件里的TAOTOKEN_API_KEY是否以sk-开头请求头是否是Bearer sk-xxx的格式。如果 Key 刚创建等几秒再试有时候控制台同步有延迟。另外确认 Base URL 是https://taotoken.net/api不要多加斜杠或路径。local proxy failed这个报错说明你的网络环境里有一个本地代理在拦截请求。常见于公司内网或某些安全软件。排查方法是检查环境变量HTTP_PROXY和HTTPS_PROXY是否被设置如果有就临时取消unset HTTP_PROXY unset HTTPS_PROXY然后在 Python 代码里显式指定不使用代理import os os.environ[NO_PROXY] taotoken.net如果你用的是 requests 库可以加proxies{http: None, https: None}参数。reading choices 报错这个错误一般出现在解析 API 响应时。TaoToken 的响应格式是标准的 OpenAI 兼容格式choices是一个数组取第一个元素的message.content。如果报KeyError: choices说明响应体里没有这个字段可能是请求被拦截或返回了错误信息。先打印完整的response.text看看实际返回了什么。常见原因是 Model ID 填错了或者请求的 endpoint 路径不对。确认 URL 是{base_url}/v1/chat/completions。OAuth 相关报错如果你在 Claude Code 里配置 TaoToken 时遇到 OAuth 错误检查是不是同时填了 Anthropic 官方的 OAuth token 和 TaoToken 的 Key。两者只能留一个。在 Claude Code 的设置里把 Anthropic API Key 清空只填 TaoToken 的 Base URL 和 Key。如果还是报错重启 Claude Code 让配置生效。CUDA out of memory训练时显存不够。降低batch参数从 64 降到 32 或 16。如果还不行把imgsz从 640 降到 416 或 320。另外检查是不是有其他进程占着 GPU用nvidia-smi看一下。数据集路径报错YOLOv8 提示找不到图像或标注。检查data.yaml里的path是否是绝对路径或正确的相对路径。相对路径是相对于你运行训练脚本的当前目录不是相对于data.yaml文件的位置。建议用绝对路径避免歧义。标注文件格式错误训练时提示Label format error。检查.txt文件里每行是否是 5 个值类别索引是整数后面四个是 0 到 1 之间的浮点数。不要有多余的空格或空行。如果标注是用其他工具生成的确认坐标已经归一化。PyQt5 界面卡死检测视频或摄像头时界面无响应。原因是model.predict在主线程里执行阻塞了 UI 更新。解决办法是把检测逻辑放到QThread里或者用QApplication.processEvents()强制刷新界面。我在图片检测里加了processEvents()视频检测建议用多线程。模型加载失败YOLO(model_path)报错。检查best.pt文件是否存在路径是否正确。如果是从其他机器拷贝过来的确认文件没有损坏。可以先用yolov8s.pt测试确认环境没问题后再换best.pt。提示遇到报错先看完整堆栈信息定位到具体文件和行号。大部分问题出在路径、Key 配置和显存上。6. 语义一致 CTA从 Demo 到可交付系统的下一步跑通这个 Demo 之后你手里已经有一套完整的超市商品识别系统295 类商品的数据集配置、YOLOv8 训练脚本、PyQt5 界面、推理验证代码。接下来可以往几个方向继续打磨。第一个方向是提升检测精度。当前用的是yolov8s.pt如果对精度要求更高可以换成yolov8m.pt或yolov8l.pt重新训练。另外可以增加数据增强的强度比如 Mosaic、MixUp、随机透视变换这些在 YOLOv8 的训练参数里都有开关。对于相似包装的商品可以专门采集更多角度的图像补充到训练集里。第二个方向是扩展功能。现在的界面支持图片、视频、摄像头三种输入可以再加一个批量图片检测功能一次性处理整个文件夹的图像并导出 CSV 报告。还可以加入商品计数功能统计每个类别的检测数量用于货架缺货预警。第三个方向是 API 化部署。把训练好的best.pt封装成一个 FastAPI 服务对外提供/detect接口接收图片返回检测结果。这样前端页面或移动端就能直接调用。TaoToken 的 API 通道在这里可以继续发挥作用用于模型版本管理和调用鉴权。如果你打算长期迭代这个项目建议开通 Coding Plan入口在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。它适合需要频繁调用模型做代码辅助和 Agent 开发的场景额度比按次调用更划算。模型对话功能可以用来做推理结果的二次校验入口在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。比如你把检测结果发给模型让它判断“这张货架图里是否出现了不该出现的商品类别”或者“置信度低于 0.5 的检测框是否应该保留”。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里面有完整的 API 参数说明和示例代码。如果你在配置过程中遇到鉴权问题先看文档里的错误码对照表。最后说一个实际踩过的坑训练集和验证集的类别分布要尽量均衡。我一开始验证集里某些类别只有几张图导致mAP50波动很大。后来按 8:2 重新划分确保每个类别在验证集里至少有 5 张图指标才稳定下来。另外标注时边界框不要重叠太多YOLOv8 对重叠框的处理不如两阶段检测器重叠严重时容易漏检。整套代码和数据集配置已经可以跑通端到端流程。你按上面的步骤操作从创建虚拟环境到训练完成再到界面集成大概需要半天到一天的时间主要耗时在数据标注和训练上。如果直接用我提供的 295 类数据集配置训练 500 轮在单卡 3090 上大约需要 6 到 8 小时。训练完成后推理速度在 640 分辨率下单张图片约 15 毫秒视频和摄像头都能做到实时。