Современные подходы в основном опираются на мощь рабочего компьютера для вывода, в то время как метод Mediapipe способен обеспечить производительность в реальном времени на большинстве современных мобильных телефонов, настольных компьютеров и в Интернете(javascript).

Кое-что об оценке позы Mediapipe

Функция MediaPipe Pose Landmark может извлекать 33 ключевые точки ориентира, как показано выше. Результатом является список ориентиров позы, и каждый ориентир состоит из координат ориентира x и y, нормализованных [0.0, 1.0]по ширине и высоте изображения соответственно.

Образец кода

import cv2
import mediapipe as mp
mp_drawing = mp.solutions.drawing_utils
mp_holistic = mp.solutions.holistic

# For static images:
IMAGE_FILES = []
with mp_holistic.Holistic(
    static_image_mode=True,
    model_complexity=2) as holistic:
  for idx, file in enumerate(IMAGE_FILES):
    image = cv2.imread(file)
    image_height, image_width, _ = image.shape
    # Convert the BGR image to RGB before processing.
    results = holistic.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))

    if results.pose_landmarks:
      print(
          f'Nose coordinates: ('
          f'{results.pose_landmarks.landmark[mp_holistic.PoseLandmark.NOSE].x * image_width}, '
          f'{results.pose_landmarks.landmark[mp_holistic.PoseLandmark.NOSE].y * image_height})'
      )
    # Draw pose, left and right hands, and face landmarks on the image.
    annotated_image = image.copy()
    mp_drawing.draw_landmarks(
        annotated_image, results.face_landmarks, mp_holistic.FACE_CONNECTIONS)
    mp_drawing.draw_landmarks(
        annotated_image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
    mp_drawing.draw_landmarks(
        annotated_image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
    mp_drawing.draw_landmarks(
        annotated_image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS)
    cv2.imwrite('/tmp/annotated_image' + str(idx) + '.png', annotated_image)
    # Plot pose world landmarks.
    mp_drawing.plot_landmarks(
        results.pose_world_landmarks, mp_holistic.POSE_CONNECTIONS)

# For webcam input:
cap = cv2.VideoCapture(0)
with mp_holistic.Holistic(
    min_detection_confidence=0.5,
    min_tracking_confidence=0.5) as holistic:
  while cap.isOpened():
    success, image = cap.read()
    if not success:
      print("Ignoring empty camera frame.")
      # If loading a video, use 'break' instead of 'continue'.
      continue

    # Flip the image horizontally for a later selfie-view display, and convert
    # the BGR image to RGB.
    image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
    # To improve performance, optionally mark the image as not writeable to
    # pass by reference.
    image.flags.writeable = False
    results = holistic.process(image)

    # Draw landmark annotation on the image.
    image.flags.writeable = True
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
    mp_drawing.draw_landmarks(
        image, results.face_landmarks, mp_holistic.FACE_CONNECTIONS)
    mp_drawing.draw_landmarks(
        image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
    mp_drawing.draw_landmarks(
        image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
    mp_drawing.draw_landmarks(
        image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS)
    cv2.imshow('MediaPipe Holistic', image)
    if cv2.waitKey(5) & 0xFF == 27:
      break
cap.release()

Если мы намереваемся определить позы двух человек на фотографии или на видео, одним из возможных подходов было определение граничной рамки человека, наложить маску на картинку(как на нижнем рисунке), кроме граничной рамки человека, повторить это действие для других найденных людей, и по очереди дать картинку в mediapipe.

Обнаружение людей через YOLO v4

 YOLO  - это современная система обнаружения объектов в реальном времени. Поскольку YOLO доказал свою высокую скорость и точность, его можно использовать для обнаружения объекта-человека. Реализацию YOLOv4 взял от сюда. И добавил в демонстрационный файл несколько строчек кода и вызывал эту функцию после детекции объектов:

# MediaPipe init.
import mediapipe as mp
mp_drawing = mp.solutions.drawing_utils
mp_holistic = mp.solutions.holistic
holistic = mp_holistic.Holistic(static_image_mode=True)



def mdp_holistic(original_image, image):
    ''' MediaPipe Pose recognition. '''


    results = holistic.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
    # cv2.imwrite('holistic_input.jpg', image)
    
    if results.pose_landmarks:
        # Draw pose, left and right hands, and face landmarks on the image.
        mp_drawing.draw_landmarks(
            original_image, results.face_landmarks, mp_holistic.FACE_CONNECTIONS)
        mp_drawing.draw_landmarks(
            original_image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
        mp_drawing.draw_landmarks(
            original_image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
        mp_drawing.draw_landmarks(
            original_image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS)

    return original_image

Другое решение этой проблемы

Выше, для обнаружения людей, мы использовали нейронную сеть YOLO v4, которая не самая простая и легкая. Мы еще по экспериментировали с mediapipe, и нашли выход одновременно обнаружить несколько поз людей, используя только mediapipe. Она реализована на рекурсии и не использует сторонние нейронные сети, так что она легкая и простая.

Логика здесь такая, делаем копию входного изображения, находим позу первого человека, определяем максимум и минимум точки и закрашиваем этот прямоугольник, дальше ищем позу второго человека и т.д. В конце рисуем на оригинальной картинке.

import cv2
import time
import mediapipe as mp
import datetime


mp_holistic = mp.solutions.holistic
holistic = mp_holistic.Holistic(static_image_mode=True)
mp_draw = mp.solutions.drawing_utils

def holistic_recursive(origin_image, image, padding = 20, recursion_depth = 20):
    results = holistic.process(image)
    X , Y = [], []
    h, w = image.shape[:2]
    person_detected = False
    if results.pose_landmarks:
        mp_draw.draw_landmarks(origin_image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS)
        for i in range(len(results.pose_landmarks.landmark)):
            X.append(results.pose_landmarks.landmark[i].x)
            Y.append(results.pose_landmarks.landmark[i].y)
        person_detected = True
    if results.face_landmarks:    
        mp_draw.draw_landmarks(origin_image, results.face_landmarks, mp_holistic.FACE_CONNECTIONS)
        for i in range(len(results.face_landmarks.landmark)):
            X.append(results.face_landmarks.landmark[i].x)
            Y.append(results.face_landmarks.landmark[i].y)
        person_detected = True
    if results.left_hand_landmarks:
        mp_draw.draw_landmarks(origin_image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
        for i in range(len(results.left_hand_landmarks.landmark)):
            X.append(results.left_hand_landmarks.landmark[i].x)
            Y.append(results.left_hand_landmarks.landmark[i].y)
        person_detected = True
    if results.right_hand_landmarks:
        mp_draw.draw_landmarks(origin_image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
        for i in range(len(results.right_hand_landmarks.landmark)):
            X.append(results.right_hand_landmarks.landmark[i].x)
            Y.append(results.right_hand_landmarks.landmark[i].y)
        person_detected = True
    if person_detected and recursion_depth > 0:
        # create bbox
        x1 = int(min(X) * w)
        y1 = int(min(Y) * h)
        x2 = int(max(X) * w)
        y2 = int(max(Y) * h)
        # Add padding
        x1 = x1 - padding if x1 - padding > 0 else 0
        y1 = y1 - padding if y1 - padding > 0 else 0
        x2 = x2 + padding if x2 + padding < w else w
        y2 = y2 + padding if y2 + padding < h else h
        # zero bbox
        image[y1:y2, x1:x2] = 0
        # cv2.imwrite('crop.jpg', image)
        holistic_recursive(origin_image, image, recursion_depth - 1)

cap = cv2.VideoCapture(0)
# cap = cv2.VideoCapture('smeny-etsn-brigadoy-tkrs_Pxv28bmL_N5en.mp4')

# Write video
# w = int(cap.get(3))
# h = int(cap.get(4))
# fourcc = cv2.VideoWriter_fourcc(*'MP4V')
# out = cv2.VideoWriter('output_recursive.mp4', fourcc, 10.0, (w, h))

start_time = time.time()
frame_count = 0
fps = 0
while True:
    _, img = cap.read()
    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    # Recursive run detection
    image = img_rgb.copy()
    holistic_recursive(img, image)
    frame_count += 1
    if frame_count > 20:
        fps = frame_count / (time.time() - start_time)  
        start_time = time.time()
        frame_count = 0
    cv2.putText(img, f'FPS: {int(fps)}', (10, 30), cv2.FONT_HERSHEY_PLAIN, 2, (255, 0, 0), 2)
    cv2.imshow("cam", img)
    # out.write(img)
    k = cv2.waitKey(1)
    if k == 27:  # close on ESC key
        break

# cap.release()
# out.release()
cv2.destroyAllWindows()

В этой реализации с помощью Mediapipe мы достигли позы с несколькими людьми. Надеюсь, вы найдете это полезным для работы или учебы.

Комментарии (1)


  1. le2
    15.07.2021 14:40

    В оригинальных примерах, действительно выделяется только одна поза.
    Но в оригинальном примере детектор вызывается только первый раз. Далее рамка расчитывается по ландмарку тела и детектор больше не вызывается, пока не случится ошибка. Это легко проверить по логам вывода калькуляторов, хотя и так видно из графов.
    Также в новых релизах они перешли от 128x128 к 256x256. Точность детекции выросла.
    То есть качественное решение было оставить старую логику, просто для нескольких людей в кадре. (не вызывать детектор, а пересчитывать на основе старых ландмарков) В этом случае fps вырастет до двух раз (в зависимости от железа).