前情提要
家里养了三只猫咪,其中一只布偶猫经常出入厕所。但因为平时忙于学业,没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关,频繁如厕可能是泌尿问题,停留过久也可能是便秘或不适。为了更科学地了解牠的如厕习惯,我计划搭建一个基于视频监控和AI识别的系统,自动识别猫咪进出厕所的行为,记录如厕时间和停留时长,并区分不同猫咪。这样即使我不在家,也能掌握猫咪的健康状态,更安心地照顾它们。
已完成工作:
✅猫咪如厕检测与分类识别系统系列【一】 功能需求分析及猫咪分类特征提取
✅猫咪如厕检测与分类识别系统系列【二】多图上传及猫咪分类特征提取更新
✅猫咪如厕检测与分类识别系统系列【三】 融合yolov11目标检测
✅猫咪如厕检测与分类识别系统系列【四】融合检测日志输出及前端展示界面制作
计划工作:
✅ 猫咪管理功能:已完成猫咪照片上传与名称登记模块。
✅ 多图上传与分类特征提取:已支持批量上传猫咪图像并自动更新个体特征库。
✅ 目标检测与事件识别集成(YOLOv11):功能开发中,正在实现猫咪行为自动识别。
⏳ 检测区域绘制功能:待开发,计划支持用户自定义如厕检测区域。
🔄 事件行为记录模块:进行中,将实现如厕进出时间、停留时长等事件记录功能。
⏳ 检测结果推流展示:待更新,计划支持算法结果实时推流。
⏳ 整体运行结果推流整合:待更新,计划集成检测图像与系统状态为统一视频流输出。
我们现在进行两项升级:
- 将如厕记录 存储到 SQLite 数据库
- 替代 CSV 存储方式
- 使用 SQLite 存储字段:猫名、进入时间、离开时间、如厕时长、图片路径等
- 前端页面支持 上传猫脸图 & 添加猫
-
用户可以从 Web 上传图片 + 猫名
-
自动提特征并写入猫数据库(支持多图上传)
🗂️ 本章节会包括:
模块 | 内容 |
---|---|
toilet_logger.py |
改写为使用 SQLite 存储记录 |
cat_database.db |
存储如厕记录表 toilet_logs |
Flask 页面上传猫图 | 上传猫图 + 猫名,调用特征提取器 + 保存 |
Flask 页面展示 | 改为从数据库查询展示记录 |
完成 SQLite 日志存储 + 查询展示(如厕记录页改数据库)
如厕记录系统已升级为使用 SQLite 数据库 存储,文件为:
records/toilet_log.db
表结构:toilet_logs(name, enter_time, exit_time, duration, enter_image, exit_image)
更新 Flask 页面,改为从数据库中查询记录并展示到表格中。
python
# app.py (使用 SQLite 显示如厕记录)
from flask import Flask, render\_template, request
import sqlite3
import os
app \= Flask(\_\_name\_\_, static\_url\_path\='')
DB\_PATH \= "records/toilet\_log.db"
@app.route('/')
def index():
query \= request.args.get('q', '').lower()
records \= \[\]
if os.path.exists(DB\_PATH):
conn \= sqlite3.connect(DB\_PATH)
cursor \= conn.cursor()
cursor.execute("SELECT name, enter\_time, exit\_time, duration, enter\_image, exit\_image FROM toilet\_logs ORDER BY id DESC")
for row in cursor.fetchall():
if query in row\[0\].lower():
records.append({
'Name': row\[0\],
'Enter Time': row\[1\],
'Exit Time': row\[2\],
'Duration(s)': row\[3\],
'Enter Image': row\[4\],
'Exit Image': row\[5\]
})
conn.close()
return render\_template('index.html', records\=records, query\=query)
if \_\_name\_\_ \== '\_\_main\_\_':
app.run(debug\=True)
✅ Flask 页面后端已完成更新,现在从 SQLite 数据库读取如厕记录,并支持通过 URL 查询参数 q
进行猫咪名称筛选。
✅ 接下来是 HTML 页面改造:
-
输入框支持搜索(发送请求到
/?q=猫名
) -
保留图片预览功能
-
所有记录从数据库加载
python
html_updated = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>猫咪如厕记录</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; background-color: #f9f9f9; }
h2 { color: #333; }
table { width: 100%; border-collapse: collapse; margin-top: 20px; }
th, td { border: 1px solid #ccc; padding: 10px; text-align: center; }
th { background-color: #e0e0e0; }
input[type="text"] { padding: 6px; width: 240px; margin-bottom: 15px; }
.thumbnail { height: 80px; cursor: pointer; border-radius: 6px; }
.modal { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
background-color: rgba(0, 0, 0, 0.7); align-items: center; justify-content: center; }
.modal img { max-width: 80%; max-height: 80%; border-radius: 10px; }
</style>
</head>
<body>
<h2>🐱 猫咪如厕记录</h2>
<form method="get" action="/">
<input type="text" name="q" value="{{ query }}" placeholder="输入猫咪名字筛选...">
<button type="submit">搜索</button>
</form>
<table id="recordTable">
<thead>
<tr>
<th>猫咪</th>
<th>进入时间</th>
<th>离开时间</th>
<th>如厕时长 (秒)</th>
<th>进入图</th>
<th>离开图</th>
</tr>
</thead>
<tbody>
{% for r in records %}
<tr>
<td>{{ r['Name'] }}</td>
<td>{{ r['Enter Time'] }}</td>
<td>{{ r['Exit Time'] }}</td>
<td>{{ r['Duration(s)'] }}</td>
<td>
<img src="{{ '/' + r['Enter Image'] }}" class="thumbnail" onclick="showModal(this.src)">
</td>
<td>
<img src="{{ '/' + r['Exit Image'] }}" class="thumbnail" onclick="showModal(this.src)">
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="modal" id="imgModal" onclick="hideModal()">
<img id="modalImg" src="">
</div>
<script>
function showModal(src) {
document.getElementById("modalImg").src = src;
document.getElementById("imgModal").style.display = "flex";
}
function hideModal() {
document.getElementById("imgModal").style.display = "none";
}
</script>
</body>
</html>
"""
with open("cat_monitor/web/templates/index.html", "w", encoding="utf-8") as f:
f.write(html_updated)
"index.html 页面已更新,支持 SQLite 查询并带搜索功能。"
✅ 前端页面已更新,现在支持:
-
从 SQLite 查询如厕记录
-
输入猫名关键词搜索(带刷新保留结果)
-
缩略图展示 + 点击预览大图
📍访问方式:
bash
cd cat_monitor/web
python app.py
浏览器打开: http://127.0.0.1:5000/
图片路径无法访问问题原因分析:
图片无法展示,说明 图片路径不对 或 Flask 无法找到图片文件。当前的记录表中,图片路径是这样的:
python
records/cat_name_enter_123456.jpg
但 Flask 默认只能访问 static/
下的资源。
✅ 解决方案:
✅ 1. 把保存的图片改为放在 web/static/records/
目录
修改你的 main.py
中这两行保存图片的位置:
python
cv2.imwrite("web/static/records/xxx.jpg", frame)
同时,保存路径传给数据库的也要改为相对路径,例如:
python
enter_img = f"static/records/{cat_name}_enter_{int(entry_time)}.jpg"
这样 Flask 页面显示时用:
html
<img src="/static/records/{{ r['Enter Image'] }}" ...>
✅ 2. 或者:设置 Flask 支持访问 /records/
如果你不想改文件保存位置,也可以在 Flask 中暴露 records 目录:
python
app = Flask(__name__, static_url_path='')
# 添加这行让 Flask 能访问 records/ 里的图片
@app.route('/records/<path:filename>')
def serve_record_file(filename):
from flask import send_from_directory
return send_from_directory('../records', filename)
然后前端页面这样写:
html
<img src="/records/{{ r['Enter Image'].split('/')[-1] }}" class="thumbnail">
✅ 推荐方式(更整洁):
我们规范所有图像都放到:
cat_monitor/web/static/records/
并使用 Flask 默认 static 路由 /static/...
展示即可。
🧩 进一步功能增强:
✅ 1. 【猫图上传页面】
-
Web 页面上传猫脸图 + 名字
-
多图批量上传(改进识别稳定性)
-
自动提取特征并存入猫数据库
✅ 2. 【统计功能】
-
每只猫如厕频率统计(柱状图)
-
如厕时长统计(平均、总和)
-
趋势图(按日期汇总)
✅ 3. 【记录导出】
-
记录导出为 CSV / Excel
-
可按猫名 / 时间筛选后导出
1.猫图上传页面
✅ 之前已经创建了独立路由 /add_cat
支持:
-
猫名输入
-
批量上传猫脸图
-
自动提取特征,保存识别样本
-
上传成功后提示处理结果
接下来我会写页面模板 add_cat.html
,支持:
-
猫名输入框
-
多图上传控件
-
上传进度提示 / 成功信息
下面是其HTML 页面,并把它集成到原 Flask 页面的顶部菜单里作为一个导航入口。
python
html_addcat = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>添加猫咪</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
h2 { color: #333; }
form { background: #f9f9f9; padding: 20px; border-radius: 8px; }
input[type="text"], input[type="file"] {
padding: 8px; width: 300px; margin-bottom: 10px;
}
input[type="submit"] {
padding: 8px 16px; background: #4CAF50; color: white; border: none; border-radius: 4px;
}
.message { margin-top: 15px; color: green; }
a { display: inline-block; margin-top: 15px; color: #007BFF; text-decoration: none; }
</style>
</head>
<body>
<h2>📤 添加猫咪识别样本</h2>
<form method="POST" enctype="multipart/form-data">
<label>猫咪名字:</label><br>
<input type="text" name="name" required><br><br>
<label>上传猫脸图片(可多选):</label><br>
<input type="file" name="images" multiple accept="image/*" required><br><br>
<input type="submit" value="上传并添加">
</form>
{% if message %}
<div class="message">{{ message }}</div>
{% endif %}
<a href="/">← 返回如厕记录页</a>
</body>
</html>
"""
with open("cat_monitor/web/templates/add_cat.html", "w", encoding="utf-8") as f:
f.write(html_addcat)
"已创建上传猫图页面 add_cat.html,可集成入口按钮到主页面。"
✅ 上传猫图页面 add_cat.html
已创建完毕。接下来我们将它集成到主页面(如厕记录页)里,方式如下:
🔧 集成步骤
在 index.html
顶部添加导航链接,例如放在 <body>
下:
html
<a href="/add_cat" style="text-decoration: none; background: #4CAF50; color: white; padding: 8px 12px; border-radius: 4px;">➕ 添加猫咪</a>
我可以立即为你修改 index.html
文件,是否现在执行?你也可以告诉我更偏好的按钮样式或放置位置~
✅ 2. 增加统计图表功能
功能目标:
-
显示每只猫如厕的 频率统计(柱状图)
-
显示每只猫的 平均如厕时长(可选)
-
使用
Chart.js
进行前端可视化
具体实现步骤如下:
-
更新后端 SQL 查询逻辑,支持:
-
按猫名聚合统计频率和平均时长
-
按日期筛选记录(支持 URL 参数)
-
-
更新
index.html
页面:-
增加统计图表容器(Chart.js)
-
增加时间筛选表单
-
增加 "导出记录" 按钮
-
下面柱状图统计(如厕频率)代码,并集成到页面中。主要展示哪种统计图为主:频率、平均时长、趋势。
python
chart_html = """
<!-- 追加在 index.html 中统计图部分 -->
<h3>📊 猫咪如厕统计</h3>
<canvas id="chartFreq" height="120"></canvas>
<canvas id="chartDur" height="120" style="margin-top: 30px;"></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const freqCtx = document.getElementById('chartFreq');
const durCtx = document.getElementById('chartDur');
const freqData = {{ freq_data|tojson }};
const durData = {{ dur_data|tojson }};
new Chart(freqCtx, {
type: 'bar',
data: {
labels: freqData.labels,
datasets: [{
label: '如厕次数',
data: freqData.counts,
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: { legend: { display: false }},
scales: { y: { beginAtZero: true } }
}
});
new Chart(durCtx, {
type: 'bar',
data: {
labels: durData.labels,
datasets: [{
label: '平均如厕时长(秒)',
data: durData.avg_durations,
backgroundColor: 'rgba(75, 192, 192, 0.6)',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: { legend: { display: false }},
scales: { y: { beginAtZero: true } }
}
});
</script>
"""
with open("cat_monitor/web/templates/chart_part.html", "w", encoding="utf-8") as f:
f.write(chart_html)
"已生成图表统计部分的 HTML,可嵌入主页面 index.html,接下来更新 Flask 后端逻辑支持统计数据。"
🔜 接下来,我将更新 Flask 后端 app.py
:
-
查询数据库生成统计数据(频率 + 平均时长)
-
传递数据给模板渲染 Chart.js 图表
Flask 后端 app.py
支持:
-
从数据库读取如厕记录
-
自动生成:
-
每只猫的如厕次数统计
-
每只猫的平均如厕时长
-
-
将数据传给前端用于 Chart.js 渲染
下一步是将我们之前准备好的图表模块 chart_part.html
嵌入 index.html
页面。是否我现在直接更新页面,让图表显示在记录表下方?
目前存在问题:
分类全是 Unknown
的情况我们可以一步步优化,目前问题大概率出现在 猫脸识别的匹配策略或特征质量 上。
🧠 问题原因分析:
1. 猫图质量不稳定
-
光照/角度/遮挡较大 → 提取特征不一致
-
建议上传正面清晰猫脸图,多张样本图增强泛化能力
2. 匹配阈值太高
-
目前分类用的是最近邻 + 相似度阈值(默认 0.7)
-
如果猫脸图本身差异大,0.7 可能过严 → 全部被判成
Unknown
✅ 临时建议:先降低阈值到 0.5,观察识别是否有改善:
python
# matcher.py 中的 match 方法
def match(self, query_vec, threshold=0.5):
3. 特征提取器泛化能力差
-
使用的是通用
resnet18
,可能对猫脸没有专门训练 -
若需要长期稳定使用,可考虑:
-
✅ 使用猫脸识别预训练模型(可提供)
-
✅ 自定义猫脸数据集做微调(我可以帮你准备 fine-tune 流程)
-
✅ 测试:
-
上传每只猫的 3~5 张角度不同的图
-
把
threshold
降低到0.5
或更低(如 0.45)观察结果 -
使用 print 输出匹配得分看是否有接近但未过阈值的情况
例如你可以在 matcher.py
中打印相似度:
python
print(f"猫 {names[best_idx]} 相似度: {best_score}")
测试结果:我们不是在做猫脸识别,而是识别每只猫的"整体外观"来区分个体,那么我们可以把"全猫裁剪图"当作识别输入,而不是专注猫脸。
✅ 新策略:猫体图识别个体(视觉外观)
核心改法:
-
保持现在的 YOLOv11 检测框不变(整只猫)
-
将裁剪下来的整猫图像作为"猫的识别样本"上传(不是脸部)
-
使用同样的分类逻辑(特征提取 + 最近邻匹配),但目标变成"全猫识别"
🔧 如何改得更好?
✅ 1. 特征提取器建议使用:
-
通用图像分类特征模型(如
resnet18
,efficientnet-b0
) -
或者我可以给你微调一个
triplet loss
结构(更适合个体识别)
✅ 2. 增强识别稳定性方法:
方法 | 描述 |
---|---|
多图样本 | 每只猫上传多张(不同角度) |
特征聚合 | 每只猫多个特征向量,识别时取最大相似度 |
动态阈值 | 允许你为不同猫设定不同相似度阈值(进阶) |
✅ 思路
-
重新上传几只猫的"整体图像"样本(清晰的检测框图,非脸)
-
在
matcher.py
中暂时将阈值调低到 0.5 以下 -
可开启打印相似度,确认是不是识别接近却没达标
因此,替换/优化现在的特征提取器(更适合全猫图)
python
# matcher.py - 最近邻匹配猫个体(加入相似度输出 + 降低阈值)
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.45): \# 降低默认阈值
embeddings, names \= self.db.get\_all()
if not embeddings:
return "Unknown"
sims \= cosine\_similarity(\[query\_vec\], embeddings)\[0\]
best\_idx \= np.argmax(sims)
best\_score \= sims\[best\_idx\]
print("--- 猫识别相似度得分 ---")
for i, sim in enumerate(sims):
print(f"{names\[i\]}: {sim:.3f}")
print(f"最相似: {names\[best\_idx\]} → {best\_score:.3f}")
if best\_score \> threshold:
return names\[best\_idx\]
return "Unknown"
已更新 matcher.py
:
-
默认阈值调为
0.45
(提高匹配成功率) -
每次识别时会输出所有猫的相似度分数
-
控制台将显示最相似的猫与相似度,方便调试