File size: 10,865 Bytes
09462dc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
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