[AI][Python]OpenCV-模型訓練2_Haar 分類器-處理正樣本、負樣本

  • 200
  • 0
  • AI
  • 2025-05-29

文、意如

一、什麼是正樣本?

定義:包含你要偵測的目標(如車牌)的圖片。

使用目的:讓分類器學習「車牌長什麼樣子」。

格式需求:圖片中要明確包含車牌。

當我們要訓練一個 Haar 特徵分類器來偵測特定目標(例如車牌)時,

第一步就是準備「正樣本」和「實測圖片」。正樣本是指那些明確包含目標的圖片,

訓練完成後會產生模擬可用實測的圖片(非訓練圖片)。

訓練完成後,我們會得到一個可以辨識車牌的分類器。
為了確認它真的有效,我們會用一些「不是訓練用的圖片」(實測圖片)來做測試。

這些測試圖片能幫助我們了解:
✅ 分類器能不能正確找出車牌?
✅ 在沒看過的新圖片上表現如何?

這樣可以確保訓練成果不只是「記住訓練圖片」,而是真的學會「辨識車牌」。

正樣本實測圖片就是含有車牌號碼的圖片。

例如:

因此準備兩個資料夾

1.放置訓練用的圖片(收集大約 200~500 張就足以開始初步訓練)

2.放置實測用的圖片(建議準備 20~50 張圖片)

拍攝車牌的注意事項(正樣本收集時)

新舊車牌數量要均勻
為了讓分類器能學會辨識不同樣式的車牌,記得收集包含新式與舊式車牌的圖片,避免只訓練單一樣式,導致辨識偏差。

拍攝時手機與車牌要平行
盡量避免仰拍或斜拍,保持相機鏡頭與車牌平行,這樣可以減少角度變形,讓訓練資料更一致。

車牌大小適中
車牌在畫面中不能太小(難以辨識),也不能太大(無法提供背景參考)。理想狀況下,車牌應該佔整張圖片的**20%~50%**左右。

保持充足光線,避免強烈反光
拍攝時應該在白天或光線充足的環境下進行,避免過暗或逆光。同時要小心反光造成車牌數字無法辨識。

背景要多樣化
車牌背後的環境應包含不同場景(如:停車場、馬路邊、白天、傍晚等),這樣訓練出來的分類器才更具泛用性。

避免重複、模糊照片
模糊或重複角度的照片會影響訓練品質,拍攝完畢後應先篩選一遍,保留清晰、有用的影像。

轉換圖片尺寸

手機或圖片拍攝的圖片解析度非常大,不適合做為訓練用,必須轉換為較小圖片才能進行訓練。

範例會將圖片轉為300*225px(這個比例適合訓練 Haar 分類器等機器學習模型。)

有兩個資料夾都需要轉換(正樣本及實測圖片),因此將轉換程式寫成函式。

資料夾:

car_plate(放置訓練用的圖片100張)

car_plate_test(實測圖片8張)

car/car01.py

import PIL  # 匯入 PIL 套件(Python Imaging Library)
from PIL import Image  # 從 PIL 套件匯入 Image 模組,用來處理圖片
import glob  # 匯入 glob 模組,用來抓取符合條件的檔案路徑
import shutil, os  # 匯入 shutil 和 os 模組,用來處理檔案與資料夾
from time import sleep  # 匯入 sleep 函數,用來暫停執行數秒(這裡用來等待刪除資料夾)

# 定義清空(刪除並重新建立)資料夾的函式
def emptydir(dirname):
    if os.path.isdir(dirname):  # 如果資料夾已存在
        shutil.rmtree(dirname)  # 刪除整個資料夾(含內容)
        sleep(2)  # 暫停 2 秒,確保系統完成刪除動作
    os.mkdir(dirname)  # 建立新的空資料夾

# 定義批次調整圖片大小的函式
def dirResize(src, dst):
    myfiles = glob.glob(src + '/*.JPG')  # 取得來源資料夾中所有 .JPG 圖片的路徑
    emptydir(dst)  # 先清空或建立目標資料夾
    print(src + '資料夾:')  # 顯示目前處理的來源資料夾
    print('開始轉換圖形尺寸!')  # 提示開始處理

    for i, f in enumerate(myfiles):  # 枚舉每一張圖片
        img = Image.open(f)  # 開啟圖片檔案
        img_new = img.resize((300, 225), PIL.Image.ANTIALIAS)  # 調整圖片大小為 300x225,使用抗鋸齒濾波提升畫質,補充 PIL.Image.ANTIALIAS 是一種高品質的縮圖過濾方式(目前新版改名為 Resampling.LANCZOS)。
        outname = str("resizejpg") + str('{:0>3d}').format(i + 1) + '.jpg'  # 組合新檔名(例如 resizejpg001.jpg)
        img_new.save(dst + '/' + outname)  # 儲存調整後的圖片至目標資料夾
    print('轉換圖形尺寸完成 \n')  # 顯示處理完成訊息

# 呼叫函式處理 'car_plate' 資料夾,輸出到 'car_resize_1'
dirResize('car_plate', 'car_resize_1')

# 呼叫函式處理 'car_plate_test' 資料夾,輸出到 'car_resize_2'
dirResize('car_plate_test', 'car_resize_2')
完成檔
正樣本圖片轉換為bmp格式

使用 OpenCV 工具 來訓練 Haar 特徵分類器時,官方建議正樣本圖片使用 .bmp 格式,原因如下:

無壓縮、無損品質
.bmp 是無壓縮格式,不會因壓縮導致特徵模糊,這對於 Haar 特徵(以亮暗區分)很重要。

OpenCV 工具相容性高
在早期 OpenCV 版本中,.bmp 是最穩定支援的格式。雖然後來支援 .jpg.png,但 .bmp 的穩定性仍較好。

 

那能不能用 .jpg.png

可以,但請注意:

.jpg 是有損壓縮,有時會模糊特徵邊緣,不利訓練。

.png 雖然是無損,但透明通道(alpha channel)有時會導致讀取錯誤。

如果你使用 .jpg,請確保畫質夠好且沒有過度壓縮。

實務建議

建議最終匯出的正樣本圖片轉存成 .bmp(例如用 PIL 批次轉存)。

或使用時觀察是否報錯,如果沒問題也可使用 .jpg

轉檔程式碼:

car/car_02.py

from PIL import Image  # 匯入 PIL 套件中的 Image 模組,用於影像處理
import glob            # 匯入 glob 模組,用來找出符合特定樣式的檔案(如 *.JPG)
import os              # 匯入 os 模組,用來操作檔案系統(如刪除檔案)

# 使用 glob 抓取 car_resize_1 資料夾中所有副檔名為 .JPG 的檔案
myfiles = glob.glob("car_resize_1/*.JPG")

print("開始轉換圖形格式")  # 印出提示訊息

# 逐一處理每一個 JPG 檔案
for f in myfiles:
    namespilt = f.split("\\")  # 分割檔案路徑字串,取得檔名部分(Windows 路徑用 "\\")
    img = Image.open(f)  # 開啟圖片檔案
    outname = namespilt[1].replace("resizejpg", "bmpraw")  # 將檔名前綴從 resizejpg 改為 bmpraw
    outname = outname.replace(".jpg", ".bmp")  # 將副檔名從 .jpg 改為 .bmp
    img.save("car_resize_1/" + outname, "bmp")  # 儲存成 BMP 格式到相同資料夾
    os.remove(f)  # 刪除原本的 JPG 檔案,避免重複

print("轉換圖形格式結束")  # 印出完成訊息

完成檔:

處理負樣本

在訓練 Haar 特徵分類器時,負樣本(Negative Samples)是指不包含目標物(例如:車牌)的圖片。它們的作用是幫助分類器學會「什麼不是目標」,進而減少誤判。

📌 補充說明:

尺寸略大於正樣本尺寸:這是為了讓分類器能從中「滑動視窗」並裁切出各種區塊進行學習,增加訓練的多樣性。

轉為灰階圖片:為了減少運算量與干擾,負樣本通常會轉成灰階。這樣分類器可以專注在亮度變化與特徵差異上,而不受顏色干擾。

例如,如果正樣本大小為 300x225,則負樣本可以是 500x375,並轉成灰階再使用。

原始檔:carNagative

轉成灰階檔後存成:carNagative1

程式碼:car/car_03.py

import PIL  # 匯入 PIL 套件(Python Imaging Library),用於圖像處理
from PIL import Image  # 從 PIL 套件中匯入 Image 模組,負責開啟、編輯、儲存圖片
import glob  # 匯入 glob 模組,可用萬用字元搜尋檔案(例如 *.JPG)
import shutil, os  # 匯入 shutil(處理檔案/資料夾操作)與 os(系統層級功能)模組
from time import sleep  # 匯入 sleep 函數,用來讓程式暫停(例如等待刪除動作完成)

# 定義清空資料夾的函式(若存在則刪除後重建)
def emptydir(dirname):
    if os.path.isdir(dirname):  # 檢查目錄是否已存在
        shutil.rmtree(dirname)  # 如果存在,刪除整個資料夾(包含所有內容)
        sleep(2)  # 暫停 2 秒,確保系統完成刪除
    os.mkdir(dirname)  # 建立一個新的、空的資料夾

# 使用 glob 抓取 carNagative 資料夾下所有 .JPG 圖片路徑
myfiles = glob.glob("carNagative/*.JPG")

# 清空 carNagative 資料夾(注意:這會刪除所有原始檔案)
emptydir("carNagative1")

print("開始轉換尺寸及灰階")  # 印出提示訊息

# 開始處理每一張圖片
for i, f in enumerate(myfiles):  # 用 enumerate 同時取得索引 i 和檔案路徑 f
    img = Image.open(f)  # 開啟圖片
    img_new = img.resize((500, 375), PIL.Image.LANCZOS)  # 調整圖片尺寸為 500x375,使用高品質縮放算法 LANCZOS #補充 PIL.Image.ANTIALIAS 是一種高品質的縮圖過濾方式(目前新版改名為 Resampling.LANCZOS)。
    img_new = img_new.convert('L')  # 將圖片轉為灰階('L' 模式)
    outname = str("negGray") + str("{:0>3d}").format(i+1) + ".jpg"  # 組合新檔名,例如 negGray001.jpg
    img_new.save("carNagative1/" + outname)  # 儲存轉換後的圖片到 carNagative1 資料夾中

print("轉換尺寸及灰階完成")

 

Yiru@Studio - 關於我 - 意如