Safemotion Lib
Loading...
Searching...
No Matches
pose_transform.py
Go to the documentation of this file.
2# PoseDecode
3# -> 키포인트, 스코어 샘플링
4
5# PoseCompact
6# -> 키포인트의 바운더리 찾고(마진 추가) -> 키포인트 좌표 변환(바운더리 왼쪽 상단을 0, 0)
7# -> img_shape를 (바운더리의 높이,너비)로 변경
8# -> 자르는 영역 계산해둠
9
10# Resize
11# -> img_shape를 64x64 로 바꿈
12# -> sacale_factor 계산함
13# -> sacale_factor를 키포인트에 곱함
14
15# RandomResizedCrop
16# -> 크롭 영역 설정
17# -> img_shape를 크롭된 크기로 설정
18# -> 키포인트 크롭 : 키포인트 좌표에서 크롭 영역의 시작점(좌상단)을 빼줌
19
20# Resize
21# -> 56x56
22
23# Flip
24# -> 키포인트 좌우 반전하고, 좌우 키포인트 및 스코어를 인덱스에 맞도록 바꿔줌
25
26import numpy as np
27import random
28
29
30def pose_sampling(sample, clip_len):
31 """
32 스켈레톤 데이터를 랜덤으로 샘플링하는 기능
33 args:
34 sample (dict): 데이터, 여러 프레임의 스켈레톤 좌표와 스코어 값이 저장된 'keypoint', 'keypoint_score'를 포함하여야함
35 'image_feature'가 존재하면 'image_feature'도 함께 샘플링함
36 clip_len (int): 샘플링 수량
37 return (dict): 샘플링된 데이터
38 """
39
40 num_frames = sample['keypoint'].shape[1] #프레임 수
41 if num_frames < clip_len: #프레임 수가 clip_len보다 작을 경우
42 start = np.random.randint(0, num_frames)
43 inds = np.arange(start, start + clip_len)
44 elif clip_len <= num_frames < 2 * clip_len: #프레임수가 clip_len의 2배수보다 작을경우
45 basic = np.arange(clip_len)
46 inds = np.random.choice(
47 clip_len + 1, num_frames - clip_len, replace=False)
48 offset = np.zeros(clip_len + 1, dtype=np.int32)
49 offset[inds] = 1
50 offset = np.cumsum(offset)
51 inds = basic + offset[:-1]
52 else: #프레임수가 clip_len의 2배수보다 클 경우
53 bids = np.array(
54 [i * num_frames // clip_len for i in range(clip_len + 1)])
55 bsize = np.diff(bids)
56 bst = bids[:clip_len]
57 offset = np.random.randint(bsize)
58 inds = bst + offset
59
60 sample['keypoint'] = sample['keypoint'][:, inds].astype(np.float32)
61 sample['keypoint_score'] = sample['keypoint_score'][:, inds].astype(np.float32)
62 if 'image_feature' in sample:
63 sample['image_feature'] = sample['image_feature'][:, inds]
64 return sample
65
66
67def pose_compact(sample):
68 """
69 모든 스켈레톤을 포함하는 영역을 계산하고 좌상단의 좌표가 (0, 0)이 되도록 스켈레톤 좌표를 shifting함
70 스켈레톤 좌표의 전체 영역의 크기(높이, 너비)는 'kp_hw'에 저장됨
71 args:
72 sample (dict): 데이터, 스켈레톤 좌표가 저장된 'keypoint'를 포함함
73 return (dict): shifting함된 스켈레톤 좌표와 영역의 크기가 포함된 데이터
74 """
75 keypoint = sample['keypoint']
76 # keypoint shape : (1, 60, 17, 2)
77 kp_x = keypoint[..., 0] #x 좌표
78 kp_y = keypoint[..., 1] #y 좌표
79
80 #좌표의 최대 최소값
81 min_x = kp_x.min()
82 min_y = kp_y.min()
83 max_x = kp_x.max()
84 max_y = kp_y.max()
85
86 #패딩(마진) 추가
87 padding = 0.25
88 center = ((max_x + min_x) / 2, (max_y + min_y) / 2) #박스 중심
89 half_width = (max_x - min_x) / 2 * (1 + padding) #패딩이 추가된 너비 절반
90 half_height = (max_y - min_y) / 2 * (1 + padding) #패딩이 추가된 높이 절반
91
92 #너비 높이 중 최대값을 사용
93 max_val = max(half_width, half_height)
94 half_width = max_val
95 half_height = max_val
96
97 #스켈레톤 영역(패딩 추가)
98 min_x, max_x = center[0] - half_width, center[0] + half_width
99 min_y, max_y = center[1] - half_height, center[1] + half_height
100
101 #타입변환
102 min_x, min_y = int(min_x), int(min_y)
103 max_x, max_y = int(max_x), int(max_y)
104
105 #스켈레톤 영역의 좌상단이 0, 0이 되도록 좌표 변환(shifting)
106 kp_x -= min_x
107 kp_y -= min_y
108
109 #영역 크기 설정
110 sample['kp_hw'] = (max_y - min_y, max_x - min_x)
111
112 return sample
113
114def pose_shift(sample, shift_ratio=0.05):
115 """
116 스켈레톤 좌표를 일정 비율만큼 이동 시키는 기능
117 args:
118 sample (dict): 데이터, 스켈레톤 좌표가 저장된 'keypoint'를 포함함
119 shift_ratio (float): 좌표 이동 비율
120 return (dict): 좌표가 이동된 스켈레톤을 포함하는 데이터
121 """
122
123 keypoint = sample['keypoint']
124 #좌표의 최대, 최소 설정
125 x1 = keypoint[:, 0, :, 0].min()
126 x2 = keypoint[:, 0, :, 0].max()
127 y1 = keypoint[:, 0, :, 1].min()
128 y2 = keypoint[:, 0, :, 1].max()
129
130 #이동 비율 설정
131 w_range = (x2-x1)*shift_ratio
132 h_range = (y2-y1)*shift_ratio
133
134 #이동할 값을 랜덤으로 설정
135 dx = random.uniform(-w_range, w_range)
136 dy = random.uniform(-h_range, h_range)
137
138 #좌표 변환
139 sample['keypoint'][:, :, :, 0] += dx
140 sample['keypoint'][:, :, :, 1] += dy
141
142 return sample
143
144def pose_resize(sample, scale=(64,64)):
145 """
146 스켈레톤 전체 영역의 크기를 지정한 크기로 변환
147 args:
148 sample (dict): 데이터, 스켈레톤 좌표가 저장된 'keypoint'와 스켈레톤 영역의 크기가 저장된 'kp_hw'를 포함함
149 scale (tuple): 변환하려는 크기, (높이, 너비)의 형태
150 return (dict): 리사이즈된 스켈레톤을 포함한 데이터
151 """
152
153 keypoint = sample['keypoint']
154 h, w = sample['kp_hw'] #현재 스켈레톤 영역의 크기
155 new_h, new_w = scale #리사이즈될 영역
156
157 #리사이즈(좌표 변환)
158 scale_factor = np.array([new_w / w, new_h / h], dtype=np.float32) #스케일 계산
159 sample['keypoint'] = keypoint * scale_factor #변환
160 sample['kp_hw'] = scale #영역 크기 설정
161
162 return sample
163
164
165def get_crop_bbox(kp_hw, area_range, aspect_ratio_range, max_attempts=10):
166 """
167 랜덤으로 자르는 영역을 생성하는 기능
168 args:
169 kp_hw (tuple): 스켈레톤을 모두 포함하는 박스의 영역 크기
170 area_range (tuple): 자르는 영역 크기의 범위, 원본 대비 비율
171 aspect_ratio_range (tuple): aspect_ratio(가로 세로 비율) 범위
172 max_attempts (int): 랜덤 생성 최대 반복횟수, 10번안에 파라미터를 만족하는 영역이 생성되지 않으면 고정된 영역을 반환함
173 return (float, float, float, float): left, top, right, bottom
174 """
175
176 #영역 크기 계산
177 h, w = kp_hw
178 area = h * w
179
180 #타겟 aspect_ratio 및 영역 크기 설정
181 min_ar, max_ar = aspect_ratio_range
182 aspect_ratios = np.exp( np.random.uniform(np.log(min_ar), np.log(max_ar), size=max_attempts) )
183 target_areas = np.random.uniform(*area_range, size=max_attempts) * area
184
185 #aspect_ratio에 따른 자르는 너비 높이 후보 설정
186 candidate_crop_w = np.round(np.sqrt(target_areas * aspect_ratios)).astype(np.int32)
187 candidate_crop_h = np.round(np.sqrt(target_areas / aspect_ratios)).astype(np.int32)
188
189 #조건에 맞는 영역이 있는지 검색, 조건에 맞는 영역이 있다면 해당 영역을 반환함
190 for i in range(max_attempts):
191 crop_w = candidate_crop_w[i]
192 crop_h = candidate_crop_h[i]
193
194 #자르는 영역의 크기가 기존 영역보다 작은지 체크
195 if crop_h <= h and crop_w <= w:
196 x_offset = random.randint(0, w - crop_w)
197 y_offset = random.randint(0, h - crop_h)
198 return x_offset, y_offset, x_offset + crop_w, y_offset + crop_h
199
200 # Fallback
201 crop_size = min(h, w)
202 x_offset = (w - crop_size) // 2
203 y_offset = (h - crop_size) // 2
204 return x_offset, y_offset, x_offset + crop_size, y_offset + crop_size
205
206def pose_random_crop(sample, area_range=(0.56, 1.0), aspect_ratio_range=(3 / 4, 4 / 3)):
207 """
208 키포인트를 랜덤으로 자르는 기능, 랜덤으로 영역을 생성하고 자름
209 args:
210 sample (dict): 데이터, 스켈레톤 좌표가 저장된 'keypoint'와 스켈레톤 영역의 크기가 저장된 'kp_hw'를 포함함
211 area_range (tuple): 자르는 영역 크기의 범위, 원본 대비 비율
212 aspect_ratio_range (tuple): aspect_ratio(가로 세로 비율) 범위
213 return (dict): 잘린 키포인트가 포함된 데이터
214 """
215
216 keypoint = sample['keypoint']
217 h, w = sample['kp_hw']
218
219 #자르는 영역 설정
220 left, top, right, bottom = get_crop_bbox( (h, w), area_range, aspect_ratio_range)
221 new_h, new_w = bottom - top, right - left
222 crop_bbox = np.array([left, top, right, bottom])
223
224 #자르기(좌표 변환)
225 sample['keypoint'] = keypoint - crop_bbox[:2]
226 sample['kp_hw'] = (new_h, new_w)
227
228 return sample
229
230def pose_flip(sample, flip_ratio=0.5):
231 """
232 스켈레톤을 좌우 반전하는 기능
233 args:
234 sample (dict): 데이터, 여러 프레임의 스켈레톤 좌표와 스코어 값이 저장된 'keypoint', 'keypoint_score'와 스켈레톤 영역의 크기가 저장된 'kp_hw'를 포함함
235 flip_ratio (float): 좌우 반전 할 확률
236 return (dict): 일정 확률로 좌우 반전이된 스켈레톤을 포함하는 데이터
237 """
238
239 #좌우 반전 진행 여부 설정
240 flip = np.random.rand() < flip_ratio
241
242 if flip: #좌우 반전 진행
243 #17개 스켈레톤 좌표 인덱스, 코(0번) 제외
244 left_kp = [1, 3, 5, 7, 9, 11, 13, 15] #왼쪽 스켈레톤
245 right_kp = [2, 4, 6, 8, 10, 12, 14, 16] #오른쪽 스켈레톤
246
247 h, w = sample['kp_hw']
248 keypoint = sample['keypoint']
249 keypoint_score = sample['keypoint_score']
250
251 keypoint[..., 0] = w - keypoint[..., 0] #좌우 반전
252
253 #스켈레톤 인덱스 변경
254 new_order = list(range(keypoint.shape[2]))
255 for left, right in zip(left_kp, right_kp):
256 new_order[left] = right
257 new_order[right] = left
258 keypoint = keypoint[:, :, new_order]
259 keypoint_score = keypoint_score[:, :, new_order]
260
261 sample['keypoint'] = keypoint
262 sample['keypoint_score'] = keypoint_score
263
264 return sample
265
266
267def generate_a_heatmap(pose_heatmap, centers, max_values):
268 """
269 하나의 스켈레톤에 대한 히트맵을 생성하는 기능, 여러명의 사람일 수 있음
270 args:
271 pose_heatmap (np.array): 1종 스켈레톤의 히트맵, shape (H, W)
272 centers (np.array): 1-종의 스켈레톤 좌표(ex-손목, 코 등), shape (num_person, 2)
273 max_values (np.array): 1-종의 스켈레톤 스코어, shape (num_person)
274 return ():
275 """
276 sigma = 0.6
277 eps = 1e-4
278 img_h, img_w = pose_heatmap.shape
279 for center, max_value in zip(centers, max_values):
280
281 if max_value < eps:
282 continue
283
284 #패치 영역 설정
285 mu_x, mu_y = center[0], center[1]
286 st_x = max(int(mu_x - 3 * sigma), 0)
287 ed_x = min(int(mu_x + 3 * sigma) + 1, img_w)
288 st_y = max(int(mu_y - 3 * sigma), 0)
289 ed_y = min(int(mu_y + 3 * sigma) + 1, img_h)
290 x = np.arange(st_x, ed_x, 1, np.float32)
291 y = np.arange(st_y, ed_y, 1, np.float32)
292
293 # if the keypoint not in the heatmap coordinate system
294 if not (len(x) and len(y)):
295 continue
296 y = y[:, None]
297
298 #패치의 값 셋팅, 가우시안 분포
299 patch = np.exp(-((x - mu_x)**2 + (y - mu_y)**2) / 2 / sigma**2)
300 patch = patch * max_value #스코어값 적용
301
302 #여러 사람일 경우 중복된 위에 값이 있을 경우 최대값 사용
303 pose_heatmap[st_y:ed_y, st_x:ed_x] = \
304 np.maximum(pose_heatmap[st_y:ed_y, st_x:ed_x], patch)
305
306def generate_heatmap(pose_heatmap, kps, kpscores):
307 """
308 한 프레임에 대한 스켈레톤 히트맵을 생성하는 기능
309 args:
310 pose_heatmap :
311 kps : 스켈레톤 좌표, shape (num_person, 17, 2)
312 kpscores : 스켈레톤 스코어, shape (num_person, 17)
313 """
314 num_kp = kps.shape[1]
315 for i in range(num_kp):
316 generate_a_heatmap(pose_heatmap[i], kps[:, i], kpscores[:, i])
317
319 """
320 스켈레톤 좌표를 사용해서 히트맵을 생성하는 기능
321 args:
322 sample (dict): 데이터, 여러 프레임의 스켈레톤 좌표와 스코어 값이 저장된 'keypoint', 'keypoint_score'와 스켈레톤 영역의 크기가 저장된 'kp_hw'를 포함함
323 return (dict): 스켈레톤을 기반으로한 히트맵이 포함된 데이터, 히트맵의 키는 'pose_heatmap'임
324 """
325 keypoint = sample['keypoint']
326 keypoint_score = sample['keypoint_score']
327
328 h, w = sample['kp_hw']
329 num_frame = keypoint.shape[1]
330 num_c = keypoint.shape[2]
331
332 #히트맵 공간 미리 생성
333 pose_heatmap = np.zeros([num_frame, num_c, h, w], dtype=np.float32)
334 for i in range(num_frame):
335
336 kps = keypoint[:, i]# shape (num_person, 17, 2), 17=스켈레톤 수
337 kpscores = keypoint_score[:, i]
338
339 #히트맵 생성
340 generate_heatmap(pose_heatmap[i], kps, kpscores)
341
342 # T, C, H, W -> C, T, H, W
343 # T : 프레임, C : 스켈레톤 수
344 pose_heatmap = np.transpose(pose_heatmap, (1, 0, 2, 3))
345 sample['pose_heatmap'] = pose_heatmap
346 return sample
pose_flip(sample, flip_ratio=0.5)
get_crop_bbox(kp_hw, area_range, aspect_ratio_range, max_attempts=10)
generate_a_heatmap(pose_heatmap, centers, max_values)
generate_heatmap(pose_heatmap, kps, kpscores)
make_pose_heatmap(sample)
pose_shift(sample, shift_ratio=0.05)
pose_random_crop(sample, area_range=(0.56, 1.0), aspect_ratio_range=(3/4, 4/3))
pose_resize(sample, scale=(64, 64))
pose_sampling(sample, clip_len)