当前位置: 首页 > news >正文

猫咪如厕检测与分类识别系统系列【十二】猫咪进出事件逻辑及日志优化

前情提要


家里养了三只猫咪,其中一只布偶猫经常出入厕所。但因为平时忙于学业,没法时刻关注牠的行为。我知道猫咪的如厕频率和时长与健康状况密切相关,频繁如厕可能是泌尿问题,停留过久也可能是便秘或不适。为了更科学地了解牠的如厕习惯,我计划搭建一个基于视频监控和AI识别的系统,自动识别猫咪进出厕所的行为,记录如厕时间和停留时长,并区分不同猫咪。这样即使我不在家,也能掌握猫咪的健康状态,更安心地照顾它们。

🎓 各位的关注与点赞是我持续分享的最大动力,衷心感谢大家的支持!
📢 欢迎正在攻读硕博学位的同学,或是对人工智能充满热情的朋友们,关注我的个人公众号。在这里,我将持续更新博士期间阅读的前沿论文解读、项目实战经验分享,以及我对AI技术趋势的思考与探讨。
✨ 无论你是科研工作者、工程开发者,还是AI初学者,都能在这里找到干货与灵感。让我们一起交流、成长、探索人工智能的无限可能!

已完成工作:

✅猫咪如厕检测与分类识别系统系列【一】 功能需求分析及猫咪分类特征提取
✅猫咪如厕检测与分类识别系统系列【二】多图上传及猫咪分类特征提取更新
✅猫咪如厕检测与分类识别系统系列【三】 融合yolov11目标检测
✅猫咪如厕检测与分类识别系统系列【四】融合检测日志输出及前端展示界面制作
✅猫咪如厕检测与分类识别系统系列【五】信息存储数据库改进+添加猫咪页面制作+猫咪躯体匹配算法架构更新
✅猫咪如厕检测与分类识别系统系列【六】分类模型训练+混合检测分类+未知目标自动更新

✅猫咪如厕检测与分类识别系统系列【七】 当前阶段总结报告

✅猫咪如厕检测与分类识别系统系列【八】 检测推理事件整合+视频推流架构分析

✅猫咪如厕检测与分类识别系统系列【九】 视频检测区域在线绘制+支持摄像头+网络摄像头+整体构建【上】

✅猫咪如厕检测与分类识别系统系列【九】 视频检测区域在线绘制+支持摄像头+网络摄像头+整体构建【下】

✅猫咪如厕检测与分类识别系统系列【十】 视频检测区域动态监测及实时更新
✅猫咪如厕检测与分类识别系统系列【十一】区域进入事件相应逻辑鲁棒性更新

本小节继续更新猫咪进出事件逻辑及日志优化


def log_enter(self, name, enter_time, enter_img, method="classifier", score=0.0):time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")with open("enter_log.txt", "a", encoding="utf-8") as f:f.write(f"[{time_str}] 🟢 {name} 进入区域(方法:{method},评分:{score:.2f}) 图像:{enter_img}\\n")

⚠️ 确保此方法放在类 ToiletLogger 里。


✅ 2. 确认导入的是 更新后的 toilet_logger.py
main.pysession_tracker.py 中导入时:

from toilet_logger import ToiletLogger

确保这个模块是刚刚更新的那一个路径下的( cat_monitor/toilet_logger.py


✅ 3. 重启环境

如果已经改了文件但还在报错,有可能是旧模块缓存没有刷新(特别是在 notebook / 热加载情况下):

# 重启 Python 解释器,确保 log_enter 方法生效

✅ 快速验证(临时测试)

在项目根目录开一个 Python 控制台测试是否含有该方法:

from toilet_logger import ToiletLogger
logger = ToiletLogger()
print(hasattr(logger, "log_enter"))  # 应输出 True

如厕记录没有被写入数据库或日志 ,很可能是以下某一部分逻辑出了问题,我们逐一定位:


✅ 一步步排查方向:

✅ 1. 是否进入了 update() 中 in_region 的判断逻辑?

print(f"[DEBUG] 猫进入区域: {cat_name}, 状态: {session['status']}")

确认是不是根本没触发记录逻辑。


✅ 2. 是否正确调用了 log()log_enter()
可以在 CatSessionTracker 中加一行:

print(f"[记录中] 调用 logger.log_enter for {cat_name}")
print(f"[记录中] 调用 logger.log for {cat_name}")

✅ 3. logger.log() 中是否有异常但被静默?

临时加上异常捕获打印看看:

try:self.logger.log(...)
except Exception as e:print(f"[错误] 写入日志失败:{e}")

✅ 4. 检查数据库是否真的写入失败

打开 records/toilet_log.db,手动查询是否有数据:

sqlite3 records/toilet_log.db
sqlite> SELECT * FROM toilet_logs ORDER BY rowid DESC LIMIT 5;

✅ 最快的恢复方案:

  • 输出当前应使用的 CatSessionTracker + toilet_logger.py

  • 包含打印和异常捕获

  • 确保进入一次就有图有日志

  • 包括一次完整的检测流程样例


经过全量排查并还原一个稳定可运行的检测 + 记录系统核心逻辑


🎯 目标

阶段行为是否记录截图保存到
猫进入区域✅ 立即记录enter_log.txt
猫离开区域✅ 如厕结束toilet_log.db

✅ 步骤

输出两段最关键的更新版代码,确保能运行:


✅ 1. CatSessionTracker(完整 + 带 debug 打印)

import time
import cv2
import os
from config.paths import RECORDS_DIR
from collections import defaultdictclass CatSessionTracker:def __init__(self, logger, min_interval=10, min_duration=10):self.logger = loggerself.min_interval = min_intervalself.min_duration = min_durationself.sessions = defaultdict(lambda: {"status": "IDLE","entry_time": None,"last_exit_time": 0,"method": "classifier","score": 0.0})def update(self, frame, result):annotated, cat_name, in_region, method, score = resultif cat_name == "Unknown":print("[跳过] 未识别猫")return annotatedsession = self.sessions[cat_name]now = time.time()# 🟢 猫进入if in_region:if session["status"] == "IDLE" and (now - session["last_exit_time"]) > self.min_interval:print(f"🟢 猫进入:{cat_name}")session["status"] = "ENTERED"session["entry_time"] = nowsession["method"] = methodsession["score"] = scoreenter_img = f"{cat_name}_enter_{int(now)}.jpg"enter_path = os.path.join("static/records", enter_img)cv2.imwrite(os.path.join(RECORDS_DIR, enter_img), frame)session["enter_img"] = enter_pathself.logger.log_enter(cat_name, now, enter_path, method, score)# 🔴 猫离开elif session["status"] == "ENTERED":session["status"] = "IDLE"exit_time = nowsession["last_exit_time"] = exit_timeduration = exit_time - session["entry_time"]if duration >= self.min_duration:print(f"🔴 猫离开:{cat_name},如厕 {int(duration)} 秒")exit_img = f"{cat_name}_exit_{int(now)}.jpg"exit_path = os.path.join("static/records", exit_img)cv2.imwrite(os.path.join(RECORDS_DIR, exit_img), frame)try:self.logger.log(cat_name,session["entry_time"],exit_time,session["enter_img"],exit_path,session["method"],session["score"])except Exception as e:print(f"❌ 写入数据库失败: {e}")else:print(f"⚠️ 猫 {cat_name} 如厕时间太短({int(duration)} 秒),未记录")return annotated

✅ 2. result 的生成方式(在 detectormain.py 中应输出的是):

result = (annotated_frame, cat_name, in_region, method, score)
tracker.update(frame, result)

🧠 然而以上代码仍然“没反应”,提供摄像头流和区域判断部分的代码(process_frame

没检测到猫咪的时候一直返回的 if cat_name == “Unknown”:
return annotated 根本没有执行到出去

问题的根源是:

没有检测到猫咪 或识别失败为 "Unknown" 时,CatSessionTracker.update() 直接 return annotated完全跳过了“猫是否离开”的判断逻辑


❗这会导致的问题

  • 猫已经离开了,但因为没有检测出猫,所以逻辑无法触发“离开”状态转移

  • session["status"] 一直卡在 ENTERED,无法恢复为 IDLE

  • 所有如厕流程卡死在中途


✅ 正确的修复方案:即使识别失败也要判断是否离开区域

我们只需小改这个判断逻辑:


✅ 原错误代码:

if cat_name == "Unknown":return annotated

✅ 修复代码:

if cat_name == "Unknown":# 即使识别失败,也可能是上一只猫已经离开for name, session in self.sessions.items():if session["status"] == "ENTERED":now = time.time()duration = now - session["entry_time"]if duration > self.min_duration:print(f"🔴 {name} 猫可能已离开(当前检测不到),如厕 {int(duration)} 秒")session["status"] = "IDLE"session["last_exit_time"] = now# 可选择:记录“模糊离开”日志或跳过写入return annotated

✅ 或者简洁一点:

if cat_name == "Unknown":# 只有状态为 ENTERED 的猫,且当前帧没有识别出它 → 视为离开for name, session in self.sessions.items():if session["status"] == "ENTERED":print(f"🔴 猫 {name} 可能离开,但当前未识别出任何猫,自动重置状态")session["status"] = "IDLE"session["last_exit_time"] = time.time()return annotated

🎯 只需要:

CatSessionTracker.update() 中找到:

if cat_name == "Unknown":return annotated

并改为上述“兜底处理所有 session”的逻辑即可 ✅


但是这样后面的记录如厕 根本执行不到,
所以我们不能一刀切地在 "Unknown" 时直接 return,否则就永远不会触发:

  • 离开区域的判断

  • 记录如厕行为的逻辑


✅ 正确的处理方式是:

始终保留上一个已知猫的状态 ,即使当前帧无法识别出猫,也要根据“区域是否空了”来判断猫是否已经离开!


🎯完美兼容“断识别”的情况:

✅ 修改逻辑:

# 猫识别失败时,不直接 return,而是判断之前是否有猫在区域中
if cat_name == "Unknown":for name, session in self.sessions.items():if session["status"] == "ENTERED":# 当前区域为空,说明猫可能已经离开if not in_region:now = time.time()duration = now - session["entry_time"]if duration >= self.min_duration:print(f"🔴 猫 {name} 离开(未被识别),记录如厕 {int(duration)} 秒")session["status"] = "IDLE"session["last_exit_time"] = now# 截图保存exit_img = f"{name}_exit_{int(now)}.jpg"exit_path = os.path.join("static/records", exit_img)cv2.imwrite(os.path.join(RECORDS_DIR, exit_img), frame)self.logger.log(name,session["entry_time"],now,session.get("enter_img", ""),exit_path,session.get("method", "unknown"),session.get("score", 0.0))else:print(f"⚠️ 猫 {name} 离开(未被识别),但如厕时间太短")session["status"] = "IDLE"session["last_exit_time"] = nowreturn annotated

✅ 整体思路简述

  1. 如果识别不出猫(unknown)

  2. 就回头看看之前哪只猫状态是 "ENTERED"

  3. 如果区域现在是空的 → 猫离开 → 自动记录 ✅


以下是 修复后的完整版本 CatSessionTracker.py ,支持:

  • 进入时记录 enter 图 & 写日志

  • 离开时记录 exit 图 & 写数据库

  • 即使识别失败,只要区域空了也能记录“猫已离开”


CatSessionTracker.py

import time
import cv2
import os
from config.paths import RECORDS_DIR
from collections import defaultdictclass CatSessionTracker:def __init__(self, logger, min_interval=10, min_duration=10):self.logger = loggerself.min_interval = min_interval  # 两次如厕最短间隔self.min_duration = min_duration  # 最短如厕时间self.sessions = defaultdict(lambda: {"status": "IDLE","entry_time": None,"last_exit_time": 0,"method": "classifier","score": 0.0})def update(self, frame, result):annotated, cat_name, in_region, method, score = resultnow = time.time()# 🟡 猫识别失败时,也检查是否有猫要离开if cat_name == "Unknown":for name, session in self.sessions.items():if session["status"] == "ENTERED" and not in_region:duration = now - session["entry_time"]if duration >= self.min_duration:print(f"🔴 猫 {name} 离开(未被识别),记录如厕 {int(duration)} 秒")session["status"] = "IDLE"session["last_exit_time"] = nowexit_img = f"{name}_exit_{int(now)}.jpg"exit_path = os.path.join("static/records", exit_img)cv2.imwrite(os.path.join(RECORDS_DIR, exit_img), frame)self.logger.log(name,session["entry_time"],now,session.get("enter_img", ""),exit_path,session.get("method", "unknown"),session.get("score", 0.0))else:print(f"⚠️ 猫 {name} 离开时间过短,未记录")session["status"] = "IDLE"session["last_exit_time"] = nowreturn annotated# 🟢 猫进入区域session = self.sessions[cat_name]if in_region:if session["status"] == "IDLE" and (now - session["last_exit_time"]) > self.min_interval:print(f"🟢 猫进入:{cat_name}")session["status"] = "ENTERED"session["entry_time"] = nowsession["method"] = methodsession["score"] = scoreenter_img = f"{cat_name}_enter_{int(now)}.jpg"enter_path = os.path.join("static/records", enter_img)cv2.imwrite(os.path.join(RECORDS_DIR, enter_img), frame)session["enter_img"] = enter_pathself.logger.log_enter(cat_name, now, enter_path, method, score)# 🔴 猫识别正常,但已经不在区域 → 离开elif session["status"] == "ENTERED":exit_time = nowduration = exit_time - session["entry_time"]if duration >= self.min_duration:print(f"🔴 猫离开:{cat_name},如厕 {int(duration)} 秒")session["status"] = "IDLE"session["last_exit_time"] = exit_timeexit_img = f"{cat_name}_exit_{int(now)}.jpg"exit_path = os.path.join("static/records", exit_img)cv2.imwrite(os.path.join(RECORDS_DIR, exit_img), frame)self.logger.log(cat_name,session["entry_time"],exit_time,session.get("enter_img", ""),exit_path,method,score)else:print(f"⚠️ 猫 {cat_name} 离开时间太短,未记录")session["status"] = "IDLE"session["last_exit_time"] = exit_timereturn annotated

相关文章:

  • 【Datawhale AI春训营】Java选手初探数据竞赛
  • 【对Linux文件权限的深入理解】
  • 有源低通滤波器 sallen-key低通滤波器原理与计算
  • 《2025最新Java面试题全解析:从基础到高并发架构设计》
  • 速查手册:TA-Lib 超过150种量化技术指标计算全解 - 2. Momentum Indicators(动量指标)
  • 超大文件处理——文件强制切割:突破存储传输限制,提升数据处理效能—星辰大文化术——未来之窗超算中心
  • PKI 公钥基础设施
  • STM32学习笔记汇总
  • JavaWeb 课堂笔记 —— 13 MySQL 事务
  • 解决win10执行批处理报编码错误
  • Nodejs数据库单一连接模式和连接池模式的概述及写法
  • Meteonorm8-免费使用教程(详细教程-免费)
  • RK3506-rtlinux
  • Linux系统之部署TestNet资产管理系统
  • 豆瓣图书数据采集与可视化分析(一)- 豆瓣图书数据爬取
  • 【DT】USB通讯失败记录
  • 整流二极管详解:原理、作用、应用与选型要点
  • Replicate Python client
  • AUTOSAR图解==>AUTOSAR_SWS_EFXLibrary
  • fragment 异常 InstantiationException
  • 长安汽车辟谣抛弃华为,重奖百万征集扩散不实内容的背后组织
  • “特朗普的欧洲耳语者”:梅洛尼白宫之行真能打破美欧关税僵局?
  • 美国防部宣布整合驻叙美军部队,将减少至不足千人
  • 不断深化“数字上海”建设!上海市数据发展管理工作领导小组会议举行
  • 贝壳CEO拟捐赠价值4.68亿港元股份:用于行业人员医疗福利及应届生租客帮扶
  • 美国佛罗里达州立大学发生枪击事件