自动化截图对比是很多场景下都需要用到的功能,比如UI自动化测试验证页面渲染是否正确、监控系统检测页面是否出现异常变化等。借助Python的PIL库,我们可以快速实现图像差异检测的能力,完成两张截图的对比工作。

实现原理概述
基于PIL的图像差异检测核心思路是:先将两张待对比的图像转换为相同的尺寸和色彩模式,再逐像素对比两个图像对应位置的像素值,统计差异像素的数量和位置,最后根据需求输出差异结果,比如生成标记了差异区域的对比图、返回差异像素占比等。
环境准备
首先需要安装PIL库,现在常用的PIL分支是Pillow,安装命令如下:
pip install pillow
基础截图对比实现
1. 获取截图
如果是对比本地已有的两张截图,可以直接读取;如果需要实时截图,可以结合pyautogui等库实现,这里先演示读取本地图像的场景。以下代码实现两张图像的读取和基础对比:
from PIL import Image
def load_image(image_path):
# 打开图像并转换为RGB模式,避免不同色彩模式导致对比异常
img = Image.open(image_path).convert("RGB")
return img
# 读取两张待对比的截图
img1 = load_image("./screenshot1.png")
img2 = load_image("./screenshot2.png")
# 确保两张图像尺寸一致,不一致则调整第二张图像尺寸和第一张相同
if img1.size != img2.size:
img2 = img2.resize(img1.size)
# 获取图像尺寸
width, height = img1.size
2. 逐像素对比计算差异
接下来逐像素对比两个图像的像素值,统计差异像素的数量和位置:
def compare_images(img1, img2, diff_threshold=30):
width, height = img1.size
diff_pixels = [] # 存储差异像素的坐标
total_diff = 0 # 差异像素总数
# 遍历所有像素
for x in range(width):
for y in range(height):
pixel1 = img1.getpixel((x, y))
pixel2 = img2.getpixel((x, y))
# 计算RGB三个通道的差值总和
diff_sum = abs(pixel1[0] - pixel2[0]) + abs(pixel1[1] - pixel2[1]) + abs(pixel1[2] - pixel2[2])
# 差值超过阈值则判定为差异像素
if diff_sum > diff_threshold:
diff_pixels.append((x, y))
total_diff += 1
return diff_pixels, total_diff
# 执行对比,阈值设为30,可根据实际场景调整
diff_pixels, total_diff = compare_images(img1, img2, diff_threshold=30)
print(f"差异像素总数:{total_diff}")
print(f"差异像素占比:{total_diff / (width * height) * 100:.2f}%")
3. 生成差异标记图
为了更直观地看到差异位置,可以生成一张标记了差异区域的图像,将差异像素标记为红色:
def generate_diff_image(img1, diff_pixels):
# 复制第一张图像作为基础
diff_img = img1.copy()
# 将差异像素标记为红色
for x, y in diff_pixels:
diff_img.putpixel((x, y), (255, 0, 0))
return diff_img
# 生成差异图并保存
diff_img = generate_diff_image(img1, diff_pixels)
diff_img.save("./diff_result.png")
print("差异标记图已保存为diff_result.png")
优化方案
1. 提升对比效率
上面的逐像素循环方式在图像尺寸较大时效率较低,可以借助numpy库将图像转换为数组后批量计算,提升运行速度:
import numpy as np
from PIL import Image
def fast_compare_images(img1, img2, diff_threshold=30):
# 转换为numpy数组
arr1 = np.array(img1)
arr2 = np.array(img2)
# 计算像素差值,取绝对值后求和
diff_arr = np.abs(arr1 - arr2)
diff_sum = np.sum(diff_arr, axis=2)
# 找到差异像素的位置
diff_mask = diff_sum > diff_threshold
total_diff = np.sum(diff_mask)
diff_pixels = list(zip(*np.where(diff_mask)[::-1])) # 转换为(x,y)坐标格式
return diff_pixels, total_diff
# 使用优化后的方法对比
diff_pixels_fast, total_diff_fast = fast_compare_images(img1, img2, diff_threshold=30)
print(f"优化后差异像素总数:{total_diff_fast}")
2. 忽略指定区域差异
如果截图中有一些固定变化的区域,比如时间戳、动态广告位,可以在对比前将这些区域屏蔽,只对比有效区域。以下代码实现指定区域忽略:
def compare_with_ignore(img1, img2, ignore_areas, diff_threshold=30):
width, height = img1.size
diff_pixels = []
total_diff = 0
for x in range(width):
for y in range(height):
# 判断当前像素是否在忽略区域内
is_ignored = False
for (start_x, start_y, end_x, end_y) in ignore_areas:
if start_x <= x <= end_x and start_y <= y <= end_y:
is_ignored = True
break
if is_ignored:
continue
# 正常对比逻辑
pixel1 = img1.getpixel((x, y))
pixel2 = img2.getpixel((x, y))
diff_sum = abs(pixel1[0] - pixel2[0]) + abs(pixel1[1] - pixel2[1]) + abs(pixel1[2] - pixel2[2])
if diff_sum > diff_threshold:
diff_pixels.append((x, y))
total_diff += 1
return diff_pixels, total_diff
# 定义要忽略的区域,格式为(起始x,起始y,结束x,结束y)
ignore_areas = [(100, 50, 300, 80)] # 忽略左上角时间戳区域
diff_pixels_ignore, total_diff_ignore = compare_with_ignore(img1, img2, ignore_areas, diff_threshold=30)
print(f"忽略指定区域后差异像素总数:{total_diff_ignore}")
常见问题说明
- 如果两张截图尺寸不一致,一定要先统一尺寸再对比,否则会出现索引错误或者对比结果无意义。
- 阈值
diff_threshold需要根据实际场景调整,图像压缩、抗锯齿等效果可能会导致正常像素有微小差异,阈值设置过低会误判,过高会漏判。 - 如果截图包含透明通道,建议先转换为RGB模式,避免透明通道对对比结果产生影响。