Files
paddleocr-interface/app.py

173 lines
9.3 KiB
Python

import streamlit as st
from pathlib import Path
from paddleocr import PPStructureV3
from PIL import Image
import json
import os
import uuid
import numpy as np
# 페이지 설정
st.set_page_config(page_title="PaddleOCR 이미지 분석", layout="wide")
st.title("📄 PaddleOCR을 이용한 이미지 분석")
st.write("이미지 파일을 업로드하면 문서 방향 분류, 왜곡 보정, 표/수식/차트 인식 등을 수행하고 결과를 보여줍니다.")
# 모델 초기화 (캐싱 사용)
@st.cache_resource
def load_model():
return PPStructureV3(
lang="korean",
use_doc_orientation_classify=True,
use_doc_unwarping=True,
use_seal_recognition=False,
use_table_recognition=True,
use_formula_recognition=True,
use_chart_recognition=True,
use_region_detection=True,
)
with st.spinner("모델을 불러오는 중입니다..."):
structure = load_model()
# 파일 업로더
uploaded_file = st.file_uploader("분석할 이미지 파일을 선택하세요.", type=["jpg", "jpeg", "png", "bmp", "tif"])
if uploaded_file is not None:
# 임시 디렉터리 설정
output_dir = Path("output_results")
output_dir.mkdir(exist_ok=True)
# 고유한 파일 이름 생성
unique_id = uuid.uuid4().hex
try:
# 원본 이미지 표시
image = Image.open(uploaded_file)
# 모델이 처리하기 쉽도록 이미지를 RGB 형식으로 변환
if image.mode != 'RGB':
image = image.convert('RGB')
col1, col2 = st.columns(2)
with col1:
st.subheader("🖼️ 원본 이미지")
st.image(image, caption="업로드된 이미지", width='stretch')
# 분석 시작 버튼
if st.button("분석 시작하기", use_container_width=True):
with st.spinner("이미지를 분석하고 있습니다..."):
# PIL 이미지를 numpy 배열로 변환하여 예측
img_array = np.array(image)
output = structure.predict(input=img_array)
if not output:
st.warning("이미지에서 구조를 감지하지 못했습니다.")
else:
# 결과 저장 경로
saved_res_path_base = output_dir / f"result_{unique_id}"
saved_res_path_base.mkdir(exist_ok=True)
json_paths = []
# 1. 모든 결과 파일(이미지, JSON)을 먼저 저장
for i, res in enumerate(output):
res.save_to_img(save_path=str(saved_res_path_base))
json_path = saved_res_path_base / f"{i}.json"
res.save_to_json(save_path=str(json_path))
json_paths.append(json_path)
# 2. 저장된 모든 결과 이미지를 설명과 함께 col2에 표시
with col2:
st.subheader("✨ 분석 결과 이미지")
image_files = sorted([f for f in saved_res_path_base.glob('*') if f.suffix.lower() in ('.jpg', '.jpeg', '.png', '.bmp', '.tif')])
if not image_files:
st.error(f"결과 이미지 파일을 찾을 수 없습니다. (검색 경로: {saved_res_path_base})")
else:
for img_path in image_files:
result_image = Image.open(img_path)
title = ""
description = ""
if "_layout_order_res" in img_path.name:
title = "레이아웃 순서 분석 (Reading Order)"
description = "각 텍스트 영역을 사람이 문서를 읽는 논리적인 순서(예: 위에서 아래로, 왼쪽에서 오른쪽으로)를 파악하여 시각화한 결과입니다. 복잡한 문서에서 텍스트를 올바른 순서로 추출하는 데 중요한 역할을 합니다."
elif "layout_det_res" in img_path.name:
title = "레이아웃 감지 (Layout Detection)"
description = "모델이 문서에서 텍스트, 표, 이미지 등의 영역을 최초로 감지한 결과입니다. 이 결과를 바탕으로 각 영역의 종류를 더 상세하게 분석합니다."
elif "_region_det_res" in img_path.name:
title = "영역 감지 (Region Detection)"
description = "레이아웃 감지 후, 각 영역의 종류('제목', '', '텍스트' 등)를 구체적으로 식별해낸 결과입니다."
elif "_overall_ocr_res" in img_path.name:
title = "종합 OCR 결과 (Overall OCR Result)"
description = "레이아웃 분석으로 찾아낸 각 텍스트 영역에 대해 광학 문자 인식(OCR)을 수행한 최종 결과를 원본 이미지 위에 표시한 것입니다."
elif "_preprocessed_img" in img_path.name:
title = "전처리된 이미지 (Preprocessed Image)"
description = "OCR의 정확도를 높이기 위해 입력 이미지의 기울기를 보정하거나, 잡음을 제거하는 등의 전처리 과정을 거친 후의 이미지를 보여줍니다."
elif "_table_cell_img" in img_path.name:
title = "표 셀 인식 (Table Cell Recognition)"
description = "'표(Table)' 영역이 감지되면, 표의 구조를 분석하여 각 셀의 경계를 찾고 그 결과를 시각화하여 보여줍니다. 이 결과를 바탕으로 구조화된 데이터를 생성할 수 있습니다."
else:
title = "기타 결과 이미지"
description = "분석 과정에서 생성된 기타 결과 이미지입니다."
st.markdown(f"##### {title}")
st.image(result_image, width='stretch')
st.info(description)
st.markdown("---")
# 3. 저장된 모든 JSON 결과를 아래에 순서대로 표시
st.subheader("📄 분석 내용")
for json_path in sorted(json_paths):
with open(json_path, 'r', encoding='utf-8') as f:
json_data = json.load(f)
st.markdown(f"---")
st.markdown(f"#### 결과 파일: `{json_path.name}`")
# 테이블 HTML을 먼저 추출하여 저장
table_htmls = [table.get('pred_html', '') for table in json_data.get('table_res_list', [])]
table_idx = 0
# 각 블록을 순회하며 의미에 맞게 표시
for block in json_data.get('parsing_res_list', []):
label = block.get('block_label', 'unknown')
content = block.get('block_content', '').strip()
if label == 'table':
st.markdown(f"##### 📊 표 (Table)")
if table_idx < len(table_htmls):
st.markdown(table_htmls[table_idx], unsafe_allow_html=True)
table_idx += 1
else:
st.warning("테이블 내용은 찾을 수 없습니다.")
elif label in ['header', 'doc_title']:
st.markdown(f"##### 📑 제목 / 헤더 ({label})")
st.markdown(f"**{content}**")
elif label in ['image', 'seal']:
st.markdown(f"##### 🖼️ 이미지 / 직인 ({label})")
st.info(f"'{label}' 영역이 감지되었습니다.")
elif content:
st.markdown(f"##### 📝 텍스트 ({label})")
st.write(content)
with st.expander(f"`{json_path.name}` 전체 내용 보기"):
st.json(json_data)
# 임시 파일 삭제 (이제 필요 없음)
# if temp_image_path.exists():
# os.remove(temp_image_path)
except Exception as e:
st.error(f"이미지 처리 중 예상치 못한 오류가 발생했습니다: {e}")
st.error(f"파일: {uploaded_file.name}")
st.info("이미지 파일이 손상되었거나, 모델이 지원하지 않는 형식일 수 있습니다. 다른 이미지로 시도해 보세요.")
else:
st.info("분석할 이미지를 업로드해주세요.")