自制 Windows 小工具 ———— 智能截屏工具

自制 Windows 小工具 ———— 智能截屏工具

  • 背景
  • 需求分析
  • 环境准备
  • 功能实现
    • 获取截图区域
    • 获取文件保存路径
    • 图像算法
    • 截屏
    • 快捷启动

背景

上网课期间,很多同学(包括老师)都会选择(提倡)截屏来记录笔记,然后课后再进行整理。但是往往越截越多,到后来干脆每张都截,课后面对几十张的照片直接泄了气,不写了。另外有些同学,继续保持在课堂上的习惯,手写笔记。但是没了黑板的帮助,老师又默认PPT上的重点大家都会截图,往往过的非常快,手写时间根本不够,最后笔记跟不上,越落越多,最后也泄了气,不写了
那么,如果既想截图,又想快速写笔记,还想着摸鱼,该怎么办呢?
当然是制作工具了 ———— 智能截屏

需求分析

  1. 将老师的重点截屏(不能遗漏)
  2. 尽量避免重复的图片,方便课后整理
  3. 操作简单智能(上课时不需要监管程序)

环境准备

  • python
  • pyautogui
  • opencv
  • numpy
  • PIL
    后面的几项都是第三方库,直接 pip即可,大家自行百度

功能实现

获取截图区域

既让要操作简单,那么图形化必然少不了,用户也可能需要自己设置截图区域
这里和上一篇文章的截图部分很像,就不做过多分析
代码大家自行研究,还是封装一个模块ScreenArea.py

import tkinter as tk
import pyautogui

#鼠标左键按下
def button_1(event):
   global x, y ,xstart,ystart
   x, y = event.x, event.y
   xstart,ystart = event.x, event.y
   xstart,ystart = event.x, event.y  
   cv.configure(height=1)
   cv.configure(width=1)
   cv.place(x=event.x, y=event.y)

   
#鼠标左键按下并移动    
def b1_Motion(event):
   global x, y
   x, y = event.x, event.y
   global cv
   cv.configure(height = event.y - ystart)
   cv.configure(width = event.x - xstart)
   
#鼠标左键松开
def buttonRelease_1(event):
   global x, y,xstart,ystart
   x, y = event.x, event.y
   if x == xstart and y == ystart:
       return
   global cv
   global root
   Pstart=[0,0]
   cv.place_forget()
   root.attributes("-alpha", 0) 
   root.destroy()

# 退出
def sys_out(even = True):
   global root
   #from tkinter import messagebox
   #if messagebox.askokcancel('Exit','Confirm to exit?'):
   root.destroy()

def SelectArea():
   global root
   root = tk.Tk()
   root.overrideredirect(True)         # 隐藏窗口的标题栏
   # root.attributes("-alpha", 0.3)    # 窗口透明度70 %
   root.attributes("-alpha", 0.3)      # 窗口透明度60 %
   root.attributes("-topmost", 1)
   #root.geometry("300x200+10+10")      # 设置窗口大小与位置
   root.geometry("{0}x{1}+0+0".format(root.winfo_screenwidth(), root.winfo_screenheight()))
   root.configure(bg="black")

   # 再创建1个Canvas用于圈选
   global cv, x, y, xstart, ystart
   cv = tk.Canvas(root)
   x, y = 0, 0
   xstart,ystart = 0 ,0

   # 绑定事件到Esc键,当按下Esc键就会调用sys_out函数,弹出对话框
   root.bind('',sys_out)
   root.bind("", button_1)
   root.bind("", b1_Motion)
   root.bind("", buttonRelease_1)
   root.mainloop()

   return (xstart,ystart,x - xstart,y - ystart) # 左上角坐标,宽,高

if __name__ == "__main__":
   print(SelectArea())

获取文件保存路径

虽然智能过滤了相似的图片,但是图片量仍然很大,所以要让用户可以自行选择保存路径
使用 tkinter 的文件选择框 主程序 screenAI.py 中 实现

import screenArea
from tkinter import filedialog
import time

def StartscreenAI(): # 一会儿可以绑定快捷键
    global x,y,width,height
    x,y,width,height = screenArea.SelectArea() # 获取截图区域
    global save_path
    save_path = filedialog.askdirectory()
    print(save_path)

if __name__ == "__main__":
    time.sleep(3) # 给操作者一个反应的时间
    StartscreenAI()

图像算法

这里为了过滤相似的图片,我们用到了一些图像处理算法,为了更加精确和灵活,我一下采用了五种算法

  1. 均值哈希算法
  2. 差值哈希算法
  3. 感知哈希算法
  4. 灰度直方图算法
  5. RGB每个通道的直方图相似度

五个算法根据场景进行加权平均得到相似值。这里封装一个模块进行实现 imageAI.py

import cv2
import numpy as np

N = 16 # 压缩程度,越大越精确,但是空间和时间越多 注意为 2 的次幂

def aHash(img):
   # 均值哈希算法
   # 缩放为N*N
   global N
   img = cv2.resize(img, (N, N))
   # 转换为灰度图
   gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
   # s为像素和初值为0,hash_str为hash值初值为''
   s = 0
   hash_str = ''
   # 遍历累加求像素和
   for i in range(N):
       for j in range(N):
           s = s+gray[i, j]
   # 求平均灰度
   avg = s/N/N
   # 灰度大于平均值为1相反为0生成图片的hash值
   for i in range(N):
       for j in range(N):
           if gray[i, j] > avg:
               hash_str = hash_str+'1'
           else:
               hash_str = hash_str+'0'
   return hash_str

def dHash(img):
   global N
   # 差值哈希算法
   # 缩放N * N
   img = cv2.resize(img, (N + 1, N))
   # 转换灰度图
   gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
   hash_str = ''
   # 每行前一个像素大于后一个像素为1,相反为0,生成哈希
   for i in range(N):
       for j in range(N):
           if gray[i, j] > gray[i, j+1]:
               hash_str = hash_str+'1'
           else:
               hash_str = hash_str+'0'
   return hash_str

def pHash(img):
   # 感知哈希算法
   # 缩放4n * 4n
   global N
   img = cv2.resize(img, (4 * N, 4 * N))   # , interpolation=cv2.INTER_CUBIC
 
   # 转换为灰度图
   gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
   # 将灰度图转为浮点型,再进行dct变换
   dct = cv2.dct(np.float32(gray))
   # opencv实现的掩码操作
   dct_roi = dct[0:N, 0:N]
 
   hash = []
   avreage = np.mean(dct_roi)
   for i in range(dct_roi.shape[0]):
       for j in range(dct_roi.shape[1]):
           if dct_roi[i, j] > avreage:
               hash.append(1)
           else:
               hash.append(0)
   return hash

def cmpHash(hash1, hash2):
   # Hash值对比
   # 算法中1和0顺序组合起来的即是图片的指纹hash。顺序不固定,但是比较的时候必须是相同的顺序。
   # 对比两幅图的指纹,计算汉明距离,即两个64位的hash值有多少是不一样的,不同的位数越小,图片越相似
   # 汉明距离:一组二进制数据变成另一组数据所需要的步骤,可以衡量两图的差异,汉明距离越小,则相似度越高。汉明距离为0,即两张图片完全一样
   N = 0
   # hash长度不同则返回-1代表传参出错
   if len(hash1) != len(hash2):
       return -1
   # 遍历判断
   for i in range(len(hash1)):
       # 不相等则n计数+1,n最终为相似度
       if hash1[i] != hash2[i]:
           N = N + 1
   return N

def calculate(image1, image2):
   # 灰度直方图算法
   # 计算单通道的直方图的相似值
   hist1 = cv2.calcHist([image1], [0], None, [256], [0.0, 255.0])
   hist2 = cv2.calcHist([image2], [0], None, [256], [0.0, 255.0])
   # 计算直方图的重合度
   degree = 0
   for i in range(len(hist1)):
       if hist1[i] != hist2[i]:
           degree = degree + \
               (1 - abs(hist1[i] - hist2[i]) / max(hist1[i], hist2[i]))
       else:
           degree = degree + 1
   degree = degree / len(hist1)
   return float(degree)

def classify_hist_with_split(image1, image2, size=(256, 256)):
   # RGB每个通道的直方图相似度
   # 将图像resize后,分离为RGB三个通道,再计算每个通道的相似值
   image1 = cv2.resize(image1, size)
   image2 = cv2.resize(image2, size)
   sub_image1 = cv2.split(image1)
   sub_image2 = cv2.split(image2)
   sub_data = 0
   for im1, im2 in zip(sub_image1, sub_image2):
       sub_data += calculate(im1, im2)
   sub_data = sub_data / 3
   return float(sub_data)

截屏

在主程序中控制截图与保存 screenAI.py

import screenArea
import imageAI
from tkinter import filedialog
import time
import os
import cv2
import numpy as np
import pyautogui

def ScreenCut():
    global x,y,width,height
    return pyautogui.screenshot(region = (x,y,width,height))

def SaveFile(img):
    global pic_cnt
    global save_path
    pic_cnt += 1
    print('保存了图片',save_path + '/' + str(pic_cnt) + '.png')
    img.save(save_path + '/' + str(pic_cnt) + '.png')

def StartscreenAI(): # 一会儿可以绑定快捷键
    global x,y,width,height
    x,y,width,height = screenArea.SelectArea() # 获取截图区域
    global save_path

    try:
        save_path = filedialog.askdirectory()
    except:
        print('已取消')
        return

    global pic_cnt
    pic_cnt = 0
    
    try:
        filenames = os.listdir(save_path)
    except:
        print('已取消(或无效路径)')
        return
        
    for filename in filenames:
        try:
            if pic_cnt < int(filename.replace('.png','')):
                pic_cnt = int(filename.replace('.png',''))
        except:
            pass
    
    # 一个小小的优化,自动找到未被使用的编号,防止多次启动覆盖

    lstimg = ScreenCut()
    lstcv2img = cv2.cvtColor(np.asarray(lstimg), cv2.COLOR_RGB2BGR)
    lstdhash = imageAI.dHash(lstcv2img)
    lstahash = imageAI.aHash(lstcv2img)
    lstphash = imageAI.pHash(lstcv2img)
    SaveFile(lstimg)

    global SCREENCUT

    SCREENCUT = True

    while SCREENCUT:
        nowimg = ScreenCut()
        nowcv2img = cv2.cvtColor(np.asarray(nowimg), cv2.COLOR_RGB2BGR)
        nowdhash = imageAI.dHash(nowcv2img)
        nowahash = imageAI.aHash(nowcv2img)
        nowphash = imageAI.pHash(nowcv2img)
        nowcalculate = round(imageAI.calculate(nowcv2img,lstcv2img),8)
        nowclassify_hist_with_split = imageAI.classify_hist_with_split(nowcv2img,lstcv2img)
        dn1 = imageAI.cmpHash(lstdhash,nowdhash)
        an1 = imageAI.cmpHash(lstahash,nowahash)
        pn1 = imageAI.cmpHash(lstphash,nowphash)
        arv = ((imageAI.N * imageAI.N - dn1) / imageAI.N / imageAI.N * 2 + (imageAI.N * imageAI.N - an1) / imageAI.N / imageAI.N + (imageAI.N * imageAI.N - pn1) / imageAI.N / imageAI.N + nowcalculate) / 5 # PPT 的较好算法

        if arv < 0.85: # ppt的较好参数
            SaveFile(nowimg)
            lstcv2img = nowcv2img
            lstdhash = nowdhash
            lstahash = nowahash
            lstphash = nowphash

if __name__ == "__main__":
    time.sleep(3) # 给操作者一个反应的时间
    StartscreenAI()

到目前为止,一次启动已经做好

快捷启动

接下来,为了多课程之间的切换和停止,我们给程序增加一些快捷键 screenAI.py
注意,因为StartscreenAI中有死循环,会影响keyboard的循环,所以要停止的检测要在while循环中用is_pressed

import screenArea
import imageAI
from tkinter import filedialog
import time
import os
import cv2
import numpy as np
import pyautogui
import keyboard
import threading

SCREENCUT = False

def ScreenCut():
    global x,y,width,height
    return pyautogui.screenshot(region = (x,y,width,height))

def SaveFile(img):
    global pic_cnt
    global save_path
    pic_cnt += 1
    print('保存了图片',save_path + '/' + str(pic_cnt) + '.png')
    img.save(save_path + '/' + str(pic_cnt) + '.png')

def StartscreenAI(): # 一会儿可以绑定快捷键
    global SCREENCUT
    if SCREENCUT != False:
        print('已开始,ctrl+alt+e 停止')
        return

    global x,y,width,height
    x,y,width,height = screenArea.SelectArea() # 获取截图区域
    global save_path

    try:
        save_path = filedialog.askdirectory()
    except:
        print('已取消')
        return

    global pic_cnt
    pic_cnt = 0
    
    try:
        filenames = os.listdir(save_path)
    except:
        print('已取消(或无效路径)')
        return

    for filename in filenames:
        try:
            if pic_cnt < int(filename.replace('.png','')):
                pic_cnt = int(filename.replace('.png',''))
        except:
            pass
    
    print('选择了路径: %s 已有编号 %d' % (save_path,pic_cnt))
    
    # 一个小小的优化,自动找到未被使用的编号,防止多次启动覆盖

    lstimg = ScreenCut()
    lstcv2img = cv2.cvtColor(np.asarray(lstimg), cv2.COLOR_RGB2BGR)
    lstdhash = imageAI.dHash(lstcv2img)
    lstahash = imageAI.aHash(lstcv2img)
    lstphash = imageAI.pHash(lstcv2img)
    SaveFile(lstimg)

    SCREENCUT = True

    while SCREENCUT:
        if keyboard.is_pressed('ctrl+alt+e'):
            SCREENCUT = False
        nowimg = ScreenCut()
        nowcv2img = cv2.cvtColor(np.asarray(nowimg), cv2.COLOR_RGB2BGR)
        nowdhash = imageAI.dHash(nowcv2img)
        nowahash = imageAI.aHash(nowcv2img)
        nowphash = imageAI.pHash(nowcv2img)
        nowcalculate = round(imageAI.calculate(nowcv2img,lstcv2img),8)
        nowclassify_hist_with_split = imageAI.classify_hist_with_split(nowcv2img,lstcv2img)
        dn1 = imageAI.cmpHash(lstdhash,nowdhash)
        an1 = imageAI.cmpHash(lstahash,nowahash)
        pn1 = imageAI.cmpHash(lstphash,nowphash)
        arv = ((imageAI.N * imageAI.N - dn1) / imageAI.N / imageAI.N * 2 + (imageAI.N * imageAI.N - an1) / imageAI.N / imageAI.N + (imageAI.N * imageAI.N - pn1) / imageAI.N / imageAI.N + nowcalculate) / 5 # PPT 的较好算法

        if arv < 0.85: # ppt的较好参数
            SaveFile(nowimg)
            lstcv2img = nowcv2img
            lstdhash = nowdhash
            lstahash = nowahash
            lstphash = nowphash
    
    print('截图已结束')

if __name__ == "__main__":
    keyboard.add_hotkey('ctrl+alt+b',StartscreenAI)
    keyboard.wait()

大功告成,希望可以帮助到专心学习的你

希望大家在评论区分享更好的做法
希望大家关注,评论,点赞

你可能感兴趣的:(python,实用编程,python,开发语言)