修复微信自身消息未跳过(自回复循环) + QQ调试截图被清理顶掉

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 <senior-developer@experts>
This commit is contained in:
youpaishidifu
2026-07-30 00:39:50 +08:00
co-authored by Senior Developer
parent 01d0604f11
commit 62928d81eb
+59 -18
View File
@@ -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