520桌面手勢告白

語言: CN / TW / HK

theme: channing-cyan highlight: androidstudio


前言

OK,又要到了一年一度的傳統了從2019,2020,2021到 2022 。每年520都會做一個小demo,今年也不能斷,雖然每年都用不上,不過還是值得記錄一下的,最起碼你會發現我寫的玩意不是從別人那裡copy的,別人是沒有的,甚至有些文章別人連抄襲的能力都沒有(因為我沒發~)

大道至簡嘛,本來我是打算把先前寫的手勢畫板和這個表白整合一下的,但是怎麼說呢,這種型別的文章畢竟是要照顧到大部分人的,所以就需要儘可能的簡單一點。所以我們就還是來簡單一點的吧,爭取大家都可以復現出來,最近時間忙所以,以前按照慣例都是會提取五天釋出的,就是為了方便各位復現,不過最近是真的沒空。一直熬夜幹比賽。

看到這篇博文便是緣分,那麼開始吧。

設計靈感

這個靈感的話其實是參考我高中寫的第二個表白demo。 教你一招520Python表白(圖片,爬蟲 處理)!!!

當時是使用爬蟲獲取圖片,然後開啟多執行緒播放背景音樂,然後動態切換表白桌面背景。當時覺得挺好玩的,還在學校白板演示了一下,可惜當時沒有女孩子配得上那個demo,現在也沒有,以後也不知道。 然後原來那裡我是說來個定時器,等女朋友過來那啥,那麼現在我們把定時器改一下,用手勢觸發即可。

所以我就想,乾脆做一個整合,基於mediapipe 來做一個。

所以我們把原來這樣的程式碼:

```java 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()

```

這樣的效果 在這裡插入圖片描述 改成桌面上顯示,然後把攝像頭顯示去掉。

當然,如果你喜歡這個效果,你可以去這裡

資源準備

老規矩還是需要資源準備的,現在我們其實只是把原來在介面顯示的玩意,給搞到桌面了,就像這樣 在這裡插入圖片描述 圖片有點模糊到時候自己注意換就好了。

專案結構如下: 在這裡插入圖片描述

有用的程式碼就兩個。

第一個檔案是存圖片的 第二個是臨時檔案 第三個是存影片的

實現

工具類

現在我們需要準備一個工具類,用於放置背景和載入影片

```python

"""" 此工具類負責完成背景圖片的替換和動態影片的設定 動作識別在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 ```

這裡的話我們是使用影片截幀的方式,來換換背景實現這個動態背景的。

但是這個方法的話實際測試效果不好,如果有能力的朋友可以直接參考這玩意來實現。

參考程式碼: ```python 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站搜“水哥”我記得是有一個影片說過這個玩意的原理的。

主函式

```python 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() ```

效果

在這裡插入圖片描述 在這裡插入圖片描述 在這裡插入圖片描述

這個圖片的質量比較低,所以你們自己玩的時候,記得搞一些質量好的。