實戰(zhàn)解析)
簡介一套面向畢業(yè)設計與醫(yī)學圖像處理學習的舌象智能診斷系統(tǒng)資源基于YOLO深度學習框架與Python腳本語言構建涵蓋圖像采集、特征提取、模型訓練到結果分析完整流程。資源共二百二十一個文件包含五十四個程序源碼與四十個編譯后文件、六十余張標注圖片、界面文件、配置文檔以及技術說明文檔壓縮包整體約四十二點七六兆字節(jié)目錄結構清晰便于檢索。已有六十七人瀏覽學習適用于高校畢業(yè)設計、課程實踐及中醫(yī)輔助診斷研究。項目采用模塊化架構核心算法獨立封裝并集成光照補償、色彩校正等預處理增強技術可實現舌體輪廓與舌苔分布等關鍵特征的精準識別。配套標注數據集與詳細注釋代碼便于初學者理解模型訓練流程和系統(tǒng)二次開發(fā)具備較強的工程參考價值。1. 舌象智能診斷為什么一張舌頭照片到了 YOLO 手里比肉眼看更穩(wěn)舌象是中醫(yī)望診里信息量最大的部位但也是主觀性最強的一項同一個舌頭在白天和晚上、在不同醫(yī)生眼里結論能差出一檔。把舌象圖片交給 YOLO 系列目標檢測模型配合 Python 做后處理和規(guī)則映射就能把“舌質淡紅、苔薄白”這類判斷固化成可重復的流程。這個基于 YOLOv 深度學習的舌象智能診斷系統(tǒng)核心價值不是替代中醫(yī)而是做一個不會疲勞、不隨光線亂變的輔助診斷入口檢測舌頭區(qū)域、提取舌色苔色、輸出結構化報告。含完整源碼和數據集意味著你可以從零跑通全流程很適合正在做畢設、想入門醫(yī)學圖像 AI、或者打算做健康管理產品原型的從業(yè)者。下面我會按真實項目落地的順序把數據準備、模型訓練、封裝推理和典型踩坑一次講透。2. 舌象檢測/識別的任務拆解與數據集準備先想清楚要檢測還是分類2.1 舌象診斷到底要模型輸出什么定位、分類還是分割很多第一次接觸舌象 AI 的人會把任務直接當成“給一張舌圖輸出一個結論”。但真正做下來你會發(fā)現YOLO 最適合也最應該承擔的是第一層任務舌體目標檢測。舌象診斷的完整鏈路通常是這樣的輸入一張含面部或口腔的圖片先由 YOLO 檢測出舌體矩形框再從框內裁剪舌頭區(qū)域交給后面的分類模型判斷舌色淡白、淡紅、紅、絳紅等、苔色白/黃/灰黑、苔質薄/厚/膩/剝等最后用規(guī)則引擎或公式模型把分類結果組合成文本報告。為什么第一層必須用檢測而不是直接整圖分類因為整圖分類會受到嘴唇、牙齒、皮膚暗影的干擾而這些區(qū)域在生理上本身就帶顏色深度學習非常容易被它們帶偏。YOLO 把舌體剪裁出來后分類模型的輸入就干凈得多訓練時也不需要為舌體位置做額外擴增。另外很多人還會糾結要不要直接上 YOLO 的實例分割模型YOLOv8-seg / YOLOv5-seg。如果只是做舌色苔色判斷分割并不是必須的檢測框裁剪已經足夠。但如果你要做舌面面積、裂紋長度、齒痕比例這類定量分析分割就是必要的。我的建議是先跑通檢測 分類的最低可用版本再用分割模型去升級不要一上來就追全功能。2.2 舌象數據的采集與標注一張舌頭照片怎么變成 YOLO 能吃的標簽目標檢測的數據標注格式常見的有 COCO、VOC 和 YOLO 三種。YOLO 訓練時最省事的是直接在 ultralytics 框架下使用 YOLO 格式。標注工具用 LabelImg 或 Label Studio 都可以我一般用 LabelImg打開圖片畫矩形框類別寫 Tongue保存后自動生成同名的.txt文件。YOLO 格式的標簽內容是一行五個數字class_id x_center y_center width height注意這些都是相對于圖片寬高的比例值范圍在 0-1 之間。舉個例子一張 640x480 的圖片若舌體框左上角坐標是 (150, 100)右下角是 (400, 350)則換算為# class_id0 (Tongue), x_center(150400)/2/6400.4297, y_center(100350)/2/4800.4688 # width(400-150)/6400.3906, height(350-100)/4800.5208 0 0.4297 0.4688 0.3906 0.5208標注時有一條血淚經驗舌體邊界要摳到舌頭輪廓最大外接矩形不要把嘴唇下緣包進去。否則后續(xù)裁剪分類時模型拿到的舌頭區(qū)域里混入唇色訓練出的舌色分類器會在嘴唇邊緣產生偽特征。每張圖標注耗時大約 30 秒到 1 分鐘300 張圖半天能完成。2.3 一套可復用的舌象數據集應該長什么樣數據集來源不外乎兩個公開數據集和自己采集。公開的舌象圖像集不算特別多國內高校和醫(yī)學圖像競賽偶爾會放出脫敏的舌圖Kaggle 上也有 Tongue Segmentation 相關的開源樣本。如果做畢設或預研先找?guī)装購堥_源舌圖足夠驗證流程如果做產品則需要聯合醫(yī)療機構做合規(guī)采集并且每張圖都要有兩位及以上中醫(yī)師標注舌質苔色做一致性檢驗。拿到原始圖片后不管來源如何都要整理成一個固定結構。我常用的是這種組織方式也強烈建議你復制tongue_dataset/ ├── images/ │ ├── train/ # 訓練圖片如 00001.jpg │ ├── val/ # 驗證圖片 │ └── test/ # 測試圖片 ├── labels/ │ ├── train/ # 與圖片同名的 .txt 標簽 │ ├── val/ │ └── test/ └── tongue.yaml # 交給 YOLO 的數據配置tongue.yaml的內容很簡單它告訴 YOLO 去哪里找圖、有幾個類別# 訓練和驗證數據的絕對或相對路徑 path: ./tongue_dataset train: images/train val: images/val test: images/test nc: 1 names: [Tongue]注意path建議用絕對路徑或者固定在項目根目錄下跑訓練命令否則 YOLO 容易找不到數據。另外.txt標簽必須和圖片文件同名一個常見翻車點是 LabelImg 保存時自動改了后綴導致標簽文件沒有被 YOLO 讀取卻不報錯只是 mAP 一直為 0。3. 用 YOLOv8 在本地訓練舌象檢測模型從配置到收斂的關鍵參數3.1 為什么選 YOLOv8 而不是更深的分類網絡舌象診斷里檢測網絡用 YOLO 幾乎是毫無疑問的。YOLOv8 是 ultralytics 目前最成熟的目標檢測框架內置了訓練、驗證、導出、推理一條龍適合快速驗證YOLOv5 仍在大量老項目中使用但新項目我建議直接用 YOLOv8。YOLOv8 提供 n / s / m / l / x 五個尺寸舌象檢測屬于中小目標且環(huán)境相對簡單用yolov8n或yolov8s就足夠推理速度在 CPU 上也能做到單張幾十到幾百毫秒。為什么不直接用 ResNet 或者 EfficientNet 這類分類網絡做端到端“舌象狀態(tài)分類”因為分類網絡輸出的是全局概率它必須把舌體在畫面中裁到歸一位置才能學習好你在數據預處理時就得做舌體對齊而這個對齊往往比模型本身更費勁。YOLO 把“找到舌頭在哪”和“識別舌頭”融合在一個網絡里訓練時只需標注框模型自動學習舌體的空間特征部署時還能把坐標一并輸出后續(xù)裁剪、歸一化、報告都有依據。3.2 開始訓練安裝 ultralytics、準備數據、跑通最小命令環(huán)境部分不需要自己搭 Darknet直接裝ultralytics包即可它內部會處理 YOLOv8 的依賴。Python 3.8-3.11 我都跑通過建議用 Python 3.9 或 3.10。安裝命令如下# 建議先創(chuàng)建虛擬環(huán)境 python -m venv tongue_env source tongue_env/bin/activate # Windows 下執(zhí)行 tongue_env\Scripts\activate # 安裝 ultralytics 會自動帶 torch如已有 CUDA 版 torch 可以跳過 pip install ultralytics # 驗證安裝 yolo --help接著在項目目錄下新建train.py內容是最小訓練腳本from ultralytics import YOLO # 加載預訓練權重。yolov8n.pt 會在第一次運行時自動從 ultralytics 官方源下載 model YOLO(yolov8n.pt) # 訓練。data 指向上一章創(chuàng)建的 tongue.yaml results model.train( datatongue_dataset/tongue.yaml, epochs120, imgsz640, batch16, lr00.01, patience20, projectruns/tongue, nameexp1, device0, )這里每個參數都值得解釋一下。epochs120對幾百張圖的數據集來說偏大但配合patience20的早停模型會在驗證集損失連續(xù) 20 輪沒有下降時自動停止所以設大一點沒關系。imgsz640是 YOLO 默認輸入尺寸舌體在圖像中通常占比較高640 夠用如果舌頭周圍有很多干擾用 960 會提升精度但訓練顯存和推理時間都會漲。batch16根據你的 GPU 顯存調整8GB 顯存跑 YOLOv8n 可以到 16如果顯存緊張就先降到 4。lr00.01是當前主流框架的默認初始學習率對遷移學習比較友好。3.3 三個必調參數imgsz、batch 和早停以及預訓練權重的陷阱訓練舌象檢測模型時需要重點關注三個直接影響收斂質量的參數。第一個是imgsz。舌象數據往往來源于手機拍攝舌體在圖像中占 1/3 到 1/2不算小目標但如果你把圖片縮得太小比如 320舌苔紋理和舌質顏色這些細膩信息就丟了。我試過 320、640、960 三檔640 和 960 的 mAP 差距可能在 2-3 個點但 960 訓練時間長一半。建議先跑 640 看整體流程再跑 960 做精度微調。第二個是batch。batch 太小會使得 BNBatch Normalization統(tǒng)計量不穩(wěn)定舌象數據本身顏色差異大batch 小于 8 時驗證集 loss 會周期性震蕩。如果顯存不足不要一味調小 batch可以用batch-1讓 YOLO 自動檢測可用顯存并選擇最大 batch但自動選擇很保守未必是最優(yōu)。第三個是patience。很多新手把 epochs 寫到 300然后去看全流程實際上你的數據可能 50 輪就收斂了。設置patience30后模型只保存驗證集指標最好的那一次權重最后一個 epoch 的權重不一定最好。訓練完成后runs/tongue/exp1/weights/best.pt就是你要的模型文件。有一個預訓練陷阱要專門提醒YOLOv8 默認下載的yolov8n.pt是 COCO 80 類預訓練權重它的 backbone特征提取部分已經學會通用形狀特征直接遷移到舌體檢測完全沒問題。但如果你誤用了yolov8n-cls.pt分類版本訓練時模型結構不同會導致維度報錯反之用 YOLOv5 的.pt權重放在 YOLOv8 里也會因為結構不匹配報錯。記得統(tǒng)一版本。4. 用 Python 把訓練好的模型封裝成舌象診斷小系統(tǒng)推理、裁剪與報告輸出4.1 推理腳本加載模型處理單張圖片并返回舌頭坐標訓練得到best.pt后下一步就是脫離訓練腳本做一個獨立的推理入口。推理代碼幾乎可以一行調起 YOLO但要拿到可用的坐標和類別還需要解析結果對象。下面是我常用的最小推理腳本from ultralytics import YOLO import cv2 model YOLO(runs/tongue/exp1/weights/best.pt) def detect_tongue(image_path): img cv2.imread(image_path) results model(img, conf0.35, iou0.45, verboseFalse) boxes results[0].boxes if len(boxes) 0: return None # 取置信度最高的一個框作為舌體區(qū)域 best boxes[0] x1, y1, x2, y2 best.xyxy[0].tolist() # 像素坐標 conf best.conf.item() cls int(best.cls.item()) return { box: [int(x1), int(y1), int(x2), int(y2)], confidence: round(conf, 4), class_id: cls } # 示例診斷一張圖片 result detect_tongue(test_img.jpg) print(result)這段代碼的要點是model(img)的輸入可以直接是 numpy 數組不用先寫文件conf0.35表示置信度低于 0.35 的框會被濾掉舌象場景下舌頭通常很大且顯眼0.35 夠用但如果圖片里有嘴唇和舌頭緊密貼近可以把 conf 調高到 0.5 以減少誤檢。iou0.45是 NMS 閾值目標重疊嚴重時調低到 0.3 會更有區(qū)分力。4.2 舌色與苔質分析從檢測框里裁剪出舌頭再交給分類模型拿到檢測框后下一級分類網絡需要單獨訓練。分類網絡可以用最輕量的 torchvision 模型也可以用 YOLO 自帶的分類頭不過我更推薦用 torchvision 里的 resnet18 或 mobilenet_v3_small方便控制輸入尺寸。下面是裁剪與預處理代碼import cv2 import torch from torchvision import transforms from PIL import Image def crop_tongue(image_path, box, margin0.1): img cv2.imread(image_path) x1, y1, x2, y2 box # 在原框基礎上外擴 margin避免裁剪時舌頭邊緣被切掉 h, w img.shape[:2] dx int((x2 - x1) * margin) dy int((y2 - y1) * margin) x1 max(0, x1 - dx) y1 max(0, y1 - dy) x2 min(w, x2 dx) y2 min(h, y2 dy) cropped img[y1:y2, x1:x2] return cropped # 加載舌色/苔質分類模型此處以 resnet18 為例 # 假設你已經用自己的數據訓練好了一個二輸出分類模型tongue_color_classifier.pt # 訓練代碼見后文提示這里只演示推理鏈路 device torch.device(cuda if torch.cuda.is_available() else cpu) model torchvision.models.resnet18(num_classes4) model.load_state_dict(torch.load(tongue_color_classifier.pt, map_locationdevice)) model.eval().to(device) transform transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) ]) def predict_tongue_color(cropped_bgr): rgb cv2.cvtColor(cropped_bgr, cv2.COLOR_BGR2RGB) tensor transform(rgb).unsqueeze(0).to(device) with torch.no_grad(): probs torch.softmax(model(tensor), dim1)[0] return probs.cpu().numpy().tolist()這里有個容易忽略的細節(jié)YOLO 訓練時用 BGR 讀入圖片還是 RGB 讀入取決于你的數據管道但 ultralytics 內部會轉為 RGB 處理。而 torchvision 的模型默認期望 RGB 輸入所以推理時一定要把 OpenCV 讀進來的 BGR 轉成 RGB否則分類準確率會明顯下降——這是典型的“黑匣子翻車現場”。關于分類模型的訓練你同樣可以用 ultralytics 的分類模式跑yolo classify train data... model...然后保存 pt 文件。如果你手頭沒有預訓練分類器可以先直接用顏色直方圖聚類做一個 baseline驗證整個鏈路。4.3 輸出診斷建議把舌色和苔質組合成結構化報告模型輸出的都是概率最后一句“舌淡紅苔薄白”需要通過規(guī)則引擎組合。規(guī)則不需要復雜甚至一張查表就能完成。下面是一個最小化的規(guī)則映射示例# 舌色概率列表對應[淡白, 淡紅, 紅, 絳紅] # 苔色概率列表對應[白苔, 黃苔, 灰黑苔, 少苔] tongue_color_labels [pale-white, pale-red, red, deep-red] coating_labels [white, yellow, gray-black, scant] def top_label(probs, labels): idx probs.index(max(probs)) return labels[idx], probs[idx] def make_report(tongue_probs, coating_probs): tongue, tongue_conf top_label(tongue_probs, tongue_color_labels) coating, coating_conf top_label(coating_probs, coating_labels) report f舌質{_label_zh(tongue)}苔{_label_zh(coating)} suggestions [] if tongue pale-white: suggestions.append(提示氣血偏虛建議規(guī)律作息、避免生冷) elif tongue red: suggestions.append(提示熱象建議清淡飲食、少熬夜) if coating yellow: suggestions.append(濕熱偏重注意飲食油膩) return report, suggestions def _label_zh(key): return { pale-white: 淡白, pale-red: 淡紅, red: 紅, deep-red: 絳紅, white: 白, yellow: 黃, gray-black: 灰黑, scant: 少 }[key]這里的關鍵是報告語句要符合中醫(yī)描述習慣而不是直接把英文標簽堆上去。規(guī)則內容可以由中醫(yī)師提供你只需要維護一個映射表。后續(xù)如果數據量變大可以用一個簡單的評分卡模型替代硬規(guī)則但在數據量不夠大時規(guī)則比模型更可靠也更好向醫(yī)生解釋。5. 舌象檢測訓練的 5 個典型踩坑癥狀、原因與改法5.1 數據集只有幾百張訓練 loss 降不下去卻不知道哪里錯現象訓練幾十輪后損失值仍然在 2.0 以上驗證集 mAP 始終低于 0.1。原因最常見的是標簽和數據不匹配標簽文件里的 class_id 寫成了 0但 yaml 里 nc1、names 列表第一個是 Tongue這種情況一般沒問題真正的大坑是標簽文件中的坐標值超過了 0-1 范圍或者用 VOC 標注后的坐標沒有歸一化就喂給 YOLO。另一個原因是學習率過高舌象檢測屬于域遷移從 COCO 到醫(yī)學圖像特征差異較大lr00.01 還可以但超過 0.02 容易在初始階段把預訓練權重沖掉。解決先用腳本掃描標簽值范圍確認所有中心點和寬高均在 0-1 之間然后把 lr0 降到 0.005把warmup_epochs保留默認最后用帶預訓練權重的yolov8n.pt重新訓練不要從隨機權重開始。5.2 舌頭和嘴唇被檢測成同一個框舌色判斷被唇色帶偏現象檢測框能包住舌頭但框的上邊界頂到嘴唇下沿框內面積一半是嘴唇。后續(xù)分類器訓練時學到的“舌色”其實是唇色。原因標注時舌根部分被嘴唇遮擋標注人員習慣把可見的舌頭區(qū)域連同嘴唇一起框進去。從數據上看這類框的 IoU 沒問題但語義不干凈。另外 NMS 后只保留最大框時嘴唇和舌頭挨得太近YOLO 把兩者當成了同一目標。解決標注規(guī)范里明確規(guī)定舌體框只畫到舌尖到舌根可見邊界寧可把舌根切掉也不要包住嘴唇。推理時對檢測結果再做一次內縮把box的上下邊界各自縮小 5%-10%這樣裁剪區(qū)域更靠近舌面中央。如果你用的是分割模型直接取分割掩膜的外接矩形能避免大部分嘴唇誤入。5.3 測試時把深膚色或暗光環(huán)境下的舌頭漏檢現象在明亮圖片上檢測準確光線偏暗或者膚色較深的人臉照片上檢測不到舌頭或置信度很低。原因訓練集里沒有覆蓋足夠的明暗和膚色變化。舌象本身的顏色和膚色有較大重疊YOLO 學到的可能只是“亮紅色區(qū)域”而不是舌頭形狀結構。解決在訓練階段開啟數據增強尤其是 HSV 通道的隨機擾動。ultralytics 默認開啟了幾種增強但不夠可以自己在數據加載階段把圖片整體亮度隨機乘 0.6-1.4或者用 OpenCV 做 CLAHE 對比度增強后再送進網絡。推理時也先對圖片做 CLAHE把光照歸一化到相對統(tǒng)一的范圍。統(tǒng)計上這能把暗光漏檢率降低一半以上。5.4 訓練中顯存爆掉換小模型后精度下降嚴重現象8GB 顯存跑 YOLOv8s 時 batch16 直接 OOM改成 batch4 后訓練過程震蕩換 YOLOv8n 后精度下降 4 個點讓人懷疑是模型太小。原因batch4 對 BN 來說統(tǒng)計噪聲過大舌象內部顏色差異大導致驗證集 loss 抖動加劇YOLOv8n 的特征通道本身就窄如果輸入分辨率又不變小模型對舌苔紋理的擬合能力確實弱。解決換 YOLOv8n 的同時把imgsz從 640 提到 480讓有效感受野和算力匹配同時開啟梯度累積batch8, accumulate2等效 batch16。另外把cacheTrue打開減少數據加載瓶頸這樣反而可能比硬跑大模型更快收斂。5.5 CPU 部署推理慢單張圖接近秒級現象把模型放在 Windows CPU 電腦上跑一張圖推理耗時 800ms 以上無法做成實時反饋。原因直接用 PyTorch 框架推理模型未做任何優(yōu)化。YOLOv8n 參數量約 3.2M在 CPU 上走 PyTorch 的動態(tài)圖路徑效率低。解決導出為 ONNX再用 OpenVINO 或 ONNXRuntime 跑推理。ultralytics 提供了現成接口model.export(formatonnx, opset12)然后使用 onnxruntime 庫加載。注意導出時指定imgsz和你訓練時一致的 640否則推理尺寸不一致會掉精度。更低級的做法是啟用量化model.export(formatonnx, int8True)但舌象顏色對量化敏感建議優(yōu)先保持 FP32 精度只做圖優(yōu)化。6. 進階給舌象診斷模型加一個簡易 Web 接口并量化實測效果模型訓練和推理腳本跑通后要想真正投入日常試用最好封裝成一個 HTTP 服務這樣可以直接用瀏覽器上傳圖片看結果。我常用 Flask 搭一個最簡接口前端不用做用 curl 就能驗證from flask import Flask, request, jsonify import base64, cv2, numpy as np from your_inference import detect_tongue, crop_tongue, predict_tongue_color, make_report app Flask(__name__) app.route(/tongue-diagnosis, methods[POST]) def diagnose(): file request.files[image] img_bytes file.read() nparr np.frombuffer(img_bytes, np.uint8) img cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 1. 檢測舌體 det detect_tongue(img) if det is None: return jsonify({error: no tongue detected}), 400 # 2. 裁剪 分類 cropped crop_tongue(img, det[box]) tongue_probs predict_tongue_color(cropped) coating_probs predict_tongue_coating(cropped) # 3. 生成報告 report, suggest make_report(tongue_probs, coating_probs) return jsonify({ box: det[box], confidence: det[confidence], report: report, suggestions: suggest }) if __name__ __main__: app.run(host0.0.0.0, port5000)這個接口把前面所有模塊串起來測試時用curl -F imagetest.jpg http://127.0.0.1:5000/tongue-diagnosis就能看到 JSON 結果。上線前記得做兩件事第一對 50 張持有醫(yī)生標注的測試圖統(tǒng)計分類準確率和檢測 mAP把結果寫進項目 README這不僅是為了驗證更是為了給使用方一個信任依據第二用多線程壓測接口確認 CPU 下吞吐能到多少再決定是否需要用消息隊列做異步處理。我自己的教訓是不要跳過評估直接給人演示。舌象 AI 最怕的不是模型不夠準而是“演示效果時好時壞說不清為什么”。用固定測試集、固定光照條件、記錄每張圖的置信度才能讓這個系統(tǒng)從“玄學”變成“可解釋的工具”。希望這篇筆記能幫你把舌象診斷的整條鏈路一次跑通少走我當年走過的彎路。祝你順利。本文還有配套的精品資源點擊獲取