本文对应GitHub项目地址

之前写了像微信一样的区域截屏功能,中提到,我的小工具集里面有滚动截屏的功能,这篇文章将讨论一下具体的实现和公布源码。。。

实现思路类似某乎上一个关于滚动截屏实现的回答。

不同于移动端的滚动截屏,pc端浏览器之外是不能获得准确的鼠标滚动距离的(或者有我不知道的方法),要实现滚动截屏就不是那么容易了;

现在网上也有一些可以滚动截屏的软件(像FSCapture、qq截屏),实现方法应该也是先截屏然后根据图片元素拼接的。本文的方法虽有拼接出错的情况,但是能用哇。。h,源码全长四百多行。。。由于是直接从我的小工具集里面整合出来的,有些地方会显得不太合理,当作学习就好,具体思路都差不多

源码

import math
import operator
import os
import sys
import time
from functools import reducefrom PIL import Image
from PyQt5.QtCore import QThread, QTimer
from PyQt5.QtWidgets import QApplication
from pynput import mouse
from pynput.mouse import Controller as MouseControllerclass Splicing_shots(object):def __init__(self):self.init_splicing_shots_thread = Commen_Thread(self.init_splicing_shots)self.init_splicing_shots_thread.start()self.clear_timer = QTimer()self.clear_timer.timeout.connect(self.setup)def init_splicing_shots(self):"""后台初始化"""self.img = []self.img_list = []self.images_data_line_list = []self.img_width = 0self.img_height = 0self.compare_row = 50self.cut_width = 0self.rate = 0.85self.roll_speed = 5self.min_head = 0self.left_border = 0self.right_border = 0self.head_pos = {}self.maybe_errorlist = []self.in_rolling = Falseself.arrange = 0self.max_arrange = 999if not os.path.exists("j_temp"):os.mkdir("j_temp")def setup(self):"""清理&初始化"""if self.clear_timer.isActive():self.clear_timer.stop()print('clear')self.img = []self.img_list = []self.images_data_line_list = []self.img_width = 0self.img_height = 0self.compare_row = 50self.cut_width = 0self.rate = 0.85self.roll_speed = 3self.min_head = 0self.left_border = 0self.right_border = 0self.head_pos = {}self.maybe_errorlist = []self.in_rolling = Falseself.arrange = 0self.max_arrange = 999self.clear_timer = QTimer()self.clear_timer.timeout.connect(self.setup)def find_left_side(self):"""寻找相同的左边界"""images_data_list = []for img in self.img_list[:2]:rotate_img = img.rotate(270, expand=1)rotate_img_data = list(rotate_img.convert('L').getdata())an_imgdata = []for line in range(rotate_img.height - 1):an_imgdata.append(rotate_img_data[line * rotate_img.width:(line + 1) * rotate_img.width])images_data_list.append(an_imgdata)rotate_height = len(images_data_list[0])min_head = rotate_heightfor i in range(1):for j in range(1, rotate_height):img1 = images_data_list[i][:j]img2 = images_data_list[i + 1][:j]if img2 != img1:if j == 1:print('没有重复左边界!')returnelif j < (min_head + 1):min_head = j - 1breakself.left_border = min_headprint('minleft', min_head)def find_right_size(self):"""寻找相同的右边界"""images_data_list = []for img in self.img_list[:2]:rotate_img = img.rotate(90, expand=1)rotate_img_data = list(rotate_img.convert('L').getdata())an_imgdata = []for line in range(rotate_img.height - 1):an_imgdata.append(rotate_img_data[line * rotate_img.width:(line + 1) * rotate_img.width])images_data_list.append(an_imgdata)rotate_height = len(images_data_list[0])min_head = rotate_heightfor i in range(1):for j in range(1, rotate_height):img1 = images_data_list[i][:j]img2 = images_data_list[i + 1][:j]# print(img2)if img2 != img1:if j == 1:print('没有重复右边界!')self.right_border = self.img_width# print(self.majority_color(self.images_data_line_list[0]))returnelif j < (min_head + 1):min_head = j - 1breakself.right_border = self.img_width - min_headprint('minright', min_head)def find_the_same_head_to_remove(self):"""寻找相同的头部(上边界)"""# if self.images_datamin_head = self.img_heightfor i in range(len(self.img_list) - 1):for j in range(1, self.img_height):img1 = self.images_data_line_list[i][:j]img2 = self.images_data_line_list[i + 1][:j]# print(img2)if img2 != img1:if j == 1:print('没有重复头!')# print(self.majority_color(self.images_data_line_list[0]))returnelif j < (min_head + 1):min_head = j - 1breakself.min_head = min_headprint('minhead', min_head)def majority_color(self, classList):'''返回颜色列表中最多的颜色'''count_dict = {}for label in classList:if label not in count_dict.keys():count_dict[label] = 0count_dict[label] += 1# print(max(zip(count_dict.values(), count_dict.keys())))return max(zip(count_dict.values(), count_dict.keys()))def isthesameline(self, line1, line2):"""判断是否两行是否相同"""same = 0rate = self.rateline1_majority_color = self.majority_color(line1)line2_majority_color = self.majority_color(line2)if line2_majority_color[1] != line1_majority_color[1]:# print(self.majority_color(line2),self.majority_color(line1))return 0elif abs(line1_majority_color[0] - line2_majority_color[0]) > self.img_width * (1 - rate) * 0.5:return 0else:majority_color_count, majority_color = line2_majority_color# print(majority_color_count,majority_color)if majority_color_count > int(self.cut_width * rate):return 1for i in range(self.cut_width):if line1[i] == majority_color or line2[i] == majority_color:# print('maj')continueelse:if abs(line1[i] - line2[i]) < 10:same += 1if same >= (self.cut_width - majority_color_count) * rate:return 1else:return 0def efind_the_pos(self):"""在滚动的同时后台寻找拼接点"""while self.in_rolling or self.arrange < self.max_arrange:  # 如果正在截屏或截屏没有处理完print(self.arrange, '  max:', self.max_arrange)min_head = self.min_headleft = self.left_borderright = self.right_borderself.cut_width = right - leftimages_data_line_list = self.images_data_line_listcompare_row = self.compare_rowi = self.arrangetry:img1 = images_data_line_list[i]  # 前一张图片img2 = images_data_line_list[i + 1]  # 后一张图片except IndexError:time.sleep(0.1)  # 图片索引超出则等待下一张截屏continuemax_line = [0, 0]for k in range(min_head, self.img_height - compare_row):  # 前一张图片从相同头部开始遍历到最后倒数compare_row行if self.in_rolling:  # 如果正在截屏则sleep一下避免过多占用主线程,也没什么用...time.sleep(0.001)sameline = 0chance_count = 0chance = 0for j in range(min_head, min_head + compare_row):  # 后一张图片从相同头部开始逐行遍历compare_row行lin1 = img1[k + sameline][left:right]lin2 = img2[min_head + sameline][left:right]if self.isthesameline(lin1, lin2):  # 如果是行相同,则sameline+1sameline += 1chance_count += 1if chance_count >= 7:  # 每7行增加一个chance,避免误判chance_count = 0chance += 1if sameline > max_line[1]:max_line[0] = kmax_line[1] = sameline  # 记录最大行数备用else:  # 否则chance-1直到退出if chance <= 0:breakelse:chance -= 1sameline += 1if sameline >= compare_row - compare_row // 20:self.head_pos[i] = kprint(i, k)print(self.head_pos)breakif i not in self.head_pos.keys():#如果没有找到符合的拼接点,则取最大的配合点,并标记为可能出错的地方if max_line[1] >= 1:self.head_pos[i] = max_line[0]print(self.head_pos)max_line.append(i)self.maybe_errorlist.append(max_line)print('max_line', i, max_line)  # 测试self.arrange += 1def find_the_pos(self):#和上面的efind_the_pos类似"""寻找拼接点,当图片数少时可以直接截完屏调用"""min_head = self.min_headleft = self.left_borderright = self.right_borderself.cut_width = right - leftimages_data_line_list = self.images_data_line_listcompare_row = self.compare_row# print(min_head, self.img_height - compare_row)for i in range(len(self.img_list) - 1):# print(i)img1 = images_data_line_list[i]img2 = images_data_line_list[i + 1]max_line = [0, 0]  # 测试for k in range(min_head, self.img_height - compare_row):sameline = 0chance_count = 0chance = 0for j in range(min_head, min_head + compare_row):lin1 = img1[k + sameline][left:right]lin2 = img2[min_head + sameline][left:right]# print(len(lin2),len(lin1))res = self.isthesameline(lin1, lin2)if res:sameline += 1chance_count += 1if chance_count >= 5:chance_count = 0chance += 1if sameline > max_line[1]:max_line[0] = kmax_line[1] = sameline  # 测试# print(i, j, k)else:# print(chance)if chance <= 0:breakelse:chance -= 1sameline += 1if sameline >= compare_row - compare_row // 20:self.head_pos[i] = kprint(i, k)print(self.head_pos)breakif i not in self.head_pos.keys():if max_line[1] >= 1:self.head_pos[i] = max_line[0]print(self.head_pos)max_line.append(i)self.maybe_errorlist.append(max_line)print('max_line', i, max_line)  # 测试def merge_all(self):"""根据拼接点拼接所有图片"""majority_pos = self.majority_color(self.head_pos.values())for i in range(len(self.img_list) - 1):if i not in self.head_pos.keys():self.head_pos[i] = majority_pos[1]print(i, '丢失,补', majority_pos)  # 丢失则补为图片拼接点的众数,虽然没有什么用...img_width = self.img_widthimg_height = 0# head_pos = []# for i in len(self.head_pos)for i in self.head_pos.keys():img_height += self.head_pos[i] - self.min_head# print(img_height)img_height += self.img_height  # 加最后一张newpic = Image.new(self.img_list[0].mode, (img_width, img_height))height = 0if self.min_head:height += self.min_headnewpic.paste(self.img_list[0].crop((0, 0, img_width, self.min_head)), (0, 0))for i in range(len(self.img_list) - 1):if self.min_head:newpic.paste(self.img_list[i].crop((0, self.min_head, self.img_width, self.head_pos[i])),(0, height))height += self.head_pos[i] - self.min_headelse:newpic.paste(self.img_list[i].crop((0, self.min_head, self.img_width, self.head_pos[i])),(0, height))height += self.head_pos[i]if self.min_head:newpic.paste(self.img_list[-1].crop((0, self.min_head, img_width, img_height)), (0, height))else:newpic.paste(self.img_list[-1], (0, height))# name = str(time.strftime("%Y-%m-%d_%H.%M.%S", time.localtime()))newpic.save('j_temp/jam_outputfile.png')print('saved in j_temp/jam_outputfile.png')def is_same(self, img1, img2):"""简单判断两幅图片是否相同,用于停止滚动截屏,速度非常快!"""h1 = img1.histogram()h2 = img2.histogram()result = math.sqrt(reduce(operator.add, list(map(lambda a, b: (a - b) ** 2, h1, h2))) / len(h1))if result <= 5:return Trueelse:return Falsedef auto_roll(self, area):"""自动滚动截屏,总函数"""x, y, w, h = areaself.img_width = wself.img_height = hspeed = round(1 / self.roll_speed, 2)screen = QApplication.primaryScreen()controler = MouseController()find_left = Commen_Thread(self.find_left_side)find_right = Commen_Thread(self.find_right_size)find_head = Commen_Thread(self.find_the_same_head_to_remove)find_pos = Commen_Thread(self.efind_the_pos)threads = [find_left, find_pos, find_right, find_head]self.in_rolling = Truei = 0img_height = 0controler.position = (area[0] + int(area[2] / 2), area[1] + int(area[3] / 2))while self.in_rolling:pix = screen.grabWindow(QApplication.desktop().winId(), x, y, w, h)  # 区域截屏获取图像pixmapimg = Image.fromqpixmap(pix)  # 将qt的pixmap转为pil模块的img对象self.img_list.append(img)  # 储存图片的列表img_data = list(img.convert('L').getdata())  # 图片灰度化,并把灰度值转为列表an_img_line_data = []for line in range(h):an_img_line_data.append(img_data[line * w:(line + 1) * w])  # 列表分行储存self.images_data_line_list.append(an_img_line_data)if i >= 1:if self.is_same(self.img_list[i - 1], self.img_list[i]):  # 每帧检查是否停止(图片是否相同)self.in_rolling = Falsei += 1breakif img_height == 0:  # 图片有两张以上后,启动线程寻找图片边界点img_height = 1find_head.start()find_left.start()find_right.start()if i == 5:  # 图片大于5张才开始寻找拼接点find_pos.start()controler.scroll(dx=0, dy=-3)  # 滚动屏幕time.sleep(speed)  # 速度控制# img.save('j_temp/{0}.png'.format(i))i += 1print('图片数', i)self.max_arrange = i - 1  # 获取图片序列用于控制寻找边界点的结束for thread in threads:  # 遍历并等待各线程结束thread.wait()# print(thread)if i <= 2:print('过短!一张图还不如直接截呐')self.clear_timer.start(0)returnelif i <= 5:self.find_the_pos()  # 图片小于5张则截完屏在拼接else:find_pos.wait()  # 等待拼接点寻找完成# self.find_the_pos()print('found_pos_done')# try:self.merge_all()  # 调用图片拼接函数# except:#     print('拼接出错错误!请重新截屏!')#     self.clear_timer.start(10000)#     returnprint('可能错误的地方:', self.maybe_errorlist)self.clear_timer.start(10000)  # 10s后初始化内存def listen():"""鼠标监听,截屏中当按下鼠标停止截屏"""global listenerprint("listen")def on_click(x, y, button, pressed):if button == mouse.Button.left:if roller.in_rolling:roller.in_rolling = Falselistener = mouse.Listener(on_click=on_click)listener.start()class Commen_Thread(QThread):"""造的轮子...可用于多线程中不同参数的输入"""def __init__(self, action, *args):super(QThread, self).__init__()self.action = actionself.args = args# print(self.args)def run(self):print('start_thread')if self.args:if len(self.args) == 1:self.action(self.args[0])print(self.args[0])elif len(self.args) == 2:self.action(self.args[0], self.args[1])else:self.action()if __name__ == '__main__':app = QApplication(sys.argv)roller = Splicing_shots()listen()roller.auto_roll((350, 50, 800, 700))#注意选好区域,边界区域过大可能会拼接失败;不能滚动的时候会立即停止!sys.exit(app.exec_())

具体思路

前面已经说了,实现滚动截屏的步骤:滚动–>截屏–>寻找拼接点–>拼接

滚动

滚动部分主要用了pynput模块的滚动功能,该模块还可以实现全局快捷键,具体方法本文不深究。

截屏

由于我的小工具集是采用pyqt作为界面的,就直接用qt的截屏方法了,可以自行改为pil或win32的截屏方法
截屏中止(滚动中止)采用了双重判定,当前后两张图片相同(到了尽头)时可以自动停止,当按下鼠标左键时也会停止

寻找拼接点

寻找拼接点就是比较相邻的图片,寻找下一张图片在前一张图片的相同部分的位置,并记录下来。
但考虑到有些截屏区域是包含不滚动部分的,即所有图片都有相同的头部或边框,所以截取的图片就不能直接用来寻找拼接点,需要比较多张图片并去除相同部分的影响;
所以这一部分又可以分几步:把图片灰度化(不然就要比较rgb3个通道,处理时间大大增加)–>把图片的灰度值储存于数组中–>比较前n张图片并找出所有相同的边界点(上、左、右,下由于拼接的时候会覆盖掉就不用识别了)–>排除相同的部分开始寻找拼接的界限(逐行比较)

更具体的实现已经在代码中标明了,仔细看注释应该可以看懂,可以根据需要改动代码,例如可以完全去除qt库(这个库是真的大!)

听说还可以用opencv实现图片拼接功能(而且效率upup的),等我研究一下再来改蛤


忘了回来更新了…
opencv实现的滚动截屏方法,比暴力拼接不知道快到哪里去了…自行查看
https://github.com/fandesfyf/roll_screenshot

本文对应GitHub项目地址开源万岁!
作者Fandes,转载请标明出处,其实不标明我也不知道…

python实现滚动截屏功能相关推荐

  1. iphone长截图哪个软件好_亲身体验过13款滚动截屏App,谁才是最好用的iPhone长截屏工具?...

    (☝聪明的人都会星标我☝) 上次我们分享了关于手机录屏怎么只录入手机系统声音而不录入外界声音,有小伙伴留言"苹果手机怎么长截屏?",必须安排一波! 与苹果手机相比,安卓手机想要长截 ...

  2. python实现区域截屏(类似于QQ微信截图)功能

    该功能是本人用python写的小工具集Jamtools里面的截屏部分整合,代码完全原创,分享出来. CSDN源码下载地址:https://download.csdn.net/download/Fand ...

  3. python截图工具和模拟鼠标键盘_python PyAutoGUI 模拟鼠标键盘操作和截屏功能

    简介 一款跨平台/无依赖的自动化测试工具,目测只能控制鼠标/键盘/获取屏幕尺寸/弹出消息框/截屏. 安装 pip install pyautogui 鼠标键盘控制 >>> impor ...

  4. ios截屏功能html,滚动截屏APP - iPhone上的长截图工具

    话说长截图功能也算是一种刚需了,如今安卓好多手机系统都会自带此功能.很难想象的是,安卓手机标配的「长截图」功能,对果粉来说是多么的奢侈.iPhone没有自带的长截图功能,只能借助第三方APP,比如Ta ...

  5. Python selenium PIL 全网页滚动截屏 headless全网页截屏

    思路 ​ 先截取当前屏幕的图片,获取其高度作为base高度 h,再获取全网页body到尾部的高度 H ,循环截取图片,再通过PIL进行拼接. 代码 # -*- coding:utf-8 -*- # a ...

  6. 安卓 多条通知_安卓11第一版发布:原生滚动截屏、屏幕录像、抄国内ROM这么多...

    自2008年第一部Android智能手机HTC G1发布,安卓手机系统已经走过十几个年头.虽然系统存在一些大家吐槽较多的问题,但安卓一直活跃在智能手机系统前沿,不断发展完善着. 昨天谷歌刚刚发布了全新 ...

  7. 滚动截屏软件_华为指关节截屏不如三指截屏好用?一步到位,实践出真知

    华为手机的指关节截屏功能想必只要是用过的朋友都知道,熟悉的朋友更会以此为依赖,比如我,现在换了个其他品牌手机用,一到截屏的时候还是会不由自主地拿指关节划区截屏,因为指关节截屏不仅仅是双击截屏,这个划区 ...

  8. 一款免费的截图、滚动截屏软件

    一款免费强大的截图工具 Bandaid 软件介绍 下载 安装 使用 Bandaid 软件介绍 Bandaid 为一款性能高效,功能齐全的截图软件.不仅涵盖基本的截图.编辑与贴图,还有灵活的滚动截屏与视 ...

  9. 滚动截屏苹果_苹果手机上的5个神器,让你的手机更高效,顺手

    大多数选择苹果都是因为IOS系统流畅,简洁,没有广告或臃肿的内容.系统是好系统但是没有高效的应用来给这个系统搭配感觉少了一些丝滑.缺点也很明显不够开源,不能独自安装第三方应用.但是还是有很多好玩有趣的 ...

  10. 限免|iOS长截图工具 滚动截屏

    使用录屏的方式来实现长截图的功能,不需要再一张张截图拼接,不限应用,无论是微信聊天还是公众号文章都可以使用滚动截屏. 软件名:滚动截屏-无需拼接快速生成长截图 优点:无内购,无广告 缺点:会有一些不足 ...

最新文章

  1. Qt计算器开发(二):信号槽实现数学表达式合法性检查
  2. python下载不了-python3下载不了
  3. 一张图看懂CSS cascade, specific, importance, inheritance
  4. 【洛谷】【堆+贪心】P1484 种树
  5. Tensorflow 获取model中的变量列表,用于模型加载等
  6. 关于Maven的7个问题
  7. 2022 最新分布式面试题合集,轻松应对 Java 面试
  8. reviewboard使用 与原理
  9. 如何把大写金额变为小写数字_如何将小写金额变成大写数值
  10. 压力变送器matlab,总结压差变送器三种不同故障以及处理方法[理论结合实际]
  11. java 进制转换类_Java基本数据类型以及进制转换
  12. 用了python抢购京东茅台脚本,为什么你还是抢不到茅台?教你这样设置时间,提升成功概率
  13. 软件设计师-计算机网络(刷题笔记)
  14. 教你实现微信公众号效果:长按图片保存到相册
  15. java分词主谓宾_英语五种结构的句子(主谓 主谓宾 主谓宾宾补 主系表 主谓双宾)谁给我讲一下…...
  16. 微软的一道前端面试题
  17. 软件设计师-设计模式
  18. Python||报错:TypeError: can only join an iterable
  19. ros-gazebo-仿真环境搭建
  20. Oracle如何查询大于1的结果,ORACLE的一些查询

热门文章

  1. 沪上各区免费停车场大全
  2. ARP协议及欺骗原理
  3. Oracle数据库备份到本地
  4. python文件操作方法seek_Python文件操作及seek偏移详解
  5. 高通高级技术标准总监李俨:C-V2X助力自动驾驶的招式和心法
  6. 计算机组装与维护重点难点,计算机组装与维修复习重难点.doc
  7. vbscript for 转 php for,VBS教程:VBScript 基础-使用循环语句
  8. SOLIDWORKS之VBA宏(三)
  9. Qt 之 模仿 QQ登陆界面——样式篇
  10. 【玩转嵌入式屏幕显示】(六)ST7789 SPI LCD硬件垂直滚动功能的使用