From 62928d81eb53d81d6e2b80f217d323196de0f628 Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Thu, 30 Jul 2026 00:39:50 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BE=AE=E4=BF=A1=E8=87=AA?= =?UTF-8?q?=E8=BA=AB=E6=B6=88=E6=81=AF=E6=9C=AA=E8=B7=B3=E8=BF=87(?= =?UTF-8?q?=E8=87=AA=E5=9B=9E=E5=A4=8D=E5=BE=AA=E7=8E=AF)=20+=20QQ?= =?UTF-8?q?=E8=B0=83=E8=AF=95=E6=88=AA=E5=9B=BE=E8=A2=AB=E6=B8=85=E7=90=86?= =?UTF-8?q?=E9=A1=B6=E6=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 微信颜色门禁误判根因:聊天浅灰背景(≈237)原阈值>225被算作"对方白气泡", 且绿色自身气泡上的白色文字也通过白像素检测,导致门禁永远不跳过→一直OCR→自回复循环。 改为:白像素须≥245(纯白,排除浅灰背景),且四邻域≥3个同为纯白才计为"对方气泡", 从而正确排除绿/蓝自身气泡上的白色文字。合成图验证:仅自身绿泡→跳过、对方白泡→OCR、 空白灰屏→跳过、绿底白字→跳过。 2. QQ调试截图"没保存"根因:_clean_old_ocr_debug 用 QQ+微信共享6张上限池, 微信轮询频率远高于QQ,其清理调用把刚保存的QQ截图顶掉。改为按前缀分别保留 (ocr_debug_qq_ / ocr_debug_wx_ 各6张),互不挤占。 3. 截图保存硬化:保存后校验文件存在并INFO记录;异常时WARNING输出真实路径(原输出的是glob); 显式指定 format=PNG。 Co-Authored-By: Senior Developer --- monitor.py | 77 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/monitor.py b/monitor.py index 739a38b..da6439d 100644 --- a/monitor.py +++ b/monitor.py @@ -1269,11 +1269,14 @@ class MessageMonitor: script_dir = os.path.dirname(os.path.abspath(__file__)) ts = datetime.now().strftime('%Y%m%d_%H%M%S.%f')[:19] _dbg_path = os.path.join(script_dir, f"ocr_debug_qq_{ts}.png") - im.save(_dbg_path) - logger.debug(f"[qq] 调试截图: {_dbg_path}") - _clean_old_ocr_debug(script_dir, keep=6) + im.save(_dbg_path, format="PNG") + _clean_old_ocr_debug(script_dir, keep=6, prefix="ocr_debug_qq_") + if os.path.exists(_dbg_path): + logger.info(f"[qq] 调试截图已保存: {_dbg_path}") + else: + logger.warning(f"[qq] 调试截图保存后未找到文件: {_dbg_path}") except Exception as e: - logger.warning(f"[qq] 调试截图保存失败: {e} (路径: {os.path.join(script_dir, 'ocr_debug_qq_*.png')})") + logger.warning(f"[qq] 调试截图保存失败: {e} (路径: {_dbg_path})") # ⭐ 颜色像素检测:如果截图中只有 bot 蓝色气泡,无白色用户消息,跳过 if not self._has_user_colors(im, "qq"): @@ -1373,12 +1376,14 @@ class MessageMonitor: script_dir = os.path.dirname(os.path.abspath(__file__)) ts = datetime.now().strftime('%Y%m%d_%H%M%S.%f')[:19] _dbg_path = os.path.join(script_dir, f"ocr_debug_wx_{ts}.png") - im.save(_dbg_path) - logger.debug(f"[wechat] 调试截图: {_dbg_path}") - # 清理 ocr_debug_* 调试图(unified pool) - _clean_old_ocr_debug(script_dir, keep=6) + im.save(_dbg_path, format="PNG") + _clean_old_ocr_debug(script_dir, keep=6, prefix="ocr_debug_wx_") + if os.path.exists(_dbg_path): + logger.info(f"[wechat] 调试截图已保存: {_dbg_path}") + else: + logger.warning(f"[wechat] 调试截图保存后未找到文件: {_dbg_path}") except Exception as e: - logger.warning(f"[wechat] 调试截图保存失败: {e} (路径: {os.path.join(script_dir, 'ocr_debug_wx_*.png')})") + logger.warning(f"[wechat] 调试截图保存失败: {e} (路径: {_dbg_path})") # ⭐ 颜色像素检测:如果截图中只有自身绿色气泡,无对方白色消息,跳过 if not self._has_user_colors(im, "wechat"): @@ -2196,6 +2201,27 @@ class MessageMonitor: bits.append('1' if left > right else '0') return ''.join(bits) +def _is_white_bubble_pixel(px, x, y, w, h) -> bool: + """判定 (x,y) 的纯白像素是否属于"白色气泡"(对方消息),而非: + - 聊天浅灰背景(本身不是 >=245 候选) + - 彩色(绿/蓝)自身气泡上的白色文字(邻域为彩色底色) + 规则:四方向(±4px)邻域中 >=3 个也为 near-white(>=245) 才算气泡。 + 白字笔画的邻域多为彩色气泡底色 → 不通过 → 不被误判为对方消息。 + """ + d = 4 + hits = 0 + for dx, dy in ((d, 0), (-d, 0), (0, d), (0, -d)): + nx, ny = x + dx, y + dy + if 0 <= nx < w and 0 <= ny < h: + try: + rr, gg, bb = px[nx, ny][:3] + if rr >= 245 and gg >= 245 and bb >= 245: + hits += 1 + except Exception: + pass + return hits >= 3 + + @staticmethod def _has_user_colors(im, platform: str) -> bool: """区域颜色检测:判断截图中是否有**对方发来的消息**,决定是否需要OCR。 @@ -2236,15 +2262,21 @@ class MessageMonitor: def analyze(img): p = img.load() bot = other = white = total = 0 - for y in range(0, img.height, step_y): - for x in range(0, img.width, step_x): + iw, ih = img.width, img.height + for y in range(0, ih, step_y): + for x in range(0, iw, step_x): total += 1 try: r, g, b = p[x, y][:3] except: continue - if r > 225 and g > 225 and b > 225: - white += 1 + if r >= 245 and g >= 245 and b >= 245: + # ⚠️ 纯白像素可能是:①对方白色气泡(背景/白字) ②聊天浅灰背景 + # ③绿色/蓝色自身气泡上的白色文字。 + # ②③ 不能算"对方消息"——否则微信自身绿气泡白字被误判为对方 + # → OCR → 自回复循环。必须确认邻域也为大面积白色(气泡)才计入。 + if _is_white_bubble_pixel(p, x, y, iw, ih): + white += 1 elif is_bot(r, g, b): bot += 1 else: @@ -2311,7 +2343,7 @@ class MessageMonitor: for sx in range(0, left_35.width, l35_step_x): try: r, g, b = l35_p[sx, sy][:3] - if r > 225 and g > 225 and b > 225: + if r >= 245 and g >= 245 and b >= 245: rw += 1 except: continue @@ -3676,15 +3708,24 @@ class MessageMonitor: self._cached_hwnd.pop(platform, None) self._cached_pid.pop(platform, None) -def _clean_old_ocr_debug(dir_path: str, keep: int = 20): - """清理 ocr_debug_*.png 文件,只保留最近 keep 张。""" +def _clean_old_ocr_debug(dir_path: str, keep: int = 20, prefix: str = "ocr_debug_"): + """清理 ocr_debug_*.png 调试截图,按 prefix 分别保留最近 keep 张。 + + 分前缀清理:微信轮询频率远高于 QQ,若共享一个 6 张上限池,微信的密集保存 + 会把 QQ 的调试截图顶掉,导致"QQ 截图没保存"。各前缀独立保留 keep 张, + 既控制总量又保证两种截图都能留存可查。 + """ try: - files = [f for f in os.listdir(dir_path) if f.startswith("ocr_debug_") and f.endswith(".png")] + files = [f for f in os.listdir(dir_path) + if f.startswith(prefix) and f.endswith(".png")] if len(files) <= keep: return files.sort() # 按字母序 = 时间序 for f in files[:-keep]: - os.remove(os.path.join(dir_path, f)) + try: + os.remove(os.path.join(dir_path, f)) + except OSError: + pass except Exception: pass