'commit'
This commit is contained in:
186
WeiXin/WxUtil.py
186
WeiXin/WxUtil.py
@@ -57,6 +57,12 @@ def find_input_box_center(image_path):
|
||||
# 1. 宽度: 屏幕宽度的 50% - 98% (通常输入框很长)
|
||||
# 2. 高度: 30px - 底部区域的 90%
|
||||
if width * 0.5 < w < width * 0.98 and 30 < h < bottom_h * 0.9:
|
||||
# 3. 增加水平居中检查:中心点X坐标必须在屏幕水平中心 10% 范围内
|
||||
# 这能有效排除左右侧的气泡或按钮
|
||||
cnt_center_x = x + w // 2
|
||||
if abs(cnt_center_x - width // 2) > width * 0.1:
|
||||
continue
|
||||
|
||||
# 计算中心点 Y 坐标相对于全图
|
||||
global_y = crop_y_start + y + h // 2
|
||||
|
||||
@@ -77,68 +83,38 @@ def find_input_box_center(image_path):
|
||||
candidates.sort(key=lambda c: c['global_y'], reverse=True)
|
||||
|
||||
# 检查最靠下的候选框是否合理
|
||||
# 正常情况下输入框应该在屏幕底部 92% - 98% 之间
|
||||
# 如果最靠下的候选框 Y < 92%,说明可能识别错了 (可能是倒数第二条消息)
|
||||
# 严格限制:必须在屏幕底部 94% - 99% 之间 (有效排除最后一条消息)
|
||||
for cand in candidates:
|
||||
if cand['global_y'] > height * 0.92:
|
||||
if cand['global_y'] > height * 0.94:
|
||||
best_candidate = cand
|
||||
break
|
||||
|
||||
# 如果没有找到足够靠下的,但有候选框,尝试放宽条件或者使用最靠下的那个
|
||||
if not best_candidate and candidates:
|
||||
logger.warning("未找到 >92% 高度的输入框,尝试使用最靠下的候选框")
|
||||
best_candidate = candidates[0]
|
||||
|
||||
# 如果没有找到符合 >94% 条件的,视为未找到,直接使用几何兜底
|
||||
if not best_candidate:
|
||||
logger.warning(f"找到 {len(candidates)} 个候选框,但没有一个满足 Y > 94% (最高候选 Y={candidates[0]['global_y']/height:.2%})")
|
||||
|
||||
if best_candidate:
|
||||
x, y, w, h = best_candidate['x'], best_candidate['y'], best_candidate['w'], best_candidate['h']
|
||||
center_x = x + w // 2
|
||||
center_y = crop_y_start + y + h // 2
|
||||
logger.info(f"找到输入框(CV-Canny): ({center_x}, {center_y}), 尺寸: {w}x{h}")
|
||||
logger.info(f"找到输入框(CV-Canny): ({center_x}, {center_y}), 尺寸: {w}x{h}, 位置: {center_y/height:.2%}")
|
||||
return (center_x, center_y), (x, crop_y_start + y, w, h)
|
||||
|
||||
# 策略2: 自适应阈值 (原有逻辑作为备份)
|
||||
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
|
||||
cv2.THRESH_BINARY_INV, 11, 2)
|
||||
contours_thresh, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
candidates_thresh = []
|
||||
for cnt in contours_thresh:
|
||||
x, y, w, h = cv2.boundingRect(cnt)
|
||||
if width * 0.4 < w < width * 0.95 and 20 < h < bottom_h * 0.8:
|
||||
global_y = crop_y_start + y + h // 2
|
||||
if global_y > height * 0.99:
|
||||
continue
|
||||
candidates_thresh.append({
|
||||
'x': x, 'y': y, 'w': w, 'h': h,
|
||||
'global_y': global_y
|
||||
})
|
||||
|
||||
if candidates_thresh:
|
||||
candidates_thresh.sort(key=lambda c: c['global_y'], reverse=True)
|
||||
best = candidates_thresh[0]
|
||||
# 同样应用 > 92% 规则
|
||||
if best['global_y'] > height * 0.92:
|
||||
x, y, w, h = best['x'], best['y'], best['w'], best['h']
|
||||
center_x = x + w // 2
|
||||
center_y = crop_y_start + y + h // 2
|
||||
logger.info(f"找到输入框(CV-Adaptive): ({center_x}, {center_y}), 尺寸: {w}x{h}")
|
||||
return (center_x, center_y), (x, crop_y_start + y, w, h)
|
||||
|
||||
# 兜底策略:使用更靠下的默认坐标 (96% - 微信输入框通常很靠下)
|
||||
# 之前的 94% 可能还是偏高
|
||||
logger.warning("未找到明显输入框轮廓,使用更靠下的默认坐标 (96.5%)")
|
||||
default_y = int(height * 0.965)
|
||||
# 策略2: 如果 CV 失败,使用几何兜底 (屏幕底部中间区域)
|
||||
logger.warning("CV 识别输入框失败,使用几何兜底策略 (Bottom Middle)")
|
||||
# 假设输入框位于屏幕底部 97% 处,宽度为屏幕的 90%
|
||||
fallback_w = int(width * 0.9)
|
||||
fallback_h = int(bottom_h * 0.4) # 估算高度
|
||||
fallback_x = (width - fallback_w) // 2
|
||||
# 估算中心点 Y: 屏幕高度的 97%
|
||||
center_x = width // 2
|
||||
center_y = int(height * 0.97)
|
||||
|
||||
# 构造假想框
|
||||
fake_w = int(width * 0.7)
|
||||
fake_h = int(height * 0.08) # 稍微加高一点,视觉上更像
|
||||
fake_x = (width - fake_w) // 2
|
||||
fake_y = default_y - fake_h // 2
|
||||
# 构造一个虚拟的 rect_box
|
||||
fallback_y_rel = center_y - crop_y_start - fallback_h // 2
|
||||
|
||||
return (center_x, default_y), (fake_x, fake_y, fake_w, fake_h)
|
||||
return (center_x, center_y), (fallback_x, crop_y_start + fallback_y_rel, fallback_w, fallback_h)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"查找输入框失败: {e}")
|
||||
return None, None
|
||||
@@ -250,14 +226,20 @@ def take_debug_screenshot(d, step_name):
|
||||
logger.error(f"截图失败 ({step_name}): {e}")
|
||||
return None
|
||||
|
||||
def perform_input_action(coords, text):
|
||||
def perform_input_action(d, coords, text, auto_send=True):
|
||||
"""
|
||||
点击坐标并输入文本
|
||||
@param d: uiautomator2 device object
|
||||
@param coords: (x, y) 坐标
|
||||
@param text: 输入文本
|
||||
@param auto_send: 是否自动点击发送,默认为 True
|
||||
"""
|
||||
# 优先尝试使用 uiautomator2 的原生控件查找 (更稳健)
|
||||
native_success = False
|
||||
try:
|
||||
d = u2.connect()
|
||||
# 如果没有传入 d,尝试连接
|
||||
if d is None:
|
||||
d = u2.connect()
|
||||
# 查找 EditText 控件
|
||||
input_elem = d(className="android.widget.EditText")
|
||||
|
||||
@@ -300,35 +282,39 @@ def perform_input_action(coords, text):
|
||||
|
||||
# 尝试发送回车键
|
||||
time.sleep(0.5)
|
||||
d.press("enter")
|
||||
if auto_send:
|
||||
d.press("enter")
|
||||
|
||||
# 尝试点击发送按钮
|
||||
try:
|
||||
if d(text="发送").exists:
|
||||
d(text="发送").click()
|
||||
logger.info("已点击 '发送' 按钮 (Native Text)")
|
||||
take_debug_screenshot(d, "native_04_after_send_click_text")
|
||||
else:
|
||||
logger.info("未找到 '发送' 文本控件,尝试图像识别...")
|
||||
send_btn_coords = find_send_button(d)
|
||||
if send_btn_coords:
|
||||
sx, sy = send_btn_coords
|
||||
d.click(sx, sy)
|
||||
logger.info(f"已点击 '发送' 按钮 (Image Rec): {sx}, {sy}")
|
||||
take_debug_screenshot(d, "native_04_after_send_click_image")
|
||||
if auto_send:
|
||||
try:
|
||||
if d(text="发送").exists:
|
||||
d(text="发送").click()
|
||||
logger.info("已点击 '发送' 按钮 (Native Text)")
|
||||
take_debug_screenshot(d, "native_04_after_send_click_text")
|
||||
else:
|
||||
width, height = d.window_size()
|
||||
fallback_x = int(width * 0.9)
|
||||
fallback_y = int(height * 0.965)
|
||||
logger.info(f"未识别到发送按钮,尝试盲点右下角: {fallback_x}, {fallback_y}")
|
||||
d.click(fallback_x, fallback_y)
|
||||
take_debug_screenshot(d, "native_04_after_send_click_fallback")
|
||||
except Exception as e:
|
||||
logger.error(f"点击发送按钮失败: {e}")
|
||||
logger.info("未找到 '发送' 文本控件,尝试图像识别...")
|
||||
send_btn_coords = find_send_button(d)
|
||||
if send_btn_coords:
|
||||
sx, sy = send_btn_coords
|
||||
d.click(sx, sy)
|
||||
logger.info(f"已点击 '发送' 按钮 (Image Rec): {sx}, {sy}")
|
||||
take_debug_screenshot(d, "native_04_after_send_click_image")
|
||||
else:
|
||||
width, height = d.window_size()
|
||||
fallback_x = int(width * 0.9)
|
||||
fallback_y = int(height * 0.965)
|
||||
logger.info(f"未识别到发送按钮,尝试盲点右下角: {fallback_x}, {fallback_y}")
|
||||
d.click(fallback_x, fallback_y)
|
||||
take_debug_screenshot(d, "native_04_after_send_click_fallback")
|
||||
except Exception as e:
|
||||
logger.error(f"点击发送按钮失败: {e}")
|
||||
else:
|
||||
logger.info("auto_send=False, 跳过发送动作")
|
||||
|
||||
logger.info("输入完成 (Native)")
|
||||
native_success = True
|
||||
return
|
||||
return True
|
||||
|
||||
else:
|
||||
logger.warning("未找到输入框元素 (Native),转入坐标点击模式...")
|
||||
@@ -337,7 +323,7 @@ def perform_input_action(coords, text):
|
||||
logger.warning(f"原生控件操作失败,降级为坐标点击: {e}")
|
||||
|
||||
if native_success:
|
||||
return
|
||||
return True
|
||||
|
||||
# 降级方案:使用坐标点击
|
||||
if not coords:
|
||||
@@ -396,36 +382,42 @@ def perform_input_action(coords, text):
|
||||
take_debug_screenshot(d, "coord_03_after_input_text")
|
||||
|
||||
time.sleep(0.5)
|
||||
d.press("enter")
|
||||
|
||||
# 尝试查找发送按钮并点击
|
||||
try:
|
||||
if d(text="发送").exists:
|
||||
d(text="发送").click()
|
||||
logger.info("已点击 '发送' 按钮 (Native Text)")
|
||||
take_debug_screenshot(d, "coord_04_after_click_send_native")
|
||||
else:
|
||||
logger.info("未找到 '发送' 文本控件,尝试图像识别...")
|
||||
send_btn_coords = find_send_button(d)
|
||||
if send_btn_coords:
|
||||
sx, sy = send_btn_coords
|
||||
d.click(sx, sy)
|
||||
logger.info(f"已点击 '发送' 按钮 (Image Rec): {sx}, {sy}")
|
||||
take_debug_screenshot(d, "coord_04_after_click_send_image")
|
||||
if auto_send:
|
||||
d.press("enter")
|
||||
|
||||
# 尝试查找发送按钮并点击
|
||||
try:
|
||||
if d(text="发送").exists:
|
||||
d(text="发送").click()
|
||||
logger.info("已点击 '发送' 按钮 (Native Text)")
|
||||
take_debug_screenshot(d, "coord_04_after_click_send_native")
|
||||
else:
|
||||
width, height = d.window_size()
|
||||
fallback_x = int(width * 0.9)
|
||||
fallback_y = int(height * 0.965)
|
||||
logger.info(f"未识别到发送按钮,尝试盲点右下角: {fallback_x}, {fallback_y}")
|
||||
d.click(fallback_x, fallback_y)
|
||||
take_debug_screenshot(d, "coord_04_after_click_send_fallback")
|
||||
except Exception as e:
|
||||
logger.error(f"点击发送按钮失败: {e}")
|
||||
logger.info("未找到 '发送' 文本控件,尝试图像识别...")
|
||||
send_btn_coords = find_send_button(d)
|
||||
if send_btn_coords:
|
||||
sx, sy = send_btn_coords
|
||||
d.click(sx, sy)
|
||||
logger.info(f"已点击 '发送' 按钮 (Image Rec): {sx}, {sy}")
|
||||
take_debug_screenshot(d, "coord_04_after_click_send_image")
|
||||
else:
|
||||
width, height = d.window_size()
|
||||
fallback_x = int(width * 0.9)
|
||||
fallback_y = int(height * 0.965)
|
||||
logger.info(f"未识别到发送按钮,尝试盲点右下角: {fallback_x}, {fallback_y}")
|
||||
d.click(fallback_x, fallback_y)
|
||||
take_debug_screenshot(d, "coord_04_after_click_send_fallback")
|
||||
except Exception as e:
|
||||
logger.error(f"点击发送按钮失败: {e}")
|
||||
else:
|
||||
logger.info("auto_send=False, 跳过坐标模式下的发送动作")
|
||||
|
||||
logger.info("输入完成 (Coordinate)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"自动化操作失败: {e}")
|
||||
return False
|
||||
|
||||
def analyze_chat_image(image_path, output_path, target_name="对方"):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user