본문 바로가기

캡스톤디자인프로젝트B_스타트16

[6] Openpose로 Keypoints 추출 및 json 파일 받아오기

  오픈포즈(openpose)는 카메라 이미지로만 사람의 뼈대를 추출하는 딥러닝 모델이다. 오픈포즈는 CVPR(Computer Vision and Pattern Recognition) 2017에서 발표한 기술로, 카네기멜론 대학교에서 개발되었다. CNN(Convolutional Neural Network) 기반으로 관절과 뼈대를 예측한다. 이번 포스팅 글은 오픈포즈의 설치, 구조 분석 및 사용 방법을 간단히 나눔하고자 한다.

 

  openpose 라이브러리를 이용해서 관절 포인트들을 검출해보겠다. 동영상을 이미 훈련된 pose estimation 모델에 넣어줘서 keypoint(관절 포인트)들이 검출된 결과를 얻어볼 것이다.

 

 

빌드 및 설치

소스코드는 다음과 같이 다운로드 할 수 있다.

CMU-Perceptual-Computing-Lab의 깃헙에서 openpose를 다운로드 받는다.
▶ git clone https://github.com/CMU-Perceptual-Computing-Lab/openpose

 

 

멀티 플랫폼을 지원하므로 윈도우에서도 실행할 수 있다. 단, 데이터 학습을 위해서는 Caffe 를 사용하므로 미리 설치하고 빌드해야 한다. 이와 관련해, 다음 링크를 참고해 설치 및 실행해 본다.
https://medium.com/@erica.z.zheng/installing-openpose-on-ubuntu-18-04-cuda-10-ebb371cf3442

 

 

만약, 빌드가 불편하다면, 윈도우즈 버전을 설치해 사용할 수도 있다. 설치는 다음 링크를 참고한다.
https://github.com/CMU-Perceptual-Computing-Lab/openpose/releases

 

 

설치 후 모델파일을 준비한다. 모델파일은 다음 링크를 참고해 다운로드한다. 

BODY_25 - pose_iter_584000.caffemodel

http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/body_25/pose_iter_584000.caffemodel
COCO - pose_iter_440000.caffemodel

http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/coco/pose_iter_440000.caffemodel
MPI - pose_iter_160000.caffemodel

http://posefs1.perception.cs.cmu.edu/OpenPose/models/pose/mpi/pose_iter_160000.caffemodel
Face - pose_iter_116000.caffemodel

http://posefs1.perception.cs.cmu.edu/OpenPose/models/face/pose_iter_116000.caffemodel
Hand - pose_iter_102000.caffemodel

http://posefs1.perception.cs.cmu.edu/OpenPose/models/hand/pose_iter_102000.caffemodel

 

 

하지만, 노트북에 GPU가 없거나 복잡한 환경설정 및 다운로드로 오픈포즈를 로컬에 실행하기 쉽지 않을 것이다.

설치가 쉽지 않다면, CoLab 환경에서 실행해보기를 바란다.

로컬이 아닌 코랩 가상환경에서 빌드 및 설치가 훨씬 간단하고 빠르다.

 

 

더 빠른 GPU, 추가 메모리, 더 긴 런타임의 혜택을 누리고 싶다면, 코랩 프로를 정기 구독할 것을 강력히 추천한다.

관련 링크 ▶ https://colab.research.google.com/signup

 

 

 

오픈포즈를 빌드하고 사용하는 코드를 첨부한다

https://colab.research.google.com/drive/1FN4-l1LSXjbwOoWxrHRgrDyFQfYH69vj

 

 

 

기능 및 자료 구조 분석

오픈포즈는 다음과 같이 사람의 몸, 혹은 얼굴과 손을 함께 인식할 수 있다.  

--write_json 옵션을 추가하면, 관전 키포인트 정보가 json파일로 출력된다.

--display 가 0이면 화면 출력 기능은 OFF할 수 있다. 

 

# Only body
./build/examples/openpose/openpose.bin --write_json output/ 
./build/examples/openpose/openpose.bin --video examples/video.avi --write_json output/ --display 0 --render_pose 0

 

# Body + face + hands
./build/examples/openpose/openpose.bin --video examples/video.avi --write_json output/ --display 0 --render_pose 0 --face --hand

 

frame 별 keypoints가 저장된 json 파일 목록
json 파일 내부

 

json 파일 구조는 다음과 같다.
pose_keypoints_2d: 몸체 위치가 x1, y1, c1, x2, y2, c2,... 와 같이 배열로 저장됨. x, y는 픽셀위치. c는 신뢰도로 0 ~ 1 사이값. (참고 https://cmu-perceptual-computing-lab.github.io/openpose/web/html/doc/md_doc_02_output.html)

 

BODY_25

 

기타 옵션 기능

렌더링 이미지를 저장하고 싶다면 --write_video 옵션을 사용하면 된다.
./build/examples/openpose/openpose.bin --video examples/video.avi --write_video output/result.avi --write_json output/

--write_video 렌더링된 영상


다음은 오픈포즈 옵션을 설명한 것이다.


--face: 얼굴 추출
--hand: 손 추출
--video input.mp4: 비디오 읽기
--camera 3: 3번 카메라 읽기
--image_dir path_to_images/: 폴더 내 이미지 입력
--ip_camera http://iris.not.iac.es/axis-cgi/mjpg/video.cgi?resolution=320x240?x.mjpeg: IP 카메라 입력
--write_video path.avi: 비디오 저장
--write_images folder_path: 폴더 내 이미지 출력
--write_keypoint path/: Output JSON, XML or YML files with the people pose data on a folder.
--process_real_time: For video, it might skip frames to display at real time.
-disable_blending: If enabled, it will render the results (keypoint skeletons or heatmaps) on a black background, not   showing the original image. Related: part_to_show, alpha_pose, and alpha_pose.
--part_to_show: Prediction channel to visualize.
--display 0: Display window not opened. Useful for servers and/or to slightly speed up OpenPose.
--num_gpu 2 -num_gpu_start 1: Parallelize over this number of GPUs starting by the desired device id. By default it    uses all the available GPUs.
--model_pose MPI: Model to use, affects number keypoints, speed and accuracy.
--logging_level 3: Logging messages threshold, range [0,255]: 0 will output any message & 255 will output none.        Current messages in the range [1-4], 1 for low priority messages and 4 for important ones.

 

 

 

활용 방법 (행동인식)

json 파일을 numpy로 변환한 다음, 각 프레임간의 함수 연산을 통해 특징벡터를 추출한다. 그후 최종적으로 cvs파일로 변환하여 lstm 모델에 넣어 학습시킨다. 

 

특징벡터 추출을 위해 다음 논문을 참고하였다.

https://www.koreascience.or.kr/article/JAKO201831960579918.pdf

 

 

# keypoint별 벡터 크기(속력) 구하기
def keypoint_vector(previous_keypointscurrent_keypointsconfidence_threshold):

  p_adult_keypoints = previous_keypoints[separate_adult(previous_keypoints, 0.1)]
  c_adult_keypoints = current_keypoints[separate_adult(current_keypoints, 0.1)]

  # 벡터 차이 구하기
  vector = np.zeros((19,2)) 
  for i in range(19):
    # confidence 역치 이상일 때만
    if (p_adult_keypoints[i][2] >= confidence_threshold) and (c_adult_keypoints[i][2] >= confidence_threshold):
      vector[i][0] = c_adult_keypoints[i][0] - p_adult_keypoints[i][0]
      vector[i][1] = c_adult_keypoints[i][1] - p_adult_keypoints[i][1]
    else:
      vector[i] = [0,0]

  return vector

def sub(x1,x2):
  return x1[0] - x2[0],x1[1] - x2[1]

import math

def dist(x1,x2) -> int:
  x = x1[0]-x2[0]
  y = x1[1]-x2[1]
  return math.sqrt(x**2 + y**2)
def relationKeypoint(current_keypointsconfidence_thresholddistanceArray):

  adult_keypoints = current_keypoints[separate_adult(current_keypoints, 0.1)]

  # 벡터 차이 구하기
  vector = np.zeros((22,1)) 

  #f4
  d4 = distanceArray[0] + distanceArray[2] + distanceArray[3] + distanceArray[5] +distanceArray[7]  
  if d4 != 0:
    vector[0],vector[1] = sub(adult_keypoints[0],adult_keypoints[1])/d4  # head - neck 
    vector[2],vector[3] = sub(adult_keypoints[4],adult_keypoints[1])/d4  # right hand - neck 
    vector[4],vector[5] = sub(adult_keypoints[7],adult_keypoints[1])/d4  # left hand - neck 
  else:
    vector[0],vector[1],vector[2],vector[3],vector[4],vector[5] = None,None,None,None,None,None
 
  #f5
  d5 = distanceArray[2] + distanceArray[3] + distanceArray[9] + distanceArray[11
  if d5 != 0:
    vector[6],vector[7] = sub(adult_keypoints[1],adult_keypoints[12])/d5     # neck - left hip 
    vector[8],vector[9] = sub(adult_keypoints[4],adult_keypoints[12])/d5     # right hand - left hip 
    vector[10],vector[11] = sub(adult_keypoints[11],adult_keypoints[12])/d5  # right foot - left hip
  else:
    vector[6],vector[7],vector[8],vector[9],vector[10],vector[11]= None,None,None,None,None,None
 
  #f6
  d6 = distanceArray[5] + distanceArray[6] + distanceArray[10] + distanceArray[12
  if d6 != 0:
    vector[12],vector[13] = sub(adult_keypoints[0],adult_keypoints[9])/d6   # head - right hip 
    vector[14],vector[15] = sub(adult_keypoints[7],adult_keypoints[9])/d6   # left hand - right hip 
    vector[16],vector[17] = sub(adult_keypoints[14],adult_keypoints[9])/d6  # left foot - right hip 
  else:
    vector[12],vector[13],vector[14],vector[15],vector[16],vector[17]= None,None,None,None,None,None
  
  #f7
  d7 = distanceArray[2] + distanceArray[3] + distanceArray[5] + distanceArray[6
  if d7 != 0:
    vector[18],vector[19] = sub(adult_keypoints[4],adult_keypoints[0])/d7  # right hand - head 
    vector[20],vector[21] = sub(adult_keypoints[7],adult_keypoints[0])/d7  # left hand - head 
  else:
    vector[18],vector[19],vector[20],vector[21]= None,None,None,None

  return vector

def distanceVector(norm_vector):
  distanceArray = np.zeros((13,1))
  
  distanceArray[0] = dist((norm_vector[0],norm_vector[1]),(norm_vector[2],norm_vector[3])) #머리-목
  distanceArray[1] = dist((norm_vector[2],norm_vector[3]),(norm_vector[4],norm_vector[5])) #목-오른어깨
  distanceArray[2] = dist((norm_vector[4],norm_vector[5]),(norm_vector[6],norm_vector[7])) #오른어깨-오른엘보우
  distanceArray[3] = dist((norm_vector[6],norm_vector[7]),(norm_vector[8],norm_vector[9])) #오른엘보우-오른손목
  distanceArray[4] = dist((norm_vector[2],norm_vector[3]),(norm_vector[10],norm_vector[11])) #목-왼어깨
  distanceArray[5] = dist((norm_vector[10],norm_vector[11]),(norm_vector[12],norm_vector[13])) #왼어꺠-왼엘보우
  distanceArray[6] = dist((norm_vector[12],norm_vector[13]),(norm_vector[14],norm_vector[15])) #왼엘보우-왼손목
  distanceArray[7] = dist((norm_vector[2],norm_vector[3]),(norm_vector[18],norm_vector[19])) #목-오른힙
  distanceArray[8] = dist((norm_vector[2],norm_vector[3]),(norm_vector[24],norm_vector[25])) #목-왼힙
  distanceArray[9] = dist((norm_vector[18],norm_vector[19]),(norm_vector[20],norm_vector[21])) #오른힙-오른무릎
  distanceArray[10] = dist((norm_vector[24],norm_vector[25]),(norm_vector[26],norm_vector[27])) #왼힙-왼무릎
  distanceArray[11] = dist((norm_vector[20],norm_vector[21]),(norm_vector[22],norm_vector[23])) #오른무릎-오른발목
  distanceArray[12] = dist((norm_vector[26],norm_vector[27]),(norm_vector[28],norm_vector[29])) #왼무릎-왼발목

  return distanceArray

def normalize(previous_keypointscurrent_keypointsconfidence_threshold) :
  
  norm_vector = np.zeros((30,1)) # 30개
  distanceArray = np.zeros((13,1)) # D1~D13
  relationVector = np.zeros((22,1))
  final_vector = np.zeros((52,1)) # 30+22

  vector = keypoint_vector(previous_keypoints, current_keypoints, 0.1)
  
  #head
  norm_vector[0],norm_vector[1] = (vector[15][0]+vector[16][0])//2,(vector[15][1]+vector[16][1])//2
  #neck
  norm_vector[2],norm_vector[3] = vector[1][0],vector[1][1]

  #norm_vector로 옮기기 (4+(13*2)=30개)
  for i,points in enumerate(vector):
    if i>=2 and i<=14:
      x_idx,y_idx = ((i-2)*2)+4,((i-2)*2)+5
      norm_vector[x_idx] = points[0]
      norm_vector[y_idx] = points[1]
  
  # 신체 거리 구하기
  distanceArray = distanceVector(norm_vector) # D1~D13


  #변위 벡터 계산
  #f1
  d1 = distanceArray[0] + distanceArray[3] + distanceArray[6] + distanceArray[11] +distanceArray[12]   # D1+D4+D7+D12+D13
  if d1 != 0:
    norm_vector[0],norm_vector[1] = norm_vector[0]/d1,norm_vector[1]/d1         #head
    norm_vector[14],norm_vector[15] = norm_vector[14]/d1,norm_vector[15]/d1     #left hand
    norm_vector[8],norm_vector[9] = norm_vector[8]/d1,norm_vector[9]/d1         #right hand 
    norm_vector[28],norm_vector[29] = norm_vector[28]/d1,norm_vector[29]/d1     #left foot
    norm_vector[22],norm_vector[23] = norm_vector[22]/d1,norm_vector[23]/d1     #right foot
  else:
    norm_vector[0],norm_vector[1],norm_vector[14],norm_vector[15],norm_vector[8],norm_vector[9],norm_vector[28],norm_vector[29],norm_vector[22],norm_vector[23]=None,None,None,None,None,None,None,None,None,None

  #f2
  d2 = distanceArray[0] + distanceArray[2] + distanceArray[5] + distanceArray[9] +distanceArray[10]
  if d2 != 0:
    norm_vector[2],norm_vector[3] = norm_vector[2]/d2,norm_vector[3]/d2         #neck
    norm_vector[12],norm_vector[13] = norm_vector[12]/d2,norm_vector[13]/d2     #left elbow
    norm_vector[6],norm_vector[9] = norm_vector[6]/d2,norm_vector[9]/d2         #right elbow
    norm_vector[26],norm_vector[27] = norm_vector[26]/d2,norm_vector[27]/d2     #left knee
    norm_vector[20],norm_vector[21] = norm_vector[20]/d2,norm_vector[21]/d2     #right knee
  else:
    norm_vector[2],norm_vector[3],norm_vector[12],norm_vector[13],norm_vector[6],norm_vector[9],norm_vector[26],norm_vector[27],norm_vector[20],norm_vector[21]= None,None,None,None,None,None,None,None,None,None

  #f3
  d3 = distanceArray[1] + distanceArray[4] + distanceArray[9] + distanceArray[10
  if d3 != 0:
    norm_vector[10],norm_vector[11] = norm_vector[10]/d3,norm_vector[11]/d3     #left shoulder
    norm_vector[4],norm_vector[5] = norm_vector[4]/d3,norm_vector[5]/d3         #right shoulder
    norm_vector[24],norm_vector[25] = norm_vector[24]/d3,norm_vector[25]/d3     #left hip
    norm_vector[18],norm_vector[19] = norm_vector[18]/d3,norm_vector[19]/d3     #right hip
    norm_vector[16],norm_vector[17] = norm_vector[16]/d3,norm_vector[17]/d3     #middle hip
  else:
    norm_vector[10],norm_vector[11],norm_vector[4],norm_vector[5],norm_vector[24],norm_vector[25],norm_vector[18],norm_vector[19],norm_vector[16],norm_vector[17] = None,None,None,None,None,None,None,None,None,None
  
  relationVector = relationKeypoint(current_keypoints,confidence_threshold,distanceArray)

  final_vector = np.concatenate((norm_vector, relationVector), axis=0)
  return final_vector

 

 

"때리기, 밀치기, 발로차기"의 폭력행위와 "쓰다듬기, 두팔뻗기, 걷기"의 유사행위에 대한 키포인트 관계벡터를 추출하여 csv로 최종적으로 변환하고 이를 lstm 모델에 input 값으로 넣어 행동인식 모델을 학습시킨다.

 

모델 구조
학습결과