1. Grayscale Image turn into Color Image
우선, 이러한 현상에 대해 논의한 내용들은 아래의 링크에 포함되어 있다.
https://discuss.pytorch.org/t/save-one-channel-normalized-tensor-to-image/38402
https://github.com/pytorch/vision/issues/7653
구글 Colab 환경에서는 딱히 문제가 발생하지 않는데,
다만 Local에서 device = torch.device("mps:0" if torch.backends.mps.is_available else "cpu")를 이용하여
mps device상에서 코드를 돌릴 경우 아래와 같은 에러가 발생한다.
학부연구생 활동을 하던 중, Generator가 생성한 이미지의 channel을 1개로 설정했는데도 불구하고,
save_image( )를 통해 저장한 이미지 파일(tiff)들을 확인해보니까 color image(channel = 3)로 변해버리는 경우가 있었다.
처음에는 단순히 Generator의 내부 구조를 설계할 때 channel 개수를 혼동했을 거라는 생각이었는데,
알고보니 전혀 문제가 없어서 하루종일 고생한 기억이 있다....
아래는 원래 나와야 할 Grayscale Image 이고 오른쪽은 의도치 않게 나와버린 Color Image 이다.
위의 의도하지 않는 컬러 이미지가 나오는 이유는 save_image( )를 사용했기 때문이다.
아래는 기존의 코드이다. (torchvision.utils의 save_image 사용)
from torchvision.utils import save_image
# 생성된 이미지 저장
save_path = "/Users/eric/Documents/Research/CDAL/2024_spring/GAN/Results/"
for i, img in enumerate(generated_images):
save_image(img, f'{save_path}{i+1}.tiff', normalize=True, value_range=(0, 1))
그리고 이게 수정된 코드이다. (maplotlib.pyplot의 imsave 사용)
# 생성된 이미지 저장
save_path = "/Users/eric/Documents/Research/CDAL/2024_spring/GAN/Results/"
for i, img in enumerate(generated_images):
plt.imsave(f'{save_path}{i+1}.png', numpy_array, cmap='gray')
2. Why?
channel = 1 이지만 컬러 이미지가 나오는 주된 이유는 save_image() 함수가 단일 채널 이미지를 저장할 때 해당 데이터를 RGB 색상 공간으로 해석하기 때문이다.
이 함수는 입력 텐서가 [1, H, W] 형태라도 그 값을 RGB 채널에 복제하여 [H, W, C] 형태의 컬러 이미지로 변환한다.
만약 특정 채널이 0이 아닌 pixel 값을 가지고 있다면,
복제 과정에서 이러한 값들이 모든 RGB 채널에 적용되어 컬러 이미지로 보이게 된다.
save_image()는 기본적으로 이미지 데이터가 0과 1 사이의 값을 갖는다고 가정한다. 따라서 이 함수는 저장할 때 이 범위를 0-255로 스케일링한다.
하지만 입력 텐서가 [1, H, W] 형태의 그레이스케일 데이터라 하더라도, 이 데이터를 RGB 채널에 복제하여 저장하기 때문에 최종 이미지 파일은 컬러로 나타난다.
이와 달리, matplotlib 같은 라이브러리를 사용하여 그레이스케일 이미지를 직접적으로 저장할 경우,
cmap='gray' 인자를 통해 데이터가 그레이스케일로 해석되어 저장된다.
그 결과, 파일은 실제로 그레이스케일 이미지로 저장된다.
따라서 PyTorch의 save_image() 함수를 사용하여 그레이스케일 이미지를 저장하려면, 데이터를 RGB 채널로 변환하지 않고, 대신 matplotlib의 imsave() 함수를 사용하는 것이 좋다.
이 함수는 cmap 인자를 받아 그레이스케일 이미지로 저장할 수 있으며, 이 방법이 더 직관적이고 올바른 접근 방식이다.
3. How?
save_image 함수는 입력으로 주어진 텐서를 이미지 파일로 저장할 때, 주어진 텐서의 채널 차원이 1인 경우(즉, GrayScale 이미지인 경우) 자동으로 이 채널을 RGB의 세 채널에 복제한다.
이 복제 과정은 다음과 같은 단계를 거친다:
1. 입력 텐서의 채널 차원이 1인지 확인한다.
2. 채널 차원이 1이라면, 이 단일 채널을 RGB의 세 채널로 확장한다.
- 예를 들어, 입력 텐서가 `[1, Height, Width]`의 형태라면, 이를 `[3, Height, Width]`로 변환한다.
- 이 때 복제된 각 채널은 동일한 데이터를 갖게 되어, 컬러 이미지로 보이게 된다.
3. 확장된 텐서는 RGB 이미지로 해석되어, 저장 과정에서 각 채널의 값을 `0-255` 범위의 값으로 스케일링하여 이미지 파일에 쓴다.
이러한 변환은 그레이스케일 이미지의 각 픽셀 값이 R, G, B 세 채널에 동일하게 적용되기 때문에 결과적으로 그레이스케일 이미지에 해당하는 RGB 이미지가 생성된다.
하지만 컴퓨터 화면에서 이 이미지를 보게 되면, RGB 값이 모두 동일하기 때문에 컬러가 있는 것처럼 보이지 않고 그레이스케일로 보인다.
즉, save_image함수를 사용할 때 명시적으로 채널을 확장하지 않더라도, 내부적으로 이러한 처리가 자동으로 수행되기 때문에, 단일 채널 텐서도 컬러 이미지로 저장된다.
그 결과, 파일 형식은 RGB 컬러 이미지가 되지만, 실제 컬러 정보는 없는, 동일한 값이 복제된 이미지가 된다.
결과적으로는 단일 채널이 이미지 파일로 저장될 때는 이 세 채널이 모두 같은 값을 가지게 되어, 원래의 흑백 이미지가 RGB 이미지로 보이지만 실제 색상 정보는 없다는 것을 의미한다.
그러나 저장할 때 이미지 뷰어나 포맷에 따라 단일 채널을 RGB 공간으로 복제할 때 각 채널이 약간 다른 방식으로 처리되어 실제로는 다른 색상이 나타날 수 있다.
예를 들어, JPEG와 같은 포맷은 색상 정보를 압축할 때 채널 간 상호작용을 통해 색상 변형이 일어날 수 있다.