这个工具写下来遇到了不少坑,直到现在还没有完全解决,先记录下来吧,后面有机会再修改,或是有心的同学帮忙分析一下为什么?

 

主要实现以下功能:

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')

实验图片如下:

android 长图保存 android截长图app_像素点

android 长图保存 android截长图app_像素点_02

android 长图保存 android截长图app_PIL.Image_03

android 长图保存 android截长图app_PIL.Image_04

完整的脚本如下: 

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('输入有误!!!')