这个工具写下来遇到了不少坑,直到现在还没有完全解决,先记录下来吧,后面有机会再修改,或是有心的同学帮忙分析一下为什么?
主要实现以下功能:
1. 在手机上截一张图至桌面.
2. 在手机在连接截多张图片拼接成一张图(按回车后手机屏幕会自动滚动1/2屏,q退出截图并拼接图片.
主要遇到的坑:
1. 多重for循环发现用break只能退出一层for循环(后用函数返回值解决)
2. 图片处理用的是PIL库, 在将两张图进行拼接时,必须先找到两张图相同的行才可以完美拼接。这个过程走了很多路。
a)截的两张图片,我们视觉上看到相同图像的同一行像素点RGB(x, y, z)其实是有一些偏差的(>30均可认为其像素点是相同的), 如果不明白这一点,估计在找原因debug时会很郁闷。
如a, b, c = img1data.getpixel((25, 25))
x, y, z = img2data.getpixel((25,25))
这是截取是相同的两张图的同一个像素点,有可能是有差异的.
for i in range(int(img1_cropfile.size[0]*3/4)):
# a/b/c为img1的RGB value
# x/y/z为img2的RGB value
a0, b0, c0 = img1_cropfile_pixel_list0[i]
a1, b1, c1 = img1_cropfile_pixel_list1[i]
a2, b2, c2 = img1_cropfile_pixel_list2[i]
x0, y0, z0 = img2_cropfile_pixel_list0[i]
x1, y1, z1 = img2_cropfile_pixel_list1[i]
x2, y2, z2 = img2_cropfile_pixel_list2[i]
if abs(a0 - x0) < 20 and abs(b0 - y0) < 20 and abs(c0 - z0) < 20:
line0sum += 1
if abs(a1 - x1) < 20 and abs(b1 - y1) < 20 and abs(c1 - z1) < 20:
line1sum += 1
if abs(a2 - x2) < 20 and abs(b2 - y2) < 20 and abs(c2 - z2) < 20:
line2sum += 1
# print(f'line0sum:{line0sum} line1sum:{line1sum} line2sum:{line2sum}')
if line0sum == line1sum == line2sum == int(img1_cropfile.size[0]*3/4):
print(f'h:{h}')
# img1_cropfile_box = (0, 0, img1_cropfile.size[0], h)
return h
b)比对图片像素找相同的行时,只对比一行是不行的, 要同时比对多行才可以。如同时比对0, 100, 200行,这样才行之有效.
img2_cropfile_pixel_list0 = list()
img2_cropfile_pixel_list1 = list()
img2_cropfile_pixel_list2 = list()
for w in range(int(img2_cropfile.size[0]*3/4)):
img2_cropfile_pixel_list0.append(img2_cropfile.getpixel((w, 0)))
img2_cropfile_pixel_list1.append(img2_cropfile.getpixel((w, 100)))
img2_cropfile_pixel_list2.append(img2_cropfile.getpixel((w, 200)))
c)右侧通常有流动条或一些不变的信息时,比如很多联系人右侧的字母(用于快速定位联系人)会异常图片的像素比对,这时在对比时应该不比对右侧的信息,排除干扰
# 只比对3/4行的内容即可,不用全部对比
for w in range(int(img2_cropfile.size[0]*3/4))
3. 当前只能拼接两张图片,如多张则拼接异常,因为多张图片时找不到相同的行,原因是以下这句传入的img1每次都是一样的,一直找不到原因,希望有心人看到帮忙留言.
# 将所有截图拼接成一个长图
def sewImg(self):
if len(self.imgPathList) == 1:
shutil.move(self.imgPathList[0], getDesktopPath())
return
print('等待长图生成至桌面ing... ...')
self.openImg()
self.findHeadOverlap()
self.findTailOverlap()
img1 = self.imgInfoList[0]
if self.tailoverlapBox == ():
self.iscropTailoverlap = False
for i in range(1, len(self.imgInfoList)):
img2 = self.imgInfoList[i]
# 对于img2是否为最后一张的处理是不一样的,因为当有时底部有重叠部时对于最后一张的处理会比较特殊
if i == len(self.imgInfoList) - 1:
# 感觉没有写错,但是每次传入img1都是self.imgInfoList[0]
img1 = self.newImg(img1, img2, True)
temp = img1
print(f'img1.size: {img1.size}')
else:
img1 = self.newImg(img1, img2, False)
temp = img1
print(f'img1.size: {img1.size}')
img1.save(getDesktopPath() + 'longImage.jpg')
实验图片如下:
完整的脚本如下:
from PIL import Image
import os
import subprocess
import time
import shutil
import random
# 得到PC桌面路径
def getDesktopPath() -> str:
return 'C:\\Users\\' + os.getlogin() + '\\Desktop\\'
class Screenshot(object):
def __init__(self, cmd='1'):
# 用于存储各个截图的位置
self.imgPathList = list()
# 用于存储Image.open()打开截图后的数据
self.imgInfoList = list()
# 用于存储各个截图的像素信息
self.imgDataList = list()
# 要执行的截图类型
self.cmd = cmd
# 图片保存位置的flag
self.save2destFlag = True
# 头部相同区域的坐标info tuple(left,upper,right,lower)
self.headoverlapBox = ()
# 尾部相同区域的坐标info tuple(left,upper,right,lower)
self.tailoverlapBox = ()
# 尾部是否有重叠部分(注:头部一般是有重叠部分的)
self.iscropTailoverlap = True
# 在C根目录下新建test_screenshot目录
if os.path.exists('c:\\test_screenshot') == False:
os.mkdir('c:\\test_screenshot')
# 执行截图动作
def screenshot(self):
imgname = str(time.time()).split('.')[0] + '.jpg'
# 截图的cmd
cmd = 'adb shell /system/bin/screencap -p /sdcard/' + imgname
if subprocess.run(cmd, shell=True).returncode == 0:
# 将截图文件copy至桌面的cmd
if self.save2destFlag:
cmd = 'adb pull /sdcard/' + imgname + ' ' + getDesktopPath() + imgname
else:
cmd = 'adb pull /sdcard/' + imgname + ' c:\\test_screenshot\\' + imgname
if subprocess.run(cmd, shell=True).returncode == 0:
self.imgPathList.append(imgname)
if subprocess.run('adb shell rm /sdcard/' + imgname, shell=True).returncode == 0:
print('本次截图成功!')
# 从self.imgList中取出图片并将各个图片的像素info存在imgDataList中
def openImg(self):
for img in self.imgPathList:
imgInfo = Image.open(img)
imgData = imgInfo.getdata()
self.imgInfoList.append(imgInfo)
self.imgDataList.append(imgData)
# 查找图1、图2头部的相同区域
def findHeadOverlap(self, ratio=0.95):
imgdata1, imgdata2 = self.imgDataList[0], self.imgDataList[1]
img_width, img_height = self.imgDataList[0].size
# print(img_width, img_height)
for h in range(img_height): # 比较每一行
totalForSame = 0
totalForSame8 = 0
for w in range(img_width): # 比较两张图片的每一个像素点info是否相同
a, b, c = imgdata1.getpixel((w, h))
x, y, z = imgdata2.getpixel((w, h))
a8, b8, c8 = imgdata1.getpixel((w, h + 5))
x8, y8, z8 = imgdata2.getpixel((w, h + 5))
if abs(a - x) < 20 and abs(b - y) < 20 and abs(c - z) < 20:
totalForSame += 1
if abs(a8 - x8) < 20 and abs(b8 - y8) < 20 and abs(c8 - z8) < 20:
totalForSame8 += 1
# 每一行比较完成后,如果相同率小于0.85, 则会找到了相同的区域
if totalForSame / img_width < ratio and totalForSame8 / img_width < ratio:
self.headoverlapBox = (0, 0, img_width, h)
print(f'找到啦:{self.headoverlapBox}')
xImg = self.imgInfoList[0].crop(self.headoverlapBox)
xImg.save(getDesktopPath() + 'head.jpg')
break
# 查找图1、图2尾部的相同区域
def findTailOverlap(self, ratio=1):
imgdata1, imgdata2 = self.imgDataList[0], self.imgDataList[1]
img_width, img_height = self.imgDataList[0].size
# print(img_width, img_height)
# 从图像下面开始向上进行比较
for h in range(img_height - 1, -1, -1):
totalForSame = 0
for w in range(img_width): # 比较两张图片的每一个像素点info是否相同
a, b, c = imgdata1.getpixel((w, h))
x, y, z = imgdata2.getpixel((w, h))
if abs(a - x) < 20 and abs(b - y) < 20 and abs(c - z) < 20:
totalForSame += 1
# 每一行比较完成后,如果相同率小于0.95, 则会找到了相同的区域
if totalForSame / img_width < ratio:
# if h == img_height - 1:
# break
self.tailoverlapBox = (0, h, img_width, img_height)
print(f'找到啦:{self.tailoverlapBox}')
xImg = self.imgInfoList[0].crop(self.tailoverlapBox)
xImg.save(getDesktopPath() + 'tail.jpg')
break
# 将两张图片拼接成一个新图片
def newImg(self, img1, img2, isEnd: bool):
if self.iscropTailoverlap is False:
if self.headoverlapBox == ():
return img1
img2_cropbox_rm_head = (0,
self.headoverlapBox[3]-1,
img2.size[0],
img2.size[1])
img1_cropfile = img1
img1_cropfilename = 'img1' + str(random.randint(1, 10000)) + '.jpg'
img1_cropfile.save(getDesktopPath() + img1_cropfilename)
img2_cropfile = img2.crop(img2_cropbox_rm_head)
img2_cropfilename = 'img2' + str(random.randint(1, 10000)) + '.jpg'
img2_cropfile.save(getDesktopPath() + img2_cropfilename)
img1_cropfile_box = ()
def findoverlapline1():
# 查找重叠的行
# 1, 分别取img1的lin0, line100, line200行的RGB值进行比对
# 2, 如果三个值都相同则找到
img2_cropfile_pixel_list0 = list()
img2_cropfile_pixel_list1 = list()
img2_cropfile_pixel_list2 = list()
# 只取宽度的3/4是因为很多时候页面右侧会有滚动条或一直不变的字线序列(如联系人的ABCD),去掉这部分就可以正常查找了
for w in range(int(img2_cropfile.size[0]*3/4)):
img2_cropfile_pixel_list0.append(img2_cropfile.getpixel((w, 0)))
img2_cropfile_pixel_list1.append(img2_cropfile.getpixel((w, 100)))
img2_cropfile_pixel_list2.append(img2_cropfile.getpixel((w, 200)))
# 在图片img1中自上而下的查找重叠的行
for h in range(img1_cropfile.size[1] - 200):
line0sum, line1sum, line2sum = 0, 0, 0
img1_cropfile_pixel_list0 = list()
img1_cropfile_pixel_list1 = list()
img1_cropfile_pixel_list2 = list()
for w in range(int(img1_cropfile.size[0]*3/4)):
img1_cropfile_pixel_list0.append(img1_cropfile.getpixel((w, h)))
img1_cropfile_pixel_list1.append(img1_cropfile.getpixel((w, h+100)))
img1_cropfile_pixel_list2.append(img1_cropfile.getpixel((w, h+200)))
for i in range(int(img1_cropfile.size[0]*3/4)):
# a/b/c为img1的RGB value
# x/y/z为img2的RGB value
a0, b0, c0 = img1_cropfile_pixel_list0[i]
a1, b1, c1 = img1_cropfile_pixel_list1[i]
a2, b2, c2 = img1_cropfile_pixel_list2[i]
x0, y0, z0 = img2_cropfile_pixel_list0[i]
x1, y1, z1 = img2_cropfile_pixel_list1[i]
x2, y2, z2 = img2_cropfile_pixel_list2[i]
if abs(a0 - x0) < 30 and abs(b0 - y0) < 30 and abs(c0 - z0) < 30:
line0sum += 1
if abs(a1 - x1) < 30 and abs(b1 - y1) < 30 and abs(c1 - z1) < 30:
line1sum += 1
if abs(a2 - x2) < 30 and abs(b2 - y2) < 30 and abs(c2 - z2) < 30:
line2sum += 1
# print(f'line0sum:{line0sum} line1sum:{line1sum} line2sum:{line2sum}')
if line0sum == line1sum == line2sum == img1_cropfile.size[0]:
print(f'h:{h}')
return h
return False
hFlag = findoverlapline1()
if hFlag == False:
pass
else:
img1_cropfile_box = (0, 0, img1_cropfile.size[0], hFlag)
# 拼接图片
if img1_cropfile_box == ():
print('没有找到重叠的行')
else:
img1_cropfile = img1_cropfile.crop(img1_cropfile_box)
img1_cropfile.save(getDesktopPath() + 'img1_cropfile.jpg')
newimgfile = Image.new('RGB', (img1_cropfile.size[0], img1_cropfile.size[1] + img2_cropfile.size[1]))
newimgfile.paste(img1_cropfile, (0, 0))
newimgfilename = 'newimgfile' + str(random.randint(1, 10000)) + '.jpg'
newimgfile.save(getDesktopPath() + newimgfilename)
newimgfile.paste(img2_cropfile, (0, img1_cropfile.size[1]))
newimgfilename = 'newimgfile' + str(random.randint(1, 10000)) + '.jpg'
newimgfile.save(getDesktopPath() + newimgfilename)
# return newimgfile
if self.iscropTailoverlap is True:
img1_cropbox_rm_tail = (0,
0,
img1.size[0],
self.tailoverlapBox[1])
# 如果img2为最后一张图片时,img2不用去掉tail部分
if isEnd:
img2_cropbox_rm_head_tail = (0,
self.headoverlapBox[3],
img2.size[0],
img2.size[1]
)
else:
img2_cropbox_rm_head_tail = (0,
self.headoverlapBox[3],
img2.size[0],
self.tailoverlapBox[1]
)
img1_cropfile = img1.crop(img1_cropbox_rm_tail)
img1_cropfilename = 'img1' + str(random.randint(1, 10000)) + '.jpg'
img1_cropfile.save(getDesktopPath() + img1_cropfilename)
img2_cropfile = img2.crop(img2_cropbox_rm_head_tail)
img2_cropfilename = 'img2' + str(random.randint(1, 10000)) + '.jpg'
img2_cropfile.save(getDesktopPath() + img2_cropfilename)
img1_cropfile_box = ()
def findoverlapline2():
# global img1_cropfile_box
# 查找重叠的行
# 1, 分别取img1的lin0, line15, line100行的RGB值进行比对
# 2, 如果三个值都相同则找到
img2_cropfile_pixel_list0 = list()
img2_cropfile_pixel_list1 = list()
img2_cropfile_pixel_list2 = list()
for w in range(int(img2_cropfile.size[0]*3/4)):
img2_cropfile_pixel_list0.append(img2_cropfile.getpixel((w, 0)))
img2_cropfile_pixel_list1.append(img2_cropfile.getpixel((w, 100)))
img2_cropfile_pixel_list2.append(img2_cropfile.getpixel((w, 200)))
# 在图片img1中自上而下的查找重叠的行
for h in range(img1_cropfile.size[1] - 200):
line0sum, line1sum, line2sum = 0, 0, 0
img1_cropfile_pixel_list0 = list()
img1_cropfile_pixel_list1 = list()
img1_cropfile_pixel_list2 = list()
for w in range(int(img1_cropfile.size[0]*3/4)):
img1_cropfile_pixel_list0.append(img1_cropfile.getpixel((w, h)))
img1_cropfile_pixel_list1.append(img1_cropfile.getpixel((w, h+100)))
img1_cropfile_pixel_list2.append(img1_cropfile.getpixel((w, h+200)))
for i in range(int(img1_cropfile.size[0]*3/4)):
# a/b/c为img1的RGB value
# x/y/z为img2的RGB value
a0, b0, c0 = img1_cropfile_pixel_list0[i]
a1, b1, c1 = img1_cropfile_pixel_list1[i]
a2, b2, c2 = img1_cropfile_pixel_list2[i]
x0, y0, z0 = img2_cropfile_pixel_list0[i]
x1, y1, z1 = img2_cropfile_pixel_list1[i]
x2, y2, z2 = img2_cropfile_pixel_list2[i]
if abs(a0 - x0) < 20 and abs(b0 - y0) < 20 and abs(c0 - z0) < 20:
line0sum += 1
if abs(a1 - x1) < 20 and abs(b1 - y1) < 20 and abs(c1 - z1) < 20:
line1sum += 1
if abs(a2 - x2) < 20 and abs(b2 - y2) < 20 and abs(c2 - z2) < 20:
line2sum += 1
# print(f'line0sum:{line0sum} line1sum:{line1sum} line2sum:{line2sum}')
if line0sum == line1sum == line2sum == int(img1_cropfile.size[0]*3/4):
print(f'h:{h}')
# img1_cropfile_box = (0, 0, img1_cropfile.size[0], h)
return h
return False
hFlag = findoverlapline2()
if hFlag == False:
pass
else:
img1_cropfile_box = (0, 0, img1_cropfile.size[0], hFlag)
# 拼接图片
findoverlapline2()
if img1_cropfile_box == ():
print('没有找到重叠的行')
else:
img1_cropfile = img1_cropfile.crop(img1_cropfile_box)
newimgfile = Image.new('RGB', (img1.size[0], img1_cropfile.size[1] + img2_cropfile.size[1]))
newimgfile.paste(img1_cropfile, (0, 0))
newimgfilename = 'newimgfile' + str(random.randint(1, 10000)) + '.jpg'
newimgfile.save(getDesktopPath() + newimgfilename)
newimgfile.paste(img2_cropfile, (0, img1_cropfile.size[1]))
newimgfilename = 'newimgfile' + str(random.randint(1, 10000)) + '.jpg'
newimgfile.save(getDesktopPath() + newimgfilename)
return newimgfile
# 将所有截图拼接成一个长图
def sewImg(self):
if len(self.imgPathList) == 1:
shutil.move(self.imgPathList[0], getDesktopPath())
return
print('等待长图生成至桌面ing... ...')
self.openImg()
self.findHeadOverlap()
self.findTailOverlap()
img1 = self.imgInfoList[0]
if self.tailoverlapBox == ():
self.iscropTailoverlap = False
for i in range(1, len(self.imgInfoList)):
img2 = self.imgInfoList[i]
# 对于img2是否为最后一张的处理是不一样的,因为当有时底部有重叠部时对于最后一张的处理会比较特殊
if i == len(self.imgInfoList) - 1:
# 感觉没有写错,但是每次传入img1都是self.imgInfoList[0]
img1 = self.newImg(img1, img2, True)
temp = img1
print(f'img1.size: {img1.size}')
else:
img1 = self.newImg(img1, img2, False)
temp = img1
print(f'img1.size: {img1.size}')
img1.save(getDesktopPath() + 'longImage.jpg')
def run(self):
# 只截一张图的case
subprocess.run('adb wait-for-device', shell=True)
if self.cmd == '1':
self.screenshot()
# 需要截长图的case
if self.cmd in ('1,', '1L', '1Long', '1long', '11'):
# 修改工作目录
os.chdir('c:\\test_screenshot')
# 修改save2destFlag,保证截长图时保存至c:\\test_screenshot
self.save2destFlag = False
# 截图过程
self.screenshot()
w, h = Image.open(self.imgPathList[0]).size
cmd = 'adb shell input swipe ' + str(int(w / 2)) + ' ' + str(int(h * 2.5 / 4)) + ' ' + str(
int(w / 2)) + ' ' + str(int(h / 4))
self.swipe_distance = int(h * 3 / 4) - int(h / 4)
enter = input('输入回车键继续截图,q结束长截图:').strip().lower()
while True:
if enter == 'q':
print(f'当前共截图{len(self.imgPathList)}张.')
break
elif enter == '':
subprocess.run(cmd, shell=True, encoding='utf-8', capture_output=True)
self.screenshot()
enter = input('输入回车键继续截图,q结束长截图:').strip().lower()
else:
enter = input('输入回车键继续截图,q结束长截图:').strip().lower()
# 长图拼接过程
start = time.time()
self.sewImg()
end = time.time()
print(f'共耗时:{end - start:>0.2f}秒')
if __name__ == '__main__':
key = input('(截图请输入1, 长截图请输入1,):')
if key.strip() in ('1', '1,', '11'):
Screenshot(key).run()
else:
print('输入有误!!!')