옛날 사진 복원 프로그램을 파이썬 OpenCV와 Tkinter로 만든 이유 (NS/Telea 인페인팅 적용 및 메모리 최적화 팁)

안녕하세요! 오키 회원 여러분.
집에 하나씩 있는 옛날 흑백 사진이나 스크래치가 난 가족사진을 복원하려고 할 때, 포토샵을 켜기엔 다소 부담스럽고 외부 웹 인페인팅 사이트에 사진을 올리자니 개인정보나 유출 우려가 생기더라고요.
그래서 "무거운 딥러닝 AI 모델 없이, 전통적인 OpenCV의 인페인팅(Inpainting) 알고리즘과 파이썬 기본 GUI만으로 가볍고 빠른 데스크톱 복원 툴을 만들어보면 어떨까?" 하는 생각에 토이 프로젝트를 진행해 보았습니다.
개발 과정에서 마주친 한글 파일 경로 문제, GUI 스레드 블로킹 이슈, 그리고 대용량 이미지 처리 시 발생할 수 있는 메모리 누수 방지 노하우를 공유해 봅니다.
1. 핵심 코드 구조 및 데이터 흐름
GUI는 파이썬 기본 모듈인 tkinter를 사용했고, 마우스로 훼손 부위를 칠하면 빨간 마스크 영역을 생성한 뒤 cv2.inpaint() 알고리즘을 수행하는 방식입니다.
(가독성을 위해 핵심 흐름만 요약했으며, 전체 예외 처리 및 상세 코드는 깃허브를 참고해 주세요.)
Python
import cv2
import numpy as np
import tkinter as tk
from tkinter import filedialog
import threading
from PIL import Image, ImageTk
class MagicRestorer:
def __init__(self, root):
self.root = root
self.root.title("Magic Restore Lite")
# 기본 변수 및 메모리 버퍼 설정
self.brush_size = 10
self.history = [] # Undo 히스토리 버퍼
self.inpaint_mode = cv2.INPAINT_NS # 기본값: Navier-Stokes 방식
# OpenCV 한글 파일 경로 깨짐 우회 로딩
self.load_image_safe()
def load_image_safe(self):
file_path = filedialog.askopenfilename(title="복원할 사진 선택")
if not file_path: return
# 바이너리를 이용한 한글 경로 안정성 확보
stream = open(file_path.encode("utf-8"), "rb")
bytes_data = bytearray(stream.read())
numpy_array = np.asarray(bytes_data, dtype=np.uint8)
self.cv_img = cv2.imdecode(numpy_array, cv2.IMREAD_UNCHANGED)
# 고해상도 이미지 가로 해상도 제한 (메모리 폭주 방지)
h, w = self.cv_img.shape[:2]
if w > 2000:
scale = 2000 / w
self.cv_img = cv2.resize(self.cv_img, (2000, int(h * scale)))
self.cv_mask = np.zeros(self.cv_img.shape[:2], dtype=np.uint8)
def push_history(self):
# Undo 버퍼 개수를 제한하여 메모리 누수 차단
if len(self.history) > 15:
self.history.pop(0)
self.history.append((self.cv_img.copy(), self.cv_mask.copy()))
def refresh_canvas(self):
# 캔버스 객체 재사용으로 GUI 메모리 오염 방지
img_rgb = cv2.cvtColor(self.cv_img, cv2.COLOR_BGR2RGB)
self.pil_img = Image.fromarray(img_rgb)
self.tk_img = ImageTk.PhotoImage(self.pil_img)
if not hasattr(self, 'img_id'):
self.img_id = self.canvas.create_image(0, 0, image=self.tk_img, anchor="nw")
else:
self.canvas.itemconfig(self.img_id, image=self.tk_img)
def run_restore(self):
if not np.any(self.cv_mask): return
self.canvas.config(cursor="watch")
def process():
restored = cv2.inpaint(self.cv_img, self.cv_mask, self.brush_size, self.inpaint_mode)
self.cv_img = restored
self.cv_mask[:] = 0
self.root.after(0, self.finish_restore)
threading.Thread(target=process, daemon=True).start()
def finish_restore(self):
self.canvas.config(cursor="crosshair")
self.refresh_canvas()
2. 주요 함수 및 OpenCV 알고리즘 해부
이 프로그램의 핵심은 OpenCV에서 제공하는 두 가지 인페인팅 알고리즘의 특성을 상황에 맞게 활용한 점입니다.
cv2.INPAINT_NS(Navier-Stokes 기반): 유체역학의 나비에-스토크스 방정식을 응용한 방식입니다. 훼손된 영역의 테두리 선과 색상 흐름을 자연스럽게 이어주는 특성이 있어 길게 긁힌 스크래치나 접힌 선 복원에 적합합니다.cv2.INPAINT_TELEA(Fast Marching Method 기반): 훼손 영역 바깥쪽부터 안쪽으로 픽셀을 채워 넣으며 거리 가중치를 적용하는 알고리즘입니다. 점 형태의 곰팡이, 얼룩, 물방울 자국 제거에 탁월한 결과를 보여줍니다.np.copyto(tgt, src, where=mask): 포토샵의 '도장툴(Clone Stamp)' 기능을 직접 구현할 때 활용한 넘파이 메소드입니다. 지정한 원본 좌표의 픽셀을 마우스로 칠한 마스크 영역에만 고속으로 복사해 줍니다.
3. 우와, 이렇게도 쓰이네? (토이 프로젝트 실전 최적화 팁)
개발 중 실제로 마주친 에러와 프로그램 안정성을 끌어올리기 위한 소소한 해결 노하우입니다.
Undo(실행 취소) 무한 누적에 의한 메모리 누수 차단
이미지 복원 툴 특성상 작업 내역을 보관하기 위해 넘파이(NumPy) 배열을
history리스트에 계속 담게 됩니다. 하지만 이를 방치하면 대용량 이미지 작업 시 RAM 메모리를 수 기가바이트씩 잡아먹다가 프로그램이 튕깁니다. 이를 방지하기 위해len(self.history) > 15조건으로 버퍼 크기를 제한하고, 오래된 배열은pop(0)으로 메모리에서 즉시 강제 방출시켰습니다.
Tkinter Canvas 이미지 레이어 재사용 (
itemconfig)마우스를 드래그하거나 화면을 다시 그릴 때마다
canvas.create_image()를 매번 호출하면 캔버스 내부 객체가 무한히 쌓여 GUI가 급격히 느려집니다. 최초 1회만create_image로 레이어 ID(self.img_id)를 생성하고, 이후 갱신 시에는canvas.itemconfig()를 통해 픽셀 데이터만 교체하는 방식으로 GUI 메모리 오염을 완벽히 차단했습니다.
고해상도 이미지 입력 시 스마트 리사이징
4K/8K 급 이미지 파일이 들어오면 연산 속도가 극도로 떨어지고 메모리가 폭주할 수 있습니다.
load_image단계에서 가로 해상도가 2,000px을 넘어가면 비율에 맞춰 적절한 해상도로 자동 축소 로드되도록 설계했습니다.
cv2.imread()한글 경로 깨짐 우회 (윈도우 환경)파이썬 OpenCV를 사용할 때 경로에 한글이나 특수문자가 섞여 있으면
cv2.imread()가 사진을 읽지 못하고None을 반환합니다. 이를 방지하기 위해 파일 시스템에서 바이너리(open(..., 'rb'))로 읽은 뒤np.asarray와cv2.imdecode로 넘겨받으면 경로 문제없이 깨끗하게 로드됩니다.
4. 소스코드 및 시연 영상 안내
간단한 알고리즘이지만 스크래치가 심한 옛날 가족사진을 복원해 보면 생각보다 만족스러운 결과물이 나옵니다.
이 코드가 실제로 굴러가는 꿀잼 시연 영상은 유튜브에서 확인해 보세요!
🎬 유튜브 시연 영상: https://youtu.be/78EH925mXL0
📦 GitHub 소스코드: https://github.com/gohard-lab/magic_restorer
💾 Windows 실행 파일 (.exe): https://github.com/gohard-lab/magic_restorer/releases/latest
📦 워드프레스: https://gohard.pe.kr/
직접 실행해 보시거나 코드 관련하여 개선할 부분이 보인다면 언제든 편하게 댓글로 피드백 주세요. 감사드립니다!