개발 꿀팁/PYTHON

Python 그림 변환, 정지화면, GIF 모두 변환 가능

Jammie 2022. 11. 30. 15:13
반응형

정지화면
먼저 정지화면을 문자화로 변환하는 것을 시연하고 기능 구현에 주로 사용되는 Python 라이브러리는 OpenCV이며 pip install opencv-python 명령을 설치하면 됩니다.

기능 구현의 기본 아이디어는 클러스터를 사용하여 픽셀 정보를 3 또는 5로 클러스터링하는 것입니다. 가장 어두운 클래스는 디지털 집약도로, 음영 클래스는 가로 막대(-)로, 밝은 부분은 공백으로 표시합니다.

주요 코드는 다음과 같이 구현됩니다

def img2strimg(frame, K=5):
if type(frame) != np.ndarray:
frame = np.array(frame)
height, width, *_ = frame.shape
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
frame_array = np.float32(frame_gray.reshape(-1))
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)
flags = cv2.KMEANS_RANDOM_CENTERS
# labels( 카테고리), centroids( 구심) 가져오기
compactness, labels, centroids = cv2.kmeans(frame_array, K, None, criteria, 10, flags)
centroids = np.uint8(centroids)
# labels의 여러 개의 구심들이 랜덤 순서로 배열되어 있기 때문에 간단한 구심 처리가 필요합니다
centroids = centroids.flatten()
centroids_sorted = sorted(centroids)
# centroids별 명암도, 0이 가장 어둡다
centroids_index = np.array([centroids_sorted.index(value) for value in centroids])
bright = [abs((3 * i - 2 * K) / (3 * K)) for i in range(1, 1 + K)]
bright_bound = bright.index(np.min(bright))
shadow = [abs((3 * i - K) / (3 * K)) for i in range(1, 1 + K)]
shadow_bound = shadow.index(np.min(shadow))
labels = labels.flatten()
# labels를 실제 밝기로 변환하는 방법 목록
labels = centroids_index[labels]
# 해석 목록
labels_picked = [labels[rows * width:(rows + 1) * width:2] for rows in range(0, height, 2)]
canvas = np.zeros((3 * height, 3 * width, 3), np.uint8)
# 가로 세로 세 배 크기의 흰색 캔버스 만들기
canvas.fill(255)
y = 8
for rows in labels_picked:
x = 0
for cols in rows:
if cols <= shadow_bound:
cv2.putText(canvas, str(random.randint(2, 9)),
(x, y), cv2.FONT_HERSHEY_PLAIN, 0.45, 1)
elif cols <= bright_bound:
cv2.putText(canvas, "-", (x, y),
cv2.FONT_HERSHEY_PLAIN, 0.4, 0, 1)
x += 6
y += 6
return canvas

원도는 다음과 같다.

효과도는 다음과 같습니다

GIF 움짤
다음으로 GIF를 문자 그림으로 변환하는 것을 시연합니다. 기능 구현에 주로 사용되는 Python 라이브러리는 imageio, Pillow이며 pip install imageio/Pillow 명령을 설치하면 됩니다.

기능 구현의 기본 아이디어는 다음과 같습니다.

gif 그림의 각 프레임을 정적 그림으로 분할합니다
모든 정적 그림을 그림으로 바꿉니다
모든 문자 그림을 gif로 재합성
주요 코드는 다음과 같이 구현됩니다

# gif를 분할하여 각 프레임을 그림으로 바꿉니다
def gif2pic(file, ascii_chars, isgray, font, scale):
'''
file: gif 파일
ascii_chars: 그레이스케일 값에 해당하는 문자열
isgray: 흑백인지 여부
font: ImageFont 개체
scale: 확대/ 축소
'''
im = Image.open(file)
path = os.getcwd()
if(not os.path.exists(path+"/tmp")):
os.mkdir(path+"/tmp")
os.chdir(path+"/tmp")
# tmp 디렉터리의 내용 비우기
for f in os.listdir(path+"/tmp"):
os.remove(f)
try:
while 1:
current = im.tell()
name = file.split('.')[0]+'_tmp_'+str(current)+'.png'
# 각 프레임의 그림 저장
im.save(name)
# 각 프레임을 그림으로 처리
img2ascii(name, ascii_chars, isgray, font, scale)
# 다음 프레임으로 계속 진행
im.seek(current+1)
except:
os.chdir(path)

# 다른 그레이스케일 값을 ASCII 문자로 매핑
def get_char(ascii_chars, r, g, b):
length = len(ascii_chars)
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
return ascii_chars[int(gray/(256/length))]


# 그림 그리기
def img2ascii(img, ascii_chars, isgray, font, scale):
scale = scale
# 그림을 RGB로 변환
im = Image.open(img).convert('RGB')
# 처리된 그림 크기 설정
raw_width = int(im.width * scale)
raw_height = int(im.height * scale)
# 설정된 글꼴 크기 가져오기
font_x, font_y = font.getsize(' ')
# 셀 크기 결정
block_x = int(font_x * scale)
block_y = int(font_y * scale)
# 가로 세로 각각 몇 개의 단원이 있는지 확정하다
w = int(raw_width/block_x)
h = int(raw_height/block_y)
# 각 셀을 픽셀로 축소
im = im.resize((w, h), Image.NEAREST)
# txts와 colors는 각각 해당 블록의 ASCII 문자와 RGB 값을 저장합니다.
txts = []
colors = []
for i in range(h):
line = ''
lineColor = []
for j in range(w):
pixel = im.getpixel((j, i))
lineColor.append((pixel[0], pixel[1], pixel[2]))
line += get_char(ascii_chars, pixel[0], pixel[1], pixel[2])
txts.append(line)
colors.append(lineColor)
# 새 캔버스 만들기
img_txt = Image.new('RGB', (raw_width, raw_height), (255, 255, 255))
# ASCII에 쓸 ImageDraw 개체 만들기
draw = ImageDraw.Draw(img_txt)
for j in range(len(txts)):
for i in range(len(txts[0])):
if isgray:
draw.text((i * block_x, j * block_y), txts[j][i], (119,136,153))
else:
draw.text((i * block_x, j * block_y), txts[j][i], colors[j][i])
img_txt.save(img)

# tmp 디렉터리에서 gif를 합성하기 위해 파일을 읽습니다
def pic2gif(dir_name, out_name, duration):
path = os.getcwd()
os.chdir(dir_name)
dirs = os.listdir()
images = []
num = 0
for d in dirs:
images.append(imageio.imread(d))
num += 1
os.chdir(path)
imageio.mimsave(out_name + '_ascii.gif',images,duration = duration)

원도는 다음과 같다.

흑백 효과도는 다음과 같습니다

컬러 효과도는 다음과 같습니다

결론
본고에서는 Python을 이용하여 정지화면과 GIF를 문자화로 변환하는 방법을 시연하였는데, 관심이 있으시다면 원하는 그림을 돌려보시고, 변환효과가 만족스럽지 않으시면 코드를 수정하여 만족스러운 효과로 바꿀 수 있습니다

 

반응형