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
| 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
| 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
| 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
| 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 ) 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
| 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.predict 的 verbose=0:Keras 的 predict 預設會在每次推論時輸出進度條,即時推論時加上 verbose=0 關閉輸出,避免干擾畫面。
📊 應用場景
- 即時品管系統:生產線攝影機即時分類,標記良品與瑕疵。
- 門禁辨識:即時對攝影機畫面進行人臉分類,辨識授權人員。
- 影片批次分析:對監控影片進行逐幀推論,標記異常片段並輸出結果影片。
🎯 結語
至此,我們完整走過了 模型訓練與微調 的全部流程:
- 資料蒐集 → 資料標註 → 模型選擇
- 遷移學習原理 → PyTorch 微調 → TensorFlow/Keras 微調
- 資料增強 → 避免過擬合 → GPU 加速
- 模型評估 → 模型保存 → 與 OpenCV 整合
掌握這個完整流程,就能針對任何特定場景訓練並部署專屬的電腦視覺模型。
下一篇是 YOLOv8 自訓練物件偵測,說明為什麼 YOLO 值得獨立一個系列,以及後續的章節架構。
📖 如在學習過程中遇到疑問,或是想了解更多相關主題,建議回顧一下 Python | OpenCV 系列導讀,掌握完整的章節目錄,方便快速找到你需要的內容。
註:以上參考了
OpenCV 官方文件 — DNN module
PyTorch 官方文件
TensorFlow 官方文件
ONNX Runtime 官方文件