520桌面手势告白

912 阅读6分钟

前言

OK,又要到了一年一度的传统了从2019,2020,2021到 2022 。每年520都会做一个小demo,今年也不能断,虽然每年都用不上,不过还是值得记录一下的,最起码你会发现我写的玩意不是从别人那里copy的,别人是没有的,甚至有些文章别人连抄袭的能力都没有(因为我没发~)

大道至简嘛,本来我是打算把先前写的手势画板和这个表白整合一下的,但是怎么说呢,这种类型的文章毕竟是要照顾到大部分人的,所以就需要尽可能的简单一点。所以我们就还是来简单一点的吧,争取大家都可以复现出来,最近时间忙所以,以前按照惯例都是会提取五天发布的,就是为了方便各位复现,不过最近是真的没空。一直熬夜干比赛。

看到这篇博文便是缘分,那么开始吧。

设计灵感

这个灵感的话其实是参考我高中写的第二个表白demo。 教你一招520Python表白(图片,爬虫 处理)!!!

当时是使用爬虫获取图片,然后开启多线程播放背景音乐,然后动态切换表白桌面背景。当时觉得挺好玩的,还在学校白板演示了一下,可惜当时没有女孩子配得上那个demo,现在也没有,以后也不知道。 然后原来那里我是说来个定时器,等女朋友过来那啥,那么现在我们把定时器改一下,用手势触发即可。

所以我就想,干脆做一个整合,基于mediapipe 来做一个。

所以我们把原来这样的代码:

import mediapipe as mp
import cv2
import os

cap = cv2.VideoCapture(0)
cap.set(3,1280)
cap.set(4,720)
mpHand = mp.solutions.hands #mp的手部捕捉
Hand = mpHand.Hands() #找到图片当中的手
mphanddraw = mp.solutions.drawing_utils #绘制工具

MediaPath = "Media"
picsdir = os.listdir(MediaPath)
pics = []
for pic in picsdir:
    img = cv2.imread(f"{MediaPath}/{pic}")
    pics.append(img)




TipsId = [4,8,12,16,20] #定点的坐标
while True:
    flag,img = cap.read()

    RGBImage = cv2.cvtColor(img,cv2.COLOR_BGR2RGB) #把图片进行转换
    result = Hand.process(RGBImage)

    if(result.multi_hand_landmarks): #如果有手,那么就会得到手的列表记录了手的坐标

        hands_data = result.multi_hand_landmarks

        for handlist in hands_data:
            h, w, c = img.shape

            fingers = []

            #判断大拇指的开关
            if(handlist.landmark[TipsId[0]-2].x < handlist.landmark[TipsId[0]+1].x):
                if handlist.landmark[TipsId[0]].x >  handlist.landmark[TipsId[0]-1].x:
                    fingers.append(0)
                else:
                    fingers.append(1)
            else:
                if handlist.landmark[TipsId[0]].x < handlist.landmark[TipsId[0] - 1].x:
                    fingers.append(0)
                else:
                    fingers.append(1)
            # 判断其他手指
            for id in range(1,5):
                if(handlist.landmark[TipsId[id]].y > handlist.landmark[TipsId[id]-2].y):
                    fingers.append(0)
                else:
                    fingers.append(1)
            # 获得手指个数,绘制图片
            totoalfingle = fingers.count(1)

            coverpic = pics[totoalfingle-1]
            hc, wc, cc = coverpic.shape
            img[0:wc,0:hc] = coverpic

            # 这个只是绘制手指关节的,可以忽略这段代码
            for id,lm in enumerate(handlist.landmark):

                cx,cy = int(lm.x * w),int(lm.y * h)
                cv2.circle(img,(cx,cy),15,(255,0,255),cv2.FILLED)

            mphanddraw.draw_landmarks(img,handlist,mpHand.HAND_CONNECTIONS,)


    cv2.imshow("Hands",img)

    if(cv2.waitKey(1)==ord("q")):
        break

cap.release()
cv2.destroyAllWindows()

这样的效果 在这里插入图片描述 改成桌面上显示,然后把摄像头显示去掉。

当然,如果你喜欢这个效果,你可以去这里

资源准备

老规矩还是需要资源准备的,现在我们其实只是把原来在界面显示的玩意,给搞到桌面了,就像这样 在这里插入图片描述 图片有点模糊到时候自己注意换就好了。

项目结构如下: 在这里插入图片描述

有用的代码就两个。

第一个文件是存图片的 第二个是临时文件 第三个是存视频的

实现

工具类

现在我们需要准备一个工具类,用于放置背景和加载视频


""""
此工具类负责完成背景图片的替换和动态视频的设置
动作识别在Main函数中完成操作
"""
import ctypes
import os
import shutil
import win32gui
import cv2

class Utils(object):
    def __init__(self):
        self.Current_Path = os.path.split(os.path.realpath(__file__))[0]

    def setBG(self,path):
        ctypes.windll.user32.SystemParametersInfoW(20, 0, path, 0)

    def setRunBg(self,Flag):
        if(Flag):
            TEMPPATH = "TEMP"
            picsdir = os.listdir(TEMPPATH)
            pics = []
            for pic in picsdir:

                img = f"{self.Current_Path}/{TEMPPATH}/{pic}"
                pics.append(img)
            for path in pics:
                ctypes.windll.user32.SystemParametersInfoW(20, 0, path, 0)

            """删除临时文件"""
            filepath = f"{self.Current_Path}/{TEMPPATH}"
            if not os.path.exists(filepath):
                os.mkdir(filepath)
            else:
                shutil.rmtree(filepath)
                os.mkdir(filepath)


    def video2frame(self,videos_path, frames_save_path, time_interval):
        '''
        :param videos_path: 视频的存放路径
        :param frames_save_path: 视频切分成帧之后图片的保存路径
        :param time_interval: 保存间隔
        :return:
        '''

        vidcap = cv2.VideoCapture(videos_path)
        success, image = vidcap.read()
        count = 0
        while success:
            success, image = vidcap.read()
            count += 1
            if count % time_interval == 0:
                try:
                    cv2.imencode('.jpg', image)[1].tofile(frames_save_path + "/%d.jpg" % count)
                except Exception as e:
                    print(count)
                    return frames_save_path


if __name__ == '__main__':
    pass

这里的话我们是使用视频截帧的方式,来换换背景实现这个动态背景的。

但是这个方法的话实际测试效果不好,如果有能力的朋友可以直接参考这玩意来实现。

参考代码

import pyglet
from PIL import ImageSequence,Image
import win32gui, win32ui, win32con


class AnimationSrn:
    
    def __init__(self):
        
        parenthwnd = self.getScreenHandle()
        print(parenthwnd )
        left, top, right, bottom = win32gui.GetWindowRect(parenthwnd)
        self.size = (right - left, bottom -top)
        self.gifpath = self.resizeGif()

    def frameIterator(self, frames):
        for frame in frames:
            framecopy = frame.copy()
            # print (type (framecopy))
            framecopy = framecopy.resize(self.size, Image.ANTIALIAS)
            yield framecopy

    # 返回一^迭代器,迭代gi仲的每一帧图像
    def resizeGif(self, originpath="gif2.gif"):
        img = Image.open(originpath)


        #获取gif的每帧图像的顺序迭代器
        # Get sequence iterator
        frames = ImageSequence.Iterator(img)

        #print(dir(self))
        frames = self.frameIterator(frames) #对每一帧图像调整其分辨率
        print(type(frames))

        outimg = next(frames) # Handle first frame separately
        outimg.info = img.info # H制顺序信息
        savepath = originpath.replace('.','_resize.')
        outimg.save(savepath, save_all=True, append_images=list(frames))
        return savepath
        
    def getScreenHandle(self):
        hwnd = win32gui.FindWindow("Progman", "Program Manager")
        win32gui.SendMessageTimeout(hwnd, 0x052C, 0, None, 0, 0x03E8)
        hwnd_WorkW = None
        while 1:
            hwnd_WorkW = win32gui.FindWindowEx(None, hwnd_WorkW, "WorkerW", None)
            if not hwnd_WorkW:
                continue
            hView = win32gui.FindWindowEx(hwnd_WorkW, None, "SHELLDLL_DefView", None)
            if not hView:
                continue
            h = win32gui.FindWindowEx(None, hwnd_WorkW, "WorkerW", None)
            while h:
                win32gui.SendMessage(h, 0x0010, 0, 0); # WM_CLOSE
                h = win32gui.FindWindowEx(None, hwnd_WorkW, "WorkerW", None)
            break
        return hwnd
        '''
        return win32gui.GetDesktopWindow()
        '''

        
    def putGifScreen(self):
        parenthwnd = self.getScreenHandle()

        #使用pyglet加载动画
        # print ("1ll", parenthwnd)
        animation = pyglet.image.load_animation(self.gifpath) #使用pyglet 加载一个gif 动图
        sprite = pyglet.sprite.Sprite(animation) # 创建一个动画

        #创建一个新的窗口
        #创建-个窗口, 并将其设置为图像大小
        newwin = pyglet.window.Window(width=sprite.width,
                                    height=sprite.height,
                                style=pyglet.window.Window.WINDOW_STYLE_BORDERLESS)

        #将默认的背景图的父窗口改为新创建的窗口
        # print(win._hwnd)
        win32gui.SetParent(newwin._hwnd, parenthwnd)
        
        @newwin.event #事件处理程序的函数装饰器.用來显示图像
        def on_draw():                         
            newwin.clear()
            sprite.draw()                                  
        pyglet.app.run()    
        
        
        
if __name__ == '__main__':
    AnimationSrn().putGifScreen()
    

我这里就不搞了,改动起来也简单,就是把当前绘制的窗口给到windows的第二个窗口句柄里面绘制。

想仔细知道原理的可以去B站搜“水哥”我记得是有一个视频说过这个玩意的原理的。

主函数

import mediapipe as mp
import cv2
import os
import ctypes
from utils import Utils
def main():

    """
    初始化,摄像头
    :return:
    """
    cap = cv2.VideoCapture(0)
    cap.set(3, 400)
    cap.set(4, 300)
    mpHand = mp.solutions.hands  # mp的手部捕捉
    Hand = mpHand.Hands()  # 找到图片当中的手
    mphanddraw = mp.solutions.drawing_utils  # 绘制工具
    utils = Utils()
    Current_Path = os.path.split(os.path.realpath(__file__))[0]
    Flag = False

    """
    加载媒体资源
    """
    MediaPath = "Media"
    picsdir = os.listdir(MediaPath)
    pics = []
    for pic in picsdir:
        # img = cv2.imread(f"{MediaPath}/{pic}")
        img = f"{Current_Path}/{MediaPath}/{pic}"
        pics.append(img)


    """加载视频"""
    #utils.video2frame(f"{Current_Path}/Viedio/show.mp4",f"{Current_Path}/Temp",8)
    #Flag = True


    TipsId = [4, 8, 12, 16, 20]  # 定点的坐标
    while True:
        flag, img = cap.read()

        RGBImage = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 把图片进行转换
        result = Hand.process(RGBImage)

        if (result.multi_hand_landmarks):  # 如果有手,那么就会得到手的列表记录了手的坐标

            hands_data = result.multi_hand_landmarks

            for handlist in hands_data:
                h, w, c = img.shape

                fingers = []

                # 判断大拇指的开关
                if (handlist.landmark[TipsId[0] - 2].x < handlist.landmark[TipsId[0] + 1].x):
                    if handlist.landmark[TipsId[0]].x > handlist.landmark[TipsId[0] - 1].x:
                        fingers.append(0)
                    else:
                        fingers.append(1)
                else:
                    if handlist.landmark[TipsId[0]].x < handlist.landmark[TipsId[0] - 1].x:
                        fingers.append(0)
                    else:
                        fingers.append(1)
                # 判断其他手指
                for id in range(1, 5):
                    if (handlist.landmark[TipsId[id]].y > handlist.landmark[TipsId[id] - 2].y):
                        fingers.append(0)
                    else:
                        fingers.append(1)

                # 获得手指个数,绘制图片
                """
                在这里进行手势的判断
                """
                totoalfingle = fingers.count(1)

                # coverpic = pics[totoalfingle - 1]
                # hc, wc, cc = coverpic.shape
                # img[0:wc, 0:hc] = coverpic

                if(totoalfingle==5):
                    utils.setBG(pics[2])
                elif(totoalfingle==2):
                    utils.setBG(pics[1])
                elif(totoalfingle==0):
                    utils.setBG(pics[0])
                    #utils.setRunBg(Flag)
                    #utils.setBG(pics[3])
                    break


                # 这个只是绘制手指关节的,可以忽略这段代码
                for id, lm in enumerate(handlist.landmark):
                    cx, cy = int(lm.x * w), int(lm.y * h)
                    cv2.circle(img, (cx, cy), 15, (255, 0, 255), cv2.FILLED)

                mphanddraw.draw_landmarks(img, handlist, mpHand.HAND_CONNECTIONS, )

        cv2.imshow("Hands", img) #自己看着要不要显示cv2的窗口,想要给点浪漫就把这个注释了

        if (cv2.waitKey(1) == ord("q")):
            break

    cap.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    main()

效果

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述

这个图片的质量比较低,所以你们自己玩的时候,记得搞一些质量好的。