4 minute read

도치 도이치 도우치 행사를 위한 영상 제작기

이미지

요새 잘나가는 해외 팝 스타를 꼽자면 DOECHII가 빠질 수 없다. 쇼츠를 즐겨보는 사람이라면 누구나 한번쯤 그녀의 대표곡 Anxiety를 들어보았을 것에다.

그런데, 그 이름, DOECHII를 한국어로 어떻게 표기해야 할까? 도치? 도이치? 도우치?

국어국립원의 외래어 표기법에 의하면 도치가 맞다고 한다. 하지만 언어라는게 쓰는 사람 맘대로 아니겠는가?

그래서 유니버설 뮤직 코리아에서는 재미있는 마케팅 기획을 준비했다. 투표를 통해서 도치, 도이치, 도우치 중 무엇을 공식 표기법으로 사용할지 결정하자는 것. 대선을 앞둔 이 시점에 참으로 시의적절한 행사가 아닐 수 없다.

여기서 내 역할은 ‘이름 결정 파티’에서 사용할 영상 제작이었다. 무슨 영상을 만드는 게 좋을까? 일단 ‘도치’, ‘도이치’, ‘도우치’ 라는 타이포가 메인이 되어야만 했다. 또, 파티에서 사용될 영상이었기 때문에 시선을 사로잡는 글자의 개성이 중요했다. 퍼뜩 떠오른 건 서체 학습을 통한 기괴한 서체를 가진 이미지의 생성, 그리고 그 보간을 통한 모션감이었다.

사실 서체를 학습해서 괴상한 글자를 만들어내겠다는 컨셉은 이전에도 몇번 시도해보려 한 적이 있었지만, 다 엎어졌다. 그래서 아쉬웠는데 이번 기회에 잘 됐다 싶었다.

학습을 위해 가장 중요한 데이터 준비를 시작했다. 서체를 생성하는 방법에는 크게 두 접근법이 있을 듯 하다. 이미지 기반의 접근법과 벡터 기반의 접근법. 벡터에서 이미지로 렌더된 녀석을 학습하느냐, 아니면 벡터 자체를 학습해버리느냐의 차이이다. 각각 포토샵과 일러스트레이터가 다루는 영역이라고 보면 편할 것 같다. 서체 생성을 대단히 깊게 연구하거나 한 것은 아니라서 잘은 모르겠으나, 이미지로 서체를 다룬다면 StyleGAN이나 Stable Diffusion같은 이미지 생성 모델이 백본으로 적합할 것이고, 벡터로 다룬다면 Transformer와 같이 시계열 데이터를 잘 예측하는 백본이 적합해 보인다.

이번 작업에서는 이미지로 서체를 다루기로 마음먹었다. 벡터로 서체를 다루면 렌더를 위한 래스터라이저도 짜야 하고 OTF나 TTF 파일 규격도 공부해야 하기 때문에 신경쓸 것이 많기 때문.

이미지 제네레이터로는 StyleGAN2를 사용했다. 24기가의 적은 램 용량으로도 학습 가능하고, 생성이 빠르고, 그리고 무엇보다 하도 많이 써서 모든 구조를 파악하고 있기 때문이다.

이미지로 서체를 다루기 때문에 학습 데이터로는 다양한 서체를 이용해 렌더된 각각의 글자 이미지가 필요했다. 간단하게 파이썬으로 렌더 코드를 짰는데 문제가 생겼다. 아무리 가운데 정렬을 해도 글자가 이미지 가운데 위치하지 않았다는 점.

이미지가 가운데 위치하지 않는 것이 왜 문제인가? 사실 정확하게 서체의 형태를 예측하고자 하는 것이 아니므로 대단히 문제될 것은 없다. 다만 생성 품질에 영향이 있긴 하다. StyleGAN3 논문에서 지적한 바와 같이, StyleGAN2의 구조는 생성 품질은 좋지만 컨볼루션 과정에서의 패딩 때문에 생성 품질이 위치에 영향을 받는 구조를 가지고 있기 때문이다. 그리고 결정적으로 가운데 정렬이 안 된 이미지가 굉장히 신경쓰였기 때문에 글자를 정렬할 방법에 대해서 생각해봤다.

이미지

간단히 떠오른 솔루션은 이미지의 각 픽셀에 대응하는 위치(위 그림 참고)에 0 혹은 1로 이진화된 텍스트의 픽셀값을 곱하고 그 평균을 x와 y에 대해서 구하는 것. 이 방식으로 검은 배경의 이미지에서 흰 부분 (1인 부분, 글자가 있는 부분)의 중심이 어디인지를 획득할 수 있다. 표준편차 계산을 통해 글자의 너비와 높이가 얼마인지 구할 수 있는 것은 덤.

이렇게 획득한 데이터로 학습을 진행했다. 결과는 그럭저럭이었지만 StyleGAN의 기본 아웃풋이 3채널이기 때문에 글씨는 흑백인데 채널마다의 값이 달라서 컬러가 분리되는 문제가 발생했다.

# To RGB 클래스 
# 출처 : https://github.com/rosinality/stylegan2-pytorch
class ToRGB(nn.Module):
    def __init__(self, in_channel, style_dim, upsample=True, blur_kernel=[1, 3, 3, 1]):
        super().__init__()

        if upsample:
            self.upsample = Upsample(blur_kernel)

        self.conv = ModulatedConv2d(in_channel, 3, 1, style_dim, demodulate=False)
        self.bias = nn.Parameter(torch.zeros(1, 3, 1, 1))

    def forward(self, input, style, skip=None):
        out = self.conv(input, style)
        out = out + self.bias

        if skip is not None:
            skip = self.upsample(skip)

            out = out + skip

        return out

간단하게 to_rgb 클래스를 수정해서 3채널이던 아웃풋을 1채널로 만들었다. 문제는 이렇게 하면 이미 학습되어 있는 체크포인트와 텐서의 형태가 일치하지 않아 체크포인트 로드가 안 된다는 것. 이미 학습된 체크포인트에서 시작하지 않으면 Mode Collapse 같은 현상이 쉽게 일어나기 때문에 해결해야 하는 문제였다.

이것도 해결 방법은 간단하다. torch.load로 로드한 체크포인트는 딕셔너리 형식인데, 여기서 to_rgb와 관계가 있는 값을 찾은 뒤 잘라내주면 잘 작동한다. 이렇게 채널을 줄이면 글자가 채널별로 분리되는 현상을 없앨 수 있을 뿐 아니라, 하나의 채널에 대한 로스만 고려하게 되므로 학습도 좀 더 잘 되는 것을 확인할 수 있었다.

학습이 끝나고, 이제 글자를 여러 시퀀스로 생성해서 가로로 잘 배치하기만 하면 된다. 시퀀스는 StyleGAN 의 Latent Code를 이리저리 움직여서 생성한 이미지를 이용해서 제작했다. 이 방식을 소위 Latent Traveling이라고 부르는 듯 하다. 이미지 생성 모델을 잘 모르는 사람이라면 이게 무슨 소리인지 알기 어려울 것이다. StyleGAN에서의 Latent Traveling이라는 건 Latent Space, 그러니까 잠재 공간에서 한 지점에서 다른 지점으로 이동하면서 그 중간중간의 값을 보간하여 이미지를 만들어내는 방식이다. 쉽게 말해서, StyleGAN은 512차원짜리 벡터 하나를 받아서 이미지를 뽑아내는 구조인데, 그 512차원의 벡터 공간에서 이리저리 움직이는 벡터를 만들고, 거기서 나온 이미지들을 쭉 늘어놓으면, 기묘한 흐름이 있는 시퀀스를 만들 수 있는 것이다.

이렇게 생성한 시퀀스를 세 개를 늘여놓으려 했는데 문제가 생겼다. 도이치, 도우치는 세 글자라서 상관없지만, 도치는 두 글자라는 점. 즉, 두 글자를 만들기 위해서는 가운데 시퀀스에 아무것도 없는 공백이 포함되어야 하는 문제가 생긴 것이다.

해결 방법은 간단하다. 검은 이미지를 만드는 Latent Code를 하나 만들어서 랜덤하게 Latent Code를 그 방향으로 보내주는 것. 그런데, 검은색을 만드는 Latent Code는 어떻게 획득할까? 이것 또한 간단하다. Latent Code를 Optimize하는 것. 더 자세히 설명하면 검은 이미지를 만들고 StyleGAN의 결과물을 그것과 비교해서 로스를 구한 뒤 Latent Code의 그라디언트를 계산하고, 그 그라디언트의 반대 방향으로 Latent Code를 움직이는 것이다.

여기서 주의해야 할 점은, StyleGAN 의 마지막 활성화 함수가 tanH라는 것. 따라서 값의 범위는 -1에서 1사이가 되고, 검은 이미지는 0이 아닌 -1로 가득 차 있어야 한다는 점이다.

이렇게 생성한 글자를 가로로 배치했더니 글자 간격이 들쭉날쭉한 것이 마음에 들지 않았다. 특히나 가운데 글자가 공백(검은색)인 경우, 글자 사이 간격이 너무 넓어지는 문제가 있었다. 이를 해결하기 위해 앞서 사용한 중심과 너비를 구하는 코드를 재사용했고 글자 이미지를 가로로 예쁘게 주르륵 배치할 수 있었다.

이렇게 1차로 완성된 영상을 공유했다. 다양한 글자가 나오는 건 좋지만, 도치 도이치 도우치라는 글자가 좀 더 많이 나왔으면 좋겠다는 피드백을 받았다.

그래서 세 글자가 좀 더 자주 나오게 만들기로 했다. 맨 앞 글자와 맨 뒷 글자를 각각 ‘도’와 ‘치’로 고정하는 것이다. 그래서 나는 1000개의 랜덤한 Latent Code와 그에 대응하는 이미지 1000개를 만들고, ‘도’와 ‘치’로 보이는 이미지를 모았다. 이 이미지를 만들 때 사용한 Latent Code를 사용하면, 항상 ‘도’ 혹은 ‘치’가 나오게 만들 수 있다.

그렇게 최종 결과물을 뽑았다. 결과물 자체는 꽤나 단순해 보이지만 실제로는 품이 좀 들었다. 다음에는 실시간으로 음악에 맞춰 글자를 생성하거나, 글자를 주르륵 배치하는 것 외에 여러 시각적 구성을 시도해 보고 싶은 바람이 있다. 아쉬움은 남지만, 아무튼 재미있는 작업이었다!

Comments