貓咪圖片來源:皇阿瑪的後宮生活
在人臉辨識的應用場景中,我們已經非常熟悉它出現在門禁系統、考勤打卡、以及手機解鎖等功能應用。
但如果將這項技術應用到「貓咪」身上,會是什麼樣的體驗?
__本 DEMO 嘗試設計一個「貓臉辨識系統」:
透過 OpenCV 偵測貓咪的臉部 --> 從資料庫比對是哪一隻貓 --> 最後在介面上顯示辨識結果
想像一下,一個家庭有多隻貓咪,只要攝影機拍攝,系統就能直接顯示:「這是 Jojo」或「這是 Momo」。這樣的技術甚至能擴展應用,例如:
自動紀錄哪隻貓先回家、追蹤每日進食次數、智慧寵物門禁
pip install opencv-python scikit-learn numpy
你需要 OpenCV 的 貓臉 Haar 模型:haarcascade_frontalcatface.xml
多數 OpenCV 會自帶在 cv2.data.haarcascades 路徑;若沒有,請自行下載並放在腳本同資料夾。
把你的資料整理成這樣:
cats/
Mimi/
001.jpg
002.jpg
...
Kuro/
a.jpg
b.jpg
...
每隻貓建議至少 15–30 張、不同角度/光線(正面為主)。
圖片來源:我家貓貓JoJo
import os
import cv2
import sys
import glob
import json
import joblib
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
# -----------------------
# 可調參數(簡化版本)
# -----------------------
DATA_DIR = "cats" # 資料集根目錄:cats/<cat_name>/*.jpg
MODEL_PATH = "cat_knn.pkl" # 模型檔
LABELS_PATH = "labels.json" # 貓名與數字 id 對照
FACE_SIZE = (128, 128) # 取樣尺寸
K = 3 # KNN 的 K 值
CASCADE_NAME = "haarcascade_frontalcatface.xml"
def get_cascade_path():
"""盡量自動找到 cat face cascade,找不到就用當前資料夾"""
default_path = os.path.join(cv2.data.haarcascades, CASCADE_NAME)
if os.path.exists(default_path):
return default_path
local = os.path.join(os.path.dirname(__file__), CASCADE_NAME)
if os.path.exists(local):
return local
raise FileNotFoundError(
f"找不到 {CASCADE_NAME}。\n"
f"請將檔案放在:{cv2.data.haarcascades} 或腳本同目錄。"
)
CAT_CASCADE = cv2.CascadeClassifier(get_cascade_path())
# -----------------------
# 工具函數
# -----------------------
def detect_cat_faces(img_bgr):
"""回傳偵測到的 cat face bounding boxes"""
gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)
faces = CAT_CASCADE.detectMultiScale(gray, scaleFactor=1.2, minNeighbors=3, minSize=(60, 60))
return faces
def face_to_feature(img_bgr, box):
"""裁切貓臉 -> 灰階 -> resize -> 向量 (L2 normalize)"""
x, y, w, h = box
face = img_bgr[y:y+h, x:x+w]
gray = cv2.cvtColor(face, cv2.COLOR_BGR2GRAY)
gray = cv2.resize(gray, FACE_SIZE, interpolation=cv2.INTER_AREA)
vec = gray.flatten().astype(np.float32)
# 簡單 L2 normalize
norm = np.linalg.norm(vec) + 1e-8
return (vec / norm)
def scan_dataset(data_dir=DATA_DIR):
"""掃描 cats/<name>/*.jpg → (X, y, label_map)"""
X, y = [], []
name2id, id2name = {}, {}
next_id = 0
if not os.path.isdir(data_dir):
raise RuntimeError(f"找不到資料夾:{data_dir}")
for name in sorted(os.listdir(data_dir)):
folder = os.path.join(data_dir, name)
if not os.path.isdir(folder):
continue
if name not in name2id:
name2id[name] = next_id
id2name[next_id] = name
next_id += 1
cid = name2id[name]
imgs = []
for ext in ("*.jpg", "*.jpeg", "*.png", "*.JPG", "*.JPEG", "*.PNG"):
imgs.extend(glob.glob(os.path.join(folder, ext)))
if not imgs:
print(f"[警告] {name} 沒有圖片,略過。")
continue
kept = 0
for p in imgs:
img = cv2.imread(p)
if img is None:
continue
faces = detect_cat_faces(img)
if len(faces) == 0:
continue
# 取第一張臉
feat = face_to_feature(img, faces[0])
X.append(feat)
y.append(cid)
kept += 1
print(f"[資料] {name} 取樣 {kept} 張可用臉部")
if len(X) < 2 or len(set(y)) < 2:
raise RuntimeError("樣本不足(至少需要 >=2 隻貓且每隻有偵測到臉的圖片)。")
X = np.vstack(X)
y = np.array(y, dtype=np.int64)
return X, y, {"name2id": name2id, "id2name": {int(k): v for k, v in id2name.items()}}
def train():
X, y, labels = scan_dataset(DATA_DIR)
print(f"[訓練] 總樣本:{len(X)},類別數:{len(set(y))}")
knn = KNeighborsClassifier(n_neighbors=K, metric="cosine")
knn.fit(X, y)
joblib.dump(knn, MODEL_PATH)
with open(LABELS_PATH, "w", encoding="utf-8") as f:
json.dump(labels, f, ensure_ascii=False, indent=2)
print(f"[完成] 模型已存:{MODEL_PATH},標籤:{LABELS_PATH}")
def load_model():
if not os.path.exists(MODEL_PATH) or not os.path.exists(LABELS_PATH):
raise RuntimeError("找不到模型或標籤,請先執行:python catfaces_demo.py train")
knn = joblib.load(MODEL_PATH)
with open(LABELS_PATH, "r", encoding="utf-8") as f:
labels = json.load(f)
id2name = {int(k): v for k, v in labels["id2name"].items()}
return knn, id2name
def predict_image(img_path, show=True):
knn, id2name = load_model()
img = cv2.imread(img_path)
if img is None:
raise RuntimeError(f"讀不到圖片:{img_path}")
faces = detect_cat_faces(img)
if len(faces) == 0:
print("沒有偵測到貓臉。")
if show:
cv2.imshow("cat", img); cv2.waitKey(0)
return
for (x, y, w, h) in faces:
feat = face_to_feature(img, (x, y, w, h)).reshape(1, -1)
pred = knn.predict(feat)[0]
proba = 1 - knn.kneighbors(feat, n_neighbors=K, return_distance=True)[0].mean() # 粗略分數
name = id2name.get(int(pred), "Unknown")
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 200, 0), 2)
cv2.putText(img, f"{name} ({proba:.2f})", (x, y-8),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 200, 0), 2, cv2.LINE_AA)
if show:
cv2.imshow("predict", img); cv2.waitKey(0)
print("預測完成。")
def webcam():
knn, id2name = load_model()
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
if not cap.isOpened():
cap = cv2.VideoCapture(0)
if not cap.isOpened():
raise RuntimeError("開啟攝影機失敗。")
print("[提示] 按 Q 離開")
while True:
ok, frame = cap.read()
if not ok:
break
faces = detect_cat_faces(frame)
for (x, y, w, h) in faces:
feat = face_to_feature(frame, (x, y, w, h)).reshape(1, -1)
pred = knn.predict(feat)[0]
proba = 1 - knn.kneighbors(feat, n_neighbors=K, return_distance=True)[0].mean()
name = id2name.get(int(pred), "Unknown")
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 200, 255), 2)
cv2.putText(frame, f"{name} ({proba:.2f})", (x, y-8),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 200, 255), 2, cv2.LINE_AA)
cv2.imshow("Cat Face ID (Q to quit)", frame)
if (cv2.waitKey(1) & 0xFF) in (ord('q'), ord('Q')):
break
cap.release()
cv2.destroyAllWindows()
# -----------------------
# CLI
# -----------------------
def main():
if len(sys.argv) < 2:
print("用法:")
print(" 訓練: python catfaces_demo.py train")
print(" 單張: python catfaces_demo.py predict <image_path>")
print(" 攝影機:python catfaces_demo.py webcam")
sys.exit(0)
cmd = sys.argv[1]
if cmd == "train":
train()
elif cmd == "predict":
if len(sys.argv) < 3:
print("請提供圖片路徑,例如:python catfaces_demo.py predict test.jpg")
sys.exit(1)
predict_image(sys.argv[2], show=True)
elif cmd == "webcam":
webcam()
else:
print("未知指令。可用:train / predict / webcam")
if __name__ == "__main__":
# 需要 joblib(sklearn 會裝),若沒有可改用 pickle
try:
import joblib # noqa
except Exception:
os.system("pip install joblib")
import joblib # noqa
main()
# 1) 先訓練
python catfaces_demo.py train
# 2) 單張圖片測試
python catfaces_demo.py predict path/to/your/cat.jpg
# 3) 攝影機即時辨識
python catfaces_demo.py webcam
經過一步步的資料準備、模型訓練與測試,我們已經完成了這個「貓臉辨識系統」的小專案 DEMO。
這個過程不只是讓電腦學會分辨不同的貓咪,更重要的是,你已經親手走過了 從影像處理、特徵擷取到模型應用 的完整流程。
現在,就換你動手試試看吧!
打開電腦,準備好資料夾,把屬於你家貓咪的照片放進去,執行程式,看看螢幕會不會告訴你:「這是你家 Jojo」或「這是 Momo」。
當 AI 開始認得你的貓咪時,你就成功地把抽象的技術變成一個 有趣又實用的生活小應用。這就是學習電腦視覺最迷人的地方──從零到一,親手做出屬於自己的 AI 小工具。