📢 公告:OpenCV 系列文章目前正在重構整理中(進度約 65%),部分文章已暫時下架,後續會陸續補上,完成時間待定。感謝耐心等候!

Like Share Discussion Bookmark Smile

J.J. Huang   2026-04-05   Python OpenCV 07.物件偵測與辨識篇   瀏覽次數:次   DMCA.com Protection Status

Python | 與 OpenCV 整合推論

📚 前言

在上一篇 模型保存與載入 中,我們學會了各種保存格式的使用方式。
這一篇是整個訓練流程的最後一站:與 OpenCV 整合推論

將訓練好的模型接入 OpenCV,就能對靜態圖片、影片檔或即時攝影機畫面進行推論,完成從訓練到應用的最後一哩路。

🎨 範例圖片與影片

圖片

  • 來源:Pexels - Dog Image,屬於無版權圖片,可自由下載與使用。
  • 內容:圖片為一張葡萄牙波登可犬照。
  • 下載後將檔名改為 dog.jpg,放到專案的 assets/ 目錄下。

影片

  • 來源:Pexels - Dog Video,屬於無版權影片,可自由下載與使用。
  • 內容:影片四隻狗狗在海邊移動畫面。
  • 下載後將檔名改為 dog.mp4,放到專案的 assets/ 目錄下。

🔎 整合架構


圖:OpenCV + 深度學習模型完整推論流程 ─ 從輸入到顯示結果的一條龍步驟

💻 PyTorch 模型 — 靜態圖片推論

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
# inference_image.py
import cv2
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
import numpy as np

# ── 載入模型 ────────────────────────────────
CLASS_NAMES = ["cat", "dog"] # 依實際類別替換
NUM_CLASSES = len(CLASS_NAMES)
TASK_NAME = "cat_dog"
MODEL_DIR = f"models/resnet18/{TASK_NAME}"

model = models.resnet18(weights=None)
model.fc = nn.Linear(model.fc.in_features, NUM_CLASSES)
model.load_state_dict(torch.load(f"{MODEL_DIR}/best_model.pth", map_location="cpu"))
model.eval()

# ── 前處理(需與訓練時一致)────────────────
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])

# ── 推論 ────────────────────────────────────
img_bgr = cv2.imread("assets/dog.jpg")
img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(img_rgb)

input_tensor = transform(pil_img).unsqueeze(0)

with torch.no_grad():
outputs = model(input_tensor)
probs = torch.nn.functional.softmax(outputs[0], dim=0)
conf, pred = torch.max(probs, 0)

label = f"{CLASS_NAMES[pred]}: {conf:.2f}"
cv2.putText(img_bgr, label, (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)

cv2.imshow("Result", img_bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()


圖:載入 PyTorch ResNet18 模型對靜態圖片進行分類推論,並將預測類別與信心度繪製在圖片上顯示

💻 PyTorch 模型 — 即時攝影機推論

整合 PyTorch 模型與攝影機串流,對每幀即時分類並依信心度變換標籤顏色,按 q 離開

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
# inference_camera_pytorch.py
import cv2
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image

CLASS_NAMES = ["cat", "dog"]
NUM_CLASSES = len(CLASS_NAMES)
TASK_NAME = "cat_dog"
MODEL_DIR = f"models/resnet18/{TASK_NAME}"

model = models.resnet18(weights=None)
model.fc = nn.Linear(model.fc.in_features, NUM_CLASSES)
model.load_state_dict(torch.load(f"{MODEL_DIR}/best_model.pth", map_location="cpu"))
model.eval()

transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])

cap = cv2.VideoCapture(0)
print("按 q 離開")

while True:
ret, frame = cap.read()
if not ret:
break

img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(img_rgb)
input_tensor = transform(pil_img).unsqueeze(0)

with torch.no_grad():
outputs = model(input_tensor)
probs = torch.nn.functional.softmax(outputs[0], dim=0)
conf, pred = torch.max(probs, 0)

label = f"{CLASS_NAMES[pred]}: {conf:.2f}"
color = (0, 255, 0) if conf > 0.7 else (0, 165, 255)

cv2.putText(frame, label, (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 2)
cv2.imshow("PyTorch + OpenCV", frame)

if cv2.waitKey(1) & 0xFF == ord('q'):
break

cap.release()
cv2.destroyAllWindows()

💻 TensorFlow/Keras 模型 — 即時攝影機推論

載入 TensorFlow/Keras SavedModel 模型與攝影機整合,對每幀即時分類並顯示預測結果

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
# inference_camera_keras.py
import cv2
import tensorflow as tf
import numpy as np

CLASS_NAMES = ["cat", "dog"]
IMG_SIZE = 224
TASK_NAME = "cat_dog"
MODEL_DIR = f"models/keras/{TASK_NAME}"

model = tf.keras.models.load_model(MODEL_DIR)

cap = cv2.VideoCapture(0)
print("按 q 離開")

while True:
ret, frame = cap.read()
if not ret:
break

img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img_resized = cv2.resize(img_rgb, (IMG_SIZE, IMG_SIZE))
x = np.expand_dims(img_resized / 255.0, axis=0).astype(np.float32)

preds = model.predict(x, verbose=0)
pred_class = np.argmax(preds[0])
conf = preds[0][pred_class]

label = f"{CLASS_NAMES[pred_class]}: {conf:.2f}"
color = (0, 255, 0) if conf > 0.7 else (0, 165, 255)

cv2.putText(frame, label, (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 2)
cv2.imshow("TF/Keras + OpenCV", frame)

if cv2.waitKey(1) & 0xFF == ord('q'):
break

cap.release()
cv2.destroyAllWindows()

💻 ONNX 模型 — 純 OpenCV 推論

使用 cv2.dnn 載入 ONNX 模型,對攝影機每幀進行前處理後推論,不依賴 PyTorch 或 TensorFlow

匯出為 ONNX 後,可直接用 cv2.dnn 讀取,不需安裝 PyTorch 或 TensorFlow:

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
# inference_camera_onnx.py
import cv2
import numpy as np

CLASS_NAMES = ["cat", "dog"]
MEAN = np.array([0.485, 0.456, 0.406]) * 255
STD = np.array([0.229, 0.224, 0.225]) * 255
TASK_NAME = "cat_dog"
ONNX_PATH = f"models/resnet18/{TASK_NAME}/onnx/model.onnx"

net = cv2.dnn.readNetFromONNX(ONNX_PATH)

cap = cv2.VideoCapture(0)
print("按 q 離開")

while True:
ret, frame = cap.read()
if not ret:
break

blob = cv2.dnn.blobFromImage(
frame,
scalefactor=1.0,
size=(224, 224),
mean=MEAN,
swapRB=True,
crop=False
)
# 手動除以 STD(blobFromImage 的 mean/scalefactor 無法完整模擬 Normalize)
blob /= STD.reshape(1, 3, 1, 1)

net.setInput(blob)
scores = net.forward()[0]
exp_s = np.exp(scores - scores.max())
probs = exp_s / exp_s.sum()
pred_class = int(np.argmax(probs))
conf = float(probs[pred_class])

label = f"{CLASS_NAMES[pred_class]}: {conf:.2f}"
cv2.putText(frame, label, (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)
cv2.imshow("ONNX + OpenCV", frame)

if cv2.waitKey(1) & 0xFF == ord('q'):
break

cap.release()
cv2.destroyAllWindows()

💻 影片檔推論並輸出結果影片

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
# inference_video.py
import cv2
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image

CLASS_NAMES = ["cat", "dog"]
NUM_CLASSES = len(CLASS_NAMES)
TASK_NAME = "cat_dog"
MODEL_DIR = f"models/resnet18/{TASK_NAME}"

model = models.resnet18(weights=None)
model.fc = nn.Linear(model.fc.in_features, NUM_CLASSES)
model.load_state_dict(torch.load(f"{MODEL_DIR}/best_model.pth", map_location="cpu"))
model.eval()

transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406],
[0.229, 0.224, 0.225])
])

cap = cv2.VideoCapture("assets/dog.mp4")
fps = cap.get(cv2.CAP_PROP_FPS)
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

writer = cv2.VideoWriter(
"output/output.mp4",
cv2.VideoWriter_fourcc(*"mp4v"),
fps, (width, height)
)

while True:
ret, frame = cap.read()
if not ret:
break

img_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
pil_img = Image.fromarray(img_rgb)
input_tensor = transform(pil_img).unsqueeze(0)

with torch.no_grad():
outputs = model(input_tensor)
probs = torch.nn.functional.softmax(outputs[0], dim=0)
conf, pred = torch.max(probs, 0)

label = f"{CLASS_NAMES[pred]}: {conf:.2f}"
cv2.putText(frame, label, (10, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, (0, 255, 0), 2)
writer.write(frame)

cap.release()
writer.release()
print("輸出影片已儲存為 output/output.mp4")


圖:使用 PyTorch 模型逐幀對影片分類推論,將預測結果標注在每幀後寫入輸出影片檔

⚠️ 注意事項

  • 前處理必須與訓練完全一致:Resize 尺寸、BGR→RGB 轉換、Normalize 的均值與標準差,任何一個步驟不同都會導致推論結果偏差。
  • OpenCV 讀取圖片為 BGR:PyTorch 與 TensorFlow 的模型都以 RGB 訓練,推論前必須做 cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
  • 即時推論的效能:若推論速度無法跟上攝影機幀率,可降低輸入解析度、改用 GPU 推論,或改用輕量模型(MobileNet)。
  • model.predictverbose=0:Keras 的 predict 預設會在每次推論時輸出進度條,即時推論時加上 verbose=0 關閉輸出,避免干擾畫面。

📊 應用場景

  • 即時品管系統:生產線攝影機即時分類,標記良品與瑕疵。
  • 門禁辨識:即時對攝影機畫面進行人臉分類,辨識授權人員。
  • 影片批次分析:對監控影片進行逐幀推論,標記異常片段並輸出結果影片。

🎯 結語

至此,我們完整走過了 模型訓練與微調 的全部流程:

  • 資料蒐集 → 資料標註 → 模型選擇
  • 遷移學習原理 → PyTorch 微調 → TensorFlow/Keras 微調
  • 資料增強 → 避免過擬合 → GPU 加速
  • 模型評估 → 模型保存 → 與 OpenCV 整合

掌握這個完整流程,就能針對任何特定場景訓練並部署專屬的電腦視覺模型。

下一篇是 YOLOv8 自訓練物件偵測,說明為什麼 YOLO 值得獨立一個系列,以及後續的章節架構。

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

註:以上參考了
OpenCV 官方文件 — DNN module
PyTorch 官方文件
TensorFlow 官方文件
ONNX Runtime 官方文件