🔥 新作首發 🎮 天堂私服 YOLOv8 物件偵測實戰 — 從資料蒐集、模型訓練到即時偵測 立即閱讀 →
熱門系列
Like Share Discussion Bookmark Smile

J.J. Huang   2026-04-15   Python OpenCV 08.專案實作篇   瀏覽次數:次   DMCA.com Protection Status

Python | OpenCV 專案:車牌辨識應用

📚 前言

在上一篇 即時人臉辨識系統 中,我們建立了一個完整的人臉辨識 pipeline。

這一篇要挑戰另一個經典應用:車牌辨識(License Plate Recognition,LPR)
車牌辨識結合了邊緣偵測、輪廓分析、透視矯正與 OCR 文字辨識,是一個整合前面多個章節知識的綜合專案。

🎯 專案目標

  • 從圖片或影片畫面中定位車牌區域
  • 對車牌區域進行透視矯正與前處理
  • 使用 EasyOCR 辨識車牌號碼
  • 以影片檔或 MJPEG 串流掃描,顯示即時辨識結果

🎨 範例圖片

  • 來源:Pexels - License Plate Image,屬於無版權圖片,可自由下載與使用。
  • 內容:圖片車頭且有拍攝到車牌,非常適合用來做車牌辨識測試。
  • 下載後將檔名改為 license_plate.jpg,放到專案的 assets/ 目錄下。

🛠️ 套件安裝

步驟 1:安裝 EasyOCR

1
pip install easyocr

步驟 2:修復 OpenCV(必做)

pip install easyocr 會連帶安裝 opencv-python-headless(無 GUI 版本),覆蓋原先的 opencv-python / opencv-contrib-python,造成下列兩種常見錯誤:

  • cv2.imshowThe function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Cocoa support.
  • cv2.imreadAttributeError: module 'cv2' has no attribute 'imread'(多個 opencv 變體並存時出現)

最穩定的做法是完整清除所有 opencv 變體後再乾淨安裝

1
2
3
4
5
6
7
8
9
# 1. 清除所有 opencv 變體(無論是否存在都執行一次)
pip uninstall -y opencv-python opencv-python-headless opencv-contrib-python opencv-contrib-python-headless

# 2. 確認已無殘留,以下指令應該沒有任何輸出
pip list | findstr opencv # Windows
# pip list | grep opencv # macOS / Linux

# 3. 乾淨安裝 contrib 版(--no-cache-dir 避免使用損壞快取)
pip install --no-cache-dir opencv-contrib-python

步驟 3:驗證安裝

1
2
3
4
import cv2
print(cv2.__file__) # 路徑裡不應該出現 "headless"
print(cv2.__version__)
print("imread" in dir(cv2)) # 應為 True

💡 EasyOCR 首次執行時會自動下載語言模型(繁體中文 + 英文約 200MB),需要網路連線。

💡 執行時若看到 Using CPU. Note: This module is much faster with a GPU.,這是 gpu=False 的正常提示訊息,不是錯誤;本篇示範以 CPU 執行即可,若機器有 CUDA GPU 可將 easyocr.Reader(["en"], gpu=True) 以加速推論。

💡 若 pip list 出現 WARNING: Ignoring invalid distribution -xxx,代表 site-packages 內有 ~xxx 開頭的殘留資料夾(先前安裝被中斷留下),不影響本次安裝;可執行 Remove-Item -Recurse -Force "<venv 路徑>\Lib\site-packages\~*" 清除。

💻 步驟一:從靜態圖片辨識車牌

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
64
65
66
67
68
69
70
# recognize_plate.py
import cv2
import numpy as np
import easyocr

reader = easyocr.Reader(["en"], gpu=False) # 台灣車牌為英數字,使用英文模型即可

def find_plate_candidates(img):
"""找出可能是車牌的矩形輪廓"""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blur = cv2.bilateralFilter(gray, 11, 17, 17)
edges = cv2.Canny(blur, 30, 200)

contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:20]

candidates = []
for c in contours:
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.018 * peri, True)
if len(approx) == 4:
candidates.append(approx)

return candidates

def crop_plate(img, contour):
"""根據輪廓裁切車牌區域"""
x, y, w, h = cv2.boundingRect(contour)
return img[y:y+h, x:x+w], (x, y, w, h)

def preprocess_plate(plate_img):
"""車牌影像前處理"""
gray = cv2.cvtColor(plate_img, cv2.COLOR_BGR2GRAY)
resized = cv2.resize(gray, (300, 80))
_, binary = cv2.threshold(resized, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)
return binary

def recognize_plate(img_path):
img = cv2.imread(img_path)
if img is None:
print("圖片讀取失敗")
return

candidates = find_plate_candidates(img)

if not candidates:
print("未找到車牌候選區域")
return

# 取面積最大的候選框
plate_region, (x, y, w, h) = crop_plate(img, candidates[0])
processed = preprocess_plate(plate_region)

results = reader.readtext(processed, allowlist="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-")

plate_text = " ".join([r[1] for r in results if r[2] > 0.3])
print(f"辨識結果:{plate_text if plate_text else '無法辨識'}")

# 繪製標記
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 3)
cv2.putText(img, plate_text, (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 1.0, (0, 255, 0), 2)

cv2.imshow("Result", img)
cv2.imshow("Plate", processed)
cv2.waitKey(0)
cv2.destroyAllWindows()

recognize_plate("assets/license_plate.jpg")


圖:偵測圖片中的車牌輪廓區域,透過 Otsu 二值化前處理後使用 EasyOCR 辨識車牌號碼

💻 步驟二:改善辨識率的前處理

對四角點車牌區域進行透視矯正,將傾斜或仰角拍攝的車牌展平為正面視角

光線與角度影響辨識率很大,可加入透視矯正提升準確度:

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
# perspective_transform.py
def four_point_transform(img, pts):
"""將四個角點矩形區域進行透視矯正"""
pts = pts.reshape(4, 2).astype(np.float32)

# 排序角點:左上、右上、右下、左下
s = pts.sum(axis=1)
diff = np.diff(pts, axis=1)
rect = np.array([
pts[np.argmin(s)], # 左上
pts[np.argmin(diff)], # 右上
pts[np.argmax(s)], # 右下
pts[np.argmax(diff)], # 左下
], dtype=np.float32)

w = max(
np.linalg.norm(rect[1] - rect[0]),
np.linalg.norm(rect[2] - rect[3]),
)
h = max(
np.linalg.norm(rect[3] - rect[0]),
np.linalg.norm(rect[2] - rect[1]),
)

dst = np.array([[0, 0], [w, 0], [w, h], [0, h]], dtype=np.float32)
M = cv2.getPerspectiveTransform(rect, dst)
return cv2.warpPerspective(img, M, (int(w), int(h)))

💻 步驟三:從影片或串流辨識車牌

步驟一的輪廓法(approxPolyDP)適合乾淨的車牌近照,但在真實行車影片中背景雜訊過多,常常找不到四邊形或找錯位置,再加上只取「面積最大」的候選框,命中率很低。

本步驟改為直接把整幀交給 EasyOCR 內建的文字偵測模型(CRAFT)偵測所有文字區塊,再用信心度與長寬比挑出最像車牌的那一塊,比輪廓法穩定很多。

同時以影片檔或 MJPEG 串流取代即時攝影機,不需要攝影機設備也不涉及個人隱私即可完整練習。建議使用行車記錄器影片、停車場監控影片或自行拍攝的車輛影片作為測試素材;若需要即時畫面,也可改用手機 App(例如 IP Webcam)提供的 MJPEG 串流 URL,程式邏輯完全相同。

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
64
65
# scan_video.py
import cv2
import easyocr
import time

reader = easyocr.Reader(["en"], gpu=False)
video_path = "assets/traffic.mp4" # 影片檔或 MJPEG 串流 URL 皆可
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
print(f"無法開啟影片:{video_path}")
exit()

last_result = ""
last_bbox = None
last_time = 0
interval = 1.0 # 每秒辨識一次,避免 OCR 頻繁呼叫拖慢幀率

print("影片辨識啟動,按 q 離開")

while True:
ret, frame = cap.read()
if not ret:
print("影片播放完畢")
break

now = time.time()
if now - last_time > interval:
# EasyOCR 直接在整幀畫面偵測文字,比輪廓法更穩定
results = reader.readtext(
frame,
allowlist="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-",
)

# 過濾:信心度 > 0.4、長寬比接近車牌比例(約 2:1 ~ 5:1)
best = None
for bbox, text, conf in results:
if conf < 0.4:
continue
(tl, tr, br, bl) = bbox
w = abs(tr[0] - tl[0])
h = abs(bl[1] - tl[1])
if h == 0 or not (1.5 < w / h < 6):
continue
if best is None or conf > best[2]:
best = (bbox, text.replace(" ", ""), conf)

if best:
last_bbox, last_result, _ = best
last_time = now

if last_bbox is not None and last_result:
tl = last_bbox[0]
br = last_bbox[2]
pt1 = (int(tl[0]), int(tl[1]))
pt2 = (int(br[0]), int(br[1]))
cv2.rectangle(frame, pt1, pt2, (0, 255, 0), 2)
cv2.putText(frame, last_result, (pt1[0], pt1[1] - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2)

cv2.imshow("License Plate Recognition", frame)
if cv2.waitKey(30) & 0xFF == ord("q"):
break

cap.release()
cv2.destroyAllWindows()


圖:讀取行車影片,EasyOCR 在整幀畫面偵測所有文字後以信心度與長寬比過濾,標出最可能的車牌並顯示辨識結果

📷 改用即時攝影機:將 video_path = "assets/traffic.mp4" 改為 cap = cv2.VideoCapture(0),即可改為即時攝影機辨識,程式邏輯完全相同。

📡 改用 MJPEG 串流:將 video_path 改為 MJPEG URL(例如手機 App「IP Webcam」提供的 http://<手機 IP>:8080/video),即可從手機或 IP 攝影機取得即時畫面。

💡 效能提示:在整幀畫面跑 OCR 比裁切小區域慢,CPU 環境下一次約 0.5~2 秒;interval = 1.0 已經留了緩衝,若仍卡頓可調高到 2.0,或將畫面先縮小(frame = cv2.resize(frame, (640, 360)))再送 OCR。

⚠️ 注意事項

  • 輪廓法的侷限性approxPolyDP 找四邊形的方法在複雜背景下容易失效,若需要更穩健的車牌定位,建議改用 YOLOv8 訓練一個車牌偵測模型(定位更精準)。
  • OCR 不設定 allowlist 會辨識出許多雜訊:台灣車牌只有英數字與 -,設定 allowlist 可大幅提升準確率。
  • 即時辨識不要每幀都跑 OCR:EasyOCR 速度較慢,用時間間隔(interval)控制呼叫頻率,避免幀率過低。
  • 繁體中文車牌:若需辨識含中文字(如機車牌),將 easyocr.Reader(["en"]) 改為 easyocr.Reader(["ch_tra", "en"])
  • MJPEG 串流延遲:透過網路讀取 MJPEG 串流時若出現畫面卡頓,可降低串流解析度或改回本機影片檔測試,避免網路狀況影響辨識結果。
  • EasyOCR 安裝會覆蓋 OpenCV:若執行時出現 cv2.imshow 未實作或 cv2 has no attribute 'imread' 等錯誤,請回頭依「套件安裝」的完整步驟清除所有 opencv 變體後再重新安裝 opencv-contrib-python。切記僅執行 pip uninstall opencv-python-headless 再安裝往往不夠,需一次移除所有變體才能避免殘留衝突。

📊 進階方向

  • 用 YOLOv8 取代輪廓法定位車牌:準確度大幅提升,適合多車輛場景
  • 加入資料庫查詢:辨識到車牌後,查詢黑名單或收費系統
  • 多幀投票:同一輛車連續辨識 5 幀,取出現最多次的號碼作為最終結果,提升準確率

🎯 結語

車牌辨識整合了輪廓分析、透視矯正與 OCR,是一個能實際部署的完整應用。
下一篇將進入 OpenCV 專案:小型圖片分類專案,把模型訓練篇學到的遷移學習技術整合成一個端對端的分類系統。

📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀,掌握完整的章節目錄,方便快速找到你需要的內容。

註:以上參考了
EasyOCR GitHub
OpenCV 官方文件 — Contour Features
OpenCV 官方文件 — Geometric Transformations