aliceblue11 commited on
Commit
ae924be
·
verified ·
1 Parent(s): 49aa211

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +303 -0
app.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import base64
3
+ import requests
4
+ import json
5
+ from PIL import Image
6
+ import io
7
+ import os
8
+ from typing import Optional, Tuple
9
+
10
+ class KoreanOCRApp:
11
+ def __init__(self):
12
+ self.api_key = None
13
+ self.project_id = None
14
+ self.location = "us-central1" # Gemini 2.5 Pro가 지원되는 리전
15
+
16
+ def set_credentials(self, api_key: str, project_id: str) -> str:
17
+ """API 키와 프로젝트 ID 설정"""
18
+ if not api_key or not project_id:
19
+ return "❌ API 키와 프로젝트 ID를 모두 입력해주세요."
20
+
21
+ self.api_key = api_key.strip()
22
+ self.project_id = project_id.strip()
23
+ return "✅ 인증 정보가 설정되었습니다."
24
+
25
+ def encode_image_to_base64(self, image: Image.Image) -> str:
26
+ """이미지를 base64로 인코딩"""
27
+ buffer = io.BytesIO()
28
+ # PNG 형식으로 저장하여 품질 보장
29
+ image.save(buffer, format='PNG')
30
+ image_bytes = buffer.getvalue()
31
+ return base64.b64encode(image_bytes).decode('utf-8')
32
+
33
+ def call_gemini_api(self, image_base64: str) -> str:
34
+ """Gemini 2.5 Pro API 호출하여 한국어 텍스트 추출"""
35
+ if not self.api_key or not self.project_id:
36
+ return "❌ 먼저 API 키와 프로젝트 ID를 설정해주세요."
37
+
38
+ url = f"https://{self.location}-aiplatform.googleapis.com/v1/projects/{self.project_id}/locations/{self.location}/publishers/google/models/gemini-2.5-pro:generateContent"
39
+
40
+ headers = {
41
+ "Authorization": f"Bearer {self.api_key}",
42
+ "Content-Type": "application/json"
43
+ }
44
+
45
+ payload = {
46
+ "contents": [{
47
+ "role": "user",
48
+ "parts": [
49
+ {
50
+ "text": """이 이미지에 포함된 모든 한국어 텍스트를 정확하게 추출해주세요.
51
+ 다음 규칙을 따라주세요:
52
+ 1. 이미지에서 발견되는 모든 한국어 텍스트를 순서대로 추출
53
+ 2. 텍스트의 위치나 레이아웃을 최대한 보존
54
+ 3. 줄바꿈과 문단 구분을 명확히 표시
55
+ 4. 특수문자, 숫자, 영어가 포함되어 있다면 그대로 유지
56
+ 5. 읽기 어려운 부분이 있다면 [불분명] 표시
57
+
58
+ 추출된 텍스트만 반환해주세요."""
59
+ },
60
+ {
61
+ "inline_data": {
62
+ "mime_type": "image/png",
63
+ "data": image_base64
64
+ }
65
+ }
66
+ ]
67
+ }],
68
+ "generation_config": {
69
+ "temperature": 0.1,
70
+ "top_p": 0.8,
71
+ "top_k": 40,
72
+ "max_output_tokens": 8192
73
+ },
74
+ "safety_settings": [
75
+ {
76
+ "category": "HARM_CATEGORY_HARASSMENT",
77
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
78
+ },
79
+ {
80
+ "category": "HARM_CATEGORY_HATE_SPEECH",
81
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
82
+ },
83
+ {
84
+ "category": "HARM_CATEGORY_SEXUALLY_EXPLICIT",
85
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
86
+ },
87
+ {
88
+ "category": "HARM_CATEGORY_DANGEROUS_CONTENT",
89
+ "threshold": "BLOCK_MEDIUM_AND_ABOVE"
90
+ }
91
+ ]
92
+ }
93
+
94
+ try:
95
+ response = requests.post(url, headers=headers, json=payload, timeout=60)
96
+ response.raise_for_status()
97
+
98
+ result = response.json()
99
+
100
+ if "candidates" in result and len(result["candidates"]) > 0:
101
+ content = result["candidates"][0]["content"]["parts"][0]["text"]
102
+ return content.strip()
103
+ else:
104
+ return "❌ 텍스트를 추출할 수 없습니다. 이미지에 한국어 텍스트가 포함되어 있는지 확인해주세요."
105
+
106
+ except requests.exceptions.RequestException as e:
107
+ return f"❌ API 호출 오류: {str(e)}"
108
+ except json.JSONDecodeError:
109
+ return "❌ API 응답 파싱 오류가 발생했습니다."
110
+ except KeyError as e:
111
+ return f"❌ 예상치 못한 API 응답 형식: {str(e)}"
112
+ except Exception as e:
113
+ return f"❌ 알 수 없는 오류: {str(e)}"
114
+
115
+ def process_image(self, image: Optional[Image.Image], api_key: str, project_id: str) -> Tuple[Optional[Image.Image], str]:
116
+ """이미지 처리 및 OCR 수행"""
117
+ if image is None:
118
+ return None, "❌ 이미지를 업로드해주세요."
119
+
120
+ # 인증 정보 ���정
121
+ auth_result = self.set_credentials(api_key, project_id)
122
+ if "❌" in auth_result:
123
+ return image, auth_result
124
+
125
+ try:
126
+ # 이미지 크기 확인 및 조정 (최대 7MB 제한)
127
+ img_byte_array = io.BytesIO()
128
+ image.save(img_byte_array, format='PNG')
129
+ img_size_mb = len(img_byte_array.getvalue()) / (1024 * 1024)
130
+
131
+ if img_size_mb > 7:
132
+ # 이미지 크기가 너무 크면 리사이즈
133
+ max_dimension = 2048
134
+ image.thumbnail((max_dimension, max_dimension), Image.Resampling.LANCZOS)
135
+
136
+ # 이미지를 base64로 인코딩
137
+ image_base64 = self.encode_image_to_base64(image)
138
+
139
+ # OCR 수행
140
+ extracted_text = self.call_gemini_api(image_base64)
141
+
142
+ # 결과 반환 (업로드된 이미지와 동일한 이미지를 표시하여 검증)
143
+ return image, extracted_text
144
+
145
+ except Exception as e:
146
+ return image, f"❌ 이미지 처리 중 오류가 발생했습니다: {str(e)}"
147
+
148
+ # 전역 앱 인스턴스
149
+ ocr_app = KoreanOCRApp()
150
+
151
+ def create_interface():
152
+ """Gradio 인터페이스 생성"""
153
+
154
+ # CSS 스타일링
155
+ css = """
156
+ .gradio-container {
157
+ font-family: 'Noto Sans KR', sans-serif;
158
+ }
159
+
160
+ .main-header {
161
+ text-align: center;
162
+ color: #2c3e50;
163
+ margin-bottom: 20px;
164
+ }
165
+
166
+ .info-box {
167
+ background-color: #e8f4fd;
168
+ border: 1px solid #bee5eb;
169
+ border-radius: 8px;
170
+ padding: 15px;
171
+ margin: 10px 0;
172
+ }
173
+
174
+ .error-text {
175
+ color: #dc3545;
176
+ font-weight: bold;
177
+ }
178
+
179
+ .success-text {
180
+ color: #28a745;
181
+ font-weight: bold;
182
+ }
183
+ """
184
+
185
+ with gr.Blocks(css=css, title="한국어 OCR - Gemini 2.5 Pro") as interface:
186
+
187
+ gr.Markdown("""
188
+ # 🔍 한국어 OCR 텍스트 추출기
189
+ ### Google Gemini 2.5 Pro를 활용한 고정밀 한국어 문자 인식
190
+
191
+ 이미지에서 한국어 텍스트를 정확하게 추출합니다. 문서, 간판, 손글씨 등 다양한 형태의 한국어를 인식할 수 있습니다.
192
+ """, elem_classes="main-header")
193
+
194
+ with gr.Row():
195
+ with gr.Column(scale=1):
196
+ gr.Markdown("""
197
+ ### 📋 사용 방법
198
+ 1. **Google Cloud 인증 정보 입력**
199
+ - API 키 (Access Token)
200
+ - 프로젝트 ID
201
+ 2. **이미지 업로드**
202
+ - PNG, JPEG, WebP 지원
203
+ - 최대 7MB 크기
204
+ 3. **텍스트 추출 실행**
205
+ """, elem_classes="info-box")
206
+
207
+ # 인증 정보 입력 섹션
208
+ gr.Markdown("## 🔐 Google Cloud 인증 설정")
209
+
210
+ with gr.Row():
211
+ with gr.Column(scale=2):
212
+ api_key_input = gr.Textbox(
213
+ label="Google Cloud Access Token",
214
+ placeholder="Google Cloud Console에서 발급받은 Access Token을 입력하세요",
215
+ type="password",
216
+ lines=1
217
+ )
218
+ with gr.Column(scale=1):
219
+ project_id_input = gr.Textbox(
220
+ label="프로젝트 ID",
221
+ placeholder="Google Cloud 프로젝트 ID",
222
+ lines=1
223
+ )
224
+
225
+ # 이미지 업로드 및 처리 섹션
226
+ gr.Markdown("## 📤 이미지 업로드 및 텍스트 추출")
227
+
228
+ with gr.Row():
229
+ with gr.Column(scale=1):
230
+ input_image = gr.Image(
231
+ label="📁 이미지 업로드",
232
+ type="pil",
233
+ sources=["upload", "clipboard"],
234
+ interactive=True
235
+ )
236
+
237
+ process_btn = gr.Button(
238
+ "🔍 텍스트 추출 시작",
239
+ variant="primary",
240
+ size="lg"
241
+ )
242
+
243
+ with gr.Column(scale=1):
244
+ output_image = gr.Image(
245
+ label="📋 업로드된 이미지 확인",
246
+ type="pil",
247
+ interactive=False
248
+ )
249
+
250
+ # 추출된 텍스트 출력
251
+ gr.Markdown("## 📝 추출된 텍스트")
252
+
253
+ extracted_text = gr.Textbox(
254
+ label="인식된 한국어 텍스트",
255
+ placeholder="추출된 텍스트가 여기에 표시됩니다...",
256
+ lines=10,
257
+ max_lines=20,
258
+ interactive=True, # 결과 편집 가능
259
+ show_copy_button=True
260
+ )
261
+
262
+ # 이벤트 핸들러
263
+ process_btn.click(
264
+ fn=ocr_app.process_image,
265
+ inputs=[input_image, api_key_input, project_id_input],
266
+ outputs=[output_image, extracted_text],
267
+ show_progress=True
268
+ )
269
+
270
+ # 추가 정보
271
+ gr.Markdown("""
272
+ ### ℹ️ 추가 정보
273
+
274
+ **지원하는 이미지 형식:** PNG, JPEG, WebP
275
+ **최대 파일 크기:** 7MB
276
+ **인식 가능한 텍스트:** 한국어, 영어, 숫자, 특수문자
277
+
278
+ **💡 팁:**
279
+ - 선명하고 해상도가 높은 이미지일수록 인식률이 향상됩니다
280
+ - 텍스트가 기울어져 있거나 왜곡된 경우 인식률이 떨어질 수 있습니다
281
+ - 추출된 텍스트는 편집이 가능하며 복사 버튼을 통해 클립보드에 복사할 수 있습니다
282
+
283
+ **🔒 개인정보 보호:**
284
+ - 업로드된 이미지는 서버에 저장되지 않습니다
285
+ - API 키는 세션 동안만 메모리에 임시 저장됩니다
286
+ """)
287
+
288
+ return interface
289
+
290
+ # 메인 실행
291
+ if __name__ == "__main__":
292
+ # 인터페이스 생성 및 실행
293
+ demo = create_interface()
294
+
295
+ # 서버 실행
296
+ demo.launch(
297
+ server_name="0.0.0.0", # 모든 IP에서 접근 가능
298
+ server_port=7860, # 포트 번호
299
+ share=True, # 공개 링크 생성
300
+ debug=True, # 디버그 모드
301
+ show_error=True, # 오류 표시
302
+ inbrowser=True # 자동으로 브라우저 열기
303
+ )