Learned Image Compression 논문을 읽다보면 비교 방법론 중 VTM -17.0 Intra라는 방법이 자주 보입니다.
동영상 압축에 주로 사용하는 VVC 방법 같은데 해당 방법을 사용하기 위해 열심히 (삽질한) 기록입니다.
개발환경
OS : Linux
https://vcgit.hhi.fraunhofer.de/jvet/VVCSoftware_VTM/-/tree/master
작업 경로에 git clone을 통해 폴더를 불러옵니다.
만약 cmake가 없다면 cmake 설치해주셔야합니다.
cd VVCSoftware_VTM
mkdir build
cd build
cmake ..
cmake를 진행했다면 make -j
를 실행합니다.
make -j
실행하면 다음과같이 Build되는 것을 확인할 수 있습니다.
파이썬에서 subprocess 라이브러리를 통해 빌드한 VTM을 사용할 수 있습니다.
VTM에서 필요한 것은 인코더, 디코더 및 설정파일의 경로입니다.
/VVCSoftware_VTM/bin/umake/gcc-9.3/x86_64/release 경로 내에 EncoderApp, DecoderApp이 존재하며, /VVCSoftware_VTM/cfg 경로 내에 VTM intra 설정 파일이 존재합니다.
make -j
실행 중 인코더, 디코더 파일 경로를 확인할 수 있으니 반드시 확인해줘야 합니다.
def compress_image_vtm(image, quality):
with tempfile.NamedTemporaryFile(delete=True, suffix=".yuv") as temp_input, \
tempfile.NamedTemporaryFile(delete=True, suffix=".bin") as temp_output, \
tempfile.NamedTemporaryFile(delete=True, suffix=".yuv") as temp_recon:
# Convert image to YUV and save to temporary file
width, height = image.size
save_yuv(image, temp_input.name)
# 인코더 및 디코더 경로
encoder_path = "/VVCSoftware_VTM/bin/umake/gcc-9.3/x86_64/release/EncoderApp" # 인코더 경로
decoder_path = "/VVCSoftware_VTM/bin/umake/gcc-9.3/x86_64/release/DecoderApp" # 디코더 경로
config_file = "/VVCSoftware_VTM/cfg/encoder_intra_vtm.cfg" # 설정파일 경로
# VTM 인코딩
subprocess.run([
encoder_path,
"-c", config_file,
"-i", temp_input.name,
"-b", temp_output.name,
"-o", temp_recon.name,
"--SourceWidth=" + str(width),
"--SourceHeight=" + str(height),
"--InputBitDepth=8",
"--InternalBitDepth=8",
"--QP=" + str(quality),
"--FrameRate=1",
"--FramesToBeEncoded=1"
], check=True)
# VTM 디코딩
subprocess.run([
decoder_path,
"-b", temp_output.name,
"-o", temp_recon.name
], check=True)
# 복원된 이미지 읽기
recon_image = load_yuv(temp_recon.name, width, height)
mse = np.mean((np.array(image) - np.array(recon_image)) ** 2)
size = os.path.getsize(temp_output.name)
if mse == 0:
return recon_image, 100, size
psnr = 20 * np.log10(255 / np.sqrt(mse))
return recon_image, psnr, size
def process_single_image_for_format(image_path, compress_function, format_name):
original_image = Image.open(image_path).convert('RGB')
width, height = original_image.size
total_pixels = width * height
if format_name == "JPEG" or format_name == "JPEG2000":
qualities = list(range(5, 101, 5))
elif format_name == 'VTM-17.0 intra':
qualities = list(range(45, 52, 1))
else:
qualities = list(range(5, 52, 5))
psnr_values = []
sizes = []
for quality in qualities:
_, psnr, size = compress_function(original_image, quality)
psnr_values.append(psnr)
sizes.append(size)
bits_per_pixel = [size * 8 / total_pixels for size in sizes]
# Filter out values where bits per pixel is greater than 1
filtered_bpp_psnr = [(bpp, psnr) for bpp, psnr in zip(bits_per_pixel, psnr_values) if bpp <= 1]
if filtered_bpp_psnr:
bits_per_pixel_avg, psnr_values_avg = zip(*filtered_bpp_psnr)
else:
bits_per_pixel_avg, psnr_values_avg = [], []
return bits_per_pixel_avg, psnr_values_avg
image_path = "/example.png"
vtm_bpp, vtm_psnr = process_single_image_for_format(image_path, compress_image_vtm, "VTM-17.0 intra")
print("VTM BPP:", vtm_bpp)
print("VTM PSNR:", vtm_psnr)
# RD 커브 플롯
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
plt.plot(vtm_bpp, vtm_psnr, marker='o', label='VTM')
plt.xlabel('Bits Per Pixel (bpp)')
plt.ylabel('PSNR (dB)')
plt.legend(loc='lower right')
plt.grid(True)
plt.show()
실행결과는 다음과 같이 출력되며, PSNR 및 Bitrate가 출력되는 것을 확인할 수 있습니다.