开发背景
家里养了三只猫咪,其中一只布偶猫经常出入厕所。但因为平时忙于学业,没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关,频繁如厕可能是泌尿问题,停留过久也可能是便秘或不适。为了更科学地了解牠的如厕习惯,我计划搭建一个基于视频监控和AI识别的系统,自动识别猫咪进出厕所的行为,记录如厕时间和停留时长,并区分不同猫咪。这样即使我不在家,也能掌握猫咪的健康状态,更安心地照顾它们。
🎯 核心需求拆解:
1. 区域检测
-
指定一个"如厕区域",只在这个区域内检测是否有猫咪。
-
可通过自定义矩形坐标或交互式选择方式设置该区域。
2. 猫咪检测 + 分类
-
使用
YOLOv11
做目标检测,识别猫咪是否进入区域。 -
分类出是哪只猫(通过猫脸识别或自定义分类模型实现)。
-
支持动态添加新猫类别
3. 事件记录与图片保存
-
检测到猫咪"进入区域"时:
-
记录时间戳
-
保存一张图片(入场图)
-
-
检测到"离开区域"时:
-
记录时间戳
-
保存一张图片(离场图)
-
计算如厕时间并保存(离开时间 - 进入时间)
-
4. 记录保存
-
记录内容包括:
-
猫咪ID / 名称
-
进入时间
-
离开时间
-
如厕时长
-
图片路径
-
-
保存为 SQLite 数据库
🛠️ 技术栈:
-
检测模型:YOLOv11 进行目标检测(识别是否为猫及位置)
-
分类模型 :使用一个轻量 CNN 或
ArcFace + ResNet
的猫脸识别模型(支持增量学习或动态注册) -
逻辑判断:跟踪猫咪是否进入区域(通过目标跟踪或 ID 跟踪)
✅ UI 界面设计:
-
区域选择(绘制检测区域)【待更新】
-
视频流预览(摄像头或视频)【待更新】
-
猫咪管理(添加猫照片及名字)【已完成】
-
事件记录展示(列表、时间线)【已完成】
-
导出功能(CSV / Excel / 图片)【待更新】
✅ 一、猫咪分类模块(支持添加新猫)
思路:
我们用猫脸图片提特征,然后进行"最近邻"匹配:
-
初次录入时,提取特征 + 存入特征库(保存为
.npy
或 SQLite) -
实时推理时,检测到猫 → 裁剪猫脸 → 提特征 → 与已知猫对比 → 分类结果
模型选型(轻量):
-
使用
ResNet18
或 MobileFaceNet 做猫脸识别 -
特征距离:欧氏距离 / 余弦相似度
✅ 二、YOLOv11 + 摄像头区域检测
功能:
-
启动摄像头实时检测
-
在图像中标注"如厕区域"
-
判断猫是否进入区域
-
裁剪猫图并交给分类模块识别
-
管理状态(进入 / 离开)并记录时间
🧱 项目目录构建:
cat_monitor/
├── detector/ # YOLOv11 推理代码
│ └── yolo_detector.py
├── recognizer/ # 猫咪识别代码
│ ├── embedder.py # 提取猫脸特征
│ ├── database.py # 猫脸数据库管理
│ └── matcher.py # 分类识别逻辑
├── data/
│ └── embeddings/ # 猫脸特征向量存储
├── records/ # 图片与如厕记录
├── web/ # Flask 前端展示
├── main.py # 实时主程序
└── config.py
🐱 猫咪分类模块目标
✅ 功能概述:
-
用户上传猫咪脸部图片 + 输入名字 → 添加新猫
-
每张图提取特征(使用轻量模型)
-
将猫的特征保存(后续用于识别)
-
实时时:检测到猫 → 裁剪猫图 → 提特征 → 与库里比对 → 得出是哪只猫
🧠 分类逻辑核心流程:
-
提特征(使用预训练猫脸模型 or 自训练轻量 CNN)
-
保存特征向量(.npy)+ 名称映射
-
比对:通过余弦相似度 / 欧氏距离 → 找出最接近的猫
✅ 模块设计方案
🔹 1. embedder.py
-- 提取猫脸特征
python
import torch
import torchvision.transforms as transforms
from torchvision.models import resnet18
from PIL import Image
class CatEmbedder:
def __init__(self, model_path=None):
self.model = resnet18(pretrained=True) # 可替换为你训练的猫脸模型
self.model.fc = torch.nn.Identity()
self.model.eval()
self.transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
])
def extract(self, img_path):
img = Image.open(img_path).convert('RGB')
img = self.transform(img).unsqueeze(0)
with torch.no_grad():
features = self.model(img)
return features.squeeze().numpy()
🔹 2. database.py
-- 管理猫的特征库
python
import os
import numpy as np
import json
class CatDatabase:
def __init__(self, db_path='data/embeddings'):
self.db_path = db_path
self.mapping_file = os.path.join(db_path, 'cat_names.json')
os.makedirs(db_path, exist_ok=True)
if os.path.exists(self.mapping_file):
with open(self.mapping_file, 'r') as f:
self.name_map = json.load(f)
else:
self.name_map = {}
def add_cat(self, name, embedding):
cat_id = str(len(self.name_map))
np.save(os.path.join(self.db_path, f"{cat_id}.npy"), embedding)
self.name_map[cat_id] = name
with open(self.mapping_file, 'w') as f:
json.dump(self.name_map, f)
def get_all(self):
embeddings = []
names = []
for cat_id, name in self.name_map.items():
vec = np.load(os.path.join(self.db_path, f"{cat_id}.npy"))
embeddings.append(vec)
names.append(name)
return embeddings, names
🔹 3. matcher.py
-- 识别猫咪身份
python
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
class CatMatcher:
def __init__(self, db):
self.db = db
def match(self, query_vec, threshold=0.7):
embeddings, names = self.db.get_all()
if not embeddings:
return "Unknown"
sims = cosine_similarity([query_vec], embeddings)[0]
best_idx = np.argmax(sims)
if sims[best_idx] > threshold:
return names[best_idx]
return "Unknown"
✅ 流程示意图:
【上传猫脸图 + 名字】
↓
extract → embedding
↓
database.add_cat(name, embedding)
↓
【实时分类时】
→ 提特征 → matcher.match() → 猫名 / Unknown
✅ 测试:
python
# 测试添加猫
from recognizer.embedder import CatEmbedder
from recognizer.database import CatDatabase
embedder = CatEmbedder()
db = CatDatabase()
vec = embedder.extract("cat_face_1.jpg")
db.add_cat("Mimi", vec)
python
# 测试识别
from recognizer.matcher import CatMatcher
matcher = CatMatcher(db)
query_vec = embedder.extract("some_query_cat.jpg")
print(matcher.match(query_vec))
接下来制作一个猫咪添加页面的 Web 前端,用 Flask 实现,功能如下:
🧩 功能:
-
上传猫咪脸部图片(JPEG/PNG)
-
输入猫咪的名字
-
点击提交 → 提取特征并存入数据库
-
页面展示已有猫咪列表(含名字)
📁 项目结构(简化版)
cat_monitor/
├── recognizer/
│ ├── embedder.py
│ ├── database.py
│ └── matcher.py
├── web/
│ ├── app.py ← Flask 主程序
│ ├── templates/
│ │ └── index.html ← 上传页面
│ └── static/
│ └── uploads/ ← 存猫图
🔧 1. Flask 后端 (web/app.py
)
python
from flask import Flask, render_template, request, redirect, url_for
import os
from recognizer.embedder import CatEmbedder
from recognizer.database import CatDatabase
app = Flask(__name__)
UPLOAD_FOLDER = 'web/static/uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
embedder = CatEmbedder()
db = CatDatabase()
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'POST':
name = request.form['name']
file = request.files['image']
if name and file:
img_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
file.save(img_path)
vec = embedder.extract(img_path)
db.add_cat(name, vec)
return redirect(url_for('index'))
# 显示已有猫
_, names = db.get_all()
return render_template('index.html', cats=names)
if __name__ == '__main__':
app.run(debug=True)
🖼️ 2. HTML 页面 (web/templates/index.html
)
html
<!DOCTYPE html>
<html>
<head>
<title>猫咪识别管理</title>
</head>
<body>
<h2>添加新猫咪</h2>
<form method="POST" enctype="multipart/form-data">
<input type="text" name="name" placeholder="猫咪名字" required>
<input type="file" name="image" accept="image/*" required>
<input type="submit" value="添加猫咪">
</form>
<h3>已录入猫咪:</h3>
<ul>
{% for name in cats %}
<li>{{ name }}</li>
{% endfor %}
</ul>
</body>
</html>
✅ 使用方法
- 启动 Flask 服务:
bash
cd web
python app.py
-
打开浏览器访问
http://127.0.0.1:5000/
-
添加猫咪并上传图片,后台会自动提特征并保存
✅ 前端效果展示
