개발로 하는 개발

[졸업프로젝트] Image completion 본문

Projects/Capstone

[졸업프로젝트] Image completion

jiwon152 2023. 11. 24. 22:48

Sketch Healer, Generative Sketch Healing 논문 리뷰 및 코드 사용법 위주로 본 image completion

저희 졸업 프로젝트의 주제는

 Stroke-based Collaborative Drawing between Robot(AI) and Human

 

으로 stroke를 기반으로 사람과 협업하여 그림을 그리는 human-like 로봇을 구현하는 것입니다. 

 

저희는 human-like를 이렇게 정의했습니다.

Human-like 한 그림이란 무작위로 획을 그리는 것이 아니라,
1. 한 획이 끝나는 지점과 가까운 곳부터 획을 그리거나 
2. 이미 그려지던 물체를 구성하는 stroke에 우선순위를 두어
그림을 완성하는 것.


 이를 달성하기 위해 이미 라벨링 된 데이터셋을 활용하여 인공지능에게 image classification과 completion을 차례대로 학습시키고, 사람이 그림을 그리면 인공지능이 그림 속 object의 종류를 파악한 다음 해당 그림의 완성본을 생성하는 코드를 작성하기로 했습니다. 나아가, 인공지능을 통해 부족한 부분이 채워진 그림을 stroke based image로 변환하고 그 그림을 전달받은 로봇이 실제 사람과 비슷한 움직임(순서)으로 기존 이미지에 n개의 stroke를 그리도록 해서. 이와 같은 방식으로 사람과 로봇이 번갈아가며 n개의 stroke를 생성함으로써 그림을 완성하는 것을 목표로 잡고 있습니다.

 

 내용이 조금 어려울 수 있지만, 핵심은

1. 로봇(AI)이 인간처럼 그림을 그릴 수 있도록 하기
2. 획 단위로 그림을 나누어서 완성할 수 있는 코드 작성하기
3. 로봇과 인간이 협업할 수 있게 하기

 

라고 할 수 있습니다.

 

 이번 학기에는 저희 팀의 목적에 맞는 논문을 찾아서, 이 논문의 코드를 검증하고 구현하는 과정을 진행하기로 했습니다. 따라서 이 논문을 어떤 식으로 검증했는지, 그리고 어떠한 내용들을 수정해야 했는지 위주로 작성해 보겠습니다.

 

 

Sketch Healer, Generative Sketch Healing 논문의 장점

 

왼쪽은 전반적인 image completion의 연구 결과들, 그리고 오른쪽은 해당 논문 두개의 결과

 

 이 두 논문은 Sketch Healer가 우선 나오고, 그 뒤 추가 연구를 통해 Generative Sketch Healing이 나온 것으로 근본적인 내용은 비슷합니다. 해당 논문에서는 image completion과 image healing을 분리하고 있는데, 여기에서 image completion은 없는 부분을 메우는 것이지만, image healing은 있는 그림을 재창조 하는 과정이라고 설명합니다. 비록 저희는 image completion과 image healing을 분리하여 보지 않았지만, 저희가 원하는 것은 이미 있는 이미지의 부분을 메우는 것이 아니라 기존 것을 가지고 유사하게 창조하는 것이므로 이 논문의 내용이 저희가 원하는 것에 더 흡사하다고 할 수 있습니다.

 

 그런데, 둘 다 코드가 제공되는 것은 아닙니다. 우리에게 제공되는 것은 더 예전의 SketchHealer 코드 뿐이여서 이것을 적당히 고쳐가면서 돌아가게 만들어야 하는 겁니다. 우선 코드를 돌릴 수 있는지 확인하기 위해서는 dataset이 필요합니다. 이 연구에서 사용되었고, 우리 연구에도 사용할 dataset은 quickdraw dataset이라는, 획과 획순, 그리고 이미지 정보가 포함되어 있는 dataset입니다. Google의 Quick, Draw 게임을 통해 만들어진 이 dataset은 여기에서 확인이 가능합니다. 우선 이 논문은 airplane, alarm clock, angel, apple, belt, bus, butterfly, cake, cat, clock, eye, fish, pig, sheep, spider, The Great Wall of China, umbrella 이렇게  17가지를 가지고 training 되어 있기 때문에 저희도 이 17가지의 npz file을 다운로드 받아줍니다. 일단 저희는 모든 npz 파일을 다운로드 받기 위해, gsutil이라는 것을 mac의 terminal을 통해 설치 한 뒤, 저 내용을 입력했습니다. 그러면 모든 npz 파일을 다운로드 할 수 있습니다.

gsutil -m cp
"gs://quickdraw_dataset/sketchrnn/*.full.npz"

 그 뒤에 일단 어떻게 하냐면... github repository를 clone 합니다. 

 

방법은 여러가지이지만 일단 Github link로 들어갑니다. link 라고 적혀있는 파란색 하이퍼링크를 누르시면 github으로 들어가집니다. 여기에서 우선, 초록색 버튼 <> Code 를 클릭합니다. Open with Github Desktop을 하셔도 되고, 아니면 직접 clone하셔도 됩니다.

 

 그 뒤, 연구 코드를 돌리기 위한 dependency를 맞춰주어야 하는데, 이것은 필요로 하는 라이브러리를 설치해줘야 한다는 것입니다. 다만, 이미 있는 것들과 충돌할 위험이 있기 때문에 virtual environment를 사용하고, 그 위에 새롭게 라이브러리들을 설치해 줍니다. 터미널에서 아까 clone 한 폴더로 cd를 하거나, finder - 아래의 path - 두손가락으로 우클릭 - open in Terminal을 눌러줍니다. venv의 이름은 다르면 헷갈리므로, sketchHealer로 작성해주고, 이 virtual environment를 활성화 시켜줍니다. 활성화 되면 각 줄 제일 앞에 (sketchHealer)가 나타납니다. 나중에 비활성화하고 싶으면 deactivate를 하면 됩니다. 

 

python -m venv sketchHealer
source sketchHealer/bin/activate

//later -> deactivate

 

 연구자 분이 코드를 올려주신 깃헙에서 

To run this code, you need to install
pytorch
torchvision
opencv
pillow
and some common dependency libraries.

 

이렇게 말씀해주셨기 때문에 필요한 라이브러리를 설치해 줍니다.

 

pip install torch torchvision
pip install opencv-python
pip install pillow
pip install matplotlib

 

 그리고 hyper_params.py 파일에서 이 부분을 찾아서 변경해 주어야 합니다. self.data_location에 아까 다운로드 받은 dataset의 경로를 작성해 주어야 프로그램이 이 dataset들을 찾아서 이용할 수 있습니다.

#hyper_params.py
def __init__(self):
    self.data_location = ''#location of origin data

 

 이후 또 에러가 나면, pretrained model - encoderRNN_epoch_146000.pth 의 경로를 확인해주어야 합니다. 컴퓨터 상에서 어떤 파일을 찾는 방법에는 절대 경로와 상대 경로라는 게 있는데, 상대 경로로 현재 있는 inference.py 파일에서 /model_save/ 아래에 encoderRNN_epoch_146000.pth 파일과, decoderRNN_epoch_146000.pth 파일이 있는지 확인해보고, 없다면 model_save라는 폴더를 만들어 넣어주어야 합니다.

 

 맥북 m1의 GPU는 NVDIA 전용으로 만들어진 CUDA를 지원하지 않기 때문에, 아래 에러가 뜨게 됩니다. 따라서 inference.py 파일을 아래 부분처럼 수정을 해주어야 합니다. 

#inference.py line 367
'''
RuntimeError: Attempting to deserialize object on a CUDA device 
but torch.cuda.is_available() is False. 
If you are running on a CPU-only machine, 
please use torch.load with map_location=torch.device('cpu') 
to map your storages to the CPU.
M1, M2는 CUDA 지원 안되서 CPU로 돌려야함
 - CUDA, which is NVIDIA-specific, was not compatible with M1 Macs.
'''
    def load(self, encoder_name, decoder_name):
        if torch.cuda.is_available():
            saved_encoder = torch.load(encoder_name)
            saved_decoder = torch.load(decoder_name)
        else:
            saved_encoder = torch.load(encoder_name, map_location=torch.device('cpu'))
            saved_decoder = torch.load(decoder_name, map_location=torch.device('cpu'))
        self.encoder.load_state_dict(saved_encoder)
        self.decoder.load_state_dict(saved_decoder)

 

 그리고, 예전에 작성되었던 모델이여서 옛날 버전의 numpy이기 때문에 나는 에러가 있습니다. np.float가 존재하지 않는다는 오류인데요, inference.py에서도 np.float32를 사용하고 있어서 sketch_processing.py 파일에 들어가서 np.float를 전부 np.float32로 변경해주었습니다.

#sketch_processing.py line 453
'''
AttributeError: module 'numpy' has no attribute 'float'.
`np.float` was a deprecated alias for the builtin `float`. 
To avoid this error in existing code, use `float` by itself. 
Doing this will not modify any behavior and is safe. 
If you specifically wanted the numpy scalar type, use `np.float64` here.
The aliases was originally deprecated in NumPy 1.20; 
for more details and guidance see the original release note at:
    https://numpy.org/devdocs/release/1.20.0-notes.html#deprecations
    
'''
def scale_sketch(sketch, size=(448, 448)):
    [_, _, h, w] = canvas_size_google(sketch)
    if h >= w:
        sketch_normalize = sketch / np.array([[h, h, 1]], dtype=np.float32)
    else:
        sketch_normalize = sketch / np.array([[w, w, 1]], dtype=np.float32)
    sketch_rescale = sketch_normalize * np.array([[size[0], size[1], 1]], dtype=np.float32)
    return sketch_rescale.astype("int16")

 

그 이후에는, 돌아갈 줄 알았으나 Index out of range 에러가 떠서 inference.py 파일의 해당 부분을 변경해주었습니다.

#inference.py -> line 234 sketches_categroy_count 
'''
line 232, in conditional_generation
    category_name = sketch_dataset.category[category_flag].split(".")[0]
IndexError: list index out of range
-> trying to split without anything to split
'''
if 0 <= category_flag < len(sketch_dataset.category):
    category_name = sketch_dataset.category[category_flag].split(".")[0]
    # Your code that uses category_name
    #print(f"Category name: {category_name}")
    #print(f"Invalid category flag: {category_flag}")
else: category_name = sketch_dataset.category[category_flag]
#category_name = sketch_dataset.category[category_flag].split(".")[0]

 

그 이후에

 python -u inference.py

 

코드를 터미널에서 입력하면 이제 드디어 작동이 시작합니다. 파란색 부분은 사용자 이름이 나와서 임의로 지워줬습니다. 앞쪽에 venv가 작동하고 있다는 것을 볼 수 있고, 각 데이터셋이 추가되고 train이 돌기 시작하는 것을 확인할 수 있습니다. 저렇게 train이 전부 돌게 되면 오른쪽처럼 SketchHealer/result/visualize2/146000/0.0/npz 경로에 결과가 나오는 것을 확인 할 수 있습니다.

 

한가지 파일만 열어보자면, npz 파일은 (2500, 1, 128)의 형태로 만들어지고, 그 파일을 읽으면 이렇게 보이는 것을 알 수 있습니다.