지난 포스트: 멀티미디어 리소스 최적화: FFmpeg 사용법과 웹 접근성 고려하기의 뒷이야기입니다.
지난 번 영상 최적화 PR을 보내면서 품질 비교를 위한 두개 영상의 링크와 함께 "사용자가 느끼는 화질에 영향이 없음을 확인했습니다." 라는 멘트를 달았습니다.
하지만 팀장님께서 아래와 같은 질문을 주셨습니다:
팀장님의 질문을 받고 나서, 해상도야 동일하게 설정했지만 처음 작성했던 '사용자가 느끼는 화질에 영향이 없음'이라는 멘트가 사실상 근거 없는 주관적인 평가였다는 것을 깨달았습니다.
그렇기에 이번 포스트는 mp4에서 webm으로의 비디오 형식 전환 후 발생할 수 있는 영상 품질 차이에 대한 신뢰할 수 있는 구체적인 지표를 제공하고자 합니다.
화질에는 직접적인 수치나 단위가 없습니다. 화질은 이미지나 비디오의 시청 경험에 대한 주관적인 평가를 나타내는 개념으로, 이미지의 선명도, 색상의 정확성, 콘트라스트, 노이즈 수준 등 다양한 요소에 의해 영향을 받습니다.
그러나 이미지나 비디오의 화질을 객관적으로 측정하려는 다양한 시도가 있습니다. 이러한 측정 방법들은 주로 피크 신호 대 노이즈 비율(PSNR), 구조적 유사성 지수(SSIM), 비주얼 정보 피델리티(VIF) 등의 알고리즘을 사용하여 이미지나 비디오의 화질을 숫자로 표현합니다.
분석에서는 3840 x 1168의 해상도를 가진 landing_page_hero_mv.mp4
와 landing_page_hero_mv.webm
영상을 대상으로 하였습니다. (이마고웍스 랜딩페이지의 히어로 영상)
13초 길이의 각 영상은 총 420개의 프레임으로 이루어져 있어, 전체 프레임을 분석하는 데 예상 연산 시간이 7~8시간에 이르는 것으로 계산되었습니다. (한 프레임을 처리하는 데 걸리는 시간 * 전체 프레임 수)
Elapsed time: 63.93s, Estimated remaining time: 26787.89s
프레임을 많이 분석하게 되면 영상의 전체 품질에 대한 평가가 더욱 정확해질 수 있습니다. 그러나, 이러한 방법은 연산에 많은 시간이 소요되며, 실용적인 한계가 있습니다. 따라서, 각 초마다의 시점에서 프레임을 샘플링하는 방식을 선택하였습니다. 그렇기에 분석 결과가 전체 품질을 완벽하게 반영하지는 못할 수도 있다는 점을 인지하고 있습니다.
그러나 각 13개의 프레임은 영상에서 균일하게 선택되었고, 다양한 장면과 상황을 대표하고 있다고 판단하였습니다. 이로 인해, 분석 결과는 영상의 품질을 객관적이고 신뢰성 있는 방식으로 평가하는 추정치를 제공할 수 있을 것이라고 예상합니다.
import cv2
import numpy as np
from skimage.metrics import structural_similarity as ssim
from sewar import full_ref
import time
# 비디오 파일 열기
cap1 = cv2.VideoCapture('landing_page_hero_mv.mp4')
cap2 = cv2.VideoCapture('landing_page_hero_mv.webm')
# 프레임 속도
fps = cap1.get(cv2.CAP_PROP_FPS)
# 두 비디오의 프레임 수가 같은지 확인
assert cap1.get(cv2.CAP_PROP_FRAME_COUNT) == cap2.get(cv2.CAP_PROP_FRAME_COUNT), "Videos must have same number of frames"
# PSNR, SSIM, VIF의 평균값을 계산하기 위한 리스트
psnr_values = []
ssim_values = []
vif_values = []
# 시간당 한 번 계산
seconds = list(range(14))
for second in seconds:
cap1.set(cv2.CAP_PROP_POS_FRAMES, second*fps)
cap2.set(cv2.CAP_PROP_POS_FRAMES, second*fps)
ret1, frame1 = cap1.read()
ret2, frame2 = cap2.read()
if not ret1 or not ret2:
break
# PSNR 계산
psnr = cv2.PSNR(frame1, frame2)
psnr_values.append(psnr)
# SSIM 계산
ssim_value = ssim(frame1, frame2, multichannel=True)
ssim_values.append(ssim_value)
# VIF 계산
vif = full_ref.vifp(frame1, frame2)
vif_values.append(vif)
print(f'Second: {second} - PSNR: {psnr}, SSIM: {ssim_value}, VIF: {vif}')
# 평균 PSNR, SSIM, VIF 계산
mean_psnr = np.mean(psnr_values)
mean_ssim = np.mean(ssim_values)
mean_vif = np.mean(vif_values)
print(f'\nMean PSNR: {mean_psnr}')
print(f'Mean SSIM: {mean_ssim}')
print(f'Mean VIF: {mean_vif}')
Second: 0 - PSNR: 51.72941644023464, SSIM: 0.9972663462534271, VIF: 0.8238966366307613
Second: 1 - PSNR: 51.28016002423844, SSIM: 0.9971142961895142, VIF: 0.8159751194658345
Second: 2 - PSNR: 51.247383723464296, SSIM: 0.9971027794204, VIF: 0.815474394953355
Second: 3 - PSNR: 50.36723361153162, SSIM: 0.9963271937936011, VIF: 0.7838099644493267
Second: 4 - PSNR: 49.1149894058391, SSIM: 0.9958364227061036, VIF: 0.7661798912647524
Second: 5 - PSNR: 47.23720458773596, SSIM: 0.9947189440742056, VIF: 0.6951382447109715
Second: 6 - PSNR: 45.81661090893473, SSIM: 0.9917016366640565, VIF: 0.6323098097941838
Second: 7 - PSNR: 45.65164671184295, SSIM: 0.9913484958726788, VIF: 0.628003489702675
Second: 8 - PSNR: 44.78948518622921, SSIM: 0.9899206244938963, VIF: 0.6307590663669763
Second: 9 - PSNR: 46.73905964203763, SSIM: 0.9918846697261742, VIF: 0.6756128021246756
Second: 10 - PSNR: 82.30843610424631, SSIM: 0.9999958685800859, VIF: 3.239495864250043e-07
Second: 11 - PSNR: 60.86661305508816, SSIM: 0.9997719803612922, VIF: 0.8448046019077773
Second: 12 - PSNR: 60.575473875847436, SSIM: 0.9998375861439448, VIF: 0.815901291358989
Second: 13 - PSNR: 62.52757422090148, SSIM: 0.9998884451282345, VIF: 0.9477504819046526
Mean PSNR: 53.58937767844085
Mean SSIM: 0.9959082349576869
Mean VIF: 0.7054011513274654
PSNR 및 SSIM은 두 영상이 매우 유사하다는 것을 나타내지만, VIF 값은 약간의 차이가 있음을 보여줍니다. 이는 눈으로 볼 때 두 영상 사이에 약간의 차이를 인지할 수 있음을 의미합니다.
import matplotlib.pyplot as plt
# 시간(초) 벡터 생성
time = list(range(14))
# 그래프 생성
plt.figure(figsize=(10, 6))
# PSNR 그래프
plt.subplot(3, 1, 1)
plt.plot(time, psnr_values, label='PSNR')
plt.xlabel('Time (s)')
plt.ylabel('PSNR')
plt.legend()
# SSIM 그래프
plt.subplot(3, 1, 2)
plt.plot(time, ssim_values, label='SSIM')
plt.xlabel('Time (s)')
plt.ylabel('SSIM')
plt.legend()
# VIF 그래프
plt.subplot(3, 1, 3)
plt.plot(time, vif_values, label='VIF')
plt.xlabel('Time (s)')
plt.ylabel('VIF')
plt.legend()
# 그래프 표시
plt.tight_layout()
plt.show()
초당 분석 결과를 시각화한 결과입니다.
특정 구간(10초)에서 PSNR과 SSIM은 각각 높은 값을 가짐으로써 두 영상이 매우 유사하다는 것을 나타내는 반면, VIF 값이 매우 낮게 나옵니다. 이렇게 낮은 VIF 값은 두 영상 사이에 극도로 차이가 있는 경우에 나타날 수 있지만, PSNR과 SSIM 값이 매우 높은 것을 고려하면 이상치(Outlier)로 판단됩니다.
문제 구간을 확인하기 위해 이상치 구간의 프레임을 시각화하였습니다.
import cv2
import matplotlib.pyplot as plt
def display_frame(video_path, sec):
cap = cv2.VideoCapture(video_path)
fps = cap.get(cv2.CAP_PROP_FPS)
frame_no = int(fps * sec)
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_no)
ret, frame = cap.read()
if not ret:
print(f"Frame not read correctly at second {sec} for {video_path}")
return
# BGR에서 RGB로 변환
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
plt.imshow(frame_rgb)
plt.show()
cap.release()
# 정상 프레임 예시
display_frame('landing_page_hero_mv.mp4', 5)
display_frame('landing_page_hero_mv.mp4', 10)
display_frame('landing_page_hero_mv.webm', 10)
첫 예시로 볼 수 있듯이 특정 구간의 프레임을 추출하는 코드입니다.
이상치에 해당하는 프레임은 아무 정보가 없는 단색 프레임입니다. PSNR과 SSIM은 두 이미지 또는 비디오 간의 절대적인 픽셀 차이나 구조적 차이를 측정하는 데 사용되므로, 두 비디오 모두 동일한 단색 프레임을 보여줄 경우 이들의 값이 높게 나올 수 있습니다. 반면에 VIF는 인간 시각의 특성을 고려하여 품질을 평가하므로, 완전한 단색 프레임에서는 별도의 시각적 정보가 없다고 판단하여 점수가 매우 낮게 나올 수 있습니다.
본 포스팅에서는 압축 전후의 영상 품질 비교를 위해 피크 신호 대 잡음비, 구조적 유사성 지수, 그리고 시각 정보 충실도 세 가지 메트릭을 활용하였습니다.
결과적으로, 피크 신호 대 잡음비 및 구조적 유사성 지수는 두 영상 사이에 큰 차이가 없음을 나타내었습니다. 그러나 VIF 값에서는 약간의 차이가 발견되었습니다. 이러한 차이는 주로 단색 프레임에서 발생하는 문제였기에, 이상치를 제외한 평균 값을 아래와 같이 도출하였습니다.
# Without outlier
Mean PSNR: 51.08
Mean SSIM: 0.9965
Mean VIF: 0.7580
VIF 값이 상대적으로 낮은 것은 특정 조건에서 두 비디오 간에 약간의 차이가 눈에 띄게 될 수 있다는 것을 의미합니다. 그렇지만 전반적인 품질 지표가 높게 나타났기 때문에, 원본과 인코딩된 비디오 사이에는 큰 차이가 없다고 판단할 수 있습니다.
이렇게 도출된 수치적 결과는 단순 참고용으로 사용되어야 합니다. 실제로 사용자의 경험을 기반으로 한 품질 평가가 가장 중요하며, 이에 따라 디자이너팀에 결과를 제출하여 평가를 받았습니다. 평가 결과, 영상의 용량이 14mb에서 1mb로 크게 줄었음에도 시각적인 차이는 미미하여 긍정적인 평가를 받았습니다.
위 과정은 객관적이고 참고할 수 있는 수치를 함께 제공할 수 있다는 점에 의의가 있다고 생각합니다. 앞으로도 이미지나 영상같은 멀티미디어 리소스를 눈으로 판단하기 이전에 객관적인 수치를 함께 기록해두면 비교 평가에 훌륭한 지표가 될 것이라 생각합니다.