AI 탐구노트

이미지, 영상 복원 테스트 본문

DIY 테스트

이미지, 영상 복원 테스트

42morrow 2024. 11. 5. 16:30

 

1.영상복원

시간이 지나 흐릿해진 옛 사진을 마주할 때마다 그 안에 담긴 추억도 조금씩 사라지는 듯한 아쉬움이 들곤 합니다. 그러나 요즘, AI 기술의 발전 덕분에 이러한 사진들이 새로운 생명력을 얻고 있습니다. 몇 년 전만 해도 흐릿하거나 손상된 사진을 선명하게 복원하는 것은 전문가의 손을 빌려야 가능한 일이었지만, 이제 AI 기반 복원 기술이 널리 퍼지며 누구나 손쉽게 과거의 추억을 선명하게 되살릴 수 있게 되었습니다. 특히, 딥러닝 모델을 활용해 사진의 해상도를 높이고 색을 되살리며, 심지어 손상된 부분까지 복원해주는 기술이 일상에 스며들고 있는데요, 이는 단순한 기술 이상의 의미를 지닙니다. 과거의 소중한 순간들이 다시금 생생하게 부활하며 새로운 감동을 안겨주는 일이니까요.

 

오래된 영상이라고 하니... 개인적인 기억만으로도 몇가지가 떠 오르네요... 어릴 적 기억인데 하나는 허름한 병원의 엑스레이 사진 판독실, 하나는 왕가위 감독의 '아비정전'을 처음 봤던 지방한 한 허름한 영화관입니다. 둘 다 장비 탓이었겠죠. 병원에선 엑스레이 장치 자체가 오래됐었고, 극장에선 영사 필름에 기스가 많이 났었을테죠. 한가지 재미난 것은 아비정전의 한 장면(아래 사진)에서 비오듯 긁힌 자국이 보였던 기억이 잘못 되어 있었다는 것을 이번에 알게 됐다는 겁니다. 저는 감독이 일부러 그런 연출을 한 것으로 생각했었는데 구글 검색을 해 보니 그런 장면은 없더군요... 이미 리마스터링이 되어 나와서 그런건지... 

사진 : 아비정전의 한 장면

 

* 최근 과거의 영화를 리마스터링해서 다시 개봉하는 경우가 있는데, 그건 필름(아날로그)로 만들어진 영화를 디지털로 전환하는 과정을 의미합니다. 약간 내용이 다르죠... 물론 그 과정에서 화질이 좋지 않거나 한 부분들을 영상 복원이 개별적으로 들어갈 겁니다. 

 

 

2.영상복원의 주요 기술

2.1.과거의 영상 복원 방식

AI를 이용하기 이전에는 영상 복원을 어떻게 했을까요? 2011년 기사 내용을 보면, 필름 보존상태가 나쁜 경우에는 새로운 필름으로 복사하는 과정을 거치는 아날로그 복원 방식이 대부분이었던 모양입니다. 그러다 디지털 복원이 등장해서 주목을 받고 있었던 것 같구요. 국내에서 디지털 복원이 시작된 것이 2006년 부터라고 하니 정말 오래되진 않았습니다. 하지만 그렇게 많은 영상이 복원 대상이 되진 못했다고 합니다. 왜냐하면 당시만해도 이 작업을 다 사람의 손으로 일일이 장면마다 해야했기 때문에 비용이 많이 들었고 작업자에 따라 결과물의 품질이 변하는 것은 다반사였기 때문이라고 하죠. 그러다 인공지능이 등장하면서 이런 작업은 예전 대비 아주 손쉽게 이뤄지고 있습니다. 

 

 

[화제]영상 디지털 복원, 영화의 재탄생

지난 5월 30일 한국영상자료원 시사실에 보존기술센터 관계자들이 모여 한 편의 영화를 보았다. 김수용 감독의 ‘야행’. 서너 달 동안의 디지털 복원작업을 거쳐 새롭게 얻은 생...

weekly.khan.co.kr

 

 

2.2.인공지능을 이용한 복원 방식

AI 기반 복원 기술에는 다음과 같은 것들이 있습니다. 

  • 딥러닝 기반 초해상도 (Super Resolution) : 해상도를  높여 선명하게 만드는 방법으로 GAN, VDSR 등 다양한 모델이 있습니다.
  • 노이즈 제거와 결함(부분 누락 등) 복원 : 필름 결함, 노이즈, 긁힘 등등. Denosing Autoencoder나 Inpainting 기술 등이 적용될 수 있습니다. 
  • 프레임 보간 (Frame Interporation) : 프레임이 적은 저화질 영상 (예: 15fps 수준 등)에서는 중간 프레임을 보간해 동작을 부드럽게 만들어 줄 수 있습니다. 
  • 흐릿함 복원 (Debluring) : 흔들림이나 초점이 맞지 않아 흐릿해진 영상을 선명하게 복원합니다. 
  • 컬러 복원과 자동 채색 (Colorization) : 흑백 필름으로 기록된 영상을 컬러로 복원합니다. DeOldify 등과 같은 모델이 있죠.
  • 모자이크 및 가상 경계 복원 (Inpainting) : 영상의 일부가 손상되거나 누락된 경우, 인페인팅 기술로 해당 영역을 주변 정보에 맞춰 메꾸는 방식으로 복원할 수 있습니다. 

 

3.영상 복원 테스트 (영상품질 복원 +업스케일)

이번에는 좀 오래된(2022년 ^^;) 모델이긴 하지만, GFPGAN을 이용해서 사진 상의 실제 얼굴 복구 및 upscale을 하는 것을 해 보겠습니다. 그리고, DeOldify를 이용해 흑백 영상을 컬러로 변환한 간단한 방법도 하나 소개하겠습니다. 

 

3.1.환경 구성 및 패키지 설치

# 모델 클론
$ git clone https://github.com/xinntao/GFPGAN.git
$ cd GFPGAN

# JIT 활장모듈 설치
$ pip install basicsr

# 얼굴감지를 위한 모듈
$ pip install facexlib
$ pip install realesrgan

# 이외에 필요한 패키지
pip install -r requirements.txt
python setup.py develop


# weight 다운로드
$ wget https://github.com/TencentARC/GFPGAN/releases/download/v0.1.0/GFPGANv1.pth -P experiments/pretrained_models

# 기타 관련 모델의 pre-trained weight 다운로드
$ wget https://github.com/TencentARC/GFPGAN/releases/download/v0.1.0/StyleGAN2_512_Cmul1_FFHQ_B12G4_scratch_800k.pth -P experiments/pretrained_models
$ wget https://github.com/TencentARC/GFPGAN/releases/download/v0.1.0/FFHQ_eye_mouth_landmarks_512.pth -P experiments/pretrained_models
$ wget https://github.com/TencentARC/GFPGAN/releases/download/v0.1.0/arcface_resnet18.pth -P experiments/pretrained_models

 

 

3.2.테스트 코드

import argparse
import cv2
import glob
import numpy as np
import os
import torch
from basicsr.utils import imwrite

from gfpgan import GFPGANer


def main():
    """Inference demo for GFPGAN (for users).
    """
    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-i',
        '--input',
        type=str,
        default='inputs/whole_imgs',
        help='Input image or folder. Default: inputs/whole_imgs')
    parser.add_argument('-o', '--output', type=str, default='results', help='Output folder. Default: results')
    # we use version to select models, which is more user-friendly
    parser.add_argument(
        '-v', '--version', type=str, default='1.3', help='GFPGAN model version. Option: 1 | 1.2 | 1.3. Default: 1.3')
    parser.add_argument(
        '-s', '--upscale', type=int, default=2, help='The final upsampling scale of the image. Default: 2')

    parser.add_argument(
        '--bg_upsampler', type=str, default='realesrgan', help='background upsampler. Default: realesrgan')
    parser.add_argument(
        '--bg_tile',
        type=int,
        default=400,
        help='Tile size for background sampler, 0 for no tile during testing. Default: 400')
    parser.add_argument('--suffix', type=str, default=None, help='Suffix of the restored faces')
    parser.add_argument('--only_center_face', action='store_true', help='Only restore the center face')
    parser.add_argument('--aligned', action='store_true', help='Input are aligned faces')
    parser.add_argument(
        '--ext',
        type=str,
        default='auto',
        help='Image extension. Options: auto | jpg | png, auto means using the same extension as inputs. Default: auto')
    args = parser.parse_args()

    args = parser.parse_args()

    # ------------------------ input & output ------------------------
    if args.input.endswith('/'):
        args.input = args.input[:-1]
    if os.path.isfile(args.input):
        img_list = [args.input]
    else:
        img_list = sorted(glob.glob(os.path.join(args.input, '*')))

    os.makedirs(args.output, exist_ok=True)

    # ------------------------ set up background upsampler ------------------------
    if args.bg_upsampler == 'realesrgan':
        if not torch.cuda.is_available():  # CPU
            import warnings
            warnings.warn('The unoptimized RealESRGAN is slow on CPU. We do not use it. '
                          'If you really want to use it, please modify the corresponding codes.')
            bg_upsampler = None
        else:
            from basicsr.archs.rrdbnet_arch import RRDBNet
            from realesrgan import RealESRGANer
            model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
            bg_upsampler = RealESRGANer(
                scale=2,
                model_path='https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth',
                model=model,
                tile=args.bg_tile,
                tile_pad=10,
                pre_pad=0,
                half=True)  # need to set False in CPU mode
    else:
        bg_upsampler = None

    # ------------------------ set up GFPGAN restorer ------------------------
    if args.version == '1':
        arch = 'original'
        channel_multiplier = 1
        model_name = 'GFPGANv1'
    elif args.version == '1.2':
        arch = 'clean'
        channel_multiplier = 2
        model_name = 'GFPGANCleanv1-NoCE-C2'
    elif args.version == '1.3':
        arch = 'clean'
        channel_multiplier = 2
        model_name = 'GFPGANv1.3'
    else:
        raise ValueError(f'Wrong model version {args.version}.')

    # determine model paths
    model_path = os.path.join('experiments/pretrained_models', model_name + '.pth')
    if not os.path.isfile(model_path):
        model_path = os.path.join('realesrgan/weights', model_name + '.pth')
    if not os.path.isfile(model_path):
        raise ValueError(f'Model {model_name} does not exist.')

    restorer = GFPGANer(
        model_path=model_path,
        upscale=args.upscale,
        arch=arch,
        channel_multiplier=channel_multiplier,
        bg_upsampler=bg_upsampler)

    # ------------------------ restore ------------------------
    for img_path in img_list:
        # read image
        img_name = os.path.basename(img_path)
        print(f'Processing {img_name} ...')
        basename, ext = os.path.splitext(img_name)
        input_img = cv2.imread(img_path, cv2.IMREAD_COLOR)

        # restore faces and background if necessary
        cropped_faces, restored_faces, restored_img = restorer.enhance(
            input_img, has_aligned=args.aligned, only_center_face=args.only_center_face, paste_back=True)

        # save faces
        for idx, (cropped_face, restored_face) in enumerate(zip(cropped_faces, restored_faces)):
            # save cropped face
            save_crop_path = os.path.join(args.output, 'cropped_faces', f'{basename}_{idx:02d}.png')
            imwrite(cropped_face, save_crop_path)
            # save restored face
            if args.suffix is not None:
                save_face_name = f'{basename}_{idx:02d}_{args.suffix}.png'
            else:
                save_face_name = f'{basename}_{idx:02d}.png'
            save_restore_path = os.path.join(args.output, 'restored_faces', save_face_name)
            imwrite(restored_face, save_restore_path)
            # save comparison image
            cmp_img = np.concatenate((cropped_face, restored_face), axis=1)
            imwrite(cmp_img, os.path.join(args.output, 'cmp', f'{basename}_{idx:02d}.png'))

        # save restored img
        if restored_img is not None:
            if args.ext == 'auto':
                extension = ext[1:]
            else:
                extension = args.ext

            if args.suffix is not None:
                save_restore_path = os.path.join(args.output, 'restored_imgs', f'{basename}_{args.suffix}.{extension}')
            else:
                save_restore_path = os.path.join(args.output, 'restored_imgs', f'{basename}.{extension}')
            imwrite(restored_img, save_restore_path)

    print(f'Results are in the [{args.output}] folder.')


if __name__ == '__main__':
    main()

 

 

3.3.실행 및 결과 확인

실행 커맨드

$ BASICSR_EXT=True python inference_gfpgan.py -i inputs/whole_imgs -o results -v 1.3 -s 8

 

실행 결과 

 

아래는 복원 + 업스케일된 이미지를 파일 정보와 함께 캡처한 것입니다. 원본 이미지를 8배 업스케일해서 해상도가 10240x7680입니다. 후덜덜... 

 

 

사진 : 얼굴 이미지 복원 및 업스케일 결과

 

 

사진 : 공개된 필름 사진 이미지를 이용한 복원 결과

 

4.흑백영화 컬러 변환 테스트 (Deoldify)

 

흑백영상을 컬러로 변환해 주는 것은 최근 많이 나와 있습니다. 그 가운데 Deoldify는 손쉽게 사용할 수 있는 것이라 선택했습니다. 취향에 따라 최신 모델로 시도도 해 보시길... 

 

4.1.환경 구성 및 패키지 설치

# 모델 클론
$ git clone https://github.com/jantic/DeOldify.git DeOldify
$ cd DeOldify

# 필요 패키지 설치
$ pip install -r requirements.txt


# 모델 download
$ mkdir models
$ wget https://www.dropbox.com/s/336vn9y4qwyg9yz/ColorizeVideo_gen.pth?dl=0 -O ./models/ColorizeVideo_gen.pth

 

4.2. 테스트 코드

import fastai
from deoldify.visualize import *
from pathlib import Path
torch.backends.cudnn.benchmark=True

colorizer = get_video_colorizer()

# 영상 변환 - 예시 영상은 유튜브에서 임의로 찾은 것
source_url = 'https://www.youtube.com/watch?v=rudAt12l9Ig' #@param {type:"string"}
render_factor = 21  #@param {type: "slider", min: 5, max: 44}


if source_url is not None and source_url !='':
    video_path = colorizer.colorize_from_url(source_url, 'video.mp4', render_factor)
    show_video_in_notebook(video_path)
else:
    print('Provide a video url and try again.')

 

4.3.테스트 결과 확인

장면이 장면이다 보니 아주 화려한 컬러로 변환되진 않았습니다. 다른 영상을 가지고 다시 테스트 해 봐야겠습니다. 

사진 : 변환 후의 비디오 한 장면, 원래는 흑백 영상이었답니다. 대충 감 잡히시죠?

 

 

사진 : 공식 github에는 이런 사진들도 있습니다.

 

 

5.후기

 

시간이 지나 흐릿해진 옛 사진을 마주할 때마다 그 안에 담긴 추억도 조금씩 사라지는 듯한 아쉬움이 들곤 합니다. 그러나 이제 AI 기반 복원 기술이 널리 퍼지며 누구나 손쉽게 과거의 추억을 선명하게 되살릴 수 있게 되었습니다. 이는 단순한 기술 이상의 의미를 지닙니다. 과거의 소중한 순간들이 다시금 생생하게 부활하며 새로운 감동을 안겨주는 일이니까요. 기술적 복원의 의미 대신 그 시절 사람들의 추억이 담긴 이야기들에 다시금 생명을 불어넣는 ‘디지털 타임머신’ 역할을 하는 셈입니다.

 

혹시 과거의 사진뿐만 아니라 우리의 기억 속 추억도 이와 같은 방식으로 '업스케일'할 수 있을까요? 대부분 머리 속에 담고 있는 추억들은 흐릿한 기억으로만 남아 있을테니 그걸 선명하게 재현해 줄 수 있다면 좋겠다 싶거든요. ^^

 

 


참고정보

 

1.GFPGAN

좀 오래 되긴 했지만, 실 세상에서의 안면사진 복원에 전문화된 모델입니다. 특징으로는 Inpainting을 이용한 사진 복워을 지원하며, v1.3부터는 colorization을 지원하고 upscale factor는 입력값 기준으로 가변적으로 동작합니다. Apache 2.0 라이선스를 따릅니다. 한가지 유의해야 할 것은 GFPGAN 사용 시 내부적으로 사용하는 stylegan2 등 다수의 모델이 함께 필요합니다. 

 

 

GitHub - TencentARC/GFPGAN: GFPGAN aims at developing Practical Algorithms for Real-world Face Restoration.

GFPGAN aims at developing Practical Algorithms for Real-world Face Restoration. - TencentARC/GFPGAN

github.com

 

 

2.Deoldify

흑백 영화를 컬러로 변환하는 모델입니다. 아주 오래되었는데, 나름 의미있는 작업이다 보니 이를 정식으로 사이트도 개설해서 서비스하고 있습니다. 

 

깃헙 (공개코드)

 

 

GitHub - jantic/DeOldify: A Deep Learning based project for colorizing and restoring old images (and video!)

A Deep Learning based project for colorizing and restoring old images (and video!) - jantic/DeOldify

github.com

 

MyHeritage (서비스 페이지)

 

 

MyHeritage In Color™, world's best technology for colorizing and restoring the colors in historical photos - MyHeritage

 

www.myheritage.co.kr