Minseong Code Lab
← Projects
Bootcamp MissionAI Service / AX

ONNX 손글씨 인식 서비스

ONNX Runtime 추론을 Streamlit 앱과 Docker 이미지로 묶은 서비스

게시 2026-07-10
PythonStreamlitONNX RuntimeDockerNumPyPillow
Contents · 목차
  1. 무엇을 하는 미션인가
  2. 화면 구성
  3. 전처리가 이 미션의 핵심
  4. 추론과 후처리
  5. 실행 환경
  6. 한계

무엇을 하는 미션인가

캔버스에 마우스로 숫자를 그리면 ONNX Runtime이 0~9 확률을 예측해 돌려주는 Streamlit 서비스입니다. 모델을 직접 학습하지 않고, ONNX Models 저장소의 mnist-12.onnx를 그대로 서비스에 얹는 것이 과제의 초점입니다.

입력은 캔버스 그림(RGBA), 출력은 예측 숫자와 클래스별 확률입니다.

화면 구성

app.py 하나에 네 영역이 들어 있습니다 — 입력 캔버스 · 전처리된 28×28 이미지 · 클래스별 확률 막대 · 저장한 그림 목록(예측값과 확률 함께 표시). 전처리 결과를 화면에 그대로 보여주는 것이 이 서비스의 특징입니다. 모델이 무엇을 보고 판단했는지 사용자가 확인할 수 있습니다.

전처리가 이 미션의 핵심

캔버스 그림을 그냥 28×28로 줄이면 MNIST 학습 분포와 어긋납니다. 그래서 숫자 영역만 잘라내고 20×20으로 맞춘 뒤 28×28 한가운데에 붙입니다.

# app.py — 캔버스 RGBA → 중앙 정렬된 1×1×28×28 float32 (발췌)
image_array = np.array(merged)
coords = np.argwhere(image_array > 20)      # 그린 영역만 추림
y_min, x_min = coords.min(axis=0)
y_max, x_max = coords.max(axis=0) + 1
digit = merged.crop((x_min, y_min, x_max, y_max))

# Keep the digit centered and close to the MNIST training distribution.
digit.thumbnail((20, 20), Image.Resampling.LANCZOS)
canvas = Image.new("L", (28, 28), 0)
canvas.paste(digit, ((28 - digit.width) // 2, (28 - digit.height) // 2))

tensor = np.array(canvas, dtype=np.float32) / 255.0
tensor = tensor.reshape(1, 1, 28, 28)

추론과 후처리

세션은 CPUExecutionProvider로 열고 @st.cache_resource로 한 번만 로드합니다. 모델 파일이 없으면 원본 저장소에서 내려받는 경로도 두었습니다.

# app.py — 입출력 이름을 세션에서 읽어 고정값을 쓰지 않는다
def predict(session, input_tensor):
    input_name = session.get_inputs()[0].name
    output_name = session.get_outputs()[0].name
    logits = session.run([output_name], {input_name: input_tensor})[0][0]
    return softmax(logits)

mnist-12.onnx는 logits를 내보내므로 softmax는 애플리케이션 쪽에서 직접 계산합니다. 오버플로를 피하려고 float64로 올린 뒤 처리합니다.

실행 환경

python:3.12-slim 기반 Dockerfile이 있고, STREAMLIT_SERVER_ADDRESS=0.0.0.0과 포트 8501을 환경변수로 고정해 컨테이너 밖에서 접속할 수 있게 했습니다.

한계

  • 추론 속도나 정확도를 측정한 기록이 없습니다. 체감으로도 수치로도 비교하지 않았습니다
  • 모델은 기성 ONNX 모델이며 학습·양자화는 이 미션 범위가 아닙니다 (양자화는 미션 16에서 다뤘습니다)
  • 저장 이미지는 컨테이너 안 파일 시스템에 쌓이므로 재시작하면 사라집니다