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