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

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

前情提要


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

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

已完成工作:

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

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

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

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

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

✅猫咪如厕检测与分类识别系统系列【十】 视频检测区域动态监测及实时更新
✅猫咪如厕检测与分类识别系统系列【十一】区域进入事件相应逻辑鲁棒性更新
✅猫咪如厕检测与分类识别系统系列【十二】猫咪进出事件逻辑及日志优化【上】
本小节继续更新猫咪进出事件逻辑及日志优化


🎯 核心问题

上一小节已经对哦猫咪进出逻辑进行了重构,但此时检测里面的cat_name = “Unknown” 默认就是unknown,在检测流程中,这行代码:

cat_name = "Unknown"

是初始化在进入检测循环之前的,并且:

  • 如果没有检测到任何 cat 类目标

  • 或者所有检测都未通过区域判断

  • 就不会更新 cat_name → 仍然是 "Unknown"

于是:

  • 即使猫已经离开,CatSessionTracker 中也会一直接收到 "Unknown" 的结果

  • 但没有对应的 in_region = False 的已识别猫信息

  • 记录逻辑就断掉了


假设有一个 process_frame(frame) 的方法,返回:

return annotated_frame, cat_name, in_region, method, score

应该只在没有任何匹配检测目标时 才返回 Unknown,否则返回检测到的那只。


def process_frame(frame):results = model.predict(frame)boxes = results[0].boxesannotated = frame.copy()for box in boxes:if int(box.cls[0]) != 15:continue  # 不是猫xmin, ymin, xmax, ymax = map(int, box.xyxy[0])cx, cy = (xmin + xmax) // 2, (ymin + ymax) // 2if is_in_region(cx, cy):cat_crop = frame[ymin:ymax, xmin:xmax]pil_img = Image.fromarray(cv2.cvtColor(cat_crop, cv2.COLOR_BGR2RGB))# 尝试识别cat_name = classifier.predict(pil_img)method = "classifier"score = 1.0if cat_name == "Unknown":vec = embedder.extract(pil_img)cat_name = matcher.match(vec)method = "matcher"score = 0.9  # 可替换为相似度评分return annotated, cat_name, True, method, score# ❗没有任何猫在如厕区域return annotated, "Unknown", False, "none", 0.0

✅ 最终目标

  • cat_name"Unknown"说明本帧没有猫在区域中

  • cat_name 是具体名字 → 有猫、识别成功

  • in_region = False → 无论识别是否成功,只要区域没猫了,系统就会判断“是否离开”


此时我们发现
line 38, in update
self.logger.log(
TypeError: log() takes 6 positional arguments but 8 were given

self.logger.log(...) 调用了 8个参数 ,但 ToiletLogger.log() 方法只接受 6个参数


✅ 原因分析:

self.logger.log(cat_name,           # namesession["entry_time"],  # enter_timeexit_time,              # exit_timesession.get("enter_img", ""),  # enter_imgexit_path,                    # exit_imgmethod,                       # ❗️ extrascore                         # ❗️ extra
)

传了 7个 值 + self = 8个 positional arguments


✅ 正确写法:

methodscore 改成命名参数 传递:

self.logger.log(cat_name,session["entry_time"],exit_time,session.get("enter_img", ""),exit_path,method=method,score=score
)

✅ 另一种解决方式

也可以在 ToiletLogger.log() 方法定义里显示声明参数顺序和名称:

def log(self, name, enter_time, exit_time, enter_img, exit_img, method="classifier", score=0.0):

这样就支持最多 8个位置参数


✅ 建议

将 log 方法定义为:

def log(self, name, enter_time, exit_time, enter_img, exit_img, method="classifier", score=0.0):

然后调用时也用关键字方式(推荐):

self.logger.log(name, enter_time, exit_time,enter_img, exit_img,method=method,score=score
)

下面是应使用的最新版 toilet_logger.py 文件内容,支持:

  • 完整如厕日志写入(包括识别方式、评分)

  • 猫进入区域时写入日志文本(enter_log.txt


✅ 完整版 toilet_logger.py

import sqlite3
import os
from datetime import datetime
from config.paths import DATABASE_PATHclass ToiletLogger:def __init__(self):self.conn = sqlite3.connect(DATABASE_PATH, check_same_thread=False)self.cursor = self.conn.cursor()def log(self, name, enter_time, exit_time, enter_img, exit_img, method="classifier", score=0.0):duration = int(exit_time - enter_time)time_str = datetime.now().strftime("%Y-%m-%d %H:%M:%S")self.cursor.execute("""INSERT INTO toilet_logs (Name, "Enter Time", "Exit Time", "Duration(s)", "Enter Image", "Exit Image","Recognition Method","Recognition Time",Score) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", (name,datetime.fromtimestamp(enter_time).strftime("%Y-%m-%d %H:%M:%S"),datetime.fromtimestamp(exit_time).strftime("%Y-%m-%d %H:%M:%S"),duration,enter_img,exit_img,method,time_str,float(score)))self.conn.commit()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")

✅ 使用方式

调用:

logger.log(..., method="classifier", score=0.88)
logger.log_enter(..., method="matcher", score=0.76)

这将自动写入数据库 + 进入日志 ✅

问题:系统虽然记录了猫离开,但始终显示为「未被识别」,也就是 cat_name == "Unknown" 的 fallback 逻辑被触发了。


🎯 问题本质

系统没有正确记住“上一只猫是谁” ,当猫走掉后,由于没有识别结果,无法判断是谁离开的。


✅ 解决思路:记住当前如厕中的猫名
我们可以引入一个变量 last_active_cat 来追踪当前进入区域的猫。哪怕后续识别失败,也能用这个名字进行离开判断和记录。


✅ 推荐修改方案

CatSessionTracker.__init__() 中加一行:

self.last_active_cat = None

然后在识别成功进入时设置它:

if cat_name != "Unknown" and in_region:self.last_active_cat = cat_name

然后在 "Unknown" 判断中使用它替代:

if cat_name == "Unknown" and self.last_active_cat:# 假设上一次识别到的猫就是这次离开的猫session = self.sessions[self.last_active_cat]...

✅ 整合代码片段示例:

if cat_name == "Unknown":# 尝试使用上一次识别到的猫名if self.last_active_cat:name = self.last_active_catsession = self.sessions[name]if session["status"] == "ENTERED" and not in_region:...# 离开处理session["status"] = "IDLE"session["last_exit_time"] = time.time()self.last_active_cat = Nonereturn annotated

✅ 效果

  • 系统将记住最近一只进入区域的猫

  • 即使后续识别失败(遮挡 / 模糊),也能合理判断谁离开了

  • 避免重复出现 未被识别 的提示


以下是 完整修复后的 CatSessionTracker.py,加入了 last_active_cat 追踪变量,确保猫咪在“离开时未被识别”也能正确记录如厕信息。


✅ 完整版 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_intervalself.min_duration = min_durationself.last_active_cat = None  # ✅ 记录最近进入区域的猫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()# 🟡 识别失败时也判断是否猫已经离开(基于 last_active_cat)if cat_name == "Unknown":if self.last_active_cat:session = self.sessions[self.last_active_cat]if session["status"] == "ENTERED" and not in_region:duration = now - session["entry_time"]if duration >= self.min_duration:print(f"🔴 猫 {self.last_active_cat} 离开(未被识别),记录如厕 {int(duration)} 秒")session["status"] = "IDLE"session["last_exit_time"] = nowexit_img = f"{self.last_active_cat}_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(self.last_active_cat,session["entry_time"],now,session.get("enter_img", ""),exit_path,session.get("method", "unknown"),session.get("score", 0.0))else:print(f"⚠️ 猫 {self.last_active_cat} 离开时间太短,未记录")session["status"] = "IDLE"session["last_exit_time"] = nowself.last_active_cat = None  # 重置return 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)self.last_active_cat = cat_name  # ✅ 设置活跃猫名# 🔴 猫被识别到,但不在区域 → 离开elif session["status"] == "ENTERED":duration = now - session["entry_time"]if duration >= self.min_duration:print(f"🔴 猫 {cat_name} 离开,如厕 {int(duration)} 秒")session["status"] = "IDLE"session["last_exit_time"] = nowexit_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"],now,session.get("enter_img", ""),exit_path,method,score)else:print(f"⚠️ 猫 {cat_name} 离开时间太短,未记录")session["status"] = "IDLE"session["last_exit_time"] = nowself.last_active_cat = None  # ✅ 离开后重置活跃猫名return annotated

✅ 效果说明

状况效果
猫被识别到正常记录 ✅
猫未被识别,但曾进入使用 last_active_cat 正确记录 ✅
猫进入即离开若不足 min_duration,忽略记录 🚫

问题:self.last_active_cat = 一直是None


🎯 当前关键问题

self.last_active_cat = None  # 始终没有被赋值

🧠 原因:赋值条件不满足!

在以下位置赋值:

if in_region and cat_name != "Unknown":self.last_active_cat = cat_name

但是有可能 cat_name != "Unknown" 成立,但 in_region == False(比如猫识别到了,但刚好还没进区域),就不会设置 last_active_cat


✅ 修复方案:只要识别成功就更新!

应该改为:

if cat_name != "Unknown":self.last_active_cat = cat_name

🔄 替换以下原有逻辑:

self.last_active_cat = cat_name  # ✅ 设置活跃猫名

✅ 新写法:

if cat_name != "Unknown":self.last_active_cat = cat_name

💡 进一步增强建议:

为了更健壮,甚至可以检查是否是同一只正在如厕的猫:

if cat_name != "Unknown":if self.sessions[cat_name]["status"] == "ENTERED":self.last_active_cat = cat_name

🔧 小结

现在只需将:

self.last_active_cat = cat_name

替换为:

if cat_name != "Unknown":self.last_active_cat = cat_name

就能保证只要识别到了,就记住这个猫 ✅

下面是修复后的 ✅【完整版本 CatSessionTracker.py】,修正了 last_active_cat 始终为 None 的问题。


✅ 完整代码(自动追踪最近如厕猫,识别失败也能记录)

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.last_active_cat = None  # ✅ 自动记录最近如厕中的猫名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":self.last_active_cat = cat_name# 🟡 如果识别失败,检查是否有猫可能离开了if cat_name == "Unknown":if self.last_active_cat:name = self.last_active_catsession = self.sessions[name]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"] = nowself.last_active_cat = Nonereturn 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":duration = now - session["entry_time"]if duration >= self.min_duration:print(f"🔴 猫 {cat_name} 离开,记录如厕 {int(duration)} 秒")session["status"] = "IDLE"session["last_exit_time"] = nowexit_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"],now,session.get("enter_img", ""),exit_path,method,score)else:print(f"⚠️ 猫 {cat_name} 离开时间太短,未记录")session["status"] = "IDLE"session["last_exit_time"] = nowself.last_active_cat = Nonereturn annotated

✅ 更新说明

修改点效果
✅ last_active_cat 追踪始终记住最后识别成功的猫
✅ 离开判断逻辑优化即使识别失败,也能判断谁离开了
✅ 状态重置离开后自动恢复 IDLE 状态,防止重复记录

toilet_logger.py 已更新:

  • 增加了 log_enter(...) 方法:当猫咪进入如厕区域时立即记录一条简洁日志

  • 日志默认写入文件:enter_log.txt


✅ 下一步

更新 CatSessionTracker,在猫进入区域时:

  1. 保存截图 ✅

  2. 立即调用 logger.log_enter(...),记录猫名、时间、识别方式、评分等

现在更新 CatSessionTracker 来调用这个方法,当然,包含进入区域时立即调用 log_enter() 的功能,同时保留完整如厕记录逻辑:


session_tracker.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_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":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:session["status"] = "ENTERED"session["entry_time"] = nowsession["method"] = methodsession["score"] = scoreenter_img = f"{cat_name}_enter_{int(now)}.jpg"cv2.imwrite(os.path.join(RECORDS_DIR, enter_img), frame)enter_path = os.path.join("static/records", enter_img)session["enter_img"] = enter_path# ✅ 进入区域时立即记录self.logger.log_enter(cat_name, now, enter_path, method, score)else:if session["status"] == "ENTERED":session["status"] = "IDLE"exit_time = nowduration = exit_time - session["entry_time"]session["last_exit_time"] = exit_timeif duration >= self.min_duration:exit_img = f"{cat_name}_exit_{int(now)}.jpg"cv2.imwrite(os.path.join(RECORDS_DIR, exit_img), frame)exit_path = os.path.join("static/records", exit_img)self.logger.log(cat_name,session["entry_time"],exit_time,session.get("enter_img", ""),exit_path,method=session.get("method", "classifier"),score=session.get("score", 0.0))print(f"✅ 记录如厕:{cat_name}, 时长 {int(duration)} 秒")else:print(f"⚠️ 猫 {cat_name} 如厕时长过短({int(duration)} 秒),未记录")return annotated

🔧 注意事项

  • result 必须为 5 元组:(annotated_frame, cat_name, in_region, method, score)

  • log_enter() 会写入 enter_log.txt,可以按需查看或导出

result 怎么输入?

为了支持 CatSessionTracker.update() 接收 5 元组的 result

annotated, cat_name, in_region, method, score

需要在检测模块(如 inference.pydetector.py)中处理每帧图像时,把检测 + 识别的结果打包好 传入 CatSessionTracker


✅ 示例(修改后的推理输出结构)

假设有如下检测函数:

def process_frame(self, frame):results = self.yolo_model.predict(frame, verbose=False)boxes = results[0].boxesannotated = frame.copy()detected_cat = Nonecat_name = "Unknown"method = "matcher"score = 0.0in_region = Falsefor box in boxes:if int(box.cls[0]) != 15:  # 只处理cat类

相关文章:

  • 计算机组成原理:指令系统
  • 10前端项目----商品详情页/滚轮行为
  • Java多线程同步有哪些方法?
  • top 命令里面可以看到进程哪些状态?
  • java排序算法-计数排序
  • 非序列实现MEMS聚焦功能
  • 【Redis】hash类型
  • day37图像处理OpenCV
  • Huffman(哈夫曼)解/压缩算法实现
  • 高职人工智能技术应用专业(计算机视觉方向)实训室解决方案
  • 蜜罐管理和数据收集服务器:Modern Honey Network (MHN)
  • Linux 内核网络协议栈中 inet_stream_ops 与 tcp_prot 的深度解析
  • Python----深度学习(基于深度学习Pytroch簇分类,圆环分类,月牙分类)
  • uniapp 仿企微左边公司切换页
  • 第11章 面向分类任务的表示模型微调
  • 同步定时器的用户数要和线程组保持一致,否则jmeter会出现接口不执行’stop‘和‘×’的情况
  • MySQL元数据库完全指南:探秘数据背后的数据
  • Axure PR 9 中继器 标签
  • MTKAndroid13-Launcher3 屏蔽部分app不让显示
  • 如何让 HTML 文件嵌入另一个 HTML 文件:详解与实践
  • “上海-日喀则”直飞航线正式通航,将于5月1日开启首航
  • 广州海关原党委委员、副关长刘小威被开除党籍
  • 暴涨96%!一季度“中国游中国购”持续升温,还有更多利好
  • 杭州打造商业航天全产业链,请看《浪尖周报》第22期
  • 旧衣服旧纸箱不舍得扔?可能是因为“囤物障碍”
  • 人民日报读者点题:规范涉企执法,怎样防止问题反弹、提振企业信心?