督正樣本訓練缺陷檢測:Python源碼實現與缺陷mask生成)
簡介這份資源面向計算機相關專業(yè)在校學生、教師及企業(yè)開發(fā)者提供一套基于Python的無監(jiān)督正樣本缺陷檢測完整源碼與項目說明解決僅有正樣本數據時如何訓練模型并輸出缺陷mask的問題。項目分兩個數據部分part1為黑灰圖part2為白邊包圍與純黑圖均屬正樣本測試集TC_image包含正常與負樣本負樣本涵蓋凸起、塊狀、線狀、缺口等缺陷類型。針對背景單一、平坦區(qū)域采用較簡單模型而復雜紋理數據集則改用無缺陷大圖原圖訓練待測圖以切片形式輸入體現了從簡單到復雜場景的模型選型思路。壓縮包共120個文件以63個py源碼、29個pyc編譯文件、7個md說明文檔為主另含bmp、jpg、png圖像樣本、xml標注、ipynb實驗筆記及Dockerfile等整體約1.59MB結構清晰便于復現。已有261人學習適合作為畢業(yè)設計、課程大作業(yè)或項目立項演示讀者可借鑒完整訓練流程、數據組織方式與缺陷判定邏輯并在此基礎上修改擴展。1. 只有正樣本也能訓缺陷檢測這套 Python 源碼到底能解決什么產線上拿到的樣本幾乎全是良品這是缺陷檢測項目里最真實的開局。你手里可能只有幾百張 OK 圖缺陷圖一張沒有或者偶爾冒出來幾張還標不全 mask。傳統(tǒng)監(jiān)督檢測在這里直接卡死——沒有負樣本分類器學不出邊界。這套基于 Python 的無監(jiān)督正樣本訓練源碼解決的正是這個場景只用正樣本訓練推理時輸入一張帶缺陷的圖模型輸出缺陷所在的 mask 區(qū)域。項目里給了兩個 part 的數據part1 是黑灰圖part2 是白邊包圍加純黑圖都算正樣本測試集 TC_image 里混了正常樣本和負樣本負樣本覆蓋凸起、塊狀、線狀、缺口幾類缺陷。背景單一、屬于平坦區(qū)域所以模型結構不復雜適合拿來當畢設、課程設計或大作業(yè)的落地骨架。如果你正在找一份能跑通、能改、能寫進論文的缺陷檢測源碼這份資源的定位就是「正樣本訓練 缺陷 mask 輸出」的最小可用閉環(huán)。2. 無監(jiān)督正樣本訓練的原理與數據組織為什么平坦圖能這么干2.1 正樣本訓練的核心邏輯把重建誤差當缺陷分數無監(jiān)督正樣本訓練的主流思路是自編碼器或重建網絡。模型只見過良品訓練目標是把輸入圖壓縮再還原讓重建誤差在正樣本上盡可能小。推理時喂一張帶缺陷的圖缺陷區(qū)域因為沒在訓練分布里出現過重建質量差誤差圖自然在缺陷位置隆起。項目里背景單一、紋理平坦這個假設成立得比較穩(wěn)——平坦區(qū)域的像素分布集中模型容易學到「正常長什么樣」缺陷一旦打破局部一致性誤差就冒出來。常見做法是編碼器-解碼器結構中間加一個瓶頸層限制信息容量逼模型只保留正樣本的主要模式。損失函數用 MSE 或 SSIM前者對像素級差異敏感后者對結構差異更穩(wěn)。這份源碼走的是較簡單的模型路線沒有堆復雜注意力模塊原因就在數據本身黑灰圖、純黑圖、白邊圖紋理信息少上重模型反而容易過擬合到噪聲。提示正樣本訓練不是萬能鑰匙。背景復雜、紋理豐富的圖簡單重建模型會把缺陷也當正常紋理重建掉漏檢率飆升。項目正文里專門提到復雜圖像數據集要用另一套思路就是這個原因。2.2 數據目錄與文件角色r.bmp、t.bmp、k.bmp、s.bmp 各是什么項目根目錄下的幾個 bmp 文件和 Dockerfile、description.ipynb 構成了最小工程。r.bmp、t.bmp、k.bmp、s.bmp 是正樣本圖或測試圖0_defect_repair_val.jpg 是缺陷修復后的驗證輸出圖。description.ipynb 是項目說明 notebook通常包含數據加載、訓練、推理的完整流程演示。Dockerfile 用來固化環(huán)境避免 Python 版本和依賴庫版本漂移。文件/目錄角色使用階段r.bmp / t.bmp / k.bmp / s.bmp正樣本或測試圖訓練與推理輸入0_defect_repair_val.jpg缺陷修復驗證輸出推理后可視化description.ipynb流程說明與演示全流程參考Dockerfile環(huán)境構建部署與復現project.imlIDE 工程配置開發(fā)環(huán)境.gitignore版本控制忽略規(guī)則代碼管理數據分兩個 partpart1 黑灰圖part2 白邊包圍加純黑圖。測試集 TC_image 里正常和負樣本混在一起需要先判定哪些是負樣本再把負樣本的缺陷 mask 標出來。負樣本缺陷類型包括凸起、塊狀、線狀、缺口這四類在平坦背景下的形態(tài)差異明顯mask 標注時要注意邊界貼合。2.3 環(huán)境搭建Python 依賴與 Dockerfile 兩條路環(huán)境搭建有兩條路。第一條是本地 Python 虛擬環(huán)境第二條是直接用 Dockerfile。本地路線適合調試Docker 路線適合交付和復現。# 創(chuàng)建虛擬環(huán)境避免污染系統(tǒng) Python python -m venv venv # Linux/macOS 激活 source venv/bin/activate # Windows 激活 venv\Scripts\activate # 安裝核心依賴版本按項目實際 requirements 調整 pip install numpy opencv-python torch torchvision matplotlib scikit-image這段命令先建隔離環(huán)境再裝依賴。numpy 和 opencv-python 負責圖像讀寫與預處理torch 和 torchvision 提供網絡與訓練工具matplotlib 做可視化scikit-image 常用于 SSIM 等結構相似度計算。參數上torch 版本要和 CUDA 匹配沒有 GPU 就用 CPU 版訓練會慢但平坦圖數據量小能跑通。# 基于官方 Python 鏡像固定版本保證復現 FROM python:3.9-slim WORKDIR /app COPY . /app RUN pip install --no-cache-dir numpy opencv-python torch torchvision matplotlib scikit-image CMD [python, train.py]Dockerfile 的邏輯是固定基礎鏡像、復制代碼、裝依賴、設啟動命令。--no-cache-dir 減小鏡像體積CMD 里的 train.py 按項目實際入口替換。用 Docker 的好處是換機器不用重新配環(huán)境壞處是鏡像構建時網絡不穩(wěn)會卡在 pip 安裝建議先本地拉好 wheel 再構建。注意項目里沒有明確寫 requirements.txt 時依賴版本以 description.ipynb 里實際 import 和運行結果為準不要盲目裝最新版torch 和 numpy 的大版本不兼容會直接報錯。3. 訓練與推理全流程從正樣本到缺陷 mask 的代碼落地3.1 數據加載與預處理把 bmp 圖轉成模型能吃的張量數據加載的第一步是讀圖、轉灰度或保持通道、歸一化。平坦圖背景單一歸一化到 [0,1] 或 [-1,1] 都行關鍵是訓練和推理用同一套預處理。import cv2 import numpy as np import torch from torch.utils.data import Dataset, DataLoader class DefectDataset(Dataset): def __init__(self, image_paths, img_size256): self.image_paths image_paths self.img_size img_size def __len__(self): return len(self.image_paths) def __getitem__(self, idx): # 以灰度方式讀取平坦圖不需要彩色信息 img cv2.imread(self.image_paths[idx], cv2.IMREAD_GRAYSCALE) # 統(tǒng)一尺寸避免網絡輸入維度不一致 img cv2.resize(img, (self.img_size, self.img_size)) # 歸一化到 [0,1]并增加通道維度 img img.astype(np.float32) / 255.0 img np.expand_dims(img, axis0) return torch.tensor(img) # 假設正樣本路徑列表已準備好 train_dataset DefectDataset([r.bmp, t.bmp, k.bmp, s.bmp]) train_loader DataLoader(train_dataset, batch_size4, shuffleTrue)這段代碼定義了數據集類讀圖用灰度模式resize 到 256×256歸一化后擴通道。參數 img_size 決定網絡輸入分辨率太小缺陷細節(jié)丟失太大顯存吃緊平坦圖 256 夠用。batch_size 按顯存調4 是保守值。shuffleTrue 打亂順序避免模型學到文件順序的偽相關。3.2 模型定義一個夠用的卷積自編碼器模型不用復雜卷積自編碼器在平坦圖上表現穩(wěn)定。編碼器逐層降維解碼器逐層還原中間瓶頸層控制信息容量。import torch.nn as nn class ConvAutoencoder(nn.Module): def __init__(self): super().__init__() # 編碼器3 層卷積逐步提取特征并降采樣 self.encoder nn.Sequential( nn.Conv2d(1, 16, 3, stride2, padding1), # 256 - 128 nn.ReLU(), nn.Conv2d(16, 32, 3, stride2, padding1), # 128 - 64 nn.ReLU(), nn.Conv2d(32, 64, 3, stride2, padding1), # 64 - 32 nn.ReLU(), ) # 解碼器轉置卷積逐步還原尺寸 self.decoder nn.Sequential( nn.ConvTranspose2d(64, 32, 3, stride2, padding1, output_padding1), nn.ReLU(), nn.ConvTranspose2d(32, 16, 3, stride2, padding1, output_padding1), nn.ReLU(), nn.ConvTranspose2d(16, 1, 3, stride2, padding1, output_padding1), nn.Sigmoid(), # 輸出歸一到 [0,1] ) def forward(self, x): z self.encoder(x) out self.decoder(z) return out編碼器每層 stride2 把空間尺寸減半通道數翻倍從 1 通道到 64 通道。解碼器用 ConvTranspose2d 還原output_padding1 補齊奇數尺寸的還原偏差。最后一層 Sigmoid 把輸出壓到 [0,1]和輸入歸一化范圍對齊。這個結構參數量小訓練快適合平坦圖。如果缺陷極細可以把第一層通道數提到 32但別盲目加深容易過擬合。3.3 訓練循環(huán)損失、優(yōu)化器與早停訓練目標是最小化重建誤差。損失用 MSE優(yōu)化器用 Adam學習率 1e-3 起步。import torch.optim as optim device torch.device(cuda if torch.cuda.is_available() else cpu) model ConvAutoencoder().to(device) criterion nn.MSELoss() optimizer optim.Adam(model.parameters(), lr1e-3) epochs 100 best_loss float(inf) for epoch in range(epochs): model.train() total_loss 0 for batch in train_loader: batch batch.to(device) optimizer.zero_grad() recon model(batch) loss criterion(recon, batch) loss.backward() optimizer.step() total_loss loss.item() avg_loss total_loss / len(train_loader) # 簡單早停連續(xù)多輪不降就停 if avg_loss best_loss: best_loss avg_loss torch.save(model.state_dict(), best_autoencoder.pth) print(fEpoch {epoch1}, Loss: {avg_loss:.6f})這段循環(huán)里zero_grad 清梯度forward 算重建criterion 算 MSEbackward 反傳step 更新。每輪記錄平均損失損失創(chuàng)新低就存模型。早停沒寫全實際可以加 patience 計數連續(xù) 10 輪不降就 break。學習率 1e-3 是 Adam 常用起點損失震蕩就降到 1e-4。3.4 推理與缺陷 mask 生成誤差圖閾值化推理時加載模型輸入測試圖算重建誤差閾值化得到 mask。import matplotlib.pyplot as plt model.load_state_dict(torch.load(best_autoencoder.pth)) model.eval() def detect_defect(img_path, threshold0.05): img cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) img cv2.resize(img, (256, 256)).astype(np.float32) / 255.0 tensor torch.tensor(img).unsqueeze(0).unsqueeze(0).to(device) with torch.no_grad(): recon model(tensor) # 計算逐像素誤差 error torch.abs(recon - tensor).squeeze().cpu().numpy() # 閾值化生成 mask mask (error threshold).astype(np.uint8) * 255 return img, error, mask img, error, mask detect_defect(TC_image/sample.bmp) plt.subplot(1,3,1); plt.imshow(img, cmapgray); plt.title(Input) plt.subplot(1,3,2); plt.imshow(error, cmaphot); plt.title(Error) plt.subplot(1,3,3); plt.imshow(mask, cmapgray); plt.title(Mask) plt.show()推理流程是讀圖、預處理、前向、算誤差、閾值化。threshold 是關鍵參數0.05 是起點誤檢多就調高漏檢多就調低。error 圖用 hot 色圖看缺陷位置會亮。mask 是二值圖白色為缺陷。實際項目里可以加形態(tài)學開閉運算去噪或者用連通域過濾小面積誤檢。提示閾值不是固定值不同批次圖的光照和對比度會變。穩(wěn)妥做法是在一批正常圖上跑推理統(tǒng)計誤差分布取 95% 或 99% 分位數當閾值。4. 避坑與排查正樣本訓練里最容易翻車的五個點4.1 現象訓練損失降得很低推理卻全是誤檢原因通常是正樣本太少或太單一模型把訓練集背下來了泛化差。解決是加數據增強旋轉、翻轉、亮度擾動或者用更強的瓶頸約束。平坦圖增強幅度別太大否則正常樣本被增強成「異常」。4.2 現象缺陷 mask 斷斷續(xù)續(xù)連不成片原因是誤差圖閾值化后噪聲多或者缺陷本身對比度低。解決是先對 error 圖做高斯模糊再閾值化或者用形態(tài)學閉運算把斷開的區(qū)域連起來。閾值調低能連片但誤檢會增加需要權衡。4.3 現象白邊包圍的 part2 圖被當成缺陷原因是白邊在訓練集里出現次數少模型沒學好。解決是把 part2 的白邊圖充分納入訓練或者預處理時裁掉白邊區(qū)域只保留純黑部分。項目正文專門區(qū)分 part1 和 part2說明這兩類正樣本的分布差異需要顯式處理。4.4 現象換一臺機器跑報 numpy 或 torch 版本不兼容原因是依賴版本沒鎖。解決是用 Dockerfile 或 requirements.txt 固定版本別用 pip install 不帶版本號。torch 和 numpy 的大版本升級經常改 API舊代碼直接崩。4.5 現象推理速度慢產線節(jié)拍跟不上原因是模型在 CPU 上跑或者輸入分辨率太高。解決是導出 ONNX 或 TensorRT 加速或者把 img_size 從 256 降到 128。平坦圖降分辨率對精度影響有限速度提升明顯。5. 進階技巧把誤差圖變成可交付的缺陷報告5.1 用連通域分析給缺陷分類和計數閾值化得到 mask 后用 OpenCV 的 connectedComponentsWithStats 做連通域分析能拿到每個缺陷的面積、外接矩形、質心。結合形態(tài)特征可以粗分類線狀缺陷長寬比大塊狀缺陷面積大且緊湊凸起和缺口看邊界曲率。num_labels, labels, stats, centroids cv2.connectedComponentsWithStats(mask, connectivity8) for i in range(1, num_labels): area stats[i, cv2.CC_STAT_AREA] x, y, w, h stats[i, cv2.CC_STAT_LEFT], stats[i, cv2.CC_STAT_TOP], stats[i, cv2.CC_STAT_WIDTH], stats[i, cv2.CC_STAT_HEIGHT] # 過濾小面積噪聲 if area 20: continue aspect_ratio max(w, h) / max(min(w, h), 1) defect_type 線狀 if aspect_ratio 3 else 塊狀 print(f缺陷 {i}: 類型{defect_type}, 面積{area}, 位置({x},{y}))這段代碼遍歷連通域過濾小面積噪聲按長寬比粗分線狀和塊狀。area 閾值 20 是經驗值按實際分辨率調。aspect_ratio 大于 3 判線狀否則塊狀。凸起和缺口需要更細的輪廓分析可以用輪廓近似或曲率計算。5.2 用 SSIM 替代 MSE 提升結構缺陷敏感度MSE 對像素級差異敏感但對結構變化不敏感。SSIM 衡量結構相似度對線狀、缺口這類結構缺陷更靈敏。scikit-image 的 structural_similarity 可以直接用。from skimage.metrics import structural_similarity as ssim def ssim_error_map(img, recon): # SSIM 返回相似度圖和總分 score, sim_map ssim(img, recon, fullTrue, data_range1.0) # 相似度取反得到誤差圖 error_map 1 - sim_map return error_mapSSIM 的 fullTrue 返回逐像素相似度圖1 減去它得到誤差圖。data_range1.0 對應歸一化輸入。SSIM 誤差圖比 MSE 更平滑閾值化后 mask 更完整但計算量稍大。實際可以 MSE 和 SSIM 加權融合兼顧像素和結構。5.3 驗證方法用正常樣本統(tǒng)計誤檢率模型上線前必須驗證誤檢率。拿一批沒參與訓練的正常圖跑推理統(tǒng)計有多少像素被誤判為缺陷。誤檢率高于 1% 就要調閾值或加后處理。驗證項方法合格標準誤檢率正常圖推理統(tǒng)計誤判像素占比 1%漏檢率已知缺陷圖推理統(tǒng)計未檢出缺陷占比 5%推理耗時單張圖端到端計時滿足產線節(jié)拍mask 完整度人工核對缺陷區(qū)域是否連片無明顯斷裂驗證要在獨立測試集上做不能拿訓練集自測。TC_image 里正常和負樣本混在一起正好用來做這個驗證。先跑正常樣本統(tǒng)誤檢再跑負樣本統(tǒng)漏檢兩個指標都達標才算可用。5.4 我踩過的坑閾值調參別在訓練集上做剛做這個項目時我在訓練集上調閾值看著 mask 很漂亮換到 TC_image 上誤檢一片。后來才明白訓練集上的誤差分布和測試集不一樣閾值必須在獨立正常樣本上統(tǒng)計。從那以后我每次調閾值都強制走一遍獨立驗證集先跑正常圖看誤檢再跑缺陷圖看漏檢兩個都過了才敢往下走。希望幫到你。本文還有配套的精品資源點擊獲取