import logging import subprocess from PIL import Image import os import time import cv2 import numpy as np import uiautomator2 as u2 logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') class ScreenshotStitcher: def __init__(self, device_serial, adb_path): self.device_serial = device_serial self.adb = adb_path self.d = u2.connect(device_serial) self.screen_width = 0 self.screen_height = 0 def check_adb_connection(self): try: output = subprocess.check_output([self.adb, '-s', self.device_serial, 'devices']).decode('utf-8') if self.device_serial in output and 'device' in output: logging.info(f"ADB 设备 {self.device_serial} 连接正常") return True logging.error(f"ADB 设备 {self.device_serial} 连接异常") return False except FileNotFoundError: logging.error("ADB 未找到,请确保已安装并配置 ADB") return False def adb_shell(self, command): try: output = subprocess.check_output( [self.adb, '-s', self.device_serial, 'shell', command], stderr=subprocess.STDOUT ).decode('utf-8') return output except subprocess.CalledProcessError as e: logging.error(f"ADB 命令执行失败:{e.output.decode('utf-8')}") raise def get_screen_size(self): output = self.adb_shell('wm size') if "Physical size: " in output: size = output.split('Physical size: ')[1].strip().split('x') self.screen_width, self.screen_height = int(size[0]), int(size[1]) elif "Size: " in output: size = output.split('Size: ')[1].strip().split('x') self.screen_width, self.screen_height = int(size[0]), int(size[1]) else: raise ValueError("无法获取屏幕尺寸") def get_screenshot(self, filename): self.adb_shell(f'screencap -p /sdcard/{filename}') subprocess.call([self.adb, '-s', self.device_serial, 'pull', f'/sdcard/{filename}', filename]) self.adb_shell(f'rm /sdcard/{filename}') def merge_screenshots_vertical(self, screenshots): images = [np.array(img) for img in screenshots] max_width = max(img.shape[1] for img in images) aligned_images = [] for img in images: if img.shape[1] != max_width: img = cv2.resize(img, (max_width, int(img.shape[0] * max_width / img.shape[1]))) aligned_images.append(img) return cv2.vconcat(aligned_images) def is_bottom_reached(self, last_screenshot, current_screenshot): """检查是否到达底部""" # 将图片转换为numpy数组 last_img = np.array(last_screenshot) current_img = np.array(current_screenshot) # 计算最后一部分的相似度 last_portion = last_img[-100:, :] current_portion = current_img[-100:, :] # 使用结构相似性指数(SSIM)比较 similarity = cv2.matchTemplate(last_portion, current_portion, cv2.TM_CCOEFF_NORMED) return similarity.max() > 0.95 def scroll_to_top(self): """滚动到页面顶部""" try: # 多次向上滑动以确保到达顶部 for _ in range(3): # 从屏幕底部往上滑 self.adb_shell( f'input swipe {self.screen_width // 2} {self.screen_height - 200} ' f'{self.screen_width // 2} {200} 800' ) time.sleep(1) logging.info("已滚动到页面顶部") return True except subprocess.CalledProcessError as e: logging.error(f"滚动到顶部失败:{e}") return False def images_are_different(self, img1, img2, threshold=0.95): """检查两张图片是否不同""" # 转换为OpenCV格式 img1_cv = cv2.cvtColor(np.array(img1), cv2.COLOR_RGB2BGR) img2_cv = cv2.cvtColor(np.array(img2), cv2.COLOR_RGB2BGR) # 计算相似度 result = cv2.matchTemplate(img1_cv, img2_cv, cv2.TM_CCOEFF_NORMED) similarity = np.max(result) return similarity < threshold def capture_long_screenshot(self, output_filename='full_screenshot.png', max_screenshots=10): """改进的长截图函数""" if not self.check_adb_connection(): return False self.get_screen_size() screenshots = [] # 第一次截图 self.get_screenshot('temp_screenshot_0.png') last_screenshot = Image.open('temp_screenshot_0.png') screenshots.append(last_screenshot) # 循环滚动和截图 for i in range(1, max_screenshots): try: # 从屏幕底部向上滑动 self.adb_shell( f'input swipe {self.screen_width // 2} {self.screen_height - 200} ' f'{self.screen_width // 2} {200} 800' ) time.sleep(1.5) # 等待滚动完成 # 截取当前页面 self.get_screenshot(f'temp_screenshot_{i}.png') current_screenshot = Image.open(f'temp_screenshot_{i}.png') # 只有当图片不同时才添加 if self.images_are_different(last_screenshot, current_screenshot): screenshots.append(current_screenshot) last_screenshot = current_screenshot else: logging.info("检测到重复页面,停止截图") break except subprocess.CalledProcessError: break try: if screenshots: result = self.merge_screenshots_vertical(screenshots) cv2.imwrite(output_filename, result) logging.info(f"长截图已保存至 {output_filename}") except Exception as e: logging.error(f"截图拼接失败:{e}") return False finally: # 清理临时文件 for i in range(max_screenshots): temp_file = f'temp_screenshot_{i}.png' if os.path.exists(temp_file): os.remove(temp_file) return True def main(): # 配置参数 adb_path = '/home/fzxs/Android/Sdk/platform-tools/adb' # 替换为实际的ADB路径 device_serial = '127.0.0.1:6555' # 替换为实际的设备序列号 # 创建截图工具实例 stitcher = ScreenshotStitcher(device_serial, adb_path) # 执行长截图 stitcher.capture_long_screenshot('long_screenshot.png') if __name__ == '__main__': main()