☰
YOLOv8文物识别系统:从数据标注到模型部署全流程解析
2026/10/2 9:37:43 网站建设 项目流程

简介:面向计算机视觉相关专业学生与毕业设计开发者,这套基于YOLOv8的考古文物识别系统是一套可直接运行的完整项目,覆盖目标检测算法训练、评估与可视化展示,可解决毕设选题落地难、代码调不通的常见问题。压缩包共97个文件,以70个Python脚本、12个pyc缓存、4个模型权重pt、5个配置文件及说明文本等组成,约24.21MB;py文件涵盖检测服务、数据处理、模型训练与可视化页面,pt为训练好的权重,配合mp4演示视频与README,按部署说明即可上手。目前已有76人学习下载。压缩包内含源码、完整数据集、可视化界面和部署教程,可产出核心指标曲线、混淆矩阵、F1曲线、精确率-召回率曲线、验证集预测结果与标签分布图,适合答辩展示,运行后即可复现,也可在此基础上二次修改。

1. 一个考古文物识别系统,凭什么用 YOLOv8 来做

如果你拿到的是“基于YOLOv8的考古文物识别系统”这个毕设项目包,那它本质上是由三块拼起来的:一个 YOLOv8 目标检测模型、一批带标注的文物图片、一个能选图出结果的可视化界面。这类项目最反直觉的地方在于:模型不是卡点,数据才是。公开的文物类目标注集少得可怜,常见做法是每个类目自己凑一两百张图,而 YOLOv8 在几百张小样本上就能训练出可演示的效果,这也正是它在毕设和课设里被反复选择的原因。适合谁?文博或计算机方向的毕业生,以及想把“识别展品/文物碎片”做成馆内演示程序的同学。这篇按我能落地的方式,从环境、数据、训练、界面到部署逐层讲清。

2. 先把 YOLOv8 跑通:CPU 环境搭建与最小推理命令

2.1 先看推理链路:你拿到的到底是个什么模型

YOLOv8 不是黑匣子,但很多人是当黑匣子用的。一张文物图片进去,先按 640×640 做 letterbox 缩放,保持宽高比并填充灰边;然后进入 backbone 提取特征,head 同时输出每个位置的类别概率和边界框回归值;最后经过 NMS 去掉重复框,剩下带置信度的预测结果。

YOLOv8 的 head 是 anchor-free 的,相比 YOLOv5 少了一套 anchor 先验参数,训练时省心一点,对边缘设备也更友好。你要做的毕设系统不需要改网络结构,只需要准备数据、训练权重、把权重接进界面。真正值得花时间的是环境能不能一次配好,以及训练数据干不干净。

2.2 Ubuntu 20.04 或 Windows 下搭环境:CPU 版也能跑

不管你是 Ubuntu 20.04 还是 Windows,我都建议先用 conda 建独立环境,别把 ultralytics 装进 base 环境里,后面版本冲突会让你想删掉重来。CPU 版本完全能跑推理,只是训练慢。

conda create -n yolo8 python=3.10 -y conda activate yolo8 pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cpu

python 3.10 是目前和 ultralytics 兼容最稳的版本,3.11、3.12 在部分 Linux 机器上会碰到 opencv 或 numpy 的预编译问题。第二条 pip 命令用 PyTorch 官方的 CPU 索引,避免把 CUDA 版 torch 装到没显卡的机器上,白白多占 2GB 空间。有 N 卡的同学把最后一行换成 cu118 或 cu121 后缀即可,但不装也能先跑通流程。

2.3 用官方权重跑一次推理:验证环境的最小命令

环境配好后别一上来就训练,先拿官方权重跑一次推理,确认模型能加载、图片能输出。

yolo predict model=yolov8n.pt source=./test_pic.jpg device=cpu conf=0.25 save=True

或者用 Python 方式,方便后面接进界面:

from ultralytics import YOLO model = YOLO("yolov8n.pt") results = model.predict(source="test_pic.jpg", conf=0.25, save=True) print(results[0].boxes)

第一次执行会自动下载 yolov8n.pt 权重,如果下载不下来,找同事拷贝一份放到项目根目录就行,权重文件很小。conf=0.25 表示只保留置信度高于 25% 的框;save=True 会在 runs/detect/predict 下生成带框的结果图。这一步跑通,说明环境没问题,后面训练、界面、部署都建立在这个基础上。

2.4 没显卡怎么办:CPU 推理参数与 GTX 1660 Ti 的取舍

CPU 推理一张 640 文物图片大约 2 到 5 秒,做成毕设演示完全能接受。训练就不一样了,CPU 一个 epoch 可能跑几分钟甚至十几分钟,不建议硬扛。

常见配置是 GTX 1660 Ti 6G 显存,这个卡跑 YOLOv8s 足够。我的习惯是:显存 6G 用 yolov8s,batch 设 8;显存 8G 用 yolov8m,batch 设 8;4G 以下老老实实用 yolov8n。显存不足时优先减 batch,而不是换更小模型,因为小模型在文物这类类间差异小的数据上,精度下降会很明显。

3. 文物数据集从哪来:采集、标注与 YOLO 格式转换

3.1 文物图片怎么凑:公开图库、博物馆拍摄与类目设计

公开的文物目标检测数据集非常少,常见做法是三个渠道混着来:博物馆官网的高清展品图、文物数字资源平台的公开图集,以及自己拍摄的实物或翻拍画册。别只盯着一类青铜器,要尽量覆盖你系统要识别的全部类目。

类目设计是第一个坑。比如“青铜鼎”和“青铜簋”外轮廓相似,模型很容易混。我的建议是控制类目在 8 到 12 类之间,每类至少 150 到 300 张。如果两类的照片连人眼都分不清,就合并成一个粗类,比如“青铜礼器”。毕设系统的演示效果比类目数量更重要。

图片收集时注意背景多样性。全挑白背景博物馆图,模型学到的可能只是“白底加器物”,而不是器物本身。多留一些展柜反光、偏斜角度、部分遮挡的照片,这些才是真实场景。

3.2 Labelme 手动标注:多边形还是矩形

文物外形不规则,用 Labelme 画多边形比矩形框更贴近真实轮廓,但 YOLO 训练只需要矩形框。常见做法是 Labelme 里画多边形,转换时取多边形外接矩形;如果器物密集排列、互相遮挡,直接画矩形框更快。

安装很简单:pip install labelme,打开后选 open dir 指向图片文件夹,用 create polygons 或 create rectangle 标注。每张图的标注结果是一个同名 json 文件,里面记录 imageWidth、imageHeight 和 shapes 数组。标注完成后,要做的不是直接把 json 丢给 YOLO,而是转成 txt 格式。

3.3 转成 YOLO txt:转换脚本与四个边界坑

import json import os import glob from PIL import Image CLASS_NAMES = ["bronze_ding", "pottery_jar", "jade_disc"] # 固定顺序,训练时与 yaml 保持一致 def labelme_to_yolo(json_path, out_dir, class_names): with open(json_path, "r", encoding="utf-8") as f: data = json.load(f) img_w = data["imageWidth"] img_h = data["imageHeight"] base_name = os.path.splitext(os.path.basename(json_path))[0] lines = [] for shape in data["shapes"]: label = shape["label"] if label not in class_names: continue cls_id = class_names.index(label) pts = shape["points"] xs = [p[0] for p in pts] ys = [p[1] for p in pts] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # YOLO 格式:class cx cy w h,全部归一化到 [0,1] cx = ((x_min + x_max) / 2.0) / img_w cy = ((y_min + y_max) / 2.0) / img_h bw = (x_max - x_min) / img_w bh = (y_max - y_min) / img_h # 越界裁剪,防止标注点在图像边缘外时产生 >1 的宽高 cx = max(0.0, min(cx, 1.0)) cy = max(0.0, min(cy, 1.0)) bw = max(0.0, min(bw, 1.0)) bh = max(0.0, min(bh, 1.0)) lines.append(f"{cls_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}") out_path = os.path.join(out_dir, base_name + ".txt") # 即使没有有效标注也要生成空 txt,否则训练端会警告标签文件缺失 with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(lines)) # 批量转换 os.makedirs("labels", exist_ok=True) for jp in glob.glob("json_files/*.json"): labelme_to_yolo(jp, "labels", CLASS_NAMES) print("converted:", os.path.basename(jp))

这里聚集了四个最容易翻车的点。第一,归一化坐标要用 json 里记录的 imageWidth/imageHeight,不能直接用图片文件尺寸,Labelme 有时会记录缩略图尺寸,两者不一致会导致框全部偏移。第二,yaml 里的类别顺序必须和这里 CLASS_NAMES 完全一致,类别 id 从 0 开始,顺序错了模型学到的名字全是错的。第三,如果一个框跨图片边缘,算出来宽度可能大于 1,训练时边界框坐标会异常,所以要 clip 到 [0,1]。第四,txt 文件名必须和图片主文件名完全一致,否则图片找不到对应标注,训练会跳过它。

3.4 数据划分:随机之外还要防泄漏

YOLO 训练需要 images/train、images/val 和 labels/train、labels/val 四个目录,图片和 txt 一一对应。常规做法是 8:2 随机划分,但要注意一个隐藏问题:如果你从同一张文物图里裁剪出多张局部图,它们必须进同一个集合,否则模型在验证集上会“见过”训练集的同源样本,mAP 虚高,答辩时被问到就会露馅。

import os import random import shutil files = os.listdir("images") random.seed(42) random.shuffle(files) train_files = files[: int(len(files) * 0.8)] val_files = files[int(len(files) * 0.8):] for split, split_files in [("train", train_files), ("val", val_files)]: os.makedirs(f"datasets/wenwu/images/{split}", exist_ok=True) os.makedirs(f"datasets/wenwu/labels/{split}", exist_ok=True) for f in split_files: base = os.path.splitext(f)[0] shutil.copy(f"images/{f}", f"datasets/wenwu/images/{split}/{f}") shutil.copy(f"labels/{base}.txt", f"datasets/wenwu/labels/{split}/{base}.txt")

random.seed 固定下来,保证每次复现同一个划分结果。注意 labels 里可能存在没有对应 txt 的图片,划分前先检查一遍,把缺标注的图片剔除,否则训练时静默跳过,你都不知道模型少学了几张图。

4. 训练自己的文物检测模型:参数、损失曲线与权重选择

4.1 数据配置 YAML:路径、类别名与 id 对齐

训练前要写一个数据 yaml,YOLOv8 通过它找到图片目录和类别名。这里我吃过亏:path 用相对路径时换个终端就找不到数据集,直接写绝对路径最稳。

path: /home/user/projects/wenwu/datasets/wenwu train: images/train val: images/val names: 0: bronze_ding 1: pottery_jar 2: jade_disc

path 指向数据集根目录,train 和 val 是相对 path 的图片目录。yaml 里的 names 顺序必须和第 3 章转换脚本里的 CLASS_NAMES 一致。这个文件放哪都行,训练命令里用绝对路径引用它。

4.2 模型尺寸选择:n、s、m 怎么选

模型体积6G 显存可否训练推荐场景
yolov8n最小可以,batch 16CPU 推理、快速验证流程
yolov8s小可以,batch 8毕设主力,精度和速度均衡
yolov8m中勉强,batch 4类别差异极小时用
yolov8l大不建议显存不足,训练太慢

文物数据样本少,通常几百张,我的经验是从 yolov8s 起步。n 模型在类间相似度高时误检明显,m 模型在 6G 卡上训练要频繁处理 OOM,s 是最稳的平衡点。如果你的数据只有一百张以内,那就用 n,避免过拟合。

4.3 训练命令与关键参数:epochs、imgsz、batch、lr

yolo train \ model=yolov8s.pt \ data=/home/user/projects/wenwu/wenwu.yaml \ epochs=100 \ imgsz=640 \ batch=8 \ device=0 \ patience=20 \ project=./runs \ name=wenwu_train

model=yolov8s.pt 是预训练权重,会从 COCO 的 80 类知识迁移过来,比从零训练收敛快得多。epochs 设 100,配合 patience=20,即验证集指标连续 20 轮不提升就自动早停,防止无效的空转。imgsz 保持 640,和预训练权重一致,改大反而可能掉点。batch=8 是 6G 显存的稳妥值,显存不够就先减到这个值的 1/2。device=0 表示第一张显卡,CPU 训练改成 device=cpu,但要有心理准备,100 轮可能要跑十几个小时。

学习率一般不用动,YOLOv8 默认的 lr0=0.01 在小数据集上是够用的。如果你发现 loss 曲线剧烈震荡,可以调低到 0.005。权重衰减、动量这些参数按默认即可,在小数据集上乱调这些只会增加玄学。

4.4 看损失曲线判断状态:画出来的才是真相

训练过程中 YOLOv8 会把每个 epoch 的指标写进 runs/wenwu_train/results.csv,我习惯每训练几轮就把它画出来看一次,不要等训练完才后悔药。

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("runs/wenwu_train/results.csv") plt.figure(figsize=(10, 6)) plt.plot(df["epoch"], df["train/box_loss"], label="train_box_loss") plt.plot(df["epoch"], df["val/box_loss"], label="val_box_loss") plt.plot(df["epoch"], df["metrics/mAP50(B)"], label="mAP50") plt.legend() plt.savefig("wenwu_train_loss.png")

正常的特征是 train/box_loss 前 20 轮快速下降,之后缓慢收敛;val/box_loss 和 mAP50 同步稳定。如果 train loss 一直降但 val loss 回升,是过拟合,说明数据量撑不起模型容量,换小模型或加数据增强。如果 loss 变成 nan,第一反应是检查数据里有没有全零的标注文件,或者学习率是不是被调太高。

4.5 选哪个权重:best.pt 与 last.pt 的差别

训练结束 runs/wenwu_train/weights 下会有 best.pt 和 last.pt。best.pt 是验证集 mAP 最高的一版,用于演示、评估、转 ONNX;last.pt 是最后一轮的结果,主要用于训练中断后续训。我的习惯是:答辩演示一律用 best.pt,并且把 best.pt 改名成更容易调用的名字复制到项目根目录,避免到时候路径找不着。

5. 可视化界面与部署避坑:从检测脚本到可交付的毕设系统

5.1 界面技术选型:PyQt5、Streamlit 还是 Flask

方案形态打包难度适用场景
PyQt5桌面程序中,可用 PyInstaller要求 exe 交付的毕设
StreamlitWeb 页面低现场演示、局域网访问
FlaskWeb API低前后端分离、手机端调用

毕设最稳的是 PyQt5,因为最后能交一个独立 exe,双击就能跑。Streamlit 做演示最快,十几分钟就能把上传图片、显示检测框串起来,适合时间紧先交差;但老师如果要求“系统”而不是“网页”,还是得回到桌面方案。Flask 适合你已经想好了要扩展到手机端或展示端的情况。

5.2 PyQt5 接入 YOLOv8 的最小代码:加载模型、选图、显示结果

import sys from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QLabel, QVBoxLayout, QFileDialog from PyQt5.QtGui import QPixmap, QImage from ultralytics import YOLO model = YOLO("best.pt") # 在界面启动前先加载模型 class MainWindow(QWidget): def __init__(self): super().__init__() self.setWindowTitle("文物识别系统") self.label = QLabel("请选择图片") self.btn = QPushButton("选择图片") self.btn.clicked.connect(self.select_image) layout = QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn) self.setLayout(layout) def select_image(self): path, _ = QFileDialog.getOpenFileName(self, "选择图片", "", "Images (*.jpg *.png *.jpeg)") if not path: return # 推理结果默认是 RGB 格式,转成 QImage 显示 results = model.predict(path, conf=0.25, imgsz=640, device="cpu") img = results[0].plot() # 已经画好检测框的 BGR 图像 rgb_img = img[:, :, ::-1] # BGR 转 RGB h, w, c = rgb_img.shape qimg = QImage(rgb_img.data, w, h, c * w, QImage.Format_RGB888) pix = QPixmap.fromImage(qimg).scaled(self.label.width(), self.label.height()) self.label.setPixmap(pix) if __name__ == "__main__": app = QApplication(sys.argv) win = MainWindow() win.show() sys.exit(app.exec_())

这段代码为了让逻辑直接,推理与界面放在同一个线程,演示没问题,但选大图时界面会卡一两秒。要上档次就加一个 QThread 处理推理,结果通过信号回传。plot() 返回的图像是 BGR 顺序,不转 RGB 直接显示颜色会偏蓝,这是最常见的界面显示翻车点。

5.3 打包成 exe:三个关键步骤

打包最好在干净的 conda 环境里做,不要在装了一堆包的系统环境里打包,否则 exe 体积会很夸张。

pip install pyinstaller pyinstaller -F -w --name wenwu_gui main.py --hidden-import cv2 --hidden-import torch

-F 打成一个单文件,-w 去掉控制台黑窗口。打包完成后,把 best.pt 复制到 exe 同目录下,程序启动时用绝对路径调YOLO("best.pt")或YOLO("./best.pt")。不建议把权重打进 exe 内部,那样每次换模型都要重新打包一次,而且 pyinstaller 解压临时目录的路径处理会让你多踩一个坑。

首次启动慢是正常的,因为 PyInstaller 单文件模式要把全部依赖解压到临时目录。如果杀毒软件误删,把它加入白名单即可,这不是代码问题。

5.4 常见部署翻车现场:现象、原因与解决

界面打开后点选图片直接卡死。原因是推理在 UI 主线程同步执行,大图推理期间界面事件循环被阻塞。解决办法是把 predict 放进 QThread,推理完通过 signal 把结果图传回主线程更新 QLabel。这是毕设答辩时最容易暴露的问题。

exe 提示找不到 best.pt。原因是程序在 exe 解压临时目录运行,相对路径找不到当前目录下的模型文件。解决办法是启动时先取 exe 所在目录绝对路径,再拼接模型路径,不要依赖 os.getcwd()。

打包后体积巨大,或提示缺少 opencv 依赖。原因是 PyInstaller 默认静态分析漏掉了 cv2 的隐式导入。解决办法是打包命令里显式加--hidden-import cv2 --hidden-import torch;如果还报错,用--collect-all ultralytics把 ultralytics 的资源和子模块全部收进去。

摄像头或视频画面全黑,推理却有结果。原因多数是 OpenCV 的 VideoCapture 与 PyQt 的图像格式转换没对齐。解决办法是先用cap = cv2.VideoCapture(0)单独测试,再逐帧把 BGR 转 RGB,再转 QImage 显示。不要上来就合并写。

同一个 best.pt,服务器上效果好,本机部署后漏检严重。原因往往是推理时 imgsz 和训练时不一致,或者图片有 EXIF 旋转信息没被处理。解决办法是作推理代码里固定imgsz=640,并用 PIL 打开图片后再转 numpy,把 EXIF 问题提前消化掉。

6. 验证效果与进阶技巧:评估、混淆矩阵与边缘部署

6.1 用模型自带的验证命令看 mAP

训练结束只贴 loss 曲线还不够,要跑一次正式的验证得到 mAP 数值。用下面这段代码,把 best.pt 在验证集上的表现量化出来。

from ultralytics import YOLO model = YOLO("runs/wenwu_train/weights/best.pt") metrics = model.val(data="/home/user/projects/wenwu/wenwu.yaml", imgsz=640, split="val") print("mAP50:", metrics.box.map50) print("mAP50-95:", metrics.box.map)

对文物识别这种类目少、样本也少的小任务,mAP50 比 mAP50-95 更能说明演示效果,mAP50 到 0.7 以上就足够撑起一个毕设系统。

6.2 混淆矩阵与漏检处理

验证跑完后会在 runs/detection/val 下生成混淆矩阵图,重点看哪两个类互相误检。如果“青铜鼎”和“青铜簋”混得很厉害,说明这两个类的训练样本外观太接近,要么补样本,要么合并类目。别试图靠调参解决类间混淆,这是数据问题,不是模型问题。

6.3 转 ONNX 做边缘部署:进阶方向

如果以后想把这套东西跑在 RK3588 这类边缘设备上,先转 ONNX 验证精度是否保留。

yolo export model=best.pt format=onnx imgsz=640

转完用 onnxruntime 加载跑一遍验证集,对比原权重 mAP 是否明显下降。正常下降幅度很小,如果掉点明显,先检查输入尺寸和归一化方式。进一步转 RKNN 时再做 int8 量化校准,这一步才是精度丢失最多的地方,需要拿一批真实场景图做校准集,否则模型基本废掉。

我的习惯是把验证集的 mAP 结果、混淆矩阵截图全部留在 runs 目录里,答辩时直接展示,这比任何口述都有说服力。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询