공부/A.I

[OpenCV] Frame latency 문제

래울 2024. 8. 17. 17:09

OpenCV로 카메라의 영상을 녹화할 때 녹화된 영상의 재생 속도가 맞지 않는 경우가 종종 있다.

cv2.VideoWriter로 영상 생성 시, 프레임 지연으로 인해 적절한 속도로 프레임을 write하지 못하기 때문일 수 있다.

 

책상 옆에 굴러다니던 라이언...

before(fps30)

 

 

아래 예시 코드를 보면

카메라의 fps는 30이고 30fps로 생성된 VideoWriter에 frame을 write한다.

하지만 추론모델을 거치기 때문에 아래와 같이 각 프레임에 지연이 발생한다.

약 0.2 ~ 정도의 지연

 

즉, 아래 그림과 같이  VideoWriter에 쌓이게 된다.

30fps -> 4fps

 

 

즉 실제로는 4~5fps만 write하고 있지만, VideoWriter를 30fps로 생성하게 되면 7초 이상이 지나야 1초의 영상이 생성된다.

6s -> 1s

 

따라서 frame latency를 고려한 VideoWriter의 fps를 주게되면 정상적인 속도의 영상을 얻을 수 있다.

 


코드1

더보기

테스트 코드(before)

from ultralytics import YOLO
import cv2
import datetime

# opencv draw 변수들 정의
red = (0, 0, 255)
green = (0, 255, 0)
width = 640
height = 480

def create_video_writer(video_cap, output_filename, record_fps):
    frame_width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
	fps = int(video_cap.get(cv2.CAP_PROP_FPS))
    fourcc = cv2.VideoWriter_fourcc(*'m', 'p', '4', 'v')
    return cv2.VideoWriter(output_filename, fourcc, fps, (frame_width, frame_height))

########### main ##########
# Load the YOLOv8 model
model = YOLO(".venv/yolov8m-pose.pt")

#width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
#height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
#cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) # 가로
#cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height

cap = cv2.VideoCapture(0)
name = datetime.datetime.now().strftime("%y%m%d_%H%M%S") + "_output"
writer = create_video_writer(cap, "./" + name + ".mp4", 4)

program_start_sec = datetime.datetime.now()
sec_sum = 0
sec_cnt = 0
is_first_frame = True

while cap.isOpened():
    # Read a frame from the video
    predict_start_sec = datetime.datetime.now()
    success, frame = cap.read()

    if success:
        # Run YOLOv8 inference on the frame
        results = model(frame, verbose=False, conf=0.6)

        annotated_frame = results[0].plot()
        # Display the annotated frame

        cv2.imshow("show", annotated_frame)
        writer.write(annotated_frame)
        if is_first_frame:
            program_start_sec = datetime.datetime.now()
            is_first_frame = False
            continue

        # latency 계산, 추론 지연 시간 계산
        predict_end_sec = datetime.datetime.now()
        predict_sec_total = (predict_end_sec - predict_start_sec).total_seconds()
        sec_cnt += 1
        sec_sum += predict_sec_total
        print(f'time to predict 1 frame: {predict_sec_total * 1000: .0f} milliseconds')

        if cv2.waitKey(1) & 0xFF == ord("q"):
            break
        program_end_sec = datetime.datetime.now()
    else:
        break

program_sec_total = (program_end_sec - program_start_sec).total_seconds()
writer.release()
print(f'mp4 play time: {format(program_sec_total, "10.2f")} seconds')
print(f'result: {sec_sum / sec_cnt * 1000: .0f} milliseconds')
# Release the video capture object and close the display window
cap.release()
cv2.destroyAllWindows()

 

코드2

더보기

코드(after)

from ultralytics import YOLO
import cv2
import datetime

# opencv draw 변수들 정의
red = (0, 0, 255)
green = (0, 255, 0)
width = 640
height = 480

def create_video_writer(video_cap, output_filename, record_fps):
    frame_width = int(video_cap.get(cv2.CAP_PROP_FRAME_WIDTH))
    frame_height = int(video_cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
    fourcc = cv2.VideoWriter_fourcc(*'m', 'p', '4', 'v')
    return cv2.VideoWriter(output_filename, fourcc, record_fps, (frame_width, frame_height))

########### main ##########
# Load the YOLOv8 model
model = YOLO(".venv/yolov8m-pose.pt")

#width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
#height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
#cap.set(cv2.CAP_PROP_FRAME_WIDTH, width) # 가로
#cap.set(cv2.CAP_PROP_FRAME_HEIGHT, height

cap = cv2.VideoCapture(0)
name = datetime.datetime.now().strftime("%y%m%d_%H%M%S") + "_output"
writer = create_video_writer(cap, "./" + name + ".mp4", 4)

program_start_sec = datetime.datetime.now()
sec_sum = 0
sec_cnt = 0
is_first_frame = True

while cap.isOpened():
    # Read a frame from the video
    predict_start_sec = datetime.datetime.now()
    success, frame = cap.read()

    if success:
        # Run YOLOv8 inference on the frame
        results = model(frame, verbose=False, conf=0.6)

        annotated_frame = results[0].plot()
        # Display the annotated frame

        cv2.imshow("show", annotated_frame)
        writer.write(annotated_frame)
        if is_first_frame:
            program_start_sec = datetime.datetime.now()
            is_first_frame = False
            continue

        # latency 계산, 추론 지연 시간 계산
        predict_end_sec = datetime.datetime.now()
        predict_sec_total = (predict_end_sec - predict_start_sec).total_seconds()
        sec_cnt += 1
        sec_sum += predict_sec_total
        print(f'time to predict 1 frame: {predict_sec_total * 1000: .0f} milliseconds')

        if cv2.waitKey(1) & 0xFF == ord("q"):
            break
        program_end_sec = datetime.datetime.now()
    else:
        break

program_sec_total = (program_end_sec - program_start_sec).total_seconds()
writer.release()
print(f'mp4 play time: {format(program_sec_total, "10.2f")} seconds')
print(f'result: {sec_sum / sec_cnt * 1000: .0f} milliseconds')
# Release the video capture object and close the display window
cap.release()
cv2.destroyAllWindows()