import numpy as np from collections import deque import numpy as np from collections import deque import math def get_bbox_area(bbox): x1, y1, x2, y2 = bbox return (x2 - x1) * (y2 - y1) def check_consistant(boxA, boxB, scoreA_lst, scoreB_lst, beta, all_threshold): """ 计算两个锚框之间的 IoU(交并比)以及分数的变化比例,来判断连续性 """ # 计算交集框的坐标 scoreA = scoreA_lst[0] scoreB = scoreB_lst[0] iou = get_IoU(boxA, boxB) reduction_ratio = (scoreA - scoreB) / (scoreA + scoreB) if scoreA > scoreB else 0 # 如果分数减少的很多,就更不连续 return iou - reduction_ratio * beta > all_threshold def get_IoU(boxA, boxB): """ 计算两个锚框之间的 IoU(交并比)以及分数的变化比例,来判断连续性 """ # 计算交集框的坐标 x1_int = max(boxA[0], boxB[0]) y1_int = max(boxA[1], boxB[1]) x2_int = min(boxA[2], boxB[2]) y2_int = min(boxA[3], boxB[3]) # 计算交集的面积 inter_width = max(0, x2_int - x1_int) inter_height = max(0, y2_int - y1_int) inter_area = inter_width * inter_height # 计算两个锚框的面积 areaA = (boxA[2] - boxA[0]) * (boxA[3] - boxA[1]) areaB = (boxB[2] - boxB[0]) * (boxB[3] - boxB[1]) # 计算并集的面积 union_area = areaA + areaB - inter_area # 计算 IoU iou = inter_area / union_area if union_area > 0 else 0.0 return iou def check_bbox_single_for_video(bbox, reference_width, reference_height, min_bbox_width=1/6, min_bbox_area=1/30): """ 每一帧都要检查,判断 bbox 是否符合视频格式下的要求 """ x1, y1, x2, y2 = bbox bbox_width = x2 - x1 bbox_height = y2 - y1 # 防止非法 bbox if bbox_width <= 0 or bbox_height <= 0: return False bbox_area = bbox_width * bbox_height if bbox_area < min_bbox_area: # print("filtered: bbox too small or too large") return False # 横屏额外筛 if reference_width > reference_height: if bbox_width < min_bbox_width: # print("filtered: bbox too wide") return False return True ##############################判断点是否满足################################ def part5_valid(valid_joints): """ 判断身体五块是不是都有点 参数: valid_joints:布尔数组 返回: bool: 如果满足要求返回True,否则返回False。 """ ### 认为以下的视频是满足我们采样需要的: ### A只有上半身手部动作的:手部有可能在挥舞过程中移出屏幕,但是上半身应该一直在屏幕内,此时1-0, 1-2, 1-5这三条骨骼应该都存在;14-17应该至少有一个点存在 ### B全身动作:1-0, 1-2, 1-5, 1-8, 1-11这五条骨骼应该都存在 top_core_joints = valid_joints[[0,1,2,5]] top_nece_joints = valid_joints[[14,15,16,17]] if all(top_core_joints) and any(top_nece_joints): return True wholebody_core_joints = valid_joints[[0,1,2,5,8,11]] if all(wholebody_core_joints): return True return False def check_valid_sequence(valid_keypoints, threshold=0.3): valid_joints = np.zeros(24) for valid_keypoint in valid_keypoints: valid_joints += valid_keypoint return part5_valid(valid_joints) def get_valid_indice_from_keypoints(ref_part_poses, ref_part_indices): # ref_part_poses: poses序列,ref_part_indices poses序列里面每个值对应的在整个序列里的index # return: 每个pose序列里面,满足要求的pose的index valid_indice = [] for i, (keypoint_all, indice) in enumerate(zip(ref_part_poses, ref_part_indices)): body_subset = keypoint_all["bodies"]["subset"][0] valid_joints = body_subset > -1 # 得到一个布尔索引 if not part5_valid(valid_joints): continue faces = keypoint_all["faces"][0] left_eye = faces[36:42] # 左眼关键点 5个关键点有4个认为有左眼 right_eye = faces[42:48] # 右眼关键点 5个关键点有4个认为有右眼 nose = faces[27:36] # 鼻子关键点 8个关键点有5个认为有鼻子 mouth = faces[48:68] # 嘴巴关键点 21个关键点有15个认为有嘴巴 # 计算每个部位有效的关键点数 left_eye_valid = sum(1 for point in left_eye if point[0] > 0 and point[1] > 0) right_eye_valid = sum(1 for point in right_eye if point[0] > 0 and point[1] > 0) nose_valid = sum(1 for point in nose if point[0] > 0 and point[1] > 0) mouth_valid = sum(1 for point in mouth if point[0] > 0 and point[1] > 0) # 如果有两个或以上部位有效,则认为是正脸 valid_face_parts = 0 if left_eye_valid >= 4: valid_face_parts += 1 if right_eye_valid >= 4: valid_face_parts += 1 if nose_valid >= 5: valid_face_parts += 1 if mouth_valid >= 15: valid_face_parts += 1 if valid_face_parts >= 2: valid_indice.append(int(indice)) return valid_indice def check_from_keypoints_core_keypoints(keypoints, bboxs): # 用于keypoints版本,根据每一帧的18个keypoints和bbox iou来判断是否满足要求 valid_sequence = deque(maxlen=4) for i, (keypoint_all, bbox_all) in enumerate(zip(keypoints, bboxs)): body_subset = keypoint_all["bodies"]["subset"][0] valid_joints = body_subset > -1 # 得到一个布尔索引 if len(valid_sequence) == 4: if not check_valid_sequence(valid_sequence): # print("filtered: 骨骼不满足要求") return False # 关键点异常 valid_sequence.append(valid_joints) return True def select_ref_from_keypoints_bbox_multi(ref_part_indices, ref_part_bboxes, bboxs): for ref_index, ref_bbox in zip(ref_part_indices, ref_part_bboxes): bbox_areas_ref = [get_bbox_area(bbox) for bbox in ref_bbox] max_bbox_area_ref = max(bbox_areas_ref) num_human_ref = sum(1 for bbox in ref_bbox if get_bbox_area(bbox) > max_bbox_area_ref * 0.5) if num_human_ref < 2 or num_human_ref > 5: continue driving_bbox_ok = True for i, bbox_all in enumerate(bboxs): bbox_areas = [get_bbox_area(bbox) for bbox in bbox_all] max_bbox_area = max(bbox_areas) num_human = sum(1 for bbox in bbox_all if get_bbox_area(bbox) > max_bbox_area * 0.5) if num_human != num_human_ref: driving_bbox_ok = False break if driving_bbox_ok: return int(ref_index) else: continue return None def check_from_keypoints_bbox(keypoints, bboxs, IoU_thresthold, reference_width, reference_height, multi_person=False): # 用于keypoints版本,根据每一帧的18个keypoints和bbox iou来判断是否满足要求 last_bbox = None for i, (keypoint_all, bbox_all) in enumerate(zip(keypoints, bboxs)): if not len(bbox_all): return False else: if multi_person: for bbox in bbox_all: if not check_bbox_single_for_video(bbox, reference_width, reference_height, min_bbox_width=1/6): return False else: bbox = bbox_all[0] if not check_bbox_single_for_video(bbox, reference_width, reference_height, min_bbox_width=1/7): return False # bbox大小异常 if last_bbox is not None: if not get_IoU(bbox, last_bbox) > IoU_thresthold: return False # IoU异常 last_bbox = bbox return True def check_from_keypoints_stick_movement(keypoints, angle_threshold): # 骨骼选择:列表中每个元组表示由两个关节确定一条骨骼:格式 (joint_a, joint_b) # bones = [(1, 0), (1, 2), (1, 5), (1, 8), (1, 11)] bones = [(1, 0), (1, 2), (1, 5), (1, 8), (1, 11), (2, 3), (5, 6), (8, 9), (11, 12)] max_delta_list = [] # 遍历从第二帧开始,对比前一帧和当前帧 human_num_list = [len(keypoints[idx]["bodies"]["candidate"]) for idx in range(0, len(keypoints))] min_human_num = min(human_num_list) for human_idx in range(min_human_num): for i in range(1, len(keypoints)): # 获取上一帧和当前帧的关键点数据(格式为 (18,3) 数组) prev_frame_subset = keypoints[i-1]["bodies"]["subset"][human_idx] curr_frame_subset = keypoints[i]["bodies"]["subset"][human_idx] prev_frame_keypoints = keypoints[i-1]["bodies"]["candidate"][human_idx] curr_frame_keypoints = keypoints[i]["bodies"]["candidate"][human_idx] max_delta = 0 for (j1, j2) in bones: # 检查上一帧中两个关节是否有效(假设 x, y 坐标需大于 0 才认为有效) if prev_frame_subset[j1] < 0 or prev_frame_subset[j2] < 0: continue if curr_frame_subset[j1] < 0 or curr_frame_subset[j2] < 0: continue # 计算上一帧和当前帧中对应骨骼的向量(方向一致,均从 j1 指向 j2) vec_prev = np.array([prev_frame_keypoints[j2][0] - prev_frame_keypoints[j1][0], prev_frame_keypoints[j2][1] - prev_frame_keypoints[j1][1]]) vec_curr = np.array([curr_frame_keypoints[j2][0] - curr_frame_keypoints[j1][0], curr_frame_keypoints[j2][1] - curr_frame_keypoints[j1][1]]) # 如果向量模长为0,则无法计算角度,跳过 if np.linalg.norm(vec_prev) == 0 or np.linalg.norm(vec_curr) == 0: continue # 计算向量对应的角度(弧度制) angle_prev = math.atan2(vec_prev[1], vec_prev[0]) angle_curr = math.atan2(vec_curr[1], vec_curr[0]) # 计算角度差,并规范到 [0, pi] 范围 delta = abs(angle_curr - angle_prev) if delta > math.pi: delta = 2 * math.pi - delta max_delta = max(delta, max_delta) max_delta_list.append(max_delta) max_delta_list = sorted(max_delta_list) max_delta_list = max_delta_list[len(max_delta_list)//8:-len(max_delta_list)//8] # 去掉两端8分之一的值 avg_movement = sum(max_delta_list) / len(max_delta_list) if avg_movement < angle_threshold: # 筛去过小的动作 return False return True