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