컴굥일지

[모션 인식] Python과 OpenPose(OpenCV)를 사용하여 실시간 자세 추정하기 본문

프로젝트/졸프 - 참여형 동화 서비스

[모션 인식] Python과 OpenPose(OpenCV)를 사용하여 실시간 자세 추정하기

gyong 2022. 5. 7. 09:53
반응형

1. 모션 인식 - OpenPose

OpenPose는 신체의 관절을 추론하는 기술입니다.
이후, 관절들을 선으로 이어서 보여줍니다.

OpenPose는 얼굴, 손, 몸의 skeleton을 그릴 수 있습니다.

자세 추정이 필요한 저희 팀은 이 기술을 사용하기로 결정했습니다. (with python)

 


2. OpenPose 설치하기

https://github.com/CMU-Perceptual-Computing-Lab/openpose

위 링크에 들어가서 파일들을 다운로드 받아야 합니다.

openpose github

 

파일을 압축해제 후, models 폴더에 들어가서 getModels 파일을 실행해야 합니다.
윈도우 환경에서는 아래에 표시한 파일을 실행하고, 그 외의 경우에는 그 밑의 getModels.sh를 실행하면 됩니다.

getModels 파일 실행

 

파일을 실행하면 아래와 같은 창이 뜨면서 모델들이 다운로드됩니다.

openpose 모델 다운로드 과정

 

OpenPose로 사용할 수 있는 모델의 종류는 body-25, coco, mpii 3가지가 있습니다.
body-25는 출력 관절이 25개, coco는 출력 관절이 18개, MPI는 출력 관절이 15개입니다.
저희 조는 mpii 모델을 사용했습니다.

위의 창에서 다운이 끝나면 caffemodel이 생겼을 겁니다.
(위쪽은 getModels 실행 전 폴더 상황, 아래는 실행 후 폴더 상황)

caffemodel

위의 사진에 보이는 파일 2개를 사용하도록 할 겁니다.

 

이후, OpenCV가 필요하므로 설치해야 합니다.  (3.4.1 이상의 버전이 필요)

cmd 창에서 pip install opencv-python을 입력해주세요.

 


3. OpenPose 사용하기

 

이것이 제 폴더 구조입니다.
file이라는 폴더 안에 위에서 받은 모델이 들어있습니다.

저희 조는 웹캠을 통해 실시간으로 영상을 받아 자세를 검출해야 하므로 이와 관련된 코드를 아래에 기술하도록 하겠습니다. (openpose2_ex.py) 

 

import cv2
from pathlib import Path

# MPII에서 각 파트 번호, 선으로 연결될 POSE_PAIRS
BODY_PARTS = { "Head": 0, "Neck": 1, "RShoulder": 2, "RElbow": 3, "RWrist": 4,
                "LShoulder": 5, "LElbow": 6, "LWrist": 7, "RHip": 8, "RKnee": 9,
                "RAnkle": 10, "LHip": 11, "LKnee": 12, "LAnkle": 13, "Chest": 14,
                "Background": 15 }

POSE_PAIRS = [ ["Head", "Neck"], ["Neck", "RShoulder"], ["RShoulder", "RElbow"],
                ["RElbow", "RWrist"], ["Neck", "LShoulder"], ["LShoulder", "LElbow"],
                ["LElbow", "LWrist"], ["Neck", "Chest"], ["Chest", "RHip"], ["RHip", "RKnee"],
                ["RKnee", "RAnkle"], ["Chest", "LHip"], ["LHip", "LKnee"], ["LKnee", "LAnkle"] ]
    
# 각 파일 path
BASE_DIR=Path(__file__).resolve().parent
protoFile = str(BASE_DIR)+"/file/pose_deploy_linevec_faster_4_stages.prototxt"
weightsFile = str(BASE_DIR)+"/file/pose_iter_160000.caffemodel"
 
# 위의 path에 있는 network 모델 불러오기
net = cv2.dnn.readNetFromCaffe(protoFile, weightsFile)

#쿠다 사용 안하면 밑에 이미지 크기를 줄이는게 나을 것이다
# net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA) #벡엔드로 쿠다를 사용하여 속도향상을 꾀한다
# net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA) # 쿠다 디바이스에 계산 요청


###카메라랑 연결...?
capture = cv2.VideoCapture(0) #카메라 정보 받아옴
# capture.set(cv2.CAP_PROP_FRAME_WIDTH, 640) #카메라 속성 설정
# capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) # width:너비, height: 높이

inputWidth=320;
inputHeight=240;
inputScale=1.0/255;

 
#반복문을 통해 카메라에서 프레임을 지속적으로 받아옴
while cv2.waitKey(1) <0:  #아무 키나 누르면 끝난다.
    #웹캠으로부터 영상 가져옴
    hasFrame, frame = capture.read()  
    
    #영상이 커서 느리면 사이즈를 줄이자
    #frame=cv2.resize(frame,dsize=(320,240),interpolation=cv2.INTER_AREA)
    
    #웹캠으로부터 영상을 가져올 수 없으면 웹캠 중지
    if not hasFrame:
        cv2.waitKey()
        break
    
    # 
    frameWidth = frame.shape[1]
    frameHeight = frame.shape[0]
    
    inpBlob = cv2.dnn.blobFromImage(frame, inputScale, (inputWidth, inputHeight), (0, 0, 0), swapRB=False, crop=False)
    
    imgb=cv2.dnn.imagesFromBlob(inpBlob)
    #cv2.imshow("motion",(imgb[0]*255.0).astype(np.uint8))
    
    # network에 넣어주기
    net.setInput(inpBlob)

    # 결과 받아오기
    output = net.forward()


    # 키포인트 검출시 이미지에 그려줌
    points = []
    for i in range(0,15):
        # 해당 신체부위 신뢰도 얻음.
        probMap = output[0, i, :, :]
    
        # global 최대값 찾기
        minVal, prob, minLoc, point = cv2.minMaxLoc(probMap)

        # 원래 이미지에 맞게 점 위치 변경
        x = (frameWidth * point[0]) / output.shape[3]
        y = (frameHeight * point[1]) / output.shape[2]

        # 키포인트 검출한 결과가 0.1보다 크면(검출한곳이 위 BODY_PARTS랑 맞는 부위면) points에 추가, 검출했는데 부위가 없으면 None으로    
        if prob > 0.1 :    
            cv2.circle(frame, (int(x), int(y)), 3, (0, 255, 255), thickness=-1, lineType=cv2.FILLED) # circle(그릴곳, 원의 중심, 반지름, 색)
            cv2.putText(frame, "{}".format(i), (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1, lineType=cv2.LINE_AA)
            points.append((int(x), int(y)))
        else :
            points.append(None)
    
    

    # 각 POSE_PAIRS별로 선 그어줌 (머리 - 목, 목 - 왼쪽어깨, ...)
    for pair in POSE_PAIRS:
        partA = pair[0]             # Head
        partA = BODY_PARTS[partA]   # 0
        partB = pair[1]             # Neck
        partB = BODY_PARTS[partB]   # 1
        
        #partA와 partB 사이에 선을 그어줌 (cv2.line)
        if points[partA] and points[partB]:
            cv2.line(frame, points[partA], points[partB], (0, 255, 0), 2)
    
            
    cv2.imshow("Output-Keypoints",frame)
 
capture.release()  #카메라 장치에서 받아온 메모리 해제
cv2.destroyAllWindows() #모든 윈도우 창 닫음

 

위 코드를 실행하면 아래와 같이 새로운 창이 뜨면서 웹캠으로 자신의 모습과 자세를 볼 수 있습니다.

openpose 웹캠 실행 결과 - (얼굴이 너무 크게 나와서 가렸다.)

위의 실행결과로 볼 수 있듯이, 자세 검출이 잘되고 있습니다.

Teachable Machine에 비해서 확실히 자세가 잘 검출됩니다.
다만 Teachable Machine과 달리, 우리가 원하는 자세인지 판단하는 코드를 따로 짜야한다는 단점이 있습니다.

개인적인 생각으로는 OpenPose가 훨씬 더 나은 것 같습니다.

OpenCV와 관련해서는 https://076923.github.io/posts/Python-opencv-1/ 이 링크를 보면 좀 더 도움이 될 것입니다.

반응형
Comments