)
簡介本資源為基于 YOLOv8 的甲骨文原始拓片圖像單字分割識別模型源碼包面向計算機視覺學(xué)習(xí)者、深度學(xué)習(xí)競賽參與者及古文字?jǐn)?shù)字化研究者解決甲骨文字形復(fù)雜、傳統(tǒng)識別效率低的問題。項目將識別流程拆分為目標(biāo)檢測與字符識別兩階段先由 YOLOv8 檢測模型圈出拓片中的文字矩形區(qū)域再借助分類模型判定字形對應(yīng)的具體字符取材自 2024 年 MathorCup 數(shù)學(xué)應(yīng)用挑戰(zhàn)賽 B 題。壓縮包共 16 個文件約 627KB以 10 個 Python 腳本為核心涵蓋檢測與分類的預(yù)測入口、數(shù)據(jù)處理及可視化繪制模塊另附 yaml 配置、requirements 依賴說明、示例圖片與操作手冊文檔目錄按 detect、classify、utils 等模塊劃分便于按階段閱讀與復(fù)現(xiàn)。目前已有 440 人學(xué)習(xí)下載適合希望掌握 YOLOv8 檢測與分類聯(lián)合流程、積累跨學(xué)科項目經(jīng)驗的讀者參考。1. 甲骨文拓片單字分割識別從一張模糊拓片到可檢索字庫手上拿到一批甲骨文拓片掃描件灰度、低對比、字跡和骨面紋理糊在一起這是做古文字?jǐn)?shù)字化最頭疼的起點?;?YOLOv8 的甲骨文原始拓片圖像單字分割識別模型要解決的就是把一張整版拓片里的單個甲骨文字自動框出來、分割出像素級掩碼、再給出對應(yīng)的識別結(jié)果。它適合三類人做古籍?dāng)?shù)字化的工程團隊、想用真實冷門數(shù)據(jù)集練 YOLOv8 分割的算法同學(xué)、以及需要把甲骨文字形做成可檢索字庫的研究者。這件事的難點不在 YOLOv8 本身而在于拓片沒有干凈背景、字與字粘連、標(biāo)注成本極高。下面按我實際落地的順序把數(shù)據(jù)、訓(xùn)練、分割、識別、部署這條鏈路拆開講清楚能抄的代碼和參數(shù)我都放出來。2. 拓片數(shù)據(jù)怎么變成 YOLOv8 能吃的分割數(shù)據(jù)集2.1 先想清楚為什么用分割而不是純檢測甲骨文單字任務(wù)里檢測框只能告訴你「這里有個字」但拓片字跡邊緣和骨面裂紋交織框內(nèi)往往混進(jìn)大量非文字紋理。如果下游要做字形比對、拓片綴合或者字庫矢量化必須拿到像素級掩碼所以實例分割是剛需。YOLOv8 的-seg系列在檢測頭之外多了一個掩碼分支輸出masks和boxes兩套結(jié)果正好匹配「先定位再摳形」的需求。選 YOLOv8 而不是 Mask R-CNN主要理由是拓片數(shù)據(jù)集通常只有幾百到幾千張YOLOv8 收斂快、顯存占用低單張 2080Ti 甚至 1660Ti 都能跑起來對預(yù)算有限的項目更友好。2.2 標(biāo)注格式labelme 轉(zhuǎn) YOLO-seg 的腳本拓片標(biāo)注我一般用 labelme 畫多邊形因為甲骨文字形不規(guī)則矩形框根本貼不住。labelme 存出來是 JSONYOLOv8 分割要的是每行class x1 y1 x2 y2 ...的歸一化坐標(biāo) txt。轉(zhuǎn)換腳本如下import json import os from pathlib import Path # 類別映射甲骨文單字項目里通常先按字或未識別字粗分 CLASS_MAP {jiaguwen: 0} def labelme_to_yolo_seg(json_path, out_dir, img_w, img_h): with open(json_path, r, encodingutf-8) as f: data json.load(f) lines [] for shape in data[shapes]: label shape[label] if label not in CLASS_MAP: continue pts shape[points] # [[x1,y1],[x2,y2],...] # 歸一化并限制在 0~1防止標(biāo)注越界導(dǎo)致訓(xùn)練報錯 norm [] for x, y in pts: norm.append(min(max(x / img_w, 0.0), 1.0)) norm.append(min(max(y / img_h, 0.0), 1.0)) line str(CLASS_MAP[label]) .join(f{v:.6f} for v in norm) lines.append(line) out_path Path(out_dir) / (Path(json_path).stem .txt) out_path.write_text(\n.join(lines), encodingutf-8) # 批量處理img_w/img_h 必須和原圖一致否則掩碼會整體偏移 for jp in Path(labels_json).glob(*.json): labelme_to_yolo_seg(jp, labels_seg, 1024, 1024)邏輯說明YOLOv8 分割標(biāo)簽要求多邊形點按順序排列且歸一化到 0~1類別用整數(shù)索引。參數(shù)上img_w/img_h一定要取原圖真實尺寸很多人直接寫 640 導(dǎo)致掩碼縮放錯位。CLASS_MAP在甲骨文項目里建議先做單類等單字分類模型穩(wěn)定后再拆多類否則每類樣本太少分割頭學(xué)不動。2.3 數(shù)據(jù)集目錄與 data.yamlYOLOv8 對目錄結(jié)構(gòu)有固定約定圖片和標(biāo)簽同名不同后綴放在 images/labels 下dataset/ ├── images/ │ ├── train/ # 訓(xùn)練圖 │ └── val/ # 驗證圖 ├── labels/ │ ├── train/ # 對應(yīng) txt │ └── val/ └── data.yamldata.yaml內(nèi)容path: /home/user/dataset train: images/train val: images/val nc: 1 names: [jiaguwen]參數(shù)說明nc是類別數(shù)單字分割階段填 1names順序必須和轉(zhuǎn)換腳本里的CLASS_MAP一致。拓片數(shù)據(jù)量少時train/val 按 8:2 切且要保證同一版拓片的不同單字不要同時出現(xiàn)在訓(xùn)練和驗證集否則驗證指標(biāo)虛高這是血淚經(jīng)驗。3. YOLOv8-seg 訓(xùn)練參數(shù)怎么設(shè)、損失怎么看3.1 環(huán)境搭建與預(yù)訓(xùn)練權(quán)重Ubuntu 20.04 上 CPU 版本也能跑通小數(shù)據(jù)集但訓(xùn)練建議至少一張 8G 顯存以上的卡。安裝命令conda create -n yolo8 python3.10 -y conda activate yolo8 pip install ultralytics labelme opencv-python # 驗證安裝 yolo checks預(yù)訓(xùn)練權(quán)重用yolov8n-seg.pt起步數(shù)據(jù)量上千后再換yolov8s-seg.pt。權(quán)重文件放到項目根目錄訓(xùn)練時自動加載。CPU 版本訓(xùn)練速度極慢只適合調(diào)試代碼是否跑通正式訓(xùn)練還是上 GPU。3.2 訓(xùn)練命令與關(guān)鍵參數(shù)yolo segment train \ modelyolov8n-seg.pt \ datadataset/data.yaml \ epochs200 \ imgsz1024 \ batch8 \ lr00.01 \ lrf0.01 \ patience30 \ mosaic0.5 \ degrees10 \ translate0.1 \ scale0.3 \ projectruns/seg \ namejiagu_v1參數(shù)說明imgsz1024是因為拓片分辨率高字跡細(xì)節(jié)在 640 下會丟batch8按顯存調(diào)爆顯存就降到 4lr00.01是初始學(xué)習(xí)率lrf0.01是最終學(xué)習(xí)率比例配合余弦退火patience30表示 30 輪無提升就早停防止過擬合mosaic0.5做馬賽克增強但拓片本身紋理復(fù)雜mosaic 太高會讓字跡更難辨認(rèn)我一般不超過 0.5degrees/translate/scale是幾何增強拓片掃描角度有偏差時有用。3.3 損失曲線與指標(biāo)解讀訓(xùn)練完在runs/seg/jiagu_v1/下有results.csv和results.png。重點看三條曲線train/seg_loss、val/seg_loss、metrics/mAP50-95(M)。分割任務(wù)里 mask 的 mAP 通常比 box 低 5~10 個點這是正常的。如果train/seg_loss持續(xù)下降而val/seg_loss抬頭說明過擬合要么加數(shù)據(jù)要么把mosaic和degrees調(diào)低。畫損失曲線的腳本import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/seg/jiagu_v1/results.csv) df.columns [c.strip() for c in df.columns] plt.plot(df[epoch], df[train/seg_loss], labeltrain_seg) plt.plot(df[epoch], df[val/seg_loss], labelval_seg) plt.xlabel(epoch) plt.ylabel(seg_loss) plt.legend() plt.savefig(seg_loss.png, dpi150)邏輯說明results.csv列名可能帶空格先 strip 再取??辞€時不要只盯 lossmetrics/mAP50(M)才是最終分割質(zhì)量的判據(jù)拓片單字任務(wù)里 mAP50 能到 0.75 以上就算可用。4. 單字分割后處理從掩碼到獨立字形4.1 掩碼提取與二值化YOLOv8 推理輸出的是masks對象需要轉(zhuǎn)成 numpy 再二值化。代碼from ultralytics import YOLO import cv2 import numpy as np model YOLO(runs/seg/jiagu_v1/weights/best.pt) results model.predict(tuopian_001.jpg, imgsz1024, conf0.25, iou0.5) for r in results: if r.masks is None: continue masks r.masks.data.cpu().numpy() # shape: (n, h, w) for i, m in enumerate(masks): binary (m 0.5).astype(np.uint8) * 255 # 形態(tài)學(xué)去噪拓片掩碼邊緣常有毛刺 kernel np.ones((3, 3), np.uint8) binary cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel) cv2.imwrite(fchar_{i}.png, binary)參數(shù)說明conf0.25是置信度閾值拓片噪聲多時可以提到 0.4 減少誤檢iou0.5控制重疊框合并m 0.5是掩碼二值化閾值調(diào)高會讓字形變瘦調(diào)低會粘連。形態(tài)學(xué)開運算用來去掉小噪點核大小 3x3 足夠太大反而吃掉筆畫。4.2 粘連字分割分水嶺與投影法甲骨文里兩個字貼在一起很常見YOLOv8 可能把它們檢成一個實例。后處理用垂直投影找谷底切分def split_by_projection(binary): col_sum binary.sum(axis0) # 找投影谷底作為切分點 threshold col_sum.mean() * 0.3 splits [] in_gap False for x, v in enumerate(col_sum): if v threshold and not in_gap: in_gap True splits.append(x) elif v threshold: in_gap False return splits邏輯說明投影法適合左右結(jié)構(gòu)的粘連字上下結(jié)構(gòu)要換成行投影。threshold取均值的 0.3 倍是經(jīng)驗值拓片筆畫細(xì)時可以降到 0.2。切分后每個子圖再單獨送識別模型不要指望分割模型一次解決所有粘連。5. 避坑與排查拓片項目里最容易翻車的五件事5.1 掩碼整體偏移現(xiàn)象訓(xùn)練 loss 正常下降但推理時掩碼和字跡錯位半個字。原因標(biāo)注時img_w/img_h用了縮放后尺寸和原圖不一致。解決轉(zhuǎn)換腳本里讀原圖cv2.imread拿真實尺寸或者用 labelme JSON 里的imageWidth/imageHeight字段。5.2 驗證集指標(biāo)虛高現(xiàn)象mAP50 到 0.9實際推理一塌糊涂。原因同一版拓片的單字被隨機切分到 train 和 val模型記住了骨面紋理而非字形。解決按拓片編號分組切分同一版拓片整體進(jìn) train 或 val。5.3 小字被漏檢現(xiàn)象大個字能檢出小字全丟。原因imgsz太小或 anchor 尺度不匹配。解決把imgsz提到 1280或者在data.yaml里加overlap_maskTrue同時把conf降到 0.15 觀察召回。5.4 訓(xùn)練中途顯存爆掉現(xiàn)象前幾十輪正常突然 OOM。原因mosaic 增強在后期關(guān)閉時 batch 內(nèi)圖像尺寸變化或者驗證階段緩存累積。解決固定batch不要用-1自動模式驗證時加valFalse分階段跑。5.5 識別模型和分割模型類別對不上現(xiàn)象分割出 5 個字識別結(jié)果只有 3 個。原因識別模型單獨訓(xùn)練時類別索引和分割的CLASS_MAP不一致。解決兩個模型共用一份classes.txt訓(xùn)練前用腳本校驗索引映射。6. 把單字送進(jìn)識別模型CRNN 微調(diào)與端到端串聯(lián)分割只是第一步真正讓拓片可用的是識別。我一般用 CRNN 做單字分類輸入是分割出的二值字形圖輸出是字符編碼。數(shù)據(jù)量少時先用分割掩碼做數(shù)據(jù)增強把每個字旋轉(zhuǎn) ±10 度、加高斯噪聲擴充 5 倍再訓(xùn)。CRNN 訓(xùn)練腳本核心部分import torch import torch.nn as nn class CRNN(nn.Module): def __init__(self, num_classes): super().__init__() self.cnn nn.Sequential( nn.Conv2d(1, 64, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(64, 128, 3, padding1), nn.ReLU(), nn.MaxPool2d(2), nn.Conv2d(128, 256, 3, padding1), nn.ReLU() ) self.rnn nn.LSTM(256, 128, bidirectionalTrue, batch_firstTrue) self.fc nn.Linear(256, num_classes) def forward(self, x): x self.cnn(x) # (B,256,H/4,W/4) B, C, H, W x.shape x x.permute(0, 3, 1, 2).reshape(B, W, C * H) x, _ self.rnn(x) return self.fc(x[:, -1, :]) # 取最后時間步做分類參數(shù)說明輸入單通道灰度圖num_classes是甲骨文字表大小常見項目里先做 500 類以內(nèi)。LSTM 隱藏層 128雙向拼接后 256。訓(xùn)練時lr1e-3batch64用 Adam。端到端串聯(lián)時分割輸出的每個掩碼裁剪原圖對應(yīng)區(qū)域縮放到 32x32 送 CRNN置信度低于 0.6 的識別結(jié)果標(biāo)為「待考釋」不要強行輸出。驗證方法上我習(xí)慣留 10% 拓片做閉集測試另找 20 個未參與訓(xùn)練的字做開集測試。閉集準(zhǔn)確率到 90% 以上、開集能到 60% 左右這個方案就值得繼續(xù)投入。最后說個習(xí)慣每次改完數(shù)據(jù)增強或后處理參數(shù)先跑 20 張圖的推理可視化肉眼看掩碼貼合度比盯指標(biāo)快得多。希望幫到你。本文還有配套的精品資源點擊獲取