import gradio as gr
import google.generativeai as genai
import PIL.Image
import difflib
import re
from typing import List, Tuple, Optional
import os
from dotenv import load_dotenv
# 환경변수 로드
load_dotenv()
# Google AI API 설정
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
if GOOGLE_API_KEY:
genai.configure(api_key=GOOGLE_API_KEY)
model = genai.GenerativeModel('gemini-1.5-flash')
class TextDiffChecker:
def __init__(self):
self.model = model if GOOGLE_API_KEY else None
def extract_text_from_image(self, image: PIL.Image.Image) -> str:
"""이미지에서 텍스트 추출"""
if not self.model:
return "API 키가 설정되지 않았습니다."
try:
# Gemini Vision으로 텍스트 추출
prompt = """
이 이미지에서 모든 텍스트를 정확히 추출해주세요.
- 배경은 무시하고 텍스트만 인식
- 글자 크기, 색상, 폰트는 무시
- 줄바꿈과 공백을 유지
- 특수문자와 숫자도 모두 포함
- 추출된 텍스트만 반환 (다른 설명 없이)
"""
response = self.model.generate_content([prompt, image])
return response.text.strip()
except Exception as e:
return f"텍스트 추출 오류: {str(e)}"
def normalize_text(self, text: str) -> str:
"""텍스트 정규화 (공백, 줄바꿈 정리)"""
# 연속된 공백을 하나로
text = re.sub(r'\s+', ' ', text)
# 앞뒤 공백 제거
text = text.strip()
return text
def find_differences(self, text1: str, text2: str) -> Tuple[str, List[str]]:
"""두 텍스트의 차이점 찾기"""
# 텍스트 정규화
norm_text1 = self.normalize_text(text1)
norm_text2 = self.normalize_text(text2)
# 단어 단위로 분할
words1 = norm_text1.split()
words2 = norm_text2.split()
# difflib를 사용한 차이점 분석
differ = difflib.unified_diff(
words1, words2,
fromfile='원본', tofile='비교본',
lineterm=''
)
diff_result = list(differ)
# 차이점 요약
differences = []
added_words = []
removed_words = []
for line in diff_result:
if line.startswith('+') and not line.startswith('+++'):
added_words.extend(line[1:].split())
elif line.startswith('-') and not line.startswith('---'):
removed_words.extend(line[1:].split())
if removed_words:
differences.append(f"❌ 삭제된 단어: {', '.join(removed_words)}")
if added_words:
differences.append(f"✅ 추가된 단어: {', '.join(added_words)}")
# HTML로 차이점 강조 표시
html_diff = self.create_html_diff(words1, words2)
return html_diff, differences
def create_html_diff(self, words1: List[str], words2: List[str]) -> str:
"""HTML로 차이점 시각화"""
matcher = difflib.SequenceMatcher(None, words1, words2)
html_parts = []
html_parts.append("
")
html_parts.append("
🔍 텍스트 비교 결과
")
# 원본 텍스트
html_parts.append("
")
html_parts.append("
📄 원본:")
html_parts.append("
")
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
if tag == 'equal':
html_parts.append(' '.join(words1[i1:i2]))
elif tag == 'delete':
html_parts.append(f"{' '.join(words1[i1:i2])}")
elif tag == 'replace':
html_parts.append(f"{' '.join(words1[i1:i2])}")
if tag != 'equal':
html_parts.append(" ")
html_parts.append("
")
# 비교본 텍스트
html_parts.append("
")
html_parts.append("
📝 비교본:")
html_parts.append("
")
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
if tag == 'equal':
html_parts.append(' '.join(words2[j1:j2]))
elif tag == 'insert':
html_parts.append(f"{' '.join(words2[j1:j2])}")
elif tag == 'replace':
html_parts.append(f"{' '.join(words2[j1:j2])}")
if tag != 'equal':
html_parts.append(" ")
html_parts.append("
")
html_parts.append("
")
return ''.join(html_parts)
# 전역 인스턴스
checker = TextDiffChecker()
def process_comparison(image1, image2, text_input, comparison_mode):
"""메인 비교 처리 함수"""
if not GOOGLE_API_KEY:
return "❌ Google API 키를 설정해주세요.", "", []
try:
# 첫 번째 소스에서 텍스트 추출
if image1 is not None:
text1 = checker.extract_text_from_image(image1)
source1_info = "📷 이미지 1에서 추출된 텍스트"
else:
return "❌ 첫 번째 이미지를 업로드해주세요.", "", []
# 두 번째 소스 처리
if comparison_mode == "이미지 vs 이미지":
if image2 is not None:
text2 = checker.extract_text_from_image(image2)
source2_info = "📷 이미지 2에서 추출된 텍스트"
else:
return "❌ 두 번째 이미지를 업로드해주세요.", "", []
else: # 이미지 vs 텍스트
if text_input.strip():
text2 = text_input.strip()
source2_info = "📝 입력된 텍스트"
else:
return "❌ 비교할 텍스트를 입력해주세요.", "", []
# 추출된 텍스트 표시
extracted_info = f"""
### 📋 추출된 텍스트
**{source1_info}:**
```
{text1}
```
**{source2_info}:**
```
{text2}
```
"""
# 차이점 분석
html_diff, differences = checker.find_differences(text1, text2)
if not differences:
differences = ["✅ 두 텍스트가 동일합니다!"]
return extracted_info, html_diff, differences
except Exception as e:
return f"❌ 처리 중 오류 발생: {str(e)}", "", []
def create_interface():
"""Gradio 인터페이스 생성"""
with gr.Blocks(
title="📝 텍스트 비교 검수 시스템",
theme=gr.themes.Soft(),
css="""
.gradio-container {
max-width: 1200px;
margin: auto;
}
.diff-output {
border: 1px solid #ddd;
border-radius: 8px;
padding: 15px;
background: white;
}
"""
) as app:
gr.Markdown("""
# 📝 텍스트 비교 검수 시스템
이미지의 텍스트를 인식하고 비교하여 차이점을 찾아드립니다.
⚙️ **기능:**
- 🖼️ 이미지에서 텍스트 자동 추출 (Google Gemini Vision API)
- 🔍 두 텍스트 간 차이점 정확한 분석
- 📊 시각적 차이점 표시 (추가/삭제/변경)
- 🎯 배경 무시, 텍스트만 집중 분석
""")
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### 📤 입력")
comparison_mode = gr.Radio(
choices=["이미지 vs 이미지", "이미지 vs 텍스트"],
value="이미지 vs 이미지",
label="비교 모드"
)
image1 = gr.Image(
label="📷 첫 번째 이미지 (원본)",
type="pil"
)
with gr.Group() as image_group:
image2 = gr.Image(
label="📷 두 번째 이미지 (비교본)",
type="pil"
)
with gr.Group(visible=False) as text_group:
text_input = gr.Textbox(
label="📝 비교할 텍스트",
placeholder="비교하고 싶은 텍스트를 직접 입력하세요...",
lines=5
)
def toggle_input_mode(mode):
if mode == "이미지 vs 텍스트":
return gr.update(visible=False), gr.update(visible=True)
else:
return gr.update(visible=True), gr.update(visible=False)
comparison_mode.change(
toggle_input_mode,
inputs=[comparison_mode],
outputs=[image_group, text_group]
)
analyze_btn = gr.Button(
"🔍 텍스트 비교 분석 시작",
variant="primary",
size="lg"
)
with gr.Column(scale=2):
gr.Markdown("### 📊 분석 결과")
with gr.Tabs():
with gr.TabItem("📋 추출된 텍스트"):
extracted_text = gr.Markdown()
with gr.TabItem("🔍 시각적 비교"):
visual_diff = gr.HTML(elem_classes=["diff-output"])
with gr.TabItem("📝 차이점 요약"):
differences_list = gr.JSON(label="발견된 차이점")
# API 키 상태 표시
if not GOOGLE_API_KEY:
gr.Markdown("""
⚠️ **설정 필요:**
1. Google AI Studio에서 API 키를 발급받으세요
2. `.env` 파일에 `GOOGLE_API_KEY=your_api_key` 추가
3. 애플리케이션을 재시작하세요
""")
# 이벤트 연결
analyze_btn.click(
process_comparison,
inputs=[image1, image2, text_input, comparison_mode],
outputs=[extracted_text, visual_diff, differences_list]
)
# 예시 및 도움말
gr.Markdown("""
### 💡 사용 팁
- 📸 **고품질 이미지 사용**: 텍스트가 선명한 이미지일수록 정확도가 높아집니다
- 🔤 **다양한 언어 지원**: 한글, 영어, 숫자, 특수문자 모두 인식 가능
- 🎨 **배경 무시**: 복잡한 배경이 있어도 텍스트만 정확히 추출합니다
- ⚡ **실시간 비교**: 업로드와 동시에 즉시 분석 결과를 확인할 수 있습니다
""")
return app
if __name__ == "__main__":
app = create_interface()
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=True
)