|
|
import mediapipe as mp
|
|
|
import math
|
|
|
from typing import List, Mapping, Optional, Tuple, Union
|
|
|
import cv2
|
|
|
import dataclasses
|
|
|
import numpy as np
|
|
|
from mediapipe.framework.formats import landmark_pb2
|
|
|
import os
|
|
|
|
|
|
mp_holistic = mp.solutions.holistic
|
|
|
|
|
|
_PRESENCE_THRESHOLD = 0.5
|
|
|
_VISIBILITY_THRESHOLD = 0.5
|
|
|
_BGR_CHANNELS = 3
|
|
|
|
|
|
WHITE_COLOR = (224, 224, 224)
|
|
|
BLACK_COLOR = (0, 0, 0)
|
|
|
RED_COLOR = (0, 0, 255)
|
|
|
GREEN_COLOR = (0, 128, 0)
|
|
|
BLUE_COLOR = (255, 0, 0)
|
|
|
|
|
|
|
|
|
@dataclasses.dataclass
|
|
|
class DrawingSpec:
|
|
|
"""
|
|
|
定义标注框的线性、色彩
|
|
|
"""
|
|
|
# Color for drawing the annotation. Default to the white color.
|
|
|
color: Tuple[int, int, int] = GREEN_COLOR
|
|
|
# Thickness for drawing the annotation. Default to 2 pixels.
|
|
|
thickness: int = 2
|
|
|
# Circle radius. Default to 2 pixels.
|
|
|
circle_radius: int = 2
|
|
|
|
|
|
|
|
|
def _normalized_to_pixel_coordinates(
|
|
|
normalized_x: float, normalized_y: float, image_width: int,
|
|
|
image_height: int) -> Union[None, Tuple[int, int]]:
|
|
|
"""
|
|
|
将关键点坐标转化为图像像素坐标
|
|
|
"""
|
|
|
|
|
|
# Checks if the float value is between 0 and 1.
|
|
|
def is_valid_normalized_value(value: float) -> bool:
|
|
|
return (value > 0 or math.isclose(0, value)) and (value < 1 or
|
|
|
math.isclose(1, value))
|
|
|
|
|
|
if not (is_valid_normalized_value(normalized_x) and
|
|
|
is_valid_normalized_value(normalized_y)):
|
|
|
# TODO: Draw coordinates even if it's outside of the image bounds.
|
|
|
return None
|
|
|
x_px = min(math.floor(normalized_x * image_width), image_width - 1)
|
|
|
y_px = min(math.floor(normalized_y * image_height), image_height - 1)
|
|
|
# return print("转化的真实坐标:",x_px, y_px)
|
|
|
return x_px,y_px
|
|
|
|
|
|
|
|
|
def draw_landmarks(
|
|
|
image: np.ndarray,
|
|
|
landmark_list: landmark_pb2.NormalizedLandmarkList,
|
|
|
connections: Optional[List[Tuple[int, int]]] = None,
|
|
|
landmark_drawing_spec: Union[DrawingSpec,Mapping[int, DrawingSpec]] = DrawingSpec(color=RED_COLOR),
|
|
|
connection_drawing_spec: Union[DrawingSpec, Mapping[Tuple[int, int],DrawingSpec]] = DrawingSpec()):
|
|
|
"""
|
|
|
主要是绘制关键点的连接图
|
|
|
image:输入的数据
|
|
|
landmark_list:关键点列表
|
|
|
connections:连接点
|
|
|
"""
|
|
|
if not landmark_list:
|
|
|
return
|
|
|
if image.shape[2] != _BGR_CHANNELS:
|
|
|
raise ValueError('Input image must contain three channel bgr data.')
|
|
|
image_rows, image_cols, _ = image.shape
|
|
|
|
|
|
idx_to_coordinates = {}
|
|
|
for idx, landmark in enumerate(landmark_list.landmark):
|
|
|
if ((landmark.HasField('visibility') and
|
|
|
landmark.visibility < _VISIBILITY_THRESHOLD) or
|
|
|
(landmark.HasField('presence') and
|
|
|
landmark.presence < _PRESENCE_THRESHOLD)):
|
|
|
continue
|
|
|
landmark_px = _normalized_to_pixel_coordinates(landmark.x, landmark.y, #将归一化坐标值转换为图像坐标值
|
|
|
image_cols, image_rows)
|
|
|
# print('图像像素坐标:',landmark_px)
|
|
|
if landmark_px:
|
|
|
idx_to_coordinates[idx] = landmark_px
|
|
|
# print("这是什么:",idx_to_coordinates[idx])
|
|
|
dot_list = []
|
|
|
if connections:
|
|
|
# num_landmarks = len(landmark_list.landmark)
|
|
|
#connections:keypoint索引元组的列表,用于指定如何在图形中连接地标。
|
|
|
# Draws the connections if the start and end landmarks are both visible.
|
|
|
|
|
|
starts = []
|
|
|
ends = []
|
|
|
for connection in connections:
|
|
|
# print("怎样连接的:",connection[0],connection[1])
|
|
|
|
|
|
start_idx = connection[0]
|
|
|
end_idx = connection[1]
|
|
|
|
|
|
starts.append(start_idx)
|
|
|
ends.append(end_idx)
|
|
|
"""取消注释部分可以绘制关键点连接的图像"""
|
|
|
# if not (0 <= start_idx < num_landmarks and 0 <= end_idx < num_landmarks):
|
|
|
# raise ValueError(f'Landmark index is out of range. Invalid connection '
|
|
|
# f'from landmark #{start_idx} to landmark #{end_idx}.')
|
|
|
# if start_idx in idx_to_coordinates and end_idx in idx_to_coordinates:
|
|
|
# drawing_spec = connection_drawing_spec[connection] if isinstance(
|
|
|
# connection_drawing_spec, Mapping) else connection_drawing_spec
|
|
|
# cv2.line(image, idx_to_coordinates[start_idx],
|
|
|
# idx_to_coordinates[end_idx], drawing_spec.color,
|
|
|
# drawing_spec.thickness)
|
|
|
|
|
|
# print("头节点:",start_list)
|
|
|
# print("尾结点:",end_list)
|
|
|
for dot in ends:
|
|
|
if dot in list(idx_to_coordinates.keys()):
|
|
|
# print((idx_to_coordinates.keys()))
|
|
|
dot_list.append(idx_to_coordinates[dot])
|
|
|
|
|
|
# if landmark_drawing_spec:
|
|
|
# for idx, landmark_px in idx_to_coordinates.items():
|
|
|
# drawing_spec = landmark_drawing_spec[idx] if isinstance(
|
|
|
# landmark_drawing_spec, Mapping) else landmark_drawing_spec
|
|
|
# # White circle border
|
|
|
# circle_border_radius = max(drawing_spec.circle_radius + 1,
|
|
|
# int(drawing_spec.circle_radius * 1.2))
|
|
|
# cv2.circle(image, landmark_px, circle_border_radius, WHITE_COLOR,
|
|
|
# drawing_spec.thickness)
|
|
|
# # Fill color into the circle
|
|
|
# cv2.circle(image, landmark_px, drawing_spec.circle_radius,
|
|
|
# drawing_spec.color, drawing_spec.thickness)
|
|
|
|
|
|
return dot_list
|
|
|
|
|
|
|
|
|
class mediapipe_detect:
|
|
|
|
|
|
def mediapipe_detection(self,image, model):
|
|
|
"""
|
|
|
mediapipe检测模块
|
|
|
image:输入数据集
|
|
|
model:调用mediapipe模型检测动作的模块
|
|
|
"""
|
|
|
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # COLOR CONVERSION BGR 2 RGB
|
|
|
image.flags.writeable = False # Image is no longer writeable
|
|
|
results = model.process(image) # 利用模块预测动作并输出坐标
|
|
|
# image.flags.writeable = True # Image is now writeable
|
|
|
# image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # COLOR COVERSION RGB 2 BGR
|
|
|
return image, results
|
|
|
|
|
|
def Drawing_bbox(self,result,bias):
|
|
|
|
|
|
'''
|
|
|
根据关键点坐标,获取最大外接矩形的坐标点
|
|
|
result:关键点坐标
|
|
|
b:修正值,增大或者减小矩形框
|
|
|
'''
|
|
|
|
|
|
result = np.array(result)
|
|
|
b = bias
|
|
|
if result.any():
|
|
|
|
|
|
rect = cv2.boundingRect(result) #返回值, 左上角的坐标[x,y, w,h]
|
|
|
|
|
|
bbox = [[rect[0] - b, rect[1] - b], [rect[0] + rect[2] + b, rect[1] - b],
|
|
|
[rect[0] - b, rect[1] + rect[3] + b], [rect[0] + rect[2] + b, rect[1] + rect[3] + b]] #四个角的坐标
|
|
|
# print(bbox)
|
|
|
return bbox
|
|
|
def get_bbox(self,image, results,face_b,left_hand_b,right_hand_b):
|
|
|
|
|
|
'''
|
|
|
主要是根据关键点坐标,绘制矩形框
|
|
|
images: 待检测数据
|
|
|
results: mediapipe检测结果
|
|
|
'''
|
|
|
|
|
|
image.flags.writeable = True
|
|
|
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
|
|
|
|
|
|
"""获取头部、手部关键点"""
|
|
|
face_location = draw_landmarks(
|
|
|
image,
|
|
|
results.face_landmarks,
|
|
|
mp_holistic.FACEMESH_CONTOURS,
|
|
|
# DrawingSpec(color=(80, 110, 10), thickness=1, circle_radius=1),
|
|
|
# DrawingSpec(color=(80, 256, 121), thickness=1, circle_radius=1)
|
|
|
)
|
|
|
|
|
|
right_hand_location = draw_landmarks(
|
|
|
image,
|
|
|
results.right_hand_landmarks,
|
|
|
mp_holistic.HAND_CONNECTIONS,
|
|
|
# DrawingSpec(color=(121, 22, 76), thickness=2, circle_radius=4),
|
|
|
# DrawingSpec(color=(121, 44, 250), thickness=2, circle_radius=2)
|
|
|
)
|
|
|
|
|
|
left_hand_location = draw_landmarks(
|
|
|
image,
|
|
|
results.left_hand_landmarks,
|
|
|
mp_holistic.HAND_CONNECTIONS,
|
|
|
# DrawingSpec(color=(245, 117, 66), thickness=2, circle_radius=4),
|
|
|
# DrawingSpec(color=(245, 66, 230), thickness=2, circle_radius=2)
|
|
|
)
|
|
|
|
|
|
"""根据关键点的坐标绘制最大外接矩形"""
|
|
|
fl_bbox = self.Drawing_bbox(face_location,face_b)
|
|
|
lh_bbox = self.Drawing_bbox(left_hand_location,left_hand_b)
|
|
|
rh_bbox = self.Drawing_bbox(right_hand_location,right_hand_b)
|
|
|
# print("fl",fl_bbox)
|
|
|
# print("lh", lh_bbox)
|
|
|
# print("rh",rh_bbox)
|
|
|
"""调整头部检测框的大小"""
|
|
|
if fl_bbox is not None:
|
|
|
"""对头部动作检测框微调"""
|
|
|
fl_bbox[3][0] = fl_bbox[3][0] - 15
|
|
|
fl_bbox[3][1] = fl_bbox[3][1] - 30
|
|
|
fl_bbox[0][0] = fl_bbox[0][0] + 30
|
|
|
fl_bbox[0][1] = fl_bbox[0][1] + 5
|
|
|
cv2.rectangle(image, fl_bbox[0], fl_bbox[3],DrawingSpec.color, DrawingSpec.thickness)
|
|
|
if lh_bbox is not None:
|
|
|
cv2.rectangle(image, lh_bbox[0], lh_bbox[3],DrawingSpec.color, DrawingSpec.thickness)
|
|
|
if rh_bbox is not None:
|
|
|
cv2.rectangle(image, rh_bbox[0], rh_bbox[3],DrawingSpec.color, DrawingSpec.thickness)
|
|
|
|
|
|
res = {'face_bbox': fl_bbox, 'hand_bbox': [lh_bbox,rh_bbox]}
|
|
|
|
|
|
return image,res
|
|
|
|
|
|
|
|
|
def main(input_path,face_b,left_hand_b,right_hand_b):
|
|
|
""""
|
|
|
图片检测模块
|
|
|
input_path:检测图片路径
|
|
|
face_b:头部标注框修正值,可根据输入值调整头部标注框的大小
|
|
|
left_hand_b:左手检测标注框...
|
|
|
right_hand_b:右手检测标注框...
|
|
|
return:返回是关键点坐标框的坐标值
|
|
|
"""
|
|
|
image = cv2.imread(input_path)
|
|
|
# image_name = os.path.basename(input_path)
|
|
|
|
|
|
with mp_holistic.Holistic(model_complexity=2,min_detection_confidence=0.5, min_tracking_confidence=0.5) as holistic:
|
|
|
image,res = mediapipe_detect().mediapipe_detection(image,holistic)
|
|
|
image ,res = mediapipe_detect().get_bbox(image,res,face_b,left_hand_b,right_hand_b)
|
|
|
# cv2.imwrite(output+"/"+image_name,image) #取消注释可以保存处理后的图片
|
|
|
cv2.namedWindow("mediapipe_detections", cv2.WINDOW_AUTOSIZE)
|
|
|
cv2.imshow("mediapipe_detections", image)
|
|
|
cv2.waitKey()
|
|
|
cv2.destroyAllWindows()
|
|
|
# print("标注框坐标值",res)
|
|
|
return res
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
input = 'D:/inference/mediapipe/mediapipe/python/video/test_picture/test11.jpg'
|
|
|
# output = 'D:/inference/mediapipe/mediapipe/python/video/output'
|
|
|
face_b = 50 #头部标注框修正值
|
|
|
left_hand_b = 7 #头部标注框修正值
|
|
|
right_hand_b = 7 #头部标注框修正值
|
|
|
main(input,face_b,left_hand_b,right_hand_b)
|
|
|
|