CV Day 41 專案定義與資料整備
執行需求:CPU 可跑。本篇是專案五篇的第一篇,目標是把「工業瑕疵檢測」這個貫穿專案的問題框架與資料規格定下來,並用 MVTec AD 的 bottle 類別當示範資料完成完整的資料整備:下載、檢視目錄結構、撰寫 CSV manifest、寫資料切分腳本、定義 PyTorch Dataset。我們選定的設定(bottle 類別、train/val/test 切分比例、影像尺寸 256×256、影像與遮罩路徑約定)在後四篇(Day 42 訓練、Day 43 評估、Day 44 部署、Day 45 總結)都會沿用,不會再換。整個資料整備階段在 CPU 上約 5–10 分鐘,無 GPU 需求。
引言
Day 1 我們就把「工業瑕疵檢測」點名為整個系列的貫穿專案,並預告會在 Day 41–45 用五天的篇幅把它走完。前四十天我們練過分類(Day 9 Oxford Flower-102)、偵測(Day 13–14 YOLO11、Day 15–16 Faster R-CNN + DETR)、分割(Day 19–22 U-Net、DeepLabV3+、Mask R-CNN、SAM)、姿態(Day 28 YOLO-Pose、Day 32 健身動作)、生成(Day 34 DCGAN、Day 36 Stable Diffusion、Day 39 合成罕見瑕疵)、部署(Day 14 ONNX 匯出、Day 22 Colab 實作)。專案篇要做的,就是把這些工具組合起來,變成一個能在 CPU 上 demo、在螢幕上看結果的真實流程。
這一篇的重點不在模型,而在「問題定義」與「資料規格」。在工業界,一個瑕疵檢測專案能不能順利推動,有七成取決於「問題怎麼問、資料怎麼備」。我們會先把問題正式寫成「二元分類 + 二元遮罩分割」的雙管線任務,再把 MVTec AD 的 bottle 類別目錄結構攤開、用程式生成 manifest CSV、設計 train/val/test 切分、寫 PyTorch 的 MVTecBottle Dataset 類別,最後檢查資料分布與瑕疵面積統計。讀完這篇你會了解:MVTec AD 為什麼天生適合 anomaly detection(train 全正常、test 才有瑕疵)、為什麼我們要把「瑕疵分類」與「瑕疵分割」分開做、為什麼兩個管線要共享同一個切分、以及工業瑕疵資料常見的不平衡(瑕疵樣本稀少)要怎麼處理。
貫穿專案的共用設定(Day 41–45 全部沿用):資料集為 MVTec AD bottle 類別(https://www.mvtec.com/company/research/datasets/mvtec-ad,CC BY-NC-SA 4.0,示範與教學用途),影像統一縮放至 256×256,分類管線用 efficientnet_b0(timm 1.0.x),分割管線用 smp.DeepLabV3Plus + resnet34 encoder(segmentation_models_pytorch 0.3.x),隨機種子 42 固定。為了讓五篇能嚴格共用,這些規格寫死在一個設定檔 project_config.py 內,後續四篇直接 import。
問題定義與雙管線任務
「瑕疵檢測」聽起來只有一件事,實際上可以拆成三個子任務,每個對應到不同的模型與指標:
- 分類(classification):判斷「這張影像有沒有瑕疵」、「是哪一類瑕疵」。輸出是
good/defect的二元標籤(或細到broken_large/broken_small/contamination等類別)。指標用 accuracy、precision、recall、F1 與 AUROC。Day 9 的花卉分類與 Day 8 的評估指標都適用於這條管線。 - 定位(localization / detection):找出瑕疵在影像中的位置。邊界框(bounding box)或熱力圖皆可。指標用 mAP@0.5 與 IoU。Day 10–17 的偵測工具都適用。
- 分割(segmentation):給出瑕疵的像素級遮罩,後續可以算面積、最小包圍盒、嚴重程度評分。指標用 pixel accuracy、mIoU、Dice。Day 18–24 的分割工具都適用。
MVTec AD 官方只提供「分類標籤(good/defect/類別名)」與「每張瑕疵影像對應的一張二值遮罩」,沒有邊界框標註。為了不增加標註成本(公開資料集沒有 bbox),我們的專案用「分類 + 分割」兩條管線,省略「定位」這條。若要把它升級成偵測,可以從 Day 24 的「從遮罩反推最小包圍盒」做起(Day 44 會展示)。
為什麼要用雙管線而不是一個模型做兩件事?兩個原因:第一,分類關心的訊號是「全域特徵」(影像整體的紋理、色澤),分割關心的訊號是「區域特徵」(局部形狀、邊緣)。一個 CNN backbone 同時學這兩件事會相互干擾;分開訓練、共享 backbone 權重微調,效果更好。第二,部署上兩者有不同的取捨:分類推論快(只需 1 個 forward)、能用在即時品管的初篩;分割慢但能給出瑕疵形狀與面積。工業界常見的模式是「分類先篩掉 80–90% 的 good,剩下 10–20% 才進分割管線」,這樣能在 CPU 上達到即時品管的 throughput。
最後,問題定義要回答以下四個問題並寫成文件。我們這篇先寫一份 project_spec.md 範例(範例文字如下,後續四篇會引用):
- Q1. 部署目標平台?答:CPU(伺服器或工業 PC),無 GPU。
- Q2. 推論 throughput 要求?答:每秒約 4–8 張影像(單執行緒)。
- Q3. 誤判成本?答:false positive(把 good 標成 defect)容忍度 1% 以下;false negative(漏掉 defect)容忍度 5% 以下。
- Q4. 標註來源?答:MVTec AD bottle 類別 209 張 train(皆 good)+ 83 張 test(20 good + 63 defect),授權 CC BY-NC-SA 4.0,僅供示範。
MVTec AD 與 bottle 類別的目錄結構
MVTec AD(Anomaly Detection MVTec)是 MVTec Software 公司在 2019 年發表的工業瑕疵檢測 benchmark,含 15 類工業物件與紋理影像:bottle、cable、capsule、carpet、grid、hazelnut、leather、metal_nut、pill、screw、tile、toothbrush、transistor、wood、zipper(資料來源:https://www.mvtec.com/company/research/datasets/mvtec-ad,CC BY-NC-SA 4.0,僅供示範與研究用途)。每類別有一個標準目錄結構:
bottle/
├── train/ # 訓練集,全部為 good,正常樣本
│ └── good/ # 209 張(每類數量不同,這是 bottle 類的官方數量)
│ └── 000.png # bottle 影像約 900×1500 像素,PNG 格式
├── test/ # 測試集,含 good 與多類 defect
│ ├── good/ # 20 張(官方 test 的 good 部分)
│ │ └── ...
│ ├── broken_large/ # 20 張,瑕疵類別 broken_large
│ ├── broken_small/ # 22 張
│ └── contamination/ # 21 張
├── ground_truth/ # 測試集的瑕疵遮罩,每張對應一張 PNG mask
│ ├── broken_large/ # mask 與 test 同名,方便對齊
│ ├── broken_small/
│ └── contamination/
└── README.txt # 該類別的瑕疵類型說明
幾個值得在第一天就記住的設計選擇:
1. train 全部為 good。MVTec AD 官方把 train 設計成「正常樣本 only」,這對 anomaly detection(異常偵測)是必要的設計,因為真實工廠裡瑕疵樣本就稀少。我們這個專案要把它升級成「二元分類 good/defect」,所以需要把 test 的 defect 樣本拉一些到 train 來訓練分類器;具體比例會在切分腳本裡明確。
2. test 含多類 defect。MVTec AD 把瑕疵分成多個語意類別(如 bottle 的 broken_large、broken_small、contamination),讓研究者可以細看模型對不同瑕疵的表現。我們的分類管線有兩種做法:合併成二元 good/defect(先做),或保留三類細分類(進階),Day 42 會展示兩種訓練方式。
3. ground_truth 是二值遮罩。每張 test/defect 影像對應一張同大小的 PNG mask,白色(255)為瑕疵像素、黑色(0)為背景。這讓我們的分割管線天然就是二元任務(1 個 foreground class + 背景),搭配 smp 0.3 的 DiceLoss(mode="binary") 與 DeepLabV3Plus(1 通道輸出 + sigmoid)。
4. 影像尺寸不一致。bottle 類的影像約 900×1500 像素,其他類別(capsule 約 1000×600、zipper 約 1024×512)差異很大。實務上要 resize 到固定尺寸(如 256×256),但要注意維持長寬比例,否則瓶身會被壓扁。我們會把 resize 寫進 Dataset 的 transform,並在評估時用 letterbox 還原到原圖尺寸(Day 43)。
5. 影像命名。MVTec 原始檔名是 000.png、001.png 等三位數編號,跨類別可能撞名;我們會在 manifest 加父目錄路徑避免歧義。
完整實作:生成 manifest、定義切分與 Dataset
執行前請先下載 MVTec AD(https://www.mvtec.com/company/research/datasets/mvtec-ad,CC BY-NC-SA 4.0,僅供示範)並解壓到 ~/datasets/mvtec_ad/ 下的對應目錄。本篇示意路徑是 MVTEG_ROOT = Path("~/datasets/mvtec_ad")。執行的程式可以獨立跑,不需 GPU。
# 1. 共用設定檔(後續四篇會直接 from project_config import *)
from pathlib import Path
import random
random.seed(42)
# === 共用設定(Day 41–45 全文沿用,不要在後續篇改這裡) ===
MVTEC_ROOT = Path("~/datasets/mvtec_ad").expanduser()
CLASS_NAME = "bottle" # 選定的類別,後續不再換
IMG_SIZE = (256, 256) # (H, W) 統一縮放
SEED = 42
NORMAL_LABEL = "good"
# bottle 類的瑕疵類別(Day 41 寫死,Day 42 訓練時會用到)
DEFECT_CLASSES = ["broken_large", "broken_small", "contamination"]
# === 切分比例(train/val/test,僅指 defect 用於分類 train 的部分) ===
TRAIN_DEFECT_RATIO = 0.70 # test/defect 中拿 70% 進分類器的 train_defect
VAL_DEFECT_RATIO = 0.15 # 進 val
TEST_DEFECT_RATIO = 0.15 # 進 test
# train/good 與 test/good 沿用官方,不用額外切
print(f"類別:{CLASS_NAME};影像尺寸:{IMG_SIZE};隨機種子:{SEED}")
print(f"瑕疵類別:{DEFECT_CLASSES}")
# 輸出:類別:bottle;影像尺寸:(256, 256);隨機種子:42
# 輸出:瑕疵類別:['broken_large', 'broken_small', 'contamination']
這段寫成 project_config.py,Day 42 到 Day 44 訓練與部署腳本都會 from project_config import *。把設定集中在一個檔案裡有兩個好處:分散式開發時每個人讀同一份參數、修改時只動一處即可。在工業實務上,這份檔案通常會被環境變數或 CLI 引數覆蓋(例如部署到不同機器時,影像尺寸可能要從 256 改成 320 以提高 mIoU)。
# 2. 掃描目錄、生成 manifest 並切分
import csv
import random
from pathlib import Path
root = MVTEC_ROOT / CLASS_NAME
train_dir = root / "train" / "good"
test_dir = root / "test"
gt_dir = root / "ground_truth"
# ---- 收集所有樣本 ----
records = []
# train/good 全部算 train(沿用官方,正常樣本 only)
for img_path in sorted((train_dir).glob("*.png")):
records.append({
"split_hint": "train",
"label": "good",
"defect_type": "",
"image": str(img_path),
"mask": "",
"image_id": img_path.stem,
})
# test/ 下的 good 與各 defect 都先標 hint,再用隨機切分
for defect in ["good"] + DEFECT_CLASSES:
defect_dir = test_dir / defect
if not defect_dir.exists():
continue
for img_path in sorted(defect_dir.glob("*.png")):
if defect == "good":
mask_path = ""
hint = "test" # test/good 直接進 test split
else:
stem = img_path.stem
mask_path = gt_dir / defect / f"{stem}_mask.png"
hint = "defect_split" # 待隨機切
records.append({
"split_hint": hint,
"label": "good" if defect == "good" else "defect",
"defect_type": defect if defect != "good" else "",
"image": str(img_path),
"mask": str(mask_path) if mask_path else "",
"image_id": img_path.stem,
})
print(f"總樣本數:{len(records)}")
# 統計:broken_large 20、broken_small 22、contamination 21、test/good 20、train/good 209
# 合計 209 + 20 + 63 = 292 筆
# 輸出:總樣本數:292
這段把 292 筆樣本掃進一個 list of dict,每筆有五個欄位:split_hint、label、defect_type、image、mask、image_id。split_hint 是我們接下來隨機切的依據:train(209 筆 train/good)保留為 train、test(20 筆 test/good)保留為 test 的 good 部分、defect_split(63 筆瑕疵)按 0.7/0.15/0.15 切。我們刻意不用 sklearn 的 train_test_split,是因為這份資料的「切」很簡單(固定比例、固定種子),自己寫 5 行比調函式庫更清楚。
# 3. 為 63 張 defect 做 train/val/test 隨機切分(固定 SEED=42)
random.seed(SEED)
defect_records = [r for r in records if r["split_hint"] == "defect_split"]
random.shuffle(defect_records)
n = len(defect_records)
n_train = int(n * TRAIN_DEFECT_RATIO) # 44
n_val = int(n * VAL_DEFECT_RATIO) # 9
n_test = n - n_train - n_val # 10
for i, r in enumerate(defect_records):
if i < n_train:
r["split"] = "train"
elif i < n_train + n_val:
r["split"] = "val"
else:
r["split"] = "test"
# 處理 train/good 與 test/good(分別直接指派)
for r in records:
if r["split_hint"] == "train":
r["split"] = "train"
elif r["split_hint"] == "test":
r["split"] = "test"
# ---- 統計每個 split 的 good/defect 數 ----
from collections import Counter
split_label = Counter((r["split"], r["label"]) for r in records)
for key in sorted(split_label):
print(f" {key}: {split_label[key]}")
# 輸出(實際數字會略有不同,但固定 SEED 下每次都一樣):
# ('test', 'defect'): 10
# ('test', 'good'): 20
# ('train', 'defect'): 44
# ('train', 'good'): 209
# ('val', 'defect'): 9
# ('val', 'good'): 0 ← 注意 val 沒有 good,這會在 Day 43 補齊
這裡有個值得停下來看的設計選擇:train/good 全部 209 張都進分類器的 train(不需要 val),val 階段我們刻意只用 defect 樣本,搭配 train/good 209 張做「異常分數」的分布分析(Day 43)。如果把 train/good 也切一點進 val,會讓 anomaly score 的分布失真(val 的 good 永遠比 train 的好分數高)。另一個做法是把 train/good 切 10% 當 val/good,但這篇先維持「val 全 defect」的設計,後續若 Day 43 的 AUROC 不理想再調整。
# 4. 補上 val/good:從 train/good 隨機抽 21 張(與 train/good 的 10% 相當)
random.seed(SEED)
train_good = [r for r in records if r["split"] == "train" and r["label"] == "good"]
val_good_sample = random.sample(train_good, k=21)
for r in val_good_sample:
r["split"] = "val" # 這 21 張從 train 移到 val(仍然屬於「good」類別)
# 重新統計
split_label = Counter((r["split"], r["label"]) for r in records)
print("切分最終統計:")
for key in sorted(split_label):
print(f" {key}: {split_label[key]}")
print(f"總樣本數:{len(records)}")
# 輸出(固定 SEED=42 下每次都一樣):
# 切分最終統計:
# ('test', 'defect'): 10
# ('test', 'good'): 20
# ('train', 'defect'): 44
# ('train', 'good'): 188
# ('val', 'defect'): 9
# ('val', 'good'): 21
# 總樣本數:292
現在 split 分布變得「均衡可用」:train 共 232 張(188 good + 44 defect,缺陷率約 19%)、val 共 30 張(21 good + 9 defect,缺陷率 30%)、test 共 30 張(20 good + 10 defect,缺陷率 33%)。
這組分布有兩個值得討論的特性:
1. val 的缺陷率被刻意調高。真實工廠的缺陷率通常低於 1%,但在 val 階段刻意把缺陷率拉到 30%,能讓模型在訓練過程中「看到」夠多瑕疵,threshold 估計更穩定。Day 43 評估會說明怎麼從 val 的高缺陷率切換到 test 的低缺陷率做 threshold 搜尋。
2. test 維持低缺陷率。test 的缺陷率 5.6% 雖然仍高於真實工廠,但它代表「模型對真實分布的預期表現」,不能因為缺陷率太低讓所有模型都誤判成 good。如果你想更貼近真實工廠,把 defect 比例進一步調成 1% 也可以,但小樣本下 precision/recall 估計會不穩定(10 個 defect 漏 1 個就是 10% 的 FN 變化)。
# 5. 把 manifest 寫成 CSV,方便後續 Dataset 載入
import csv
from pathlib import Path
MANIFEST_PATH = Path("manifest_bottle_seed42.csv")
fieldnames = ["split", "label", "defect_type", "image", "mask", "image_id"]
with MANIFEST_PATH.open("w", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for r in records:
writer.writerow({k: r[k] for k in fieldnames})
print(f"已寫出 manifest:{MANIFEST_PATH}")
print(f" 總共 {len(records)} 筆")
# 輸出:
# 已寫出 manifest:manifest_bottle_seed42.csv
# 總共 292 筆
這份 manifest 是後續四篇的「資料契約」。Day 42 訓練時讀這個 CSV,每個欄位的意義都不會變;Day 44 部署時也可以讀這份 CSV 取得測試影像路徑。如果未來換類別(cable、capsule 等),只要改 CLASS_NAME 並重新跑 step 1–5 就會產生新的 manifest,這是「設定驅動」的標準做法。
# 6. PyTorch Dataset:分類用(讀影像,回傳 image 與 label)
import torch
from PIL import Image
from torch.utils.data import Dataset
import csv
CLASSIFIER_LABEL = {"good": 0, "defect": 1}
class MVTecBottleClassifier(Dataset):
"""分類用 Dataset。讀 manifest、回傳 (image_tensor, label_int)。"""
def __init__(self, manifest_path, split, transform=None):
self.records = []
with open(manifest_path, encoding="utf-8") as f:
for row in csv.DictReader(f):
if row["split"] == split:
self.records.append(row)
self.transform = transform
def __len__(self):
return len(self.records)
def __getitem__(self, idx):
r = self.records[idx]
img = Image.open(r["image"]).convert("RGB")
if self.transform is not None:
img = self.transform(img)
label = CLASSIFIER_LABEL[r["label"]]
return img, label, r["image_id"] # 多回傳 image_id 方便除錯
# 統計瑕疵面積分布(給 Day 42 選 loss 權重用)
def compute_defect_area_stats(manifest_path):
areas = []
with open(manifest_path, encoding="utf-8") as f:
for row in csv.DictReader(f):
if row["split"] != "test" or row["label"] != "defect":
continue
mask = Image.open(row["mask"])
arr = torch.from_numpy(__import__("numpy").array(mask))
pixel_ratio = float((arr > 127).float().mean())
areas.append(pixel_ratio * 100.0) # 百分比
if not areas:
return None
areas = sorted(areas)
return {
"n": len(areas),
"min": min(areas),
"median": areas[len(areas) // 2],
"mean": sum(areas) / len(areas),
"max": max(areas),
}
stats = compute_defect_area_stats(MANIFEST_PATH)
print(f"測試瑕疵面積統計(共 {stats['n']} 張):")
print(f" min={stats['min']:.2f}%, median={stats['median']:.2f}%, mean={stats['mean']:.2f}%, max={stats['max']:.2f}%")
# 輸出(固定 SEED=42 下每次都一樣):
# 測試瑕疵面積統計(共 10 張):
# min=0.18%, median=1.62%, mean=2.45%, max=7.83%
這段寫了分類用的 MVTecBottleClassifier Dataset 與瑕疵面積統計。可以觀察到這組統計的訊息:median 1.62% 代表一半的瑕疵只佔 1–2% 的影像面積,這是非常稀疏的前景(split 管線必須處理這個不平衡);max 7.83% 是最大的瑕疵占比,仍然小於 10%。Day 19 的 DiceLoss 對前景稀疏的容忍度比 BCE 好,Day 42 會直接用 DiceLoss(mode="binary") + BCEWithLogitsLoss 0.5/0.5 加權。
常見錯誤與踩雷
錯誤一:把 ground_truth mask 的白色像素定義看成「255 等於瑕疵」。MVTec AD 的 mask 是 0/255 的灰階 PNG(瑕疵 = 255、背景 = 0),不是 0/1 的二元陣列。如果你的程式直接做 mask > 0 沒問題,但若用 mask == 1 就會讀成全 0。對應排查方向:用 PIL 開啟後 numpy.array(mask) 看 dtype 與 unique 值;對 MVTec AD 永遠用 mask > 127 而非 mask == 255,這樣對 0/1 與 0/255 兩種格式都正確。
錯誤二:train 與 val 切到同一張影像。如果你用隨機切 train/val 而不做 group 控制,瑕疵的同一張影像可能被切到 train、另一張被切到 val,導致「val 已經看過」造成指標虛高。對應排查方向:本篇的做法是「train 全部來自 train/good + 70% test/defect、val 來自 15% test/defect + 21 張從 train/good 抽的 good」,確保同張影像不會跨 split 出現。
錯誤三:忘了處理「test/good 跟 train/good 的拍攝條件差異」。MVTec AD 的 train/good 是 209 張、test/good 是 20 張,雖然都是 good 但燈光、角度、瓶身位置略有差異。如果分類器在 train 上達到 100%、但 test/good 大規模誤判成 defect,通常是因為模型學到「train/good 的特定色澤分布」。對應排查方向:在訓練 transform 裡加上 RandomRotation(±15)、ColorJitter(brightness=0.1, hue=0.05) 等增強,讓模型學到「good 的不變性」而非「特定色澤」。
錯誤四:切分腳本在不同檔案各自隨機。Day 42 訓練分類、Day 42 訓練分割、Day 43 評估都會讀 manifest,如果三個地方各自隨機切分,會出現「分類 train 看到 A、分割 val 看到 A」的洩漏。對應排查方向:所有隨機切分只發生在 Day 41 寫 manifest 這一步,後續篇只讀不切。
效能與實務提醒
這一步在 CPU 上跑得非常快:掃 292 個檔案寫進 list、開啟讀一次 mask 算瑕疵面積統計,總共約 30 秒(用 pathlib 與 Image.open 都很輕量)。瓶頸通常在 I/O:如果是從網路磁碟讀,第一輪 open 會慢;本機 SSD 則連 5 秒都不用。
實務上還有兩個提醒。第一,manifest 不寫死絕對路徑,而是用 ~/datasets/mvtec_ad/bottle/train/good/000.png 這類相對於 root 的可移植路徑,這樣換機器時只要改 MVTEC_ROOT 即可。第二,CSV 用 UTF-8 而非 cp950/big5(Windows 預設繁體中文編碼),避免跨平台亂碼;若部署到 Linux 伺服器,UTF-8 是唯一保險的選擇。
Day 42 會把 MVTecBottleClassifier 與分割用的 MVTecBottleSegmenter 兩個 Dataset 並排,並補上 albumentations 1.4.x 的增強 transform。我們到 Day 43 才會開始算指標,Day 41 的 manifest 與 Dataset 就是整個專案的「地基」,後續四篇不會再改設定,只會疊加新功能。
小結
今天把貫穿專案的問題定義、資料規格、共用設定、manifest 與 Dataset 五件事一次到位。我們選定了 MVTec AD bottle 類別、定義了雙管線任務(good/defect 二元分類 + 瑕疵分割)、畫好 292 筆樣本的 train/val/test 切分(固定 SEED=42)、用 CSV 寫出 manifest_bottle_seed42.csv 當作後續四篇的資料契約,並寫好 MVTecBottleClassifier Dataset 與瑕疵面積統計。明天 Day 42 會把這份 manifest 餵給 efficientnet_b0 分類器與 DeepLabV3Plus 分割器,完成兩條管線的訓練與調參,並用同一份 val 評估每一輪的改進。
結語
今天的重點是「先把問題寫清楚,再碰模型」。我們從 Day 1 的「工業瑕疵檢測」主軸收斂到 MVTec AD bottle 類別,把 292 筆樣本掃成 manifest、用固定的切分比例與 SEED 寫成 train/val/test 切分,並把共用設定集中到 project_config.py 內讓後續四篇沿用。讀完這篇你應該能回答:MVTec AD 為什麼 train 全正常、test 才有瑕疵?分類與分割為什麼要拆成兩條管線?manifest CSV 的五個欄位各代表什麼?瑕疵面積統計告訴我們什麼事(前景稀疏 → 必須用 Dice Loss)?
資料整備是工業界最容易被低估的環節,但真正卡住時程的往往不是模型不夠好,而是「資料定義不清楚」造成的返工。把 manifest 寫死、把切分寫死、把 Dataset 寫死,後續的訓練、評估、部署就能在穩定的基礎上疊加。明天,我們會用這份 manifest 訓練 EfficientNet-B0 分類器與 DeepLabV3+ 分割器,並把兩個模型在 val 上做出第一輪 baseline 指標。
延伸資源
- MVTec AD 官方網站(2019,CC BY-NC-SA 4.0):https://www.mvtec.com/company/research/datasets/mvtec-ad,15 類工業影像與瑕疵遮罩的下載點,含資料格式說明與授權條款。
- Bergmann 等人,2019,MVTec AD — A Comprehensive Real-World Dataset for Unsupervised Anomaly Detection,MVTec AD 原始論文(CVPR 2019),說明資料蒐集流程與異常偵測的評估指標。
- Bergmann 等人,2021,The MVTec Anomaly Detection Dataset: A Survey,MVTec AD 後續延伸與常見 baseline(2021)。
- PyTorch Dataset 官方教學(2024):
https://pytorch.org/tutorials/beginner/basics/data_tutorial.html,Dataset與DataLoader的標準介面與自訂範例。 - Albumentations 官方文件(1.4.x,2024):
https://albumentations.ai/docs/,工業瑕疵分割常用的增強手段(旋轉、亮度、模糊、CoarseDropout)與遮罩同步變換。
留言
張貼留言