最近某些项目需要用到人体姿势识别。当前有很多著名的人体识别的开源项目,比如CMU的OpenPose,上交的AlphaPose,效果都很好。我昨天试着安装一下AlphaPose,配环境配了一天,终于可以运行Demo的时候,显存溢出。。。因此我换个思路,我的项目不要求实时性,使用API也是可以接受的。发现百度的人体识别这方面的API还不错,QPS限制为2,也能用,于是便有了这篇文章。

    本文通过调用百度开放的人体关键点检测API,实现关键点检测并将其在Python的轻量级GUI库Tkinter绘制出来。当然最直接的方法是直接用opencv显示出来,但是我们更多的时候需要进行交互,因此使用Tkinter。

 

一、百度API获取

    若想使用百度的API,需要在百度AI的官网上注册帐号,然后在人体识别功能下创建一个应用,即可得到

APP_ID、API_KEY、SECRET_KEY,这样就可以调用它的API了。调用方式可以参考其API文档https://ai.baidu.com/ai-doc/BODY/0k3cpyxme

    若想使用Python调用,首先要安装百度的API接口模块:


pip install baidu-aip


 

二、人体关键点获取和绘制

    可以通过下面的类调用百度API

class BaiduAIP(object):
    def __init__(self):
        self.client = AipBodyAnalysis(cfg.APP_ID, cfg.API_KEY, cfg.SECRET_KEY)
    
    def bodyAnalysis(self,img_jpg):
        etval, buffer = cv2.imencode('.jpg', img_jpg)
        result = self.client.bodyAnalysis(buffer) #内部把buffer转换为base64了
        return result

    然后是绘制关键点和连线和方框的函数:

def draw_line(img,dic,text):
    color=(0,255,0)
    thickness=2
    if(text=='warn'):
        color=(0,0,255)
    
    #nose ---> neck
    cv2.line(img, (int(dic['nose']['x']),int(dic['nose']['y'])),(int(dic['neck']['x']),int(dic['neck']['y'])), color, thickness)
    #neck --> left_shoulder
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['left_shoulder']['x']),int(dic['left_shoulder']['y'])), color, thickness)
    #neck --> right_shoulder
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['right_shoulder']['x']),int(dic['right_shoulder']['y'])), color, thickness)
    #left_shoulder --> left_elbow
    cv2.line(img, (int(dic['left_shoulder']['x']),int(dic['left_shoulder']['y'])),(int(dic['left_elbow']['x']),int(dic['left_elbow']['y'])), color, thickness)
    #left_elbow --> left_wrist
    cv2.line(img, (int(dic['left_elbow']['x']),int(dic['left_elbow']['y'])),(int(dic['left_wrist']['x']),int(dic['left_wrist']['y'])), color, thickness)
    #right_shoulder --> right_elbow
    cv2.line(img, (int(dic['right_shoulder']['x']),int(dic['right_shoulder']['y'])),(int(dic['right_elbow']['x']),int(dic['right_elbow']['y'])), color, thickness)
    #right_elbow --> right_wrist
    cv2.line(img, (int(dic['right_elbow']['x']),int(dic['right_elbow']['y'])),(int(dic['right_wrist']['x']),int(dic['right_wrist']['y'])), color, thickness)
    #neck --> left_hip
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['left_hip']['x']),int(dic['left_hip']['y'])), color, thickness)
    #neck --> right_hip
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['right_hip']['x']),int(dic['right_hip']['y'])), color, thickness)
    #left_hip --> left_knee
    cv2.line(img, (int(dic['left_hip']['x']),int(dic['left_hip']['y'])),(int(dic['left_knee']['x']),int(dic['left_knee']['y'])), color, thickness)
    #right_hip --> right_knee
    cv2.line(img, (int(dic['right_hip']['x']),int(dic['right_hip']['y'])),(int(dic['right_knee']['x']),int(dic['right_knee']['y'])), color, thickness)
    #left_knee --> left_ankle
    cv2.line(img, (int(dic['left_knee']['x']),int(dic['left_knee']['y'])),(int(dic['left_ankle']['x']),int(dic['left_ankle']['y'])), color, thickness)
    #right_knee --> right_ankle
    cv2.line(img, (int(dic['right_knee']['x']),int(dic['right_knee']['y'])),(int(dic['right_ankle']['x']),int(dic['right_ankle']['y'])), color, thickness)

def draw_point(img,dic,text):
    color=(0,255,0)
    thickness=2
    if(text=='warn'):
        color=(0,0,255)
    for i in dic:
        cv2.circle(img,(int(dic[i]['x']),int(dic[i]['y'])),5,color,thickness)

def draw_box(img,dic,text):
    color=(255,0,0)
    if(text=='warn'):
        color=(0,0,255)
    
    left_top=(int(dic['left']),int(dic['top']))
    left_bottom=(int(dic['left']),int(dic['top']+dic['height']))
    right_bottom=(int(dic['left']+dic['width']),int(dic['top']+dic['height']))
    right_top=(int(dic['left']+dic['width']),int(dic['top']))
    cv2.line(img, left_top,left_bottom, color, 2)
    cv2.line(img, left_top,right_top, color, 2)
    cv2.line(img, right_bottom,left_bottom,color, 2)
    cv2.line(img, right_bottom,right_top,color, 2)
    
    cv2.putText(img, text, (int(dic['left']),int(dic['top'])+20), cv2.FONT_HERSHEY_COMPLEX, 1,color, 1)

    测试一下:

if __name__ == '__main__':
    baiduapi = BaiduAIP()
    img = cv2.imread('/media/chen/chen/Photo/others/littleCookies.jpg')
    d=baiduapi.bodyAnalysis(img)
    print("api time= "+str(t2-t1))
    print(d["person_num"])
    print(d["log_id"])
    persion=d["person_info"]
    for p in persion:
        draw_line(img,p['body_parts'],'ok')
        draw_point(img,p['body_parts'],'ok')
        draw_box(img,p['location'],'beauty')

    cv2.imwrite("image1.jpg",img)

    结果:

人物关节识别python python 人体关键点检测_百度AIP

 

 

 

三、tkinter界面开发

    先定义窗口和组件:

IMG_HEIGT=352*2
IMG_WIDTH=640*2
#根窗口
window = tk.Tk()

#显示变量
num_people = StringVar()
num_people.set('当前人数:0')
num_desertion = StringVar()
num_desertion.set('开小差:0')

#UI绘制
window.title("智能课堂管理")
sw = window.winfo_screenwidth()#获取屏幕宽
sh = window.winfo_screenheight()#获取屏幕高

#设置窗口大小和位置
wx = IMG_WIDTH+100
wh = IMG_HEIGT+100
window.geometry("%dx%d+%d+%d" %(wx,wh,(sw-wx)/2,(sh-wh)/2-100))#窗口至指定位置

#顶部是信息栏
fm1 = Frame(window)
Label(fm1, text="总人数:20").pack(side='left')
label_num = Label(fm1, textvariable=num_people).pack(side='left')
label_num = Label(fm1, textvariable=num_desertion).pack(side='left')
fm1.pack(side='top', padx=10)

#左侧是操作栏
fm2 = Frame(window)
fm2.pack(side='left', padx=10)

canvas1 = tk.Canvas(window,bg="#c4c2c2",height=IMG_HEIGT,width=IMG_WIDTH)#绘制画布
canvas1.pack(side="left")

job=ImgProcThread()

bt_start = tk.Button(fm2,text="启动",height=2,width=15,command=job.start).pack(side="top")
bt_pause = tk.Button(fm2,text="暂停",height=2,width=15,command=job.pause).pack(side="top")
bt_resume = tk.Button(fm2,text="恢复",height=2,width=15,command=job.resume).pack(side="top")
bt_stop = tk.Button(fm2,text="结束线程",height=2,width=15,command=job.stop).pack(side="top")
bt_quit = tk.Button(fm2,text="退出程序",height=2,width=15,command=window.quit).pack(side="top")

window.mainloop()

    ImgProcThread是什么?由于tkiner中没有提供直接播放视频的组件,因此我们使用cavas显示图片,并将关键点检测和图片检测放到另一个线程。但是由于Python自带的线程类功能功能不足,因此需要自己实现线程阻塞、结束等功能,如下:

class ImgProcThread(threading.Thread):
    def __init__(self, *args, **kwargs):
        super(ImgProcThread, self).__init__(*args, **kwargs)
        self.__flag = threading.Event()     # 用于暂停线程的标识
        self.__flag.set()       # 设置为True
        self.__running = threading.Event()      # 用于停止线程的标识
        self.__running.set()      # 将running设置为True
        
    def run(self):
        capture = cv2.VideoCapture('/media/chen/chen/Photo/others/1.mp4')
        while self.__running.isSet() and capture.isOpened():
            self.__flag.wait()      # 为True时立即返回, 为False时阻塞直到内部的标识位为True后返回
            pass
            
    def pause(self):
        self.__flag.clear()     # 设置为False, 让线程阻塞
    
    def resume(self):
        self.__flag.set()    # 设置为True, 让线程停止阻塞
    
    def stop(self):
        self.__flag.set()       # 将线程从暂停状态恢复, 如何已经暂停的话
        self.__running.clear()        # 设置为Fals

 

四、总程序

    本程序是根据我自己的需求来写的,因此会有一些在别人看起来蜜汁操作的代码,大家可以略过。

    config.py

APP_ID="***"
API_KEY="***"
SECRET_KEY="***"

 

    aip_bodyanalysis.py

from aip import AipBodyAnalysis
import sys
if('/opt/ros/kinetic/lib/python2.7/dist-packages' in sys.path):
    sys.path.remove('/opt/ros/kinetic/lib/python2.7/dist-packages')
import cv2
import os
import  config  as cfg
import base64
import time

def pose_analyse(img,dic):
    nose=(int(dic['nose']['x']),int(dic['nose']['y']))
    neck=(int(dic['neck']['x']),int(dic['neck']['y']))
    if(neck[1]<=nose[1]):
        return 'warn'
    return 'ok'

def draw_line(img,dic,text):
    color=(0,255,0)
    thickness=2
    if(text=='warn'):
        color=(0,0,255)
    
    #nose ---> neck
    cv2.line(img, (int(dic['nose']['x']),int(dic['nose']['y'])),(int(dic['neck']['x']),int(dic['neck']['y'])), color, thickness)
    #neck --> left_shoulder
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['left_shoulder']['x']),int(dic['left_shoulder']['y'])), color, thickness)
    #neck --> right_shoulder
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['right_shoulder']['x']),int(dic['right_shoulder']['y'])), color, thickness)
    #left_shoulder --> left_elbow
    cv2.line(img, (int(dic['left_shoulder']['x']),int(dic['left_shoulder']['y'])),(int(dic['left_elbow']['x']),int(dic['left_elbow']['y'])), color, thickness)
    #left_elbow --> left_wrist
    cv2.line(img, (int(dic['left_elbow']['x']),int(dic['left_elbow']['y'])),(int(dic['left_wrist']['x']),int(dic['left_wrist']['y'])), color, thickness)
    #right_shoulder --> right_elbow
    cv2.line(img, (int(dic['right_shoulder']['x']),int(dic['right_shoulder']['y'])),(int(dic['right_elbow']['x']),int(dic['right_elbow']['y'])), color, thickness)
    #right_elbow --> right_wrist
    cv2.line(img, (int(dic['right_elbow']['x']),int(dic['right_elbow']['y'])),(int(dic['right_wrist']['x']),int(dic['right_wrist']['y'])), color, thickness)
    #neck --> left_hip
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['left_hip']['x']),int(dic['left_hip']['y'])), color, thickness)
    #neck --> right_hip
    cv2.line(img, (int(dic['neck']['x']),int(dic['neck']['y'])),(int(dic['right_hip']['x']),int(dic['right_hip']['y'])), color, thickness)
    #left_hip --> left_knee
    cv2.line(img, (int(dic['left_hip']['x']),int(dic['left_hip']['y'])),(int(dic['left_knee']['x']),int(dic['left_knee']['y'])), color, thickness)
    #right_hip --> right_knee
    cv2.line(img, (int(dic['right_hip']['x']),int(dic['right_hip']['y'])),(int(dic['right_knee']['x']),int(dic['right_knee']['y'])), color, thickness)
    #left_knee --> left_ankle
    cv2.line(img, (int(dic['left_knee']['x']),int(dic['left_knee']['y'])),(int(dic['left_ankle']['x']),int(dic['left_ankle']['y'])), color, thickness)
    #right_knee --> right_ankle
    cv2.line(img, (int(dic['right_knee']['x']),int(dic['right_knee']['y'])),(int(dic['right_ankle']['x']),int(dic['right_ankle']['y'])), color, thickness)

def draw_point(img,dic,text):
    color=(0,255,0)
    thickness=2
    if(text=='warn'):
        color=(0,0,255)
    for i in dic:
        cv2.circle(img,(int(dic[i]['x']),int(dic[i]['y'])),5,color,thickness)

def draw_box(img,dic,text):
    color=(255,0,0)
    if(text=='warn'):
        color=(0,0,255)
    
    left_top=(int(dic['left']),int(dic['top']))
    left_bottom=(int(dic['left']),int(dic['top']+dic['height']))
    right_bottom=(int(dic['left']+dic['width']),int(dic['top']+dic['height']))
    right_top=(int(dic['left']+dic['width']),int(dic['top']))
    cv2.line(img, left_top,left_bottom, color, 2)
    cv2.line(img, left_top,right_top, color, 2)
    cv2.line(img, right_bottom,left_bottom,color, 2)
    cv2.line(img, right_bottom,right_top,color, 2)
    
    cv2.putText(img, text, (int(dic['left']),int(dic['top'])+20), cv2.FONT_HERSHEY_COMPLEX, 1,color, 1)

    
class BaiduAIP(object):
    def __init__(self):
        self.client = AipBodyAnalysis(cfg.APP_ID, cfg.API_KEY, cfg.SECRET_KEY)
    
    def bodyAnalysis(self,img_jpg):
        etval, buffer = cv2.imencode('.jpg', img_jpg)
        result = self.client.bodyAnalysis(buffer) #内部把buffer转换为base64了
        return result

        
if __name__ == '__main__':
    baiduapi = BaiduAIP()
    img = cv2.imread('/media/chen/chen/Photo/others/littleCookies.jpg')
    t1=time.time()
    d=baiduapi.bodyAnalysis(img)
    t2=time.time()
    print("api time= "+str(t2-t1))
    print(d["person_num"])
    print(d["log_id"])
    persion=d["person_info"]
    for p in persion:
        draw_line(img,p['body_parts'],'ok')
        draw_point(img,p['body_parts'],'ok')
        draw_box(img,p['location'],'beauty')
    t3=time.time()
    print("draw time= "+str(t3-t2))
    cv2.imwrite("image1.jpg",img)

 

    tkinter_body.py

import sys
if('/opt/ros/kinetic/lib/python2.7/dist-packages' in sys.path):
    sys.path.remove('/opt/ros/kinetic/lib/python2.7/dist-packages')
import cv2
import tkinter as tk
from tkinter import *#文件控件
from PIL import Image, ImageTk#图像控件
import threading#多线程
from aip_bodyanalysis import *
import time


IMG_HEIGT=352*2
IMG_WIDTH=640*2

aip=BaiduAIP()
is_run=True

#申请线程锁
lock=threading.Lock()

#根窗口
window = tk.Tk()

#显示变量
num_people = StringVar()
num_people.set('当前人数:0')
num_desertion = StringVar()
num_desertion.set('不正常:0')

#UI绘制
window.title("测试")
sw = window.winfo_screenwidth()#获取屏幕宽
sh = window.winfo_screenheight()#获取屏幕高

#设置窗口大小和位置
wx = IMG_WIDTH+100
wh = IMG_HEIGT+100
window.geometry("%dx%d+%d+%d" %(wx,wh,(sw-wx)/2,(sh-wh)/2-100))#窗口至指定位置

#顶部是信息栏
fm1 = Frame(window)
Label(fm1, text="总人数:20").pack(side='left')
label_num = Label(fm1, textvariable=num_people).pack(side='left')
label_num = Label(fm1, textvariable=num_desertion).pack(side='left')
fm1.pack(side='top', padx=10)

#左侧是操作栏
fm2 = Frame(window)
fm2.pack(side='left', padx=10)
canvas1 = tk.Canvas(window,bg="#c4c2c2",height=IMG_HEIGT,width=IMG_WIDTH)#绘制画布
canvas1.pack(side="left")


def getResult(img):
    d=aip.bodyAnalysis(img)
    if("person_info" not in d):
        return
    persion=d["person_info"]
    #获取人数
    num=len(persion)
    num_people.set( "当前人数:"+str(num))
    #绘制人体姿势
    for p in persion:
        status=pose_analyse(img,p['body_parts'])
        draw_line(img,p['body_parts'],status)
        draw_point(img,p['body_parts'],status)
        draw_box(img,p['location'],status)
        
def cc():
    capture = cv2.VideoCapture('/media/chen/chen/Photo/others/1.mp4')
    #capture = cv2.VideoCapture(0)
    while capture.isOpened():
        t0=time.time()
        _, frame = capture.read()
        #清除缓存
        for i in range(15):
            _, frame = capture.read()
        getResult(frame)
        frame = cv2.flip(frame, 1)#翻转 0:上下颠倒 大于0水平颠倒
        cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
        img = Image.fromarray(cv2image)
        image_file=ImageTk.PhotoImage(img)
        canvas1.create_image(0,0,anchor="nw",image=image_file)
        print(time.time()-t0)
    print("ending")
    
    
class ImgProcThread(threading.Thread):
    def __init__(self, *args, **kwargs):
        super(ImgProcThread, self).__init__(*args, **kwargs)
        self.__flag = threading.Event()     # 用于暂停线程的标识
        self.__flag.set()       # 设置为True
        self.__running = threading.Event()      # 用于停止线程的标识
        self.__running.set()      # 将running设置为True
        
    def run(self):
        capture = cv2.VideoCapture('/media/chen/chen/Photo/others/1.mp4')
        while self.__running.isSet() and capture.isOpened():
            self.__flag.wait()      # 为True时立即返回, 为False时阻塞直到内部的标识位为True后返回
            t0=time.time()
            _, frame = capture.read()
            for i in range(15):
                _, frame = capture.read()
            frame = cv2.resize(frame,(IMG_WIDTH,IMG_HEIGT), interpolation=cv2.INTER_NEAREST)
            getResult(frame)
            #frame = cv2.flip(frame, 1)#翻转 0:上下颠倒 大于0水平颠倒
            cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
            img = Image.fromarray(cv2image)
            image_file=ImageTk.PhotoImage(img)
            canvas1.create_image(0,0,anchor="nw",image=image_file)
            print(time.time()-t0)
            
    def pause(self):
        self.__flag.clear()     # 设置为False, 让线程阻塞
    
    def resume(self):
        self.__flag.set()    # 设置为True, 让线程停止阻塞
    
    def stop(self):
        self.__flag.set()       # 将线程从暂停状态恢复, 如何已经暂停的话
        self.__running.clear()        # 设置为Fals

def video_demo():
    t=threading.Thread(target=cc)
    t.start()
    
job=ImgProcThread()

bt_start = tk.Button(fm2,text="启动",height=2,width=15,command=job.start).pack(side="top")
bt_pause = tk.Button(fm2,text="暂停",height=2,width=15,command=job.pause).pack(side="top")
bt_resume = tk.Button(fm2,text="恢复",height=2,width=15,command=job.resume).pack(side="top")
bt_stop = tk.Button(fm2,text="结束线程",height=2,width=15,command=job.stop).pack(side="top")
bt_quit = tk.Button(fm2,text="退出程序",height=2,width=15,command=window.quit).pack(side="top")

window.mainloop()

    

    代码执行结果:

人物关节识别python python 人体关键点检测_人体关键点检测_02

人物关节识别python python 人体关键点检测_百度AIP_03

人物关节识别python python 人体关键点检测_人物关节识别python_04