AIhub에 있는 '이상행동 CCTV 동영상 데이터셋'에서 이상행동을 하는 부분만 잘라내어 학습 데이터로 쓰려고 합니다.
이상행동명칭 | 영상 개수 | 영상 시간 |
---|---|---|
01.폭행(Assault) | 913 | 78:05:41 |
02.싸움(Fight) | 1174 | 99:54:45 |
03.절도(Burglary) | 839 | 69:33:24 |
04.기물파손(Vandalism) | 490 | 41:28:46 |
05.실신(Swoon) | 912 | 84:26:16 |
06.배회(Wander) | 645 | 55:24:50 |
07.침입(Trespass) | 259 | 22:03:15 |
08.투기(Dump) | 259 | 22:03:15 |
09.강도(Robbery) | 259 | 22:03:15 |
10.데이트폭력 및 추행(Datefight) | 693 | 58:21:45 |
11.납치(Kidnap) | 262 | 22:24:08 |
12.주취행동(Drunken) | 1262 | 104:20:37 |
합계 | 8436 | 717:03:33 |
<action>
안에 <actionname>
과 <frame>
이 필요한 정보입니다. 영상 안에서 Kick이나 punch등 다양한 이상행동들이 나와요. 우리는 영상에서 이상행동이 나오는 부분을 잘라 행동 종류별로 각각 폴더에 담을겁니다.FFmpeg는 영상 처리를 위한 라이브러리입니다. 자세하게 알고 싶다면 여기 wikidocs로 들어가 보세요. 저는 대략 이해하는데 도움이 됐어요.
!ffmpeg -i {input_file} -vf scale=3840x2160
-vf trim=start_frame={start_frame}:end_frame={end_frame} {output_file}
보통 cmd창에서 영상을 편집하는데 저는 python의 매직커맨드 기능을 사용해서 cmd 명령어를 python에서 사용했어요. 앞에 '!' 를 붙이면 python에서 cmd 명령어를 사용할 수 있어요.
코드를 설명은 아래 있어요.
다른 포스트 보니까 -ss 같은 옵션을 쓰면 초단위로 설정해서 자를 수도 있다고 하네요.
!ffplay -i {file_path} -video_size 3840x2160 -loop 0
편집한 영상을 코드로 실행하려면 위 코드를 사용하시면 됩니다.
우선 파일들을 가져옵시다.
import os
data_base_folder = 'C:\\Users\\luna_f1\\Desktop\\aihub_video_edit'
video_path = data_base_folder + '\\videos'
label_path = data_base_folder + '\\labels'
save_folder = data_base_folder + '\\save'
video_file_list = os.listdir(video_path)
label_file_list = os.listdir(label_path)
video_file_list.sort()
label_file_list.sort()
print('img len: {}, label len: {}\n'.format(len(video_file_list), len(label_file_list)))
print(video_file_list[:10])
print(label_file_list[:10])
## 출력 예시
## img len: 1, label len: 1
## ['488-3_cam03_vandalism01_place09_day_winter.mp4']
## ['488-3_cam03_vandalism01_place09_day_winter.xml']
label_file_list에는 xml파일들이, video_file_list에는 영상들 파일 이름이 들어있어요.
그리고 먼저 영상 편집을 할 함수를 설계해봅시다.
def cut_video(start_frame, end_frame, input_file, output_file):
!ffmpeg -i {input_file} -vf scale=3840x2160
-vf trim=start_frame={start_frame}:end_frame={end_frame} {output_file}
return
함수에서 시작프레임, 끝프레임, 입력 파일, 출력 파일을 매개변수로 받아 편집하도록 만들었어요.
그러면 xml파일을 읽어서 함수를 호출하기만 하면 됩니다. 아래는 그 코드에요.
import xml.etree.ElementTree as ET
import os
# 영상과 xml 라벨을 짝꿍을 맞춰 가져옴
for video_file, label_file in zip(video_file_list, label_file_list):
video_file_path = video_path + '\\' + video_file
label_file_path = label_path + '\\' + label_file
label = ET.parse(label_file_path).getroot()
# 잘라낼 영상 부분을 actionname에 따라 분류함.
highlights = { }
for clss in label.iter("actionname"):
clss = clss.text
highlights[clss] = []
# 영상들을 저장할 폴더를 actionname별로 각각 만듦
try:
os.mkdir(save_folder + '\\' + clss)
except:
pass
# 영상 안의 이상행동들의 시작프레임, 끝프레임을 리스트로 묶어 이상행동 종류별로 dict에 저장함.
# 구조: highlights['punching'] = [[start, end], [start, end]...]
for action in label.iter("action"):
hlight_class = action.find('actionname').text
for start, end in zip(action.iter('start'), action.iter('end')):
highlights[hlight_class].append([start.text, end.text])
# 이상행동 부분들을 잘라서 now_save_folder_path에 저장함
for clss in highlights.keys():
now_save_folder_path = save_folder + '\\' + clss
#video_file_path = video_path + '\\' + video_file
for [start, end] in highlights[clss]:
cut_video(start, end, video_file_path, now_save_folder_path + '\\' + start + end + '.yuv')
이미지 데이터에서 OpenCV를 써서 동영상 자르는것도 이걸로 하려고 했는데 생각보다 뻘짓이 길어졌네요. 내가 실력이 모자라서 그런거 같습니. 혹시 누군가에게 도움이 될지 모르니 실패한 과정을 적어놓겠습니다.
######### 동영상 처리는 OpenCV보다는FFmpeg를 쓰자!
import cv2
import numpy as np
import xml.etree.ElementTree as ET
for video_file, label_file in zip(video_file_list, label_file_list):
video_file_path = video_path + '\\' + video_file
label_file_path = label_path + '\\' + label_file
cap = cv2.VideoCapture(video_file_path)
label = ET.parse(label_file_path).getroot()
# 잘라낼 영상 부분을 punching, kicking, pushing 3가지로 분류함. (각각 여러개 있을 수 있음)
highlight = {
'punching' : [],
'kicking' : [],
'pushing' : []
}
# 구조: highlight['punching'] = [[start, end], [start, end]...]
for action in label.iter("action"):
hlight_class = action.find('actionname').text
for start, end in zip(action.iter('start'), action.iter('end')):
highlight[hlight_class].append([int(start.text), int(end.text)])
#재생할 파일의 넓이와 높이
width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
fourcc = cv2.VideoWriter_fourcc(*'DIVX')
# 영상을 여러개 출력해야하므로 영상 하나마다 out writer 하나를 지정함.
outs = {
'punching' : [],
'kicking' : [],
'pushing' : []
}
for hlight_class in highlight.keys():
for i in range(len(highlight[hlight_class])):
outs[hlight_class].append(cv2.VideoWriter(save_folder + "\\output_"+ hlight_class + str(i) + '_'+ video_file, fourcc, 30.0, (int(width), int(height))))
while cap.isOpened():
success, image = cap.read()
if not success:
print("카메라를 찾을 수 없습니다.")
break
now_frame = int(cap.get(cv2.CAP_PROP_POS_FRAMES))
if not now_frame % 20 == 0: continue
# 필요에 따라 성능 향상을 위해 이미지 작성을 불가능함으로 기본 설정합니다.
image.flags.writeable = False
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 비디오 저장 파트입니다.
cv2.imshow('video_show', image)
for hlight_class in highlight.keys():
for i in range(len(outs[hlight_class])):
if now_frame >= highlight[hlight_class][i][0] and now_frame <= highlight[hlight_class][i][1]: # event frame 값이면
outs[hlight_class][i].write(image) # 이 부분이 write하는 부분.
if cv2.waitKey(2) & 0xFF == 27:
break
cap.release()
for key in outs.keys():
for out in outs[key]:
out.release()
cv2.destroyAllWindows()
영상을 쭉 진행하다가 현재 프레임이 이상행동 프레임과 일치하면 동영상으로 저장하는 식으로 설계했습니다. MediaPipe에서 쓰던 코드를 재활용하다 이렇게 비효율적을 짠것같습니다. 여러모로 고칠점이 많아서 배울것도 많았어요.