520桌面手势告白
Posted Huterox
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了520桌面手势告白相关的知识,希望对你有一定的参考价值。
文章目录
前言
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()
效果
这个图片的质量比较低,所以你们自己玩的时候,记得搞一些质量好的。
开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系以上是关于520桌面手势告白的主要内容,如果未能解决你的问题,请参考以下文章
圣诞纯情手势告白(Mediapipe基本使用&手势识别详解)
呕心沥血整理了~这100款告白源码❤学妹们看呆了~(520/七夕/告白/求婚/脱单)