cms网站后台模版,项城市住房和城乡建设局网站,广西网站建设价钱,爱站网关键词#x1f680; 项目背景在零售行业#xff0c;货架陈列面分析是商品管理和营销决策的重要依据。排面占比指的是某商品在货架上的陈列面积占总陈列面积的比例#xff0c;直接关系到商品的曝光率和销售潜力。传统的人工统计方式耗时耗力#xff0c;且难以保证准确性。本文基于… 项目背景在零售行业货架陈列面分析是商品管理和营销决策的重要依据。排面占比指的是某商品在货架上的陈列面积占总陈列面积的比例直接关系到商品的曝光率和销售潜力。传统的人工统计方式耗时耗力且难以保证准确性。本文基于YOLO系列目标检测算法开发一套自动化货架商品检测与排面占比分析系统支持YOLOv5/v8/v10三种主流模型并提供友好的UI界面。项目难点商品种类繁多外观相似度高货架图像中存在遮挡、光照不均问题排面占比需要精确计算每个商品的检测框面积实时性要求与检测精度的平衡 数据集构建数据采集我们采集了某大型超市的5000张货架图像包含饮料区1500张零食区2000张日用品区1500张标注规范使用LabelImg进行标注标注类别包括pythonCLASSES [ coca_cola, # 可口可乐 pepsi, # 百事可乐 lays, # 乐事薯片 oreo, # 奥利奥 noodle, # 方便面 water, # 矿泉水 juice, # 果汁 chocolate, # 巧克力 cookie, # 饼干 yogurt # 酸奶 ]数据集划分python# 数据集统计 total_images 5000 train_count 4000 # 80% val_count 500 # 10% test_count 500 # 10% print(f训练集: {train_count}张) print(f验证集: {val_count}张) print(f测试集: {test_count}张)数据增强pythonimport albumentations as A transform A.Compose([ A.RandomBrightnessContrast(p0.5), A.HueSaturationValue(p0.3), A.Rotate(limit15, p0.5), A.HorizontalFlip(p0.5), A.GaussNoise(var_limit(10.0, 50.0), p0.3), ]) YOLO系列模型详解YOLOv5YOLOv5由Ultralytics于2020年发布采用CSPDarknet作为骨干网络具有以下特点自适应锚框计算Mosaic数据增强Focus层代替传统卷积多种模型规模n/s/m/l/xYOLOv82023年发布的YOLOv8是当前最流行的版本无锚框检测头C2f模块替代C3Decoupled Head支持分类、检测、分割、姿态估计YOLOv102024年最新发布的YOLOv10NMS-free训练策略轻量级分类头空间通道解耦下采样大核卷积优化 环境配置创建虚拟环境bashconda create -n yolo_shelf python3.9 conda activate yolo_shelf安装依赖bash# 基础依赖 pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 pip install opencv-python pillow matplotlib numpy pandas tqdm # YOLOv5依赖 git clone https://github.com/ultralytics/yolov5 cd yolov5 pip install -r requirements.txt # YOLOv8/v10依赖 pip install ultralytics pip install yolov10 # 安装YOLOv10 YOLOv5实现数据准备python# dataset.yaml path: ./dataset # 数据集根目录 train: images/train # 训练集路径 val: images/val # 验证集路径 test: images/test # 测试集路径 nc: 10 # 类别数量 names: [coca_cola, pepsi, lays, oreo, noodle, water, juice, chocolate, cookie, yogurt]模型训练pythonimport torch from yolov5 import train # 训练配置 train.run( datadataset.yaml, weightsyolov5s.pt, batch_size16, epochs100, imgsz640, device0, projectruns/train, nameyolov5_shelf, exist_okTrue )模型推理pythonimport torch import cv2 import numpy as np class YOLOv5Detector: def __init__(self, weights_pathbest.pt): self.model torch.hub.load(ultralytics/yolov5, custom, pathweights_path, force_reloadTrue) self.model.conf 0.5 # 置信度阈值 self.model.iou 0.45 # IOU阈值 def detect(self, image_path): 检测单张图片 results self.model(image_path) detections results.pandas().xyxy[0] # 转为pandas DataFrame return detections def detect_batch(self, image_paths): 批量检测 results self.model(image_paths) return results def visualize(self, image, detections): 可视化检测结果 img image.copy() for _, det in detections.iterrows(): x1, y1, x2, y2 int(det[xmin]), int(det[ymin]), int(det[xmax]), int(det[ymax]) conf det[confidence] cls int(det[class]) label f{self.model.names[cls]}: {conf:.2f} # 绘制边界框 cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2) return img YOLOv8实现训练代码pythonfrom ultralytics import YOLO import torch class YOLOv8Trainer: def __init__(self, model_nameyolov8s.pt): self.model YOLO(model_name) def train(self, data_yaml, epochs100, batch_size16, imgsz640): 训练YOLOv8模型 results self.model.train( datadata_yaml, epochsepochs, batchbatch_size, imgszimgsz, device0, workers8, lr00.01, augmentTrue, patience50, save_period10, projectruns/train, nameyolov8_shelf ) return results def validate(self, data_yaml): 验证模型 metrics self.model.val(datadata_yaml) return metrics def export_model(self, formatonnx): 导出模型 self.model.export(formatformat) # 开始训练 trainer YOLOv8Trainer(yolov8s.pt) trainer.train(dataset.yaml, epochs100)推理代码pythonfrom ultralytics import YOLO import cv2 import numpy as np class YOLOv8Detector: def __init__(self, weights_pathbest.pt): self.model YOLO(weights_path) self.class_names self.model.names def detect(self, image_path, conf_thres0.5, iou_thres0.45): 执行目标检测 results self.model(image_path, confconf_thres, iouiou_thres) detections [] for result in results: boxes result.boxes if boxes is not None: for box in boxes: x1, y1, x2, y2 box.xyxy[0].tolist() conf float(box.conf[0]) cls int(box.cls[0]) detections.append({ bbox: [x1, y1, x2, y2], confidence: conf, class_id: cls, class_name: self.class_names[cls] }) return detections def detect_video(self, video_path, output_pathNone): 视频检测 cap cv2.VideoCapture(video_path) if output_path: fourcc cv2.VideoWriter_fourcc(*mp4v) fps int(cap.get(cv2.CAP_PROP_FPS)) width int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) out cv2.VideoWriter(output_path, fourcc, fps, (width, height)) while cap.isOpened(): ret, frame cap.read() if not ret: break # 检测 results self.model(frame) annotated_frame results[0].plot() if output_path: out.write(annotated_frame) else: cv2.imshow(Detection, annotated_frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() if output_path: out.release() cv2.destroyAllWindows()⚡ YOLOv10实现安装与训练python# 安装YOLOv10 pip install githttps://github.com/THU-MIG/yolov10.git # 训练脚本 from yolov10 import train import yaml # 训练配置 train_args { data: dataset.yaml, weights: yolov10s.pt, epochs: 100, batch_size: 16, imgsz: 640, device: 0, project: runs/train, name: yolov10_shelf } train.run(**train_args)推理代码pythonimport torch import cv2 import numpy as np from yolov10.models import YOLOv10 from yolov10.utils import non_max_suppression, scale_coords class YOLOv10Detector: def __init__(self, weights_pathbest.pt, devicecuda): self.device torch.device(device) self.model YOLOv10(weights_path).to(self.device) self.model.eval() # 加载类别名称 with open(classes.txt, r) as f: self.class_names [line.strip() for line in f.readlines()] def preprocess(self, image): 图像预处理 img cv2.cvtColor(image, cv2.COLOR_BGR2RGB) img cv2.resize(img, (640, 640)) img img.transpose(2, 0, 1) # HWC to CHW img np.ascontiguousarray(img) img torch.from_numpy(img).to(self.device) img img.float() / 255.0 # 归一化 if img.ndimension() 3: img img.unsqueeze(0) return img def detect(self, image, conf_thres0.5, iou_thres0.45): 执行检测 # 预处理 img_tensor self.preprocess(image) # 推理 with torch.no_grad(): pred self.model(img_tensor)[0] # NMS pred non_max_suppression(pred, conf_thres, iou_thres) detections [] if pred[0] is not None: for *xyxy, conf, cls in pred[0]: detections.append({ bbox: [float(x) for x in xyxy], confidence: float(conf), class_id: int(cls), class_name: self.class_names[int(cls)] }) return detections def visualize(self, image, detections): 可视化结果 img image.copy() for det in detections: x1, y1, x2, y2 [int(x) for x in det[bbox]] conf det[confidence] label f{det[class_name]}: {conf:.2f} # 随机颜色 color tuple(np.random.randint(0, 255, 3).tolist()) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, label, (x1, y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) return img 排面占比计算算法核心算法pythonimport numpy as np import pandas as pd from collections import defaultdict class ShelfSpaceAnalyzer: def __init__(self): self.area_ratios {} def calculate_area(self, bbox): 计算单个检测框面积 x1, y1, x2, y2 bbox width x2 - x1 height y2 - y1 return width * height def calculate_shelf_ratio(self, detections, image_areaNone): 计算排面占比 detections: 检测结果列表 image_area: 图像总面积可选 if not detections: return {} # 按类别分组 class_areas defaultdict(float) total_area 0 for det in detections: area self.calculate_area(det[bbox]) class_name det[class_name] class_areas[class_name] area total_area area # 如果提供了图像总面积使用图像面积否则使用检测框总面积 if image_area and image_area 0: denominator image_area else: denominator total_area # 计算占比 ratios {} for class_name, area in class_areas.items(): ratios[class_name] (area / denominator) * 100 # 按占比降序排序 ratios dict(sorted(ratios.items(), keylambda x: x[1], reverseTrue)) return ratios def analyze_multi_images(self, detections_list): 分析多张图像的平均占比 all_ratios [] for detections in detections_list: ratios self.calculate_shelf_ratio(detections) all_ratios.append(ratios) # 计算平均占比 avg_ratios defaultdict(float) count_ratios defaultdict(int) for ratios in all_ratios: for class_name, ratio in ratios.items(): avg_ratios[class_name] ratio count_ratios[class_name] 1 for class_name in avg_ratios: avg_ratios[class_name] / count_ratios[class_name] return dict(sorted(avg_ratios.items(), keylambda x: x[1], reverseTrue)) def generate_report(self, ratios, output_pathshelf_report.csv): 生成分析报告 df pd.DataFrame(list(ratios.items()), columns[商品类别, 排面占比(%)]) df[排面占比(%)] df[排面占比(%)].round(2) df.to_csv(output_path, indexFalse, encodingutf-8-sig) return df def visualize_ratios(self, ratios, save_pathNone): 可视化占比结果 import matplotlib.pyplot as plt plt.figure(figsize(12, 6)) categories list(ratios.keys()) values list(ratios.values()) # 创建条形图 bars plt.bar(categories, values, colorskyblue, edgecolornavy) # 添加数值标签 for bar, value in zip(bars, values): plt.text(bar.get_x() bar.get_width()/2, bar.get_height() 0.5, f{value:.1f}%, hacenter, vabottom) plt.xlabel(商品类别) plt.ylabel(排面占比 (%)) plt.title(货架商品排面占比分析) plt.xticks(rotation45, haright) plt.tight_layout() if save_path: plt.savefig(save_path, dpi300, bbox_inchestight) plt.show() UI界面开发主界面设计 (PyQt5)pythonimport sys import os from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * import cv2 from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.figure import Figure class ShelfAnalyzerUI(QMainWindow): def __init__(self): super().__init__() self.detector None self.current_image None self.detections None self.initUI() def initUI(self): 初始化UI self.setWindowTitle(货架排面占比分析系统) self.setGeometry(100, 100, 1400, 800) # 设置样式 self.setStyleSheet( QMainWindow { background-color: #f0f0f0; } QPushButton { background-color: #4CAF50; color: white; border: none; padding: 8px 16px; border-radius: 4px; font-size: 14px; } QPushButton:hover { background-color: #45a049; } QComboBox { padding: 5px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } QLabel { font-size: 14px; } ) # 创建中央部件 central_widget QWidget() self.setCentralWidget(central_widget) # 主布局 main_layout QHBoxLayout() central_widget.setLayout(main_layout) # 左侧控制面板 left_panel self.create_left_panel() main_layout.addWidget(left_panel, 1) # 右侧显示区域 right_panel self.create_right_panel() main_layout.addWidget(right_panel, 3) def create_left_panel(self): 创建左侧控制面板 panel QWidget() layout QVBoxLayout() panel.setLayout(layout) panel.setMaximumWidth(300) # 模型选择 model_group QGroupBox(模型选择) model_layout QVBoxLayout() self.model_combo QComboBox() self.model_combo.addItems([YOLOv5, YOLOv8, YOLOv10]) self.model_combo.currentTextChanged.connect(self.on_model_changed) model_layout.addWidget(QLabel(选择检测模型:)) model_layout.addWidget(self.model_combo) # 权重文件选择 weight_layout QHBoxLayout() self.weight_path QLineEdit() self.weight_path.setPlaceholderText(选择权重文件...) weight_btn QPushButton(浏览) weight_btn.clicked.connect(self.browse_weights) weight_layout.addWidget(self.weight_path) weight_layout.addWidget(weight_btn) model_layout.addLayout(weight_layout) model_group.setLayout(model_layout) layout.addWidget(model_group) # 图像输入 input_group QGroupBox(图像输入) input_layout QVBoxLayout() # 单张图片 single_img_btn QPushButton(选择单张图片) single_img_btn.clicked.connect(self.load_single_image) input_layout.addWidget(single_img_btn) # 批量处理 batch_btn QPushButton(批量处理文件夹) batch_btn.clicked.connect(self.load_batch_images) input_layout.addWidget(batch_btn) # 视频处理 video_btn QPushButton(处理视频) video_btn.clicked.connect(self.load_video) input_layout.addWidget(video_btn) input_group.setLayout(input_layout) layout.addWidget(input_group) # 检测参数 param_group QGroupBox(检测参数) param_layout QFormLayout() self.conf_thres QDoubleSpinBox() self.conf_thres.setRange(0.1, 1.0) self.conf_thres.setValue(0.5) self.conf_thres.setSingleStep(0.05) param_layout.addRow(置信度阈值:, self.conf_thres) self.iou_thres QDoubleSpinBox() self.iou_thres.setRange(0.1, 1.0) self.iou_thres.setValue(0.45) self.iou_thres.setSingleStep(0.05) param_layout.addRow(IOU阈值:, self.iou_thres) param_group.setLayout(param_layout) layout.addWidget(param_group) # 操作按钮 detect_btn QPushButton(开始检测) detect_btn.clicked.connect(self.run_detection) detect_btn.setStyleSheet( QPushButton { background-color: #2196F3; font-size: 16px; padding: 12px; } QPushButton:hover { background-color: #1976D2; } ) layout.addWidget(detect_btn) analyze_btn QPushButton(分析排面占比) analyze_btn.clicked.connect(self.analyze_shelf_ratio) analyze_btn.setStyleSheet( QPushButton { background-color: #FF9800; font-size: 16px; padding: 12px; } QPushButton:hover { background-color: #F57C00; } ) layout.addWidget(analyze_btn) # 导出选项 export_group QGroupBox(导出结果) export_layout QVBoxLayout() export_csv_btn QPushButton(导出CSV报告) export_csv_btn.clicked.connect(self.export_csv) export_layout.addWidget(export_csv_btn) export_img_btn QPushButton(导出检测图像) export_img_btn.clicked.connect(self.export_image) export_layout.addWidget(export_img_btn) export_group.setLayout(export_layout) layout.addWidget(export_group) # 添加弹簧 layout.addStretch() return panel def create_right_panel(self): 创建右侧显示区域 panel QWidget() layout QVBoxLayout() panel.setLayout(layout) # 选项卡 tabs QTabWidget() # 图像显示选项卡 image_tab QWidget() image_layout QVBoxLayout() self.image_label QLabel() self.image_label.setAlignment(Qt.AlignCenter) self.image_label.setMinimumSize(800, 500) self.image_label.setStyleSheet(border: 1px solid #ddd; background-color: white;) image_layout.addWidget(self.image_label) image_tab.setLayout(image_layout) # 分析结果选项卡 result_tab QWidget() result_layout QVBoxLayout() # Matplotlib图表 self.figure Figure(figsize(8, 5)) self.canvas FigureCanvas(self.figure) result_layout.addWidget(self.canvas) # 数据表格 self.result_table QTableWidget() self.result_table.setColumnCount(2) self.result_table.setHorizontalHeaderLabels([商品类别, 排面占比(%)]) self.result_table.horizontalHeader().setStretchLastSection(True) result_layout.addWidget(self.result_table) result_tab.setLayout(result_layout) tabs.addTab(image_tab, 图像显示) tabs.addTab(result_tab, 分析结果) layout.addWidget(tabs) # 状态栏 self.status_label QLabel(就绪) self.status_label.setStyleSheet(color: green;) layout.addWidget(self.status_label) return panel def browse_weights(self): 浏览权重文件 file_path, _ QFileDialog.getOpenFileName( self, 选择权重文件, , Model Files (*.pt)) if file_path: self.weight_path.setText(file_path) def load_single_image(self): 加载单张图片 file_path, _ QFileDialog.getOpenFileName( self, 选择图片, , Image Files (*.jpg *.png *.jpeg)) if file_path: self.current_image cv2.imread(file_path) self.current_image_path file_path self.display_image(self.current_image) self.status_label.setText(f已加载图片: {os.path.basename(file_path)}) def display_image(self, image): 在界面上显示图片 if image is None: return # 调整图像大小以适应显示区域 height, width image.shape[:2] max_height 500 max_width 800 if height max_height or width max_width: scale min(max_height/height, max_width/width) new_width int(width * scale) new_height int(height * scale) image cv2.resize(image, (new_width, new_height)) # 转换为QImage rgb_image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape bytes_per_line ch * w qt_image QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) # 显示 pixmap QPixmap.fromImage(qt_image) self.image_label.setPixmap(pixmap) def run_detection(self): 运行目标检测 if self.current_image is None: QMessageBox.warning(self, 警告, 请先加载图片) return if not self.weight_path.text(): QMessageBox.warning(self, 警告, 请选择模型权重文件) return try: self.status_label.setText(正在检测中...) QApplication.processEvents() # 根据选择的模型创建检测器 model_name self.model_combo.currentText() if model_name YOLOv5: from yolov5_detector import YOLOv5Detector self.detector YOLOv5Detector(self.weight_path.text()) elif model_name YOLOv8: from yolov8_detector import YOLOv8Detector self.detector YOLOv8Detector(self.weight_path.text()) else: # YOLOv10 from yolov10_detector import YOLOv10Detector self.detector YOLOv10Detector(self.weight_path.text()) # 执行检测 self.detections self.detector.detect( self.current_image, conf_thresself.conf_thres.value(), iou_thresself.iou_thres.value() ) # 显示检测结果 result_image self.detector.visualize(self.current_image, self.detections) self.display_image(result_image) self.status_label.setText(f检测完成检测到 {len(self.detections)} 个目标) except Exception as e: QMessageBox.critical(self, 错误, f检测失败{str(e)}) self.status_label.setText(检测失败) def analyze_shelf_ratio(self): 分析排面占比 if self.detections is None: QMessageBox.warning(self, 警告, 请先进行目标检测) return # 计算排面占比 analyzer ShelfSpaceAnalyzer() image_area self.current_image.shape[0] * self.current_image.shape[1] ratios analyzer.calculate_shelf_ratio(self.detections, image_area) # 显示图表 self.figure.clear() ax self.figure.add_subplot(111) categories list(ratios.keys()) values list(ratios.values()) bars ax.bar(categories, values, colorskyblue, edgecolornavy) ax.set_xlabel(商品类别) ax.set_ylabel(排面占比 (%)) ax.set_title(货架商品排面占比分析) ax.tick_params(axisx, rotation45) # 添加数值标签 for bar, value in zip(bars, values): ax.text(bar.get_x() bar.get_width()/2, bar.get_height() 0.5, f{value:.1f}%, hacenter, vabottom) self.figure.tight_layout() self.canvas.draw() # 更新表格 self.result_table.setRowCount(len(ratios)) for i, (class_name, ratio) in enumerate(ratios.items()): self.result_table.setItem(i, 0, QTableWidgetItem(class_name)) self.result_table.setItem(i, 1, QTableWidgetItem(f{ratio:.2f})) QMessageBox.information(self, 完成, 排面占比分析完成) def export_csv(self): 导出CSV报告 if self.result_table.rowCount() 0: QMessageBox.warning(self, 警告, 没有可导出的数据) return file_path, _ QFileDialog.getSaveFileName( self, 保存CSV文件, , CSV Files (*.csv)) if file_path: import pandas as pd data [] for i in range(self.result_table.rowCount()): class_name self.result_table.item(i, 0).text() ratio float(self.result_table.item(i, 1).text()) data.append([class_name, ratio]) df pd.DataFrame(data, columns[商品类别, 排面占比(%)]) df.to_csv(file_path, indexFalse, encodingutf-8-sig) QMessageBox.information(self, 成功, f报告已保存至{file_path}) def on_model_changed(self, model_name): 模型切换时的响应 if model_name YOLOv5: default_weights yolov5s.pt elif model_name YOLOv8: default_weights yolov8s.pt else: default_weights yolov10s.pt self.weight_path.setText(default_weights)