Files
aiData/Test/T2_testWeXinChat.py
HuangHai af46512212 'commit'
2026-01-21 14:13:26 +08:00

272 lines
9.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# coding=utf-8
import uiautomator2 as u2
import time
import logging
import sys
import os
import cv2
import numpy as np
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger("TestWeChat")
def analyze_chat_image(image_path, output_path):
"""
识别微信聊天截图中的头像并画框
"""
logger.info(f"正在分析图片: {image_path}")
# 读取图片
# 注意:cv2.imread 不支持中文路径,需要用 np.fromfile 读取
try:
img_data = np.fromfile(image_path, dtype=np.uint8)
img = cv2.imdecode(img_data, cv2.IMREAD_COLOR)
except Exception as e:
logger.error(f"读取图片失败: {e}")
return
if img is None:
logger.error("图片读取为空")
return
height, width = img.shape[:2]
logger.info(f"图片尺寸: {width}x{height}")
# 1. 预处理
# 转为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 使用自适应阈值二值化
thresh = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV, 11, 2)
# 形态学操作:闭运算,填充内部空洞
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
closed = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel)
# 2. 轮廓查找
contours, _ = cv2.findContours(closed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
logger.info(f"检测到轮廓数量: {len(contours)}")
# 收集所有符合条件的头像
avatars = []
# 3. 筛选轮廓
for contour in contours:
# 获取外接矩形
x, y, w, h = cv2.boundingRect(contour)
# 筛选条件优化:
# 1. 形状接近正方形 (放宽宽高比限制: 0.8 ~ 1.2)
aspect_ratio = float(w) / h
# 2. 尺寸适中
# 假设头像宽度在屏幕宽度的 6% 到 15% 之间
min_w = width * 0.06
max_w = width * 0.15
# 3. 位置筛选
# 排除底部输入框区域 (假设底部 10% 为输入区域)
if y > height * 0.9:
continue
# 左侧头像:靠左边 (x < width * 0.18)
# 右侧头像:靠右边 (x > width * 0.82)
is_left = x < width * 0.18
is_right = x > width * 0.82
if 0.8 <= aspect_ratio <= 1.2 and min_w < w < max_w:
if is_left or is_right:
side = "Left" if is_left else "Right"
avatars.append({
'x': x, 'y': y, 'w': w, 'h': h,
'side': side
})
# 按 y 坐标排序
avatars.sort(key=lambda a: a['y'])
logger.info(f"找到有效头像数量: {len(avatars)}")
# 4. 绘制对话内容框 (Green/Red Boxes)
# 策略:按顺序遍历头像,如果发现同侧连续,则视为一组。
# 从当前组的第一个头像上方开始,直到下一个不同侧的头像上方(或底部)。
if avatars:
i = 0
while i < len(avatars):
current_group_start = i
current_side = avatars[i]['side']
# 找到当前组的结束位置 (即下一个不同侧头像的索引)
j = i + 1
while j < len(avatars) and avatars[j]['side'] == current_side:
j += 1
# 当前组范围: avatars[i] ... avatars[j-1]
# 确定绘制区域的 Y 轴范围
# Start Y: 当前组第一个头像的上方 (例如 -10px)
start_y = max(0, avatars[i]['y'] - 10)
# End Y: 下一组第一个头像的上方 (减去较大间距,例如 -30px),或者当前组最后一个头像的底部加上边距
# 为了让框之间有明显间隔,我们采取策略:
# 如果有下一组,End Y = 下一组第一个头像的 y - 30 (留出间隙)
# 如果没有下一组,End Y = 屏幕底部区域上方
if j < len(avatars):
end_y = max(start_y + 10, avatars[j]['y'] - 30)
else:
end_y = int(height * 0.9) # 到底部输入框上方
# 绘制大框
# 左侧 (Left) -> 对方 -> 绿色 (0, 255, 0)
# 右侧 (Right) -> 我 -> 红色 (0, 0, 255)
# 注意 OpenCV 颜色是 BGR
box_color = (0, 255, 0) if current_side == "Left" else (0, 0, 255)
# 绘制矩形 (空心,线宽 5)
# X 轴范围:0 到 width
cv2.rectangle(img, (0, start_y), (width, end_y), box_color, 5)
logger.info(f"绘制内容框: 侧别={current_side}, 范围 Y={start_y} to {end_y}")
# 移动到下一组
i = j
# 5. 绘制头像框 (Blue/Yellow Boxes) - 画在内容框之上
for av in avatars:
x, y, w, h = av['x'], av['y'], av['w'], av['h']
# 左侧:蓝色 (BGR: 255, 0, 0)
# 右侧:黄色 (BGR: 0, 255, 255)
color = (255, 0, 0) if av['side'] == "Left" else (0, 255, 255)
cv2.rectangle(img, (x, y), (x + w, y + h), color, 10)
logger.info(f"绘制头像: 位置=({x},{y}), 侧别={av['side']}")
logger.info(f"共标记了 {len(avatars)} 个头像")
# 6. 保存结果
try:
# cv2.imwrite 不支持中文路径,使用 imencode + tofile
ext = os.path.splitext(output_path)[1]
cv2.imencode(ext, img)[1].tofile(output_path)
logger.info(f"✅ 分析结果已保存至: {output_path}")
except Exception as e:
logger.error(f"保存分析图片失败: {e}")
def main():
logger.info("开始执行微信搜索测试...")
# 连接设备
try:
d = u2.connect()
logger.info(f"设备连接成功: {d.info.get('serial')}")
except Exception as e:
logger.error(f"设备连接失败: {e}")
return
# 1. 启动微信
logger.info("步骤 1: 启动微信...")
d.app_start("com.tencent.mm", stop=True)
# 等待微信启动完成
time.sleep(5)
# 获取屏幕尺寸
w, h = d.window_size()
logger.info(f"屏幕尺寸: {w}x{h}")
# 2. 点击搜索按钮 (参考 Opener.py 的坐标比例: w * 0.84, h * 0.08)
search_x = int(w * 0.84)
search_y = int(h * 0.08)
logger.info(f"步骤 2: 点击搜索按钮 (坐标: {search_x}, {search_y})...")
d.click(search_x, search_y)
time.sleep(2)
# 3. 输入 "糖豆爸爸"
target_name = "糖豆爸爸"
logger.info(f"步骤 3: 输入搜索内容 '{target_name}'...")
try:
# 启用 FastInputIME 以支持中文输入
d.set_input_ime(True)
# 点击搜索框获取焦点 (参考 Opener.py: w * 0.4, h * 0.08)
d.click(int(w * 0.4), int(h * 0.08))
time.sleep(1)
# 输入文字
d.send_keys(target_name)
time.sleep(2)
# 恢复输入法
d.set_input_ime(False)
except Exception as e:
logger.error(f"输入文字失败: {e}")
try:
d(focused=True).set_text(target_name)
except:
pass
# 4. 点击搜索结果
logger.info("步骤 4: 查找并点击搜索结果...")
time.sleep(2)
found = False
# 策略 A: 精确匹配文本
if d(text=target_name).exists:
logger.info(f"找到文本为 '{target_name}' 的元素,点击...")
d(text=target_name).click()
found = True
else:
logger.warning(f"未找到文本为 '{target_name}' 的元素,尝试模糊匹配或坐标点击...")
# 策略 B: 坐标点击 (参考 Opener.py 点击第一个结果: w * 0.5, h * 0.18)
result_x = int(w * 0.5)
result_y = int(h * 0.18)
logger.info(f"尝试点击第一个搜索结果位置 ({result_x}, {result_y})...")
d.click(result_x, result_y)
found = True
if found:
logger.info("✅ 已执行点击操作,应该已进入对话框。")
# 5. 截图保存结果
logger.info("步骤 5: 截图并分析...")
# 等待界面加载稳定
time.sleep(3)
# 创建截图目录
screenshot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "Screenshots")
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
# 生成文件名
timestamp = time.strftime("%Y%m%d_%H%M%S")
filename = f"chat_result_{timestamp}.jpg"
save_path = os.path.join(screenshot_dir, filename)
try:
d.screenshot(save_path)
logger.info(f"✅ 原始截图已保存: {save_path}")
# 6. 分析截图并标记头像
logger.info("步骤 6: 自动标记头像...")
analyzed_filename = f"chat_result_{timestamp}_analyzed.jpg"
analyzed_path = os.path.join(screenshot_dir, analyzed_filename)
analyze_chat_image(save_path, analyzed_path)
except Exception as e:
logger.error(f"❌ 截图或分析失败: {e}")
else:
logger.error("❌ 未能定位到搜索结果,跳过截图。")
logger.info("测试结束。")
if __name__ == "__main__":
main()