본문 바로가기
Image Processing/Project

[Image Processing] 1. Python으로 Dither 만들기: Truncation, Round

by 리미와감자 2023. 11. 13.

1. Python으로 Dither 만들기: Truncation, Round

6Bit Dither 만들기

 

 

파이썬으로 디더(Dither)를 만들어볼 예정이다. Dither는 시간적 디더링과 공간적 디더링이 있는데, 둘 중 공간적 디더를 만들어 볼 것이다.

 

공간적 디더링에 관한 자세한 내용은 아래 링크로 가면 자세히 알 수 있다.

https://rimeestore.tistory.com/entry/Error-Diffusion%EC%9D%B4%EB%9E%80

 

Error Diffusion이란?

Dithering Dithering이란 Bit Depth가 높은 영상데이터를 Bit Depth가 낮은 영상데이터로 바꿀 때 적용하는 알고리즘 중 하나이다. 예를 들어, 12 Bit 영상데이터는 RGB 각각 0 ~ 4095까지 총 4096단계의 밝기 Value

rimeestore.tistory.com

 

본격적으로 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 라이브러리를 설치하고 불러온다.

 


 

Input Image(Lenna)

 

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 파일로 변환하면 이미지 포맷을 바꿀 수 있기 때문에 정확한 방법이지만 귀찮아지기 때문에, 나눈 값을 다시 곱해주는 방법으로 우선 진행할 것이다.

 

Lenna Truncation(8bit to 2bit)

 

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형으로 다시 바꿔준다.

 

Lenna Round(8bit to 2bit), Error Version

 

위의 결과를 보면 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한다.

 

 

Lenna Round(8bit to 2bit)

 

 


최종 결과

 

Input Image(Lenna), Lenna Truncation(8bit to 2bit) , Lenna Round(8bit to 2bit)

 

Round Method가 조금 더 밝다. 이유는 Error를 결정하는 기준선을 높였기 때문이다. 높인만큼 max값에서 Overflow가 발생하는 단점이 발생했다. 이 Overflow를 처리하는 알고리즘이 반영되어야만 한다.

 

결과를 보면 Truncation이든 Round이든 별로 쓰고싶지 않다. 어렸을 때 갖고 놀던 다마고치로 이미지를 보는 것 같은 느낌을 준다.

댓글