[FFmpeg] 동영상 편집할 때는 OpenCV말고 FFmpeg쓰는게 훨 낫다.

정기용·2023년 1월 30일
0

lunalabs

목록 보기
5/5
post-thumbnail

목표

AIhub에 있는 '이상행동 CCTV 동영상 데이터셋'에서 이상행동을 하는 부분만 잘라내어 학습 데이터로 쓰려고 합니다.

1. 데이터 살펴보기

  • 데이터 종류와 크기
이상행동명칭영상 개수영상 시간
01.폭행(Assault)91378:05:41
02.싸움(Fight)117499:54:45
03.절도(Burglary)83969:33:24
04.기물파손(Vandalism)49041:28:46
05.실신(Swoon)91284:26:16
06.배회(Wander)64555:24:50
07.침입(Trespass)25922:03:15
08.투기(Dump)25922:03:15
09.강도(Robbery)25922:03:15
10.데이트폭력 및 추행(Datefight)69358:21:45
11.납치(Kidnap)26222:24:08
12.주취행동(Drunken)1262104:20:37
합계8436717:03:33
  • 라벨 (XML파일)
    라벨 <action>안에 <actionname><frame>이 필요한 정보입니다. 영상 안에서 Kick이나 punch등 다양한 이상행동들이 나와요. 우리는 영상에서 이상행동이 나오는 부분을 잘라 행동 종류별로 각각 폴더에 담을겁니다.

2. FFmpeg 간단 소개

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 명령어를 사용할 수 있어요.

코드를 설명은 아래 있어요.

  • -i 뒤에 편집할 동영상 경로 입력
  • vf scale= 뒤에 화면 사이즈 입력 (영상 속성에서 확인 가능)
  • vf trim= 뒤에 시작 frame과 끝 frame을 입력
  • output_file칸에 저장할 파일 이름 입력 (확장자까지 입력)

다른 포스트 보니까 -ss 같은 옵션을 쓰면 초단위로 설정해서 자를 수도 있다고 하네요.

!ffplay -i {file_path} -video_size 3840x2160 -loop 0

편집한 영상을 코드로 실행하려면 위 코드를 사용하시면 됩니다.

3. Python에서 XML파일 읽기

우선 파일들을 가져옵시다.

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를 써서 동영상 자르는것도 이걸로 하려고 했는데 생각보다 뻘짓이 길어졌네요. 내가 실력이 모자라서 그런거 같습니. 혹시 누군가에게 도움이 될지 모르니 실패한 과정을 적어놓겠습니다.

######### 동영상 처리는 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에서 쓰던 코드를 재활용하다 이렇게 비효율적을 짠것같습니다. 여러모로 고칠점이 많아서 배울것도 많았어요.

profile
SKKU CS 23

0개의 댓글