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