1. Python으로 Dither 만들기: Truncation, Round
6Bit Dither 만들기
파이썬으로 디더(Dither)를 만들어볼 예정이다. Dither는 시간적 디더링과 공간적 디더링이 있는데, 둘 중 공간적 디더를 만들어 볼 것이다.
공간적 디더링에 관한 자세한 내용은 아래 링크로 가면 자세히 알 수 있다.
https://rimeestore.tistory.com/entry/Error-Diffusion%EC%9D%B4%EB%9E%80
본격적으로 Dither를 개발하기 전에, Dithering을 적용하지 않으면 이미지가 어떻게 보이는지 알아볼 계획이다. 인터넷의 대부분의 이미지는 8bit 이미지이므로 0 ~ 255 Gray 값을 가진다. 우리는 8bit Image를 2bit Image로 바꿔볼 것이다.
8bit 이미지를 2bit 이미지로 변환하기 위한 방법을 간단하게 생각하면 8bit Gray 값을 64으로 나눠주면 된다. 예를 들어, RGB 값 [244, 137, 16]을 64로 나누면 [3.8125, 2.140625, 0.25]이 된다. 그러면 소수가 생기게 되는데 이 소수를 처리하는 방법이 Dithering이라고 할 수 있겠다. 앞으로 이 소수를 Error라고 하겠다.
1.1 Truncation Method
첫 번째 방법은 Error를 버리는 방법(Truncation)이다.
pip install numpy
pip install pillow
import numpy as np
from PIL import Image
우선 파이썬으로 영상처리할 때 사용하는 numpy, pillow 라이브러리를 설치하고 불러온다.
img = Image.open('in_img/Lenna.jpg') # Import Image
img_arr = np.array(img) # Image to Array
# print(np.shape(img_arr)) # (height, width, pixel)
Input Image를 불러온다. 불러온 이미지를 array로 바꾸고 배열의 사이즈(이미지의 사이즈)를 확인한다.
img_tnc_arr = img_arr // 64 # Image Truncation
img_tnc = Image.fromarray(img_tnc_arr * 64) # Array to Image
img_tnc.save('out_img/Lenna_Truncation.jpg') # Save Image
img_tnc.show() # Show Image
Image Array를 64로 나눠준다. 그리고 pillow 라이브러리의 Image.fromarray() 함수를 사용하여 Array를 다시 Image로 바꿔준다.
이 과정에서 64를 다시 곱해주어야만 하는데, 이 이유는 컴퓨터에서 사용하는 대부분의 이미지 포맷이 8bit로 정해져있기 때문에 임시방편으로 다시 64를 곱해주어야만 한다. 만약 곱해주지 않으면 최대값이 255 Gray인 이미지에서 매우 작은 Gray이기 때문에 어두워서 잘 보이지 않을 것이다.
물론, 이미지를 PPM 파일로 변환하면 이미지 포맷을 바꿀 수 있기 때문에 정확한 방법이지만 귀찮아지기 때문에, 나눈 값을 다시 곱해주는 방법으로 우선 진행할 것이다.
Code
import numpy as np
from PIL import Image
img = Image.open('in_img/Lenna.jpg') # Import Image
img_arr = np.array(img) # Image to Array
# print(np.shape(img_arr)) # (height, width, pixel)
img_tnc_arr = img_arr // 64 # Image Truncation
img_tnc = Image.fromarray(img_tnc_arr * 64) # Array to Image
img_tnc.save('out_img/Lenna_Truncation.jpg') # Save Image
img_tnc.show() # Show Image
1.2 Round Method
두 번째 방법은 Error를 반올림하는 방법(Round)이다.
import numpy as np
from PIL import Image
img = Image.open('in_img/Lenna.jpg') # Import Image
img_arr = np.array(img) # Image to Array
# print(np.shape(img_arr)) # (height, width, pixel)
img_rnd_arr = np.round(img_arr / 64) # Image Round
img_rnd_arr = img_rnd_arr * 64 # Image Round
img_rnd_arr = img_rnd_arr.astype(np.uint8) # float to int
img_rnd = Image.fromarray(img_rnd_arr) # Array to Image
img_rnd.save('out_img/Lenna_Round.jpg') # Save Image
img_rnd.show() # Show Image
Image Array를 64로 나눈 후에 다시 64를 곱했다. 하지만 이 과정에서 데이터형이 float으로 바뀌기 때문에, astype(np.uint8)로 데이터를 int형으로 다시 바꿔준다.
위의 결과를 보면 Truncation과 비교하여 많이 이상하다. 이 이유는 Round는 반올림화는 과정에서 최대값을 초과해버리기 때문이다. 예를 들어, 255 Gray를 64로 나누면 3.984375가 된다. 이 값을 반올림하게 되면 4가 된다. 그런데, 2bit Image의 최대값은 3이므로 Data range를 벗어나게 된 것이다. 즉 overflow가 발생한 것이다.
그래서 round로 처리할 때는 overflow를 처리해주어야 한다.
Code
import numpy as np
from PIL import Image
img = Image.open('in_img/Lenna.jpg') # Import Image
img_arr = np.array(img) # Image to Array
# print(np.shape(img_arr)) # (height, width, pixel)
img_rnd_arr = np.round(img_arr / 64) # Image Round
img_rnd_arr = np.clip(img_rnd_arr, 0, 3) # Clip (min = 0, max = 3)
img_rnd_arr = img_rnd_arr * 64 # Image Round
img_rnd_arr = img_rnd_arr.astype(np.uint8) # float to int
img_rnd = Image.fromarray(img_rnd_arr) # Array to Image
img_rnd.save('out_img/Lenna_Round.jpg') # Save Image
img_rnd.show() # Show Image
numpy의 clip 함수를 사용하여 최대값과 최소값을 정할 수 있다. 최소값은 0, 최대값은 3로 지정하여. 0 미만인 값들은 0으로, 3 초과인 값들은 3으로 clipping한다.
최종 결과
Round Method가 조금 더 밝다. 이유는 Error를 결정하는 기준선을 높였기 때문이다. 높인만큼 max값에서 Overflow가 발생하는 단점이 발생했다. 이 Overflow를 처리하는 알고리즘이 반영되어야만 한다.
결과를 보면 Truncation이든 Round이든 별로 쓰고싶지 않다. 어렸을 때 갖고 놀던 다마고치로 이미지를 보는 것 같은 느낌을 준다.
'Algorithm > Image Processing' 카테고리의 다른 글
감마 보정(Gamma Correction)이란? (2) | 2024.11.03 |
---|---|
[Image Processing] 1. Python으로 Dither 만들기: Floyd Steinberg Dithering (0) | 2023.11.15 |
Error Diffusion이란? (0) | 2023.01.08 |
FRC(Frame Rate Control)란? (0) | 2023.01.08 |
파이썬에서 RGB 이미지를 Grayscale 이미지로 바꾸기 (5) | 2022.09.02 |
댓글