Python | OpenCV 資料增強
📚 前言 在上一篇 TensorFlow/Keras 微調範例 中,我們完成了兩階段微調的完整流程。 這一篇深入介紹 資料增強 (Data Augmentation) 的原理與各種常用技巧。
前兩篇的訓練腳本中已經各自用了少量資料增強:
PyTorch 微調範例 的 train_transform 用了 RandomHorizontalFlip、RandomRotation、ColorJitter
TensorFlow/Keras 微調範例 的 data_augmentation 用了 RandomFlip、RandomRotation、RandomZoom、RandomBrightness
🔎 資料增強的原理 增強不會在磁碟上產生新圖片 很多人以為資料增強是把 000001.jpg(貓)翻轉後另存成 000002.jpg,讓磁碟上的圖片數量增加。實際上完全不是這樣 。增強發生在記憶體中、訓練的當下 ,原始圖片永遠不會被修改或複製。
1 2 3 4 5 6 7 8 9 ❌ 常見誤解:增強 = 另存新圖(磁碟上的圖片數量增加) 000001.jpg → [翻轉] → 000002.jpg 000001.jpg → [旋轉] → 000003.jpg ✅ 實際做法:增強在每次讀取時於記憶體中即時發生(磁碟完全不變) Epoch 1:讀取 000001.jpg → [即時左右翻轉] → 餵給模型 Epoch 2:讀取 000001.jpg → [即時旋轉 8°] → 餵給模型 Epoch 3:讀取 000001.jpg → [亮度 +12%,不翻轉] → 餵給模型 (000001.jpg 始終是那一張,磁碟上沒有新增任何圖片)
DataLoader 每次從磁碟讀取圖片,就在記憶體中套用一組隨機 的變換,然後把這個即時生成的版本餵給模型。 原始檔案不受影響,下次讀取同一張圖時又是另一組隨機變換。
訓練 15 個 epoch,模型實際上看過 15 個版本的 000001.jpg,但磁碟上始終只有那一張。
增強的核心假設 資料增強的假設是:圖片在合理的變換後,其類別標籤不應改變 。
例如,一張貓的圖片左右翻轉後仍然是貓;亮度調暗後仍然是貓。 透過這些變換,模型學會了不依賴特定的方向、亮度或細節來辨識類別,從而提升泛化能力。
💡 資料增強只在訓練集使用,驗證集與測試集不做增強,確保評估結果真實反映模型的泛化能力。
🧠 常用增強技巧 圖:常用資料增強技巧 ─ 各種增強方式的簡單說明與適合使用情境
🛠️ 環境安裝 本篇以三種工具示範資料增強的實作,各有不同的整合方式與適用場景:
圖:常用資料增強套件比較 ─ torchvision.transforms、tf.keras.layers 與 albumentations 的特色差異
1 2 3 4 pip install torch torchvision pip install tensorflow pip install albumentations
💡 先看效果,再看設定 在進入三種工具的完整設定之前,先用十幾行程式看看增強的效果。 這段程式碼只需要 torchvision,不需要 DataLoader 或模型,執行後你會看到來自同一張圖片的五種不同隨機結果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from PIL import Imageimport matplotlib.pyplot as pltfrom torchvision import transformsimg = Image.open("assets/test.png" ).convert("RGB" ) augment = transforms.Compose([ transforms.RandomHorizontalFlip(p=1.0 ), transforms.RandomRotation(20 ), transforms.ColorJitter(brightness=0.4 ), ]) fig, axes = plt.subplots(1 , 5 , figsize=(15 , 3 )) axes[0 ].imshow(img) axes[0 ].set_title("原始" ) for i in range(1 , 5 ): axes[i].imshow(augment(img)) axes[i].set_title(f"增強 {i} " ) plt.tight_layout() plt.savefig("output/simple_augment_demo.png" ) plt.show()
圖:對同一張圖片連續套用增強管線四次並並排顯示,每次的翻轉角度與亮度都不同
看完這個效果之後,下面的三種工具就是教你如何把相同的邏輯整合進 DataLoader 和訓練迴圈。
在 PyTorch 微調範例 的 train.py 裡,train_transform 只有三個基本增強。 以下示範的完整管線可以直接替換 train.py 的 train_transform 定義 ,訓練迴圈的其他部分不需要修改。
圖:PyTorch 資料載入與即時增強流程 ─ 從資料夾到模型輸入 Tensor 的完整過程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 from torchvision import datasets, transformsfrom torch.utils.data import DataLoadertrain_transform = transforms.Compose([ transforms.Resize((256 , 256 )), transforms.RandomCrop(224 ), transforms.RandomHorizontalFlip(p=0.5 ), transforms.RandomVerticalFlip(p=0.2 ), transforms.RandomRotation(degrees=15 ), transforms.ColorJitter( brightness=0.3 , contrast=0.3 , saturation=0.3 , hue=0.1 ), transforms.RandomAffine( degrees=0 , translate=(0.1 , 0.1 ), scale=(0.9 , 1.1 ) ), transforms.ToTensor(), transforms.Normalize([0.485 , 0.456 , 0.406 ], [0.229 , 0.224 , 0.225 ]) ]) val_transform = transforms.Compose([ transforms.Resize((224 , 224 )), transforms.ToTensor(), transforms.Normalize([0.485 , 0.456 , 0.406 ], [0.229 , 0.224 , 0.225 ]) ]) train_dataset = datasets.ImageFolder("data/cat_dog/train" , transform=train_transform) val_dataset = datasets.ImageFolder("data/cat_dog/val" , transform=val_transform) train_loader = DataLoader(train_dataset, batch_size=32 , shuffle=True , num_workers=0 ) val_loader = DataLoader(val_dataset, batch_size=32 , shuffle=False , num_workers=0 ) images, labels = next(iter(train_loader)) print(f"Batch shape : {images.shape} " ) print(f"Label shape : {labels.shape} " ) print(f"Classes : {train_dataset.classes} " )
圖:定義 torchvision.transforms 增強管線並整合至 ImageFolder 與 DataLoader,印出 batch 形狀確認資料流正確
視覺化增強結果 把完整的增強管線套用在單張圖片上,看看每次的隨機結果有多大差異。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import matplotlib.pyplot as pltfrom PIL import Imagefrom torchvision import transformsimg = Image.open("assets/test.png" ).convert("RGB" ) augment = transforms.Compose([ transforms.Resize((224 , 224 )), transforms.RandomHorizontalFlip(p=1.0 ), transforms.ColorJitter(brightness=0.4 , contrast=0.4 ), transforms.RandomRotation(20 ), ]) fig, axes = plt.subplots(1 , 5 , figsize=(15 , 3 )) axes[0 ].imshow(img) axes[0 ].set_title("原始" ) for i in range(1 , 5 ): axes[i].imshow(augment(img)) axes[i].set_title(f"增強 {i} " ) plt.tight_layout() plt.savefig("output/augmentation_preview.png" ) plt.show()
圖:對同一張圖片套用 PyTorch 完整增強管線四次並並排顯示,直觀比較各次增強的隨機效果
💻 TensorFlow/Keras — 增強層 在 TensorFlow/Keras 微調範例 的 train.py 裡,data_augmentation 只有四層基本增強。 以下展示更完整的增強層組合,可以直接替換 train.py 的 data_augmentation 定義 ,不需要修改其他程式碼。
圖:TensorFlow 資料載入與自動增強流程 ─ image_dataset_from_directory 與資料增強層的完整過程
Keras 的增強層嵌入模型架構內部,model.fit 時自動啟用、model.predict 時自動關閉,不需要手動切換。 這是 Keras 與 PyTorch 在增強實作上最大的差異:PyTorch 在 DataLoader 層處理,Keras 在模型內部處理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import tensorflow as tffrom tensorflow.keras import layersdata_augmentation = tf.keras.Sequential([ layers.RandomFlip("horizontal" ), layers.RandomFlip("vertical" ), layers.RandomRotation(0.15 ), layers.RandomZoom(0.15 ), layers.RandomTranslation(0.1 , 0.1 ), layers.RandomBrightness(0.2 ), layers.RandomContrast(0.2 ), ], name="data_augmentation" ) base_model = tf.keras.applications.MobileNetV2( input_shape=(224 , 224 , 3 ), include_top=False , weights="imagenet" ) base_model.trainable = False inputs = tf.keras.Input(shape=(224 , 224 , 3 )) x = data_augmentation(inputs) x = tf.keras.applications.mobilenet_v2.preprocess_input(x) x = base_model(x, training=False ) x = layers.GlobalAveragePooling2D()(x) x = layers.Dense(128 , activation="relu" )(x) outputs = layers.Dense(2 , activation="softmax" )(x) model = tf.keras.Model(inputs, outputs) model.compile(optimizer="adam" , loss="sparse_categorical_crossentropy" , metrics=["accuracy" ]) train_ds = tf.keras.utils.image_dataset_from_directory( "data/cat_dog/train" , image_size=(224 , 224 ), batch_size=32 ) val_ds = tf.keras.utils.image_dataset_from_directory( "data/cat_dog/val" , image_size=(224 , 224 ), batch_size=32 ) model.fit(train_ds, validation_data=val_ds, epochs=10 )
圖:將 Keras 增強層嵌入 MobileNetV2 模型架構,以 image_dataset_from_directory 載入資料後執行 model.fit 訓練
視覺化增強結果 在模型架構之外,手動呼叫增強層對單張圖片套用四次,確認增強效果是否符合預期。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import matplotlib.pyplot as pltimport tensorflow as tfimport numpy as npimport cv2img = cv2.imread("assets/test.png" ) img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img_tensor = tf.expand_dims(img_rgb, 0 ) augment = tf.keras.Sequential([ tf.keras.layers.RandomFlip("horizontal" ), tf.keras.layers.RandomRotation(0.15 ), tf.keras.layers.RandomBrightness(0.3 ), ]) fig, axes = plt.subplots(1 , 5 , figsize=(15 , 3 )) axes[0 ].imshow(img_rgb) axes[0 ].set_title("原始" ) for i in range(1 , 5 ): aug_img = augment(img_tensor, training=True )[0 ].numpy().astype(np.uint8) axes[i].imshow(aug_img) axes[i].set_title(f"增強 {i} " ) plt.tight_layout() plt.savefig("output/augmentation_preview_keras.png" ) plt.show()
圖:使用 TensorFlow Keras 增強層對圖片套用四次隨機增強並並排視覺化比較
💻 Albumentations — 更豐富的增強選項 Albumentations 是 torchvision.transforms 的替代方案,整合方式對應 PyTorch 微調範例 的 DataLoader 流程,但提供更豐富的增強種類,也支援物件偵測任務的邊界框同步變換 (翻轉、旋轉時邊界框座標自動跟著調整)。
圖:使用 Albumentations 的 PyTorch 資料載入流程 ─ 自訂 Dataset + Albumentations 增強管線
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 import albumentations as Afrom albumentations.pytorch import ToTensorV2import cv2import osfrom torch.utils.data import Dataset, DataLoadertrain_transform = A.Compose([ A.Resize(224 , 224 ), A.HorizontalFlip(p=0.5 ), A.VerticalFlip(p=0.2 ), A.Rotate(limit=15 , p=0.5 ), A.RandomBrightnessContrast(p=0.4 ), A.GaussNoise(var_limit=(10.0 , 50.0 ), p=0.2 ), A.Blur(blur_limit=3 , p=0.2 ), A.CLAHE(p=0.2 ), A.CoarseDropout(p=0.2 ), A.Normalize(mean=[0.485 , 0.456 , 0.406 ], std=[0.229 , 0.224 , 0.225 ]), ToTensorV2() ]) val_transform = A.Compose([ A.Resize(224 , 224 ), A.Normalize(mean=[0.485 , 0.456 , 0.406 ], std=[0.229 , 0.224 , 0.225 ]), ToTensorV2() ]) class ImageDataset (Dataset) : def __init__ (self, root, transform=None) : self.transform = transform self.classes = sorted(os.listdir(root)) self.samples = [] for idx, cls in enumerate(self.classes): cls_dir = os.path.join(root, cls) for fname in os.listdir(cls_dir): self.samples.append((os.path.join(cls_dir, fname), idx)) def __len__ (self) : return len(self.samples) def __getitem__ (self, i) : path, label = self.samples[i] img = cv2.imread(path) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if self.transform: img = self.transform(image=img)["image" ] return img, label train_dataset = ImageDataset("data/cat_dog/train" , transform=train_transform) val_dataset = ImageDataset("data/cat_dog/val" , transform=val_transform) train_loader = DataLoader(train_dataset, batch_size=32 , shuffle=True , num_workers=0 ) val_loader = DataLoader(val_dataset, batch_size=32 , shuffle=False , num_workers=0 ) images, labels = next(iter(train_loader)) print(f"Batch shape : {images.shape} " ) print(f"Classes : {train_dataset.classes} " )
圖:以自訂 Dataset 整合 Albumentations 增強管線,透過 DataLoader 取出一個 batch 並印出形狀確認
視覺化增強結果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import albumentations as Aimport cv2import matplotlib.pyplot as pltaugment = A.Compose([ A.HorizontalFlip(p=1.0 ), A.RandomBrightnessContrast(p=1.0 ), A.Rotate(limit=20 , p=1.0 ), A.GaussNoise(var_limit=(10.0 , 50.0 ), p=0.5 ), ]) img = cv2.imread("assets/test.png" ) img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) fig, axes = plt.subplots(1 , 5 , figsize=(15 , 3 )) axes[0 ].imshow(img) axes[0 ].set_title("原始" ) for i in range(1 , 5 ): aug_img = augment(image=img)["image" ] axes[i].imshow(aug_img) axes[i].set_title(f"增強 {i} " ) plt.tight_layout() plt.savefig("output/albumentations_preview.png" ) plt.show()
圖:對同一張圖片套用 Albumentations 增強管線四次並並排顯示,比較翻轉、旋轉、雜訊的隨機效果
⚠️ 注意事項
不是所有增強都適用所有任務 :垂直翻轉對自然場景的分類任務通常沒有幫助(天空在上、地面在下);文字辨識任務也不能做大幅旋轉。選擇增強前,先想清楚「這個變換後的圖片,人還是能正確辨識嗎?」
增強強度要適中 :過度增強(如旋轉 90 度、極端色彩變換)可能讓圖片失去原有的語意,反而降低訓練效果。建議從常用組合開始,若準確率未改善再調整強度。
驗證集不做增強 :驗證集只做 Resize 與 Normalize,不加任何增強,確保評估結果穩定、可比較。
物件偵測要同步變換標註 :對偵測任務做幾何增強(翻轉、旋轉)時,邊界框座標也必須一起變換。使用 Albumentations 時,在 A.Compose 加入 bbox_params=A.BboxParams(format="yolo") 可自動處理。
Windows 上 num_workers 設 0 :PyTorch DataLoader 在 Windows 上使用多進程可能造成死鎖,建議設為 0;Linux / macOS 可設 2 或 4 提升效率。
📊 應用場景
分類任務資料不足 :以 PyTorch transforms 或 Keras 增強層快速擴充訓練集,不需額外收集資料。
物件偵測與標註同步 :Albumentations 支援邊界框、遮罩的同步變換,是 YOLO、Faster R-CNN 等偵測任務的主流增強工具。
醫療影像 :病理切片、X 光片可用隨機旋轉與翻轉增強,但不適合大幅色彩變換(色彩可能帶有診斷意義)。
衛星與航拍影像 :沒有固定方向,適合同時使用水平與垂直翻轉,大幅擴充訓練資料。
工廠品管 :瑕疵偵測場景可加入高斯雜訊與模糊,模擬不同品質的相機鏡頭效果。
🎯 結語 資料增強是提升模型泛化能力最直接的方式,尤其在資料量有限的情況下效果顯著。 PyTorch 的 transforms 整合 DataLoader、Keras 的增強層嵌入模型架構、Albumentations 支援最豐富的增強種類,三者各有適用場景,可依框架與任務需求選擇。 下一步是 避免過擬合 ,進一步強化模型的泛化能力。
📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀 ,掌握完整的章節目錄,方便快速找到你需要的內容。
註:以上參考了PyTorch 官方文件 — torchvision.transforms TensorFlow 官方文件 — Preprocessing Layers Albumentations 官方文件