Python调用adb截屏的代码效率优化问题咨询
优化ADB循环截屏的内存与效率方案
一、解决单次循环创建Popen的内存开销问题
你最初的代码每次循环都启动新的adb进程,频繁的进程创建与销毁会带来额外的内存波动和性能开销,尤其高频循环时问题更明显。最优方案是保持单个adb进程长连接,复用进程而非每次重启。
优化后的代码(保存帧图片)
import subprocess from time import sleep # 启动单个adb交互进程,维持长连接 adb_process = subprocess.Popen( ['adb', 'shell'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0 # 无缓冲,确保实时读取输出 ) FRAME_COUNT = 0 while True: # 向adb进程发送截屏命令 adb_process.stdin.write(b'screencap -p\n') adb_process.stdin.flush() # 读取截屏的PNG数据(截断adb shell的提示符) png_data = b'' while True: chunk = adb_process.stdout.read(4096) if b'$' in chunk: # 分离PNG数据与shell提示符 png_data += chunk[:chunk.find(b'$')] break png_data += chunk # 保存帧图片 with open(f'./frames/frame_{FRAME_COUNT}.png', 'wb') as file: file.write(png_data) sleep(1) FRAME_COUNT += 1
优势说明
- 仅启动一次
adb进程,避免了循环中进程创建/销毁的资源消耗,内存占用更稳定。 - 复用连接减少了ADB与设备的握手开销,整体效率提升明显。
二、OpenCV版本代码的效率优化
更新后的代码仍存在每次循环重启adb的问题,同时还有可优化的计算环节,以下是针对性改进:
优化后的代码(实时显示+FPS计算)
import subprocess import cv2 import numpy as np from time import time # 启动单个adb交互进程 adb_process = subprocess.Popen( ['adb', 'shell'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0 ) # 预获取屏幕尺寸,提前计算缩放目标尺寸(避免重复运算) # 先执行一次截屏获取初始图像尺寸 adb_process.stdin.write(b'screencap -p\n') adb_process.stdin.flush() init_png = b'' while True: chunk = adb_process.stdout.read(4096) if b'$' in chunk: init_png += chunk[:chunk.find(b'$')] break init_png += chunk init_img = cv2.imdecode(np.frombuffer(init_png, np.uint8), cv2.IMREAD_COLOR) TARGET_WIDTH = int(init_img.shape[1] * 0.45) TARGET_HEIGHT = int(init_img.shape[0] * 0.45) # 滑动平均计算FPS,避免单次波动导致的数值跳变 fps_samples = [] MAX_SAMPLES = 10 while True: t_start = time() # 发送截屏命令 adb_process.stdin.write(b'screencap -p\n') adb_process.stdin.flush() # 读取PNG数据 png_data = b'' while True: chunk = adb_process.stdout.read(4096) if b'$' in chunk: png_data += chunk[:chunk.find(b'$')] break png_data += chunk # 解码图像并缩放 img = cv2.imdecode(np.frombuffer(png_data, np.uint8), cv2.IMREAD_COLOR) # 缩小图像用INTER_AREA插值,效率更高且画质更好 resized_img = cv2.resize(img, (TARGET_WIDTH, TARGET_HEIGHT), interpolation=cv2.INTER_AREA) # 计算滑动平均FPS t_end = time() current_fps = 1 / (t_end - t_start) fps_samples.append(current_fps) if len(fps_samples) > MAX_SAMPLES: fps_samples.pop(0) avg_fps = sum(fps_samples) / len(fps_samples) # 绘制FPS文本 cv2.putText(resized_img, f'FPS: {avg_fps:.2f}', (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) cv2.imshow('Screen Capture', resized_img) # 按q键退出循环 if cv2.waitKey(1) & 0xFF == ord('q'): break # 清理资源 adb_process.stdin.close() adb_process.stdout.close() adb_process.stderr.close() adb_process.terminate() cv2.destroyAllWindows()
核心优化点
- 长连接adb进程:彻底消除每次循环启动adb的开销,这是提升效率的核心。
- 预计算缩放尺寸:避免每次循环重复计算宽高,减少冗余运算。
- 滑动平均FPS:让显示的FPS数值更稳定,避免单次帧耗时波动影响观感。
- 指定缩放插值方法:
INTER_AREA在缩小图像时比默认插值更高效,画质更清晰。 - 资源清理:添加进程终止和窗口销毁逻辑,避免资源泄漏。
额外进阶优化:改用RAW格式截屏
如果不需要PNG的压缩特性,可以用screencap -c获取RAW格式屏幕数据,跳过PNG解压缩步骤,进一步提升解码速度:
# 先获取屏幕分辨率 adb_process.stdin.write(b'wm size\n') adb_process.stdin.flush() size_data = b'' while True: chunk = adb_process.stdout.read(1024) if b'$' in chunk: size_data += chunk[:chunk.find(b'$')] break size_data += chunk # 解析分辨率(示例:"Physical size: 1080x2340") size_str = size_data.decode().strip().split(': ')[1] SCREEN_WIDTH, SCREEN_HEIGHT = map(int, size_str.split('x')) # 后续截屏改用RAW格式 adb_process.stdin.write(b'screencap -c\n') adb_process.stdin.flush() # RAW格式为RGBA,每个像素4字节,读取对应长度的数据 raw_data = adb_process.stdout.read(SCREEN_WIDTH * SCREEN_HEIGHT * 4) # 转换为numpy数组并转成OpenCV支持的BGR格式 img = np.frombuffer(raw_data, np.uint8).reshape((SCREEN_HEIGHT, SCREEN_WIDTH, 4)) img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGR)
RAW格式无压缩,读取和解码速度远快于PNG,能显著提升高分辨率屏幕下的FPS。
内容的提问来源于stack exchange,提问作者Muhammad Ikhwan Perwira
相关产品推荐
相关产品推荐

