【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. 如果是鼠标左键就记录鼠标起始位置,否则就取消当前操作

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

Created with Raphaël 2.3.0 开始 是否左键? 记录当前坐标 设置属性is_mouse_pressLeft为True 结束 处理右键 是否选择截图区域? 取消截图区域 取消当前操作 yes no yes no

实现代码如下,

    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-x H(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知识。

猜你喜欢

转载自blog.csdn.net/weixin_47754149/article/details/127470534