【Python • 项目实战】pytesseract+pyqt实现图片识别软件小项目——(二)实现QQ截图功能

简介: 【Python • 项目实战】pytesseract+pyqt实现图片识别软件小项目——(二)实现QQ截图功能



前言

经过上次学习,我们安装了tesseract识别引擎,并通过pytesseract实现了快速识别图片的内容。然后通过PyQt5DesignMode项目模板创建了我们的项目,并且已经绘制好了软件的界面,为截图按钮添加了一个简单的点击事件。

本篇我们将继续完善这个项目,实现QQ截图的功能。


一、任务目的

在上节的基础上,实现QQ截屏的功能,并将这个图片保存,显示在软件界面上。

要求

  • 可以使用快捷键截屏
  • 可以点击截屏按钮截屏
  • 截屏显示在软件上

二、实现截图功能

1. 截图功能分析

理论依据

1. 截图功能

Qt提供了可以截屏的方法——grabWindow,函数原型

def grabWindow(WID window, x=0, y=0, width=-1, height=-1)

参数是窗口ID要截取的区域(x、y、width和height组成的矩形区域)。

窗口ID可以通过QWidget的winId()获得,若截取整个屏幕窗口ID传入0。

2. 支持鼠标移动事件

Qt的QWidgets提供了可以使窗口支持鼠标移动事件的方法——mouseTracking,表示窗口的鼠标跟踪属性是否生效。函数原型

def setMouseTracking(self, bool)

参数只有一个bool类型的参数,表示是否开启鼠标跟踪事件。

3. 窗口无边框

Qt的QWidgets提供了可以使窗口无边框的方法——setWindowFlag,需要传入窗口的Flag,也就是Qt_WindowType来启用不同风格的窗口。函数原型

def setWindowFlag(self, Qt_WindowType, on=True)

这里我们主要用到的参数就是Qt_WindowType,表示窗口的风格,其可选项如下

  • Qt::Widget : QWidget构造函数的默认值,如新的窗口部件没有父窗口部件,则它是一个独立的窗口,否则就是一个子窗口部件。
  • Qt::Window : 无论是否有父窗口部件,新窗口部件都是一个窗口,通常有一个窗口边框和一个标题栏。
  • Qt::Dialog : 新窗口部件是一个对话框
  • Qt::Sheet : 新窗口部件是一个Macintosh表单。
  • Qt::Drawer : 新窗口部件是一个Macintosh抽屉。
  • Qt::Popup : 新窗口部件是一个弹出式顶层窗口。
  • Qt::Tool : 新窗口部件是一个工具窗口,它通常是一个用于显示工具按钮的小窗口,
    如果一个工具窗口有父窗口部件,则它将显示在父窗口部件的上面,否则,将相当于使用了
  • Qt::WindowStaysOnTopHint展示。
  • Qt::Tooltip : 新窗口部件是一个提示窗口,没有标题栏和窗口边框.
  • Qt::SplashScreen : 新窗口部件是一个欢迎窗口,它是QSplashScreen构造函数的默认值。
  • Qt::Desktop : 新窗口部件是桌面,它是QDesktopWidget构造函数的默认值。
  • Qt::SubWindow : 新窗口部件是一个子窗口,而无论该窗口部件是否有父窗口部件。
  • Qt::X11BypassWindowManagerHint : 完全忽视窗口管理器,它的作用是产生一个根本不被管理器的无窗口边框的窗口,此时,用户无法使用键盘进行输入,除非手动调用QWidget::ActivateWindow()函数。
  • Qt::FramelessWindowHint : 产生一个无窗口边框的窗口,此时用户无法移动该窗口和改变它的大小。
  • Qt::CustomizeWindowHint : 关闭默认的窗口标题提示。
4. 窗口全屏

Qt的QWidgets提供了可以使窗口全屏展示的方法——setWindowState,表示设置窗口的状态。函数原型

def setWindowState(self, Union, Qt_WindowStates=None, Qt_WindowState=None):

我们在使用时,主要使用的是Qt_WindowState,其可选项如下

  • Qt: :WindowNoState 正常状态
  • Qt: :WindowMinimized 窗口最小化
  • Qt:: WindowMaximized 窗口最大化
  • Qt::WindowFullScreen 窗口填充整个屏幕,而且没有边框
  • Qt:: Window Active 变为活动的窗口,例如可以接收键盘输入

实施思路

继承QWidgets类实现一个无边框占满全屏的窗口,覆盖到整个屏幕,并且给予一个黑色透明的背景。如下图

处理以下几个事件

  • 鼠标左键按下,记录开始坐标
  • 鼠标右键按下,取消操作
  • 鼠标移动,记录鼠标坐标
  • 鼠标左键放开,记录鼠标移动结束坐标
  • 鼠标双击,保存截图内容
  • 绘图事件,实时绘制鼠标拖动的方框

此处的逻辑应该是这样的。当鼠标左键按下,就开始记录鼠标点击位置的坐标,鼠标移动时,更新一个新坐标,这个新坐标和之前的坐标就可以实时计算出这块矩形区域的x,y,width,height。当鼠标左键放开,就会记录下这一点的坐标,与第一个起始坐标进行计算,就可以得出截图区域的坐标。

鼠标移动事件是一直要运行的,因为要实时的预览我们截图的效果,因此要一直取得鼠标的位置。同时绘图事件也是需要一直运行的,它需要进行实时的计算我们预览的矩形区域,并且绘制在界面上。

鼠标右键的事件很简单,就是取消当前的操作,它的逻辑就是如果当前已经选择好区域了,那就取消这块区域,如果没有选择区域,那就关闭窗口。最终实现效果如下。

2. 截图功能实现

以下代码思路来自CSDN用户 @Karbob,感谢大佬提供的思路。

1. 创建窗口

创建个继承QWidget的一个窗口,我们将窗口进行基础的设置。init_window方法是我们用来初始化窗口的,我们开启了鼠标追踪功能,设置了鼠标光标,设置了窗口无边框和窗口全屏。

class CaptureScreen(QWidget):
    def __init__(self):
        super(QWidget, self).__init__()
        self.init_window()  # 初始化窗口
        self.capture_full_screen()  # 获取全屏
    def init_window(self):
        self.setMouseTracking(True)  # 鼠标追踪
        self.setCursor(Qt.CrossCursor)  # 设置光标
        self.setWindowFlag(Qt.FramelessWindowHint)  # 窗口无边框
        self.setWindowState(Qt.WindowFullScreen)  # 窗口全屏

值得一提的是,我们在窗口初始化的时候就对桌面全屏截了个图,并且保存到窗口的字段中去,在我们后面绘制截图的时候会用得到。

self.capture_full_screen()  # 获取全屏

他的实现就是调用grabWindow方法对桌面进行截屏,其实现代码如下,

def capture_full_screen(self):
        self.full_screen_image = QGuiApplication.primaryScreen().grabWindow(QApplication.desktop().winId())

还有一些变量是需要初始化的

begin_position = None
    end_position = None
    full_screen_image = None
    capture_image = None
    is_mouse_pressLeft = None
    painter = QPainter()
  • begin_position 代表鼠标的开始坐标
  • end_position 代表鼠标的结束坐标
  • full_screen_image 存放全屏截图的图片
  • capture_image 截取的图片
  • is_mouse_pressLeft 鼠标左键是否按下
  • painter 进行绘制的对象

2. 实现鼠标按下事件

鼠标按下事件主要做两件事,

  1. 判断是按下鼠标左键还是鼠标右键
  2. 如果是鼠标左键就记录鼠标起始位置,否则就取消当前操作

事件处理程序流程图如下所示,

开始是否左键?记录当前坐标设置属性is_mouse_pressLeft为True结束处理右键是否选择截图区域?取消截图区域取消当前操作yesnoyesno

实现代码如下,

def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.begin_position = event.pos()
            self.is_mouse_pressLeft = True
        if event.button() == Qt.RightButton:
            # 如果选取了图片,则按一次右键开始重新截图
            if self.capture_image is not None:
                self.capture_image = None
                self.paint_background_image()
                self.update()
            else:
                self.close()

3. 实现鼠标移动事件

鼠标移动事件主要的工作是获取当实时的鼠标坐标,然后调用widgets的update方法,更新界面,这里的代码如下

def mouseMoveEvent(self, event):
        if self.is_mouse_pressLeft is True:
            self.end_position = event.pos()
            self.update()

4. 实现鼠标放开事件

鼠标释放事件主要工作是记录鼠标释放时的坐标,然后将is_mouse_pressLeft置为false,标识鼠标不再是按下了,其代码如下

def mouseReleaseEvent(self, event):
        self.end_position = event.pos()
        self.is_mouse_pressLeft = False

5. 实现绘图事件

绘图事件其实是执行的两件事,一是将背景图片设置为桌面的截图,并且加深颜色,一是将没有加深颜色的矩形图片放到鼠标框选的区域

def paintEvent(self, event):
        self.painter.begin(self)  # 开始重绘
        self.paint_background_image()
        pen_color = QColor(30, 144, 245)  # 画笔颜色
        self.painter.setPen(QPen(pen_color, 1, Qt.SolidLine, Qt.RoundCap))  # 设置画笔,蓝色,1px大小,实线,圆形笔帽
        if self.is_mouse_pressLeft is True:
            pick_rect = self.get_rectangle(self.begin_position, self.end_position)  # 获得要截图的矩形框
            self.capture_image = self.full_screen_image.copy(pick_rect)  # 捕获截图矩形框内的图片
            self.painter.drawPixmap(pick_rect.topLeft(), self.capture_image)  # 填充截图的图片
            self.painter.drawRect(pick_rect)  # 画矩形边框
        self.painter.end()  # 结束重绘

代码中,self.paint_background_image()是绘制灰黑色背景的方法,他的实现代码如下

def paint_background_image(self):
        shadow_color = QColor(0, 0, 0, 100)  # 黑色半透明
        self.painter.drawPixmap(0, 0, self.full_screen_image)
        self.painter.fillRect(self.full_screen_image.rect(), shadow_color)  # 填充矩形阴影

self.get_rectangle(self.begin_position, self.end_position)计算鼠标框选区域矩形,其实现代码如下,

def get_rectangle(self, begin_point, end_point):
        pick_rect_width = int(qAbs(begin_point.x() - end_point.x()))
        pick_rect_height = int(qAbs(begin_point.y() - end_point.y()))
        pick_rect_top = begin_point.x() if begin_point.x() < end_point.x() else end_point.x()
        pick_rect_left = begin_point.y() if begin_point.y() < end_point.y() else end_point.y()
        pick_rect = QRect(pick_rect_top, pick_rect_left, pick_rect_width, pick_rect_height)
        # 避免高度宽度为0时候报错
        if pick_rect_width == 0:
            pick_rect.setWidth(2)
        if pick_rect_height == 0:
            pick_rect.setHeight(2)
        return pick_rect

计算矩形其实很简单,他的思想就是记录开始坐标和结束坐标,这样就可以通过结束坐标计算出矩形的宽度和高度,因此得出以下两个公式

H ( z ) = y 1 − y , W ( z ) = x 1 − x H(z) = y1-y, W(z) = x1-xH(z)=y1y,W(z)=x1x

这里还有两个重要的地方,就是截取鼠标矩形区域的图形,通过上面的方法计算出鼠标框选区域,然后拷贝鼠标框选区域的原始图片,我们采用full_screen_image.copy来实现

pick_rect = self.get_rectangle(self.begin_position, self.end_position)  # 获得要截图的矩形框
self.capture_image = self.full_screen_image.copy(pick_rect)  # 捕获截图矩形框内的图片

这里实际上实现的过程如下

通过开始坐标,和矩形的w和h,就可以确定我们要的是那一块的区域,传入full_screen_image.copy就能得到对应区域的截图,QRect就是这么一个数据类型,因此我们只要传入QRect类型的数据就可以了。

6. 鼠标双击事件

鼠标双击事件我们是调用了保存图片功能,然后关闭该窗口。

这里和后面的窗口程序之间的联动有联系,因此要联合后续内容看

其实现代码如下

def mouseDoubleClickEvent(self, event):
        if self.capture_image is not None:
            self.save_image()
            self.close()

3. 将截图传递到主界面

在controller包下创建文件CaptureScreen.py,并且继承UI_CaptureScreen.py,我们来写窗口之间的逻辑

我们在鼠标双击事件中调用了save_image(),这里我们实现一下

def save_image(self):
        self._signal[QPixmap].emit(self.capture_image)

这里触发了一个信号,以下是信号的定义

_signal = pyqtSignal(QPixmap)

触发信号可以给窗口之间传递数据,我们这里就是定义了一个QPixmap类型的信号,然后我们在MainWindow中来使用一个槽接收这个数据

self.screenWindow._signal[QPixmap].connect(self.handle_capture_picture)

然后就是处理这个数据的方法,就是收到数据以后,显示到Label上

@pyqtSlot(QPixmap)
    def handle_capture_picture(self, img):
        print("获取到图片", img)
        self.img_raw = img
        local_img = QPixmap(img).scaled(self.picture_label.width(), self.picture_label.height())
        # self.picture_label.setScaledContents(True)
        self.picture_label.setPixmap(local_img)

以上逻辑就是当save_image触发信号以后,MainWIndow的handle_capture_picture就会处理这个信号,然后将图片放到界面上。

4. 实现快捷键截图

实现快捷键截图这里使用的是system_hotkey库,也是使用的信号槽实现的全局热键,首先定义一个信号

sig_keyhot = pyqtSignal(str)

然后连接到处理函数

self.sig_keyhot[str].connect(self.MKey_pressEvent)

初始化两个热键,并且注册热键

self.hk_start, self.hk_stop = SystemHotkey(), SystemHotkey()
self.hk_start.register(('control', '1'), callback=lambda x: self.send_key_event("capture_start"))
self.hk_stop.register(('control', '2'), callback=lambda x: self.send_key_event("None"))

然后就是处理函数

@pyqtSlot(str)
    def MKey_pressEvent(self, i_str):
        if i_str == 'capture_start':
            self.screenWindow.show()
        elif i_str == 'None':
            QMessageBox.information(self, '温馨提示', '其他功能请等待后续添加哦')

总结

以上就是本篇的全部内容。本篇完成本项目的截图功能,采用pyqt原生的方式实现截图,并且与主界面进行响应,这也是PyQt5DesignMode的强大功能之一,提高了程序的开发效率。

本次的小工具只是这个项目中的一部分,实际上后面我们还会加入pdf格式转化等工具,直到本项目做到可以发布的地步,请期待后面的文章吧。

PyQt5DesignMode是本人结合MVC思想与pyqt5实现的一个项目模板,旨在可以用pyqt5实现多窗口应用,如果你感兴趣就给我个star吧。

欢迎订阅本专栏,学习更多python知识。

目录
相关文章
|
5天前
|
缓存 监控 测试技术
Python中的装饰器:功能扩展与代码复用的利器###
本文深入探讨了Python中装饰器的概念、实现机制及其在实际开发中的应用价值。通过生动的实例和详尽的解释,文章展示了装饰器如何增强函数功能、提升代码可读性和维护性,并鼓励读者在项目中灵活运用这一强大的语言特性。 ###
|
8天前
|
缓存 开发者 Python
探索Python中的装饰器:简化代码,增强功能
【10月更文挑战第35天】装饰器在Python中是一种强大的工具,它允许开发者在不修改原有函数代码的情况下增加额外的功能。本文旨在通过简明的语言和实际的编码示例,带领读者理解装饰器的概念、用法及其在实际编程场景中的应用,从而提升代码的可读性和复用性。
|
13天前
|
设计模式 缓存 测试技术
Python中的装饰器:功能增强与代码复用的艺术####
本文将深入探讨Python中装饰器的概念、用途及实现方式,通过实例演示其如何为函数或方法添加新功能而不影响原有代码结构,从而提升代码的可读性和可维护性。我们将从基础定义出发,逐步深入到高级应用,揭示装饰器在提高代码复用性方面的强大能力。 ####
|
14天前
|
缓存 测试技术 数据安全/隐私保护
探索Python中的装饰器:简化代码,增强功能
【10月更文挑战第29天】本文通过深入浅出的方式,探讨了Python装饰器的概念、使用场景和实现方法。文章不仅介绍了装饰器的基本知识,还通过实例展示了如何利用装饰器优化代码结构,提高代码的可读性和重用性。适合初学者和有一定经验的开发者阅读,旨在帮助读者更好地理解和应用装饰器,提升编程效率。
|
15天前
|
弹性计算 Linux iOS开发
Python 虚拟环境全解:轻松管理项目依赖
本文详细介绍了 Python 虚拟环境的概念、创建和使用方法,包括 `virtualenv` 和 `venv` 的使用,以及最佳实践和注意事项。通过虚拟环境,你可以轻松管理不同项目的依赖关系,避免版本冲突,提升开发效率。
|
21天前
|
开发者 Python
探索Python中的装饰器:简化代码,增强功能
【10月更文挑战第22天】在Python的世界里,装饰器是一个强大的工具,它能够让我们以简洁的方式修改函数的行为,增加额外的功能而不需要重写原有代码。本文将带你了解装饰器的基本概念,并通过实例展示如何一步步构建自己的装饰器,从而让你的代码更加高效、易于维护。
|
15天前
|
文字识别 自然语言处理 API
Python中的文字识别利器:pytesseract库
`pytesseract` 是一个基于 Google Tesseract-OCR 引擎的 Python 库,能够从图像中提取文字,支持多种语言,易于使用且兼容性强。本文介绍了 `pytesseract` 的安装、基本功能、高级特性和实际应用场景,帮助读者快速掌握 OCR 技术。
32 0
|
Python 人工智能 小程序
拯救Python新手的几个项目实战
Python 做小游戏 实例一:24点游戏 项目名称:经典趣味24点游戏程序设计(python) 如果你不想错过Python这么好的工具,又担心自学遇到问题无处解决,现在就可以Python的学习q u n 227-435-450可以来了解一起进步一起学习!免费分享视频资料 实例二:五子棋游戏 项目...
3753 0
|
4天前
|
机器学习/深度学习 人工智能 TensorFlow
人工智能浪潮下的自我修养:从Python编程入门到深度学习实践
【10月更文挑战第39天】本文旨在为初学者提供一条清晰的道路,从Python基础语法的掌握到深度学习领域的探索。我们将通过简明扼要的语言和实际代码示例,引导读者逐步构建起对人工智能技术的理解和应用能力。文章不仅涵盖Python编程的基础,还将深入探讨深度学习的核心概念、工具和实战技巧,帮助读者在AI的浪潮中找到自己的位置。
|
4天前
|
机器学习/深度学习 数据挖掘 Python
Python编程入门——从零开始构建你的第一个程序
【10月更文挑战第39天】本文将带你走进Python的世界,通过简单易懂的语言和实际的代码示例,让你快速掌握Python的基础语法。无论你是编程新手还是想学习新语言的老手,这篇文章都能为你提供有价值的信息。我们将从变量、数据类型、控制结构等基本概念入手,逐步过渡到函数、模块等高级特性,最后通过一个综合示例来巩固所学知识。让我们一起开启Python编程之旅吧!