
简介这是一份面向具备Python基础、从事计算机视觉或仓储自动化方向技术人员的学习资料围绕基于Python与计算机视觉的仓库货位识别系统展开解决货架货位自动识别、库存准确性提升与人工录入错误等实际问题。内容涵盖OpenCV图像预处理与透视校正、YOLO目标检测定位货架标签、PaddleOCR编号识别以及像素坐标到业务货位编码的几何映射并引入多帧确认、置信度融合与异常复核机制。资源包为1个docx文档约101KB以图文与代码示例形式组织包含数据库建表语句、API接口规范、核心代码详解与部署方案目录从项目背景、模型架构到应用领域层层展开。已有75人学习适合高校学生与初中级工程师按目录结构搭建本地环境逐步实践数据采集、模型推理、服务部署到前端交互的全流程深入掌握智能仓储视觉平台的构建方法。1. 仓库货位识别系统从一堆货架照片到能用的坐标输出仓库里最容易被低估的活是找货。货架有 4 层 6 列SKU 上千拣货员拿着纸质单子绕一圈回来告诉你「第三排那箱没找到」。你想上系统第一反应是买套工业相机加视觉软件报价单一看六位数起步还得配专人维护。其实用 Python 加 OpenCV、YOLO 这套组合一台普通 USB 摄像头加一台工控机就能把「货位有没有货、货在哪个格口」这件事跑通。这篇讲的是基于 Python 的仓库货位识别系统怎么做摄像头怎么架、货位怎么标、YOLO 怎么训、识别结果怎么落到数据库、GUI 怎么给仓管用。适合两类人——想拿计算机视觉做课程设计或大作业的学生以及仓库里想先做个原型验证再决定要不要上商用系统的工程师。核心链路是「图像采集 → 货位检测 → 状态判定 → 数据落库 → 界面展示」每一环都有能踩的坑下面按落地顺序拆开讲。2. 货位识别到底识别什么先定义输出再谈模型2.1 货位、货格、货位状态三个概念别混很多人一上来就想着「训练一个模型识别货物」方向就偏了。仓库货位识别系统的输出不是「这是什么货」而是「这个格口当前是什么状态」。所以要先定义清楚三个对象货位Location货架上的一个物理格口有唯一编码比如A-03-02表示 A 区第 3 排第 2 层。货格Bin货位在图像里对应的矩形区域是检测框的标注对象。货位状态Status常见三分类——空置、有货、异常货物倾斜、超出格口、遮挡。把输出定义成「货位编码 状态 置信度 时间戳」后面数据库表结构、GUI 展示、报警逻辑全都围绕这个结构展开。如果一开始定义成「识别货物名称」你会发现 SKU 一多、包装一换模型就得重训项目直接烂尾。2.2 为什么选 YOLO 而不是传统模板匹配传统做法是拿货架空置时的照片做模板用 OpenCV 的absdiff加阈值判断格口有没有变化。这个方法在光照稳定、摄像头固定的实验室环境能跑但仓库现场有三个变量扛不住叉车经过导致的光照突变、货物包装颜色和货架接近、摄像头轻微震动导致模板错位。YOLO 这类单阶段检测器的优势在于它学的是「货格区域的视觉特征」而不是「和模板的像素差」。即使光照变了、货物换了只要货格边框还在模型就能定位。选 YOLOv8n 或 YOLOv5s 这种轻量模型在 1080p 下用 TensorRT 加速单卡跑 25 帧以上没问题足够覆盖一个通道的摄像头。如果只是做课程设计CPU 上跑 YOLOv8n 推理一张图 200ms 左右也能接受。提示不要一上来就上实例分割。货位识别只需要矩形框和分类分割标注成本是检测的 3 倍以上收益不明显。2.3 最小可跑通的链路长什么样在动手写代码前先把数据流画清楚摄像头按固定间隔抓帧比如每 2 秒一帧不需要连续视频流。帧图像送入 YOLO 模型输出每个货格的边界框和状态分类。把检测框坐标映射回货位编码靠预先标定的货位区域映射表。状态写入 SQLite 或 MySQL同时推给 GUI 刷新。如果某货位状态从「有货」变「空置」但系统没出库记录触发异常提醒。这条链路里模型只是其中一环标定映射和状态去抖往往比调模型更花时间。下一章讲环境搭建和最小推理脚本。3. 用 Python 把检测链路跑起来环境、推理、坐标映射3.1 环境安装与 OpenCV 常见报错处理先解决环境。Python 建议 3.9 或 3.10太新的版本有些 YOLO 依赖还没跟上。安装命令如下# 创建虚拟环境避免和系统包冲突 python -m venv venv # Windows 激活 venv\Scripts\activate # Linux / macOS 激活 source venv/bin/activate # 安装核心依赖 pip install opencv-python4.8.1.78 pip install ultralytics8.0.200 pip install numpy1.24.3 pip install pyqt55.15.9参数说明opencv-python选 4.8.x 是因为 4.9 之后部分cv2.dnn接口有变动和旧版 YOLO 权重加载容易冲突ultralytics是 YOLOv8 的官方库自带推理和训练接口numpy锁 1.24 是因为 1.25 之后和部分 OpenCV 版本有 ABI 兼容问题。两个高频报错ModuleNotFoundError: No module named opencv不是没装是装到了系统 Python 而不是虚拟环境。用pip -V确认 pip 路径在 venv 目录下。cv2.error: OpenCV(4.4.0) ...多半是cv2.imread读到了空路径或中文路径。OpenCV 对中文路径支持差用cv2.imdecode(np.fromfile(path, dtypenp.uint8), cv2.IMREAD_COLOR)替代。3.2 用 YOLO 做货格检测的最小推理脚本下面这段代码是整条链路的核心读一张货架图输出每个货格的框和状态import cv2 import numpy as np from ultralytics import YOLO # 加载训练好的货位检测模型没有的话先用预训练权重跑通流程 model YOLO(runs/detect/train/weights/best.pt) # 类别映射0空置 1有货 2异常 CLASS_NAMES {0: empty, 1: occupied, 2: abnormal} def detect_bins(image_path, conf_thres0.5): # 用 imdecode 兼容中文路径 img cv2.imdecode(np.fromfile(image_path, dtypenp.uint8), cv2.IMREAD_COLOR) if img is None: raise ValueError(f图像读取失败: {image_path}) # 推理conf 控制置信度阈值iou 控制 NMS 重叠阈值 results model.predict(img, confconf_thres, iou0.45, verboseFalse) detections [] for r in results: for box in r.boxes: x1, y1, x2, y2 box.xyxy[0].tolist() cls_id int(box.cls[0]) conf float(box.conf[0]) detections.append({ bbox: [round(x1), round(y1), round(x2), round(y2)], status: CLASS_NAMES.get(cls_id, unknown), confidence: round(conf, 3) }) return img, detections if __name__ __main__: img, dets detect_bins(shelf_01.jpg) for d in dets: print(d)逻辑说明model.predict返回的是 Results 对象列表每个 Results 里boxes包含所有检测框。box.xyxy是左上右下坐标box.cls是类别索引box.conf是置信度。conf0.5是经验值仓库场景光照不均时可以降到 0.35 召回更多框但误检会增加需要配合后面的映射表过滤。参数说明iou0.45是 NMS 的 IoU 阈值货格之间挨得近时不要调太低否则相邻格口会被合并如果发现同一个格口出两个框说明标注时框重叠太多回去检查标注。3.3 检测框到货位编码的映射表怎么建模型只给你像素坐标系统要的是A-03-02这种编码。做法是在部署时拍一张标准图人工标定每个货位编码对应的区域存成 JSONimport json # 货位区域映射表key 是货位编码value 是标准图上的像素区域 LOCATION_MAP { A-01-01: [120, 80, 260, 200], A-01-02: [270, 80, 410, 200], A-02-01: [120, 210, 260, 330], # ... 按实际货架继续补 } def match_location(bbox, loc_map, iou_thres0.3): 把检测框匹配到货位编码用 IoU 最大的那个 def iou(a, b): ax1, ay1, ax2, ay2 a bx1, by1, bx2, by2 b ix1, iy1 max(ax1, bx1), max(ay1, by1) ix2, iy2 min(ax2, bx2), min(ay2, by2) iw, ih max(0, ix2 - ix1), max(0, iy2 - iy1) inter iw * ih area_a (ax2 - ax1) * (ay2 - ay1) area_b (bx2 - bx1) * (by2 - by1) return inter / (area_a area_b - inter 1e-6) best_code, best_iou None, 0 for code, region in loc_map.items(): score iou(bbox, region) if score best_iou: best_code, best_iou code, score return best_code if best_iou iou_thres else None逻辑说明match_location遍历映射表算检测框和每个货位区域的 IoU取最大的。iou_thres0.3是下限低于这个值说明检测框和任何已知货位都对不上可能是新货架或误检直接丢弃并记日志。参数说明映射表必须用和推理时同一分辨率的图来标。如果摄像头分辨率变了映射表要按比例缩放否则全部错位。建议把映射表存成独立 JSON 文件换货架时只改文件不改代码。4. 数据落库与 GUI让仓管能看懂识别结果4.1 SQLite 表结构设计与状态去抖识别结果不能每帧都写库否则一天几十万条。做法是状态变化时才写加一个去抖逻辑连续 3 帧同一货位状态一致才认为状态真的变了。import sqlite3 from datetime import datetime def init_db(db_pathwarehouse.db): conn sqlite3.connect(db_path) conn.execute( CREATE TABLE IF NOT EXISTS location_status ( id INTEGER PRIMARY KEY AUTOINCREMENT, location_code TEXT NOT NULL, status TEXT NOT NULL, confidence REAL, updated_at TEXT NOT NULL ) ) conn.execute( CREATE TABLE IF NOT EXISTS status_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, location_code TEXT NOT NULL, old_status TEXT, new_status TEXT, changed_at TEXT NOT NULL ) ) conn.commit() return conn def update_status(conn, code, new_status, conf): cur conn.execute( SELECT status FROM location_status WHERE location_code?, (code,) ) row cur.fetchone() now datetime.now().strftime(%Y-%m-%d %H:%M:%S) if row is None: conn.execute( INSERT INTO location_status (location_code, status, confidence, updated_at) VALUES (?,?,?,?), (code, new_status, conf, now) ) elif row[0] ! new_status: # 状态变化写历史表 conn.execute( INSERT INTO status_history (location_code, old_status, new_status, changed_at) VALUES (?,?,?,?), (code, row[0], new_status, now) ) conn.execute( UPDATE location_status SET status?, confidence?, updated_at? WHERE location_code?, (new_status, conf, now, code) ) conn.commit()逻辑说明location_status存当前状态status_history存变更记录。update_status先查旧状态只有变化时才写历史并更新当前表。这样一天的数据量从几十万条降到几百条。参数说明confidence存下来是为了后续排查误报。如果某个货位频繁在「有货」和「空置」之间跳说明该位置光照或遮挡有问题需要调整摄像头角度或加补光灯。4.2 PyQt5 做一个能用的货位看板GUI 不需要花哨仓管要的是「一眼看到哪个格口异常」。用 PyQt5 做一个表格加颜色标记就够了import sys from PyQt5.QtWidgets import QApplication, QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget from PyQt5.QtGui import QColor import sqlite3 class WarehouseBoard(QWidget): def __init__(self): super().__init__() self.setWindowTitle(仓库货位状态看板) self.resize(800, 600) self.table QTableWidget() layout QVBoxLayout() layout.addWidget(self.table) self.setLayout(layout) self.refresh() def refresh(self): conn sqlite3.connect(warehouse.db) rows conn.execute( SELECT location_code, status, confidence, updated_at FROM location_status ORDER BY location_code ).fetchall() conn.close() self.table.setColumnCount(4) self.table.setHorizontalHeaderLabels([货位编码, 状态, 置信度, 更新时间]) self.table.setRowCount(len(rows)) color_map {empty: QColor(200, 255, 200), occupied: QColor(200, 220, 255), abnormal: QColor(255, 200, 200)} for i, (code, status, conf, ts) in enumerate(rows): self.table.setItem(i, 0, QTableWidgetItem(code)) item_status QTableWidgetItem(status) item_status.setBackground(color_map.get(status, QColor(255, 255, 255))) self.table.setItem(i, 1, item_status) self.table.setItem(i, 2, QTableWidgetItem(str(conf))) self.table.setItem(i, 3, QTableWidgetItem(ts)) if __name__ __main__: app QApplication(sys.argv) board WarehouseBoard() board.show() sys.exit(app.exec_())逻辑说明refresh从数据库读全量状态按货位编码排序用背景色区分状态。实际部署时可以用QTimer每 5 秒调一次refresh实现准实时刷新。参数说明颜色映射里abnormal用红色背景仓管扫一眼就能定位问题格口。如果货位超过 200 个表格会很长建议按区域分页或加筛选框。4.3 摄像头采集与推理的调度节奏不要用while True死循环抓帧推理CPU 会跑满。用定时器控制节奏import cv2 import time cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1920) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 1080) INTERVAL 2.0 # 每 2 秒处理一帧 last_time 0 while True: ret, frame cap.read() if not ret: break now time.time() if now - last_time INTERVAL: continue last_time now # 这里调用 detect_bins 和 update_status # 实际项目里把 frame 存临时文件或直接传 numpy 数组给模型逻辑说明INTERVAL2.0意味着每秒处理 0.5 帧对货位状态监控足够。如果要做实时视频流分析降到 0.1 秒但要确认硬件能扛住。参数说明cap.set设置分辨率要和标定映射表时的分辨率一致。USB 摄像头实际输出分辨率可能和设置值不同用cap.get确认一下再标定。5. 避坑与排查货位识别项目最容易翻车的 5 个点5.1 光照变化导致同一货位状态反复跳变现象某货位在「有货」和「空置」之间每分钟跳好几次数据库历史表被刷屏。原因仓库顶部灯光有频闪或者叉车经过时遮挡了光源导致模型对同一格口的置信度在阈值附近波动。解决一是加去抖逻辑连续 N 帧状态一致才写库二是把摄像头曝光模式设为手动锁定曝光值三是在货架侧面加一条 LED 灯带保证光照均匀。去抖的 N 取 3 到 5 之间太大响应慢太小没效果。5.2 货格标注框重叠导致 NMS 误合并现象相邻两个货位只检测出一个框另一个格口的状态丢失。原因标注时两个货格的框有重叠推理时 NMS 把 IoU 高的框合并了。解决标注时框要贴紧货格内边缘不要框到货架立柱。如果物理上确实有重叠把iou阈值从 0.45 调到 0.6让 NMS 更宽容。同时检查标注文件用脚本算一下所有框之间的最大 IoU超过 0.3 的都要复查。5.3 摄像头分辨率变了但映射表没更新现象换了摄像头或改了分辨率后所有检测框匹配到的货位编码全错A 区的货报成 B 区。原因映射表是按旧分辨率标定的像素坐标分辨率一变同样的像素位置对应不同物理区域。解决映射表里存归一化坐标除以图像宽高匹配时先把检测框也归一化。或者每次换摄像头重新标定一次标定脚本里加一步校验用已知货位拍一张图看匹配结果对不对。5.4 模型在训练集上准但现场误检多现象训练时 mAP 0.9现场跑起来把货架立柱、地面标线都识别成货格。原因训练集全是空货架或整齐摆放的图没有负样本。模型没见过立柱和标线把它们当成了货格特征。解决训练集里加入 10% 到 20% 的负样本图即没有货格的区域图标注为空。另外把置信度阈值从 0.5 提到 0.6过滤掉低置信度的误检。如果误检集中在某个区域单独补拍那个角度的图。5.5 数据库写入频繁导致 GUI 卡顿现象GUI 刷新时界面卡住几秒仓管以为系统死机。原因每次刷新都全量查location_status表货位多了之后查询变慢或者推理线程和 GUI 线程共用一个数据库连接锁冲突。解决GUI 刷新用单独的数据库连接并且只查变化的记录。加一个last_refresh_time每次只查updated_at大于上次刷新时间的行。如果货位超过 500 个考虑用分页或按区域加载。6. 进阶技巧用 TensorRT 加速和置信度校准把系统跑稳模型训完之后如果部署在工控机上CPU 推理一张 1080p 图要 300ms 以上多路摄像头就扛不住。用 TensorRT 把 YOLO 转成 engine 文件推理时间能降到 20ms 以内。转换命令如下# 把 YOLOv8 的 pt 权重转成 TensorRT engine yolo export modelruns/detect/train/weights/best.pt formatengine halfTrue device0参数说明halfTrue表示用 FP16 精度速度比 FP32 快接近一倍精度损失在货位识别这种任务上可以忽略device0指定第一块 GPU。转换完成后推理时把模型路径换成.engine文件即可ultralytics的YOLO类会自动识别格式。另一个容易被忽略的点是置信度校准。模型输出的conf是相对值不是真实概率。如果发现「置信度 0.6 的框其实是对的0.7 的框反而是错的」说明模型校准不好。做法是拿一批现场图跑推理按置信度分桶统计准确率找到准确率骤降的阈值把它设为conf下限。我一般会在 0.4 到 0.7 之间以 0.05 为步长扫一遍选误报和漏报平衡最好的那个值。还有一个实战习惯每次调整摄像头角度、换货架、改光照之后不要直接信模型输出先拍 20 张图跑一遍人工核对检测框和货位编码的匹配结果。这个步骤花 10 分钟能省掉后面几小时的排查。货位识别系统的稳定性三分靠模型七分靠部署时的标定和校验。希望帮到你。本文还有配套的精品资源点击获取