520桌面手勢告白
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() ```
效果
這個圖片的質量比較低,所以你們自己玩的時候,記得搞一些質量好的。
- 還在調API寫所謂的AI“女友”,嘮了嘮了,教你基於python咱們“new”一個(深度學習)
- Java前後端分離實戰Auto2.0使用者登入註冊--基本的使用者登入 郵箱驗證
- 卡爾曼濾波器(目標跟蹤一)(上)
- 手把手教你如何自制目標檢測框架(從理論到實現)
- 基於Python深度圖生成3D點雲
- Pandas基礎使用(機器學習基礎)
- CEC2017基礎函式說明Python版本
- 全國空氣質量爬取實戰
- 智慧演算法整合測試平臺V0.1實戰開發
- DDPG神經網路實戰(基於強化學習優化粒子群演算法)
- 關於強化學習優化粒子群演算法的論文解讀(全)
- 關於強化學習優化粒子群演算法的論文解讀(上)
- 基於多種群機制的PSO演算法(優化與探索三 *混合種群思想優化多種群與廣義PSO求解JSP)
- 基於多種群機制的PSO演算法Python實現(優化與探索二)
- 基於多種群機制的PSO演算法Python實現
- 520桌面手勢告白
- 嘿~瞎話一下,為啥要用Sigmoid?!