aliceblue11 commited on
Commit
e0c9260
·
verified ·
1 Parent(s): ac8c98b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +194 -76
app.py CHANGED
@@ -6,18 +6,22 @@ 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 "✅ 인증 정보가 설정되었습니다."
@@ -25,26 +29,31 @@ class KoreanOCRApp:
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": """이 이미지에 포함된 모든 한국어 텍스트를 정확하게 추출해주세요.
@@ -59,19 +68,19 @@ class KoreanOCRApp:
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"
@@ -93,6 +102,14 @@ class KoreanOCRApp:
93
 
94
  try:
95
  response = requests.post(url, headers=headers, json=payload, timeout=60)
 
 
 
 
 
 
 
 
96
  response.raise_for_status()
97
 
98
  result = response.json()
@@ -100,6 +117,8 @@ class KoreanOCRApp:
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
 
@@ -112,34 +131,111 @@ class KoreanOCRApp:
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:
@@ -171,57 +267,73 @@ def create_interface():
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
 
@@ -255,34 +367,40 @@ def create_interface():
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
@@ -294,10 +412,10 @@ if __name__ == "__main__":
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
  )
 
6
  import io
7
  import os
8
  from typing import Optional, Tuple
9
+ import re
10
 
11
  class KoreanOCRApp:
12
  def __init__(self):
13
  self.api_key = None
14
  self.project_id = None
 
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
+ # 프로젝트 ID 검증 (영문, 숫자, 하이픈만 허용)
22
+ if not re.match(r'^[a-z0-9\-]+$', project_id.strip()):
23
+ return "❌ 유효하지 않은 프로젝트 ID 형식입니다. 영문 소문자, 숫자, 하이픈만 사용 가능합니다."
24
+
25
  self.api_key = api_key.strip()
26
  self.project_id = project_id.strip()
27
  return "✅ 인증 정보가 설정되었습니다."
 
29
  def encode_image_to_base64(self, image: Image.Image) -> str:
30
  """이미지를 base64로 인코딩"""
31
  buffer = io.BytesIO()
32
+ # JPEG 형식으로 저장하여 파일 크기 최적화
33
+ if image.mode == 'RGBA':
34
+ # RGBA 이미지는 RGB로 변환
35
+ background = Image.new('RGB', image.size, (255, 255, 255))
36
+ background.paste(image, mask=image.split()[-1])
37
+ image = background
38
+
39
+ image.save(buffer, format='JPEG', quality=95)
40
  image_bytes = buffer.getvalue()
41
  return base64.b64encode(image_bytes).decode('utf-8')
42
 
43
+ def call_gemini_api_direct(self, image_base64: str) -> str:
44
+ """Gemini API 직접 호출 (Google AI Studio API 사용)"""
45
+ if not self.api_key:
46
+ return "❌ 먼저 API 키를 설정해주세요."
47
 
48
+ # Google AI Studio API 엔드포인트 사용
49
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent?key={self.api_key}"
50
 
51
  headers = {
 
52
  "Content-Type": "application/json"
53
  }
54
 
55
  payload = {
56
  "contents": [{
 
57
  "parts": [
58
  {
59
  "text": """이 이미지에 포함된 모든 한국어 텍스트를 정확하게 추출해주세요.
 
68
  },
69
  {
70
  "inline_data": {
71
+ "mime_type": "image/jpeg",
72
  "data": image_base64
73
  }
74
  }
75
  ]
76
  }],
77
+ "generationConfig": {
78
  "temperature": 0.1,
79
+ "topP": 0.8,
80
+ "topK": 40,
81
+ "maxOutputTokens": 8192
82
  },
83
+ "safetySettings": [
84
  {
85
  "category": "HARM_CATEGORY_HARASSMENT",
86
  "threshold": "BLOCK_MEDIUM_AND_ABOVE"
 
102
 
103
  try:
104
  response = requests.post(url, headers=headers, json=payload, timeout=60)
105
+
106
+ if response.status_code == 401:
107
+ return "❌ API 키가 유효하지 않습니다. Google AI Studio에서 발급받은 올바른 API 키를 입력해주세요."
108
+ elif response.status_code == 403:
109
+ return "❌ API 접근 권한이 없습니다. Gemini API가 활성화되어 있는지 확인해주세요."
110
+ elif response.status_code == 429:
111
+ return "❌ API 호출 한도를 초과했습니다. 잠시 후 다시 시도해주세요."
112
+
113
  response.raise_for_status()
114
 
115
  result = response.json()
 
117
  if "candidates" in result and len(result["candidates"]) > 0:
118
  content = result["candidates"][0]["content"]["parts"][0]["text"]
119
  return content.strip()
120
+ elif "error" in result:
121
+ return f"❌ API 오��: {result['error'].get('message', '알 수 없는 오류')}"
122
  else:
123
  return "❌ 텍스트를 추출할 수 없습니다. 이미지에 한국어 텍스트가 포함되어 있는지 확인해주세요."
124
 
 
131
  except Exception as e:
132
  return f"❌ 알 수 없는 오류: {str(e)}"
133
 
134
+ def call_vertex_ai_api(self, image_base64: str) -> str:
135
+ """Vertex AI API 호출 (서비스 계정 키 사용)"""
136
+ if not self.api_key or not self.project_id:
137
+ return "❌ 먼저 API 키와 프로젝트 ID를 설정해주세요."
138
+
139
+ location = "us-central1"
140
+ url = f"https://{location}-aiplatform.googleapis.com/v1/projects/{self.project_id}/locations/{location}/publishers/google/models/gemini-1.5-pro:generateContent"
141
+
142
+ headers = {
143
+ "Authorization": f"Bearer {self.api_key}",
144
+ "Content-Type": "application/json"
145
+ }
146
+
147
+ payload = {
148
+ "contents": [{
149
+ "role": "user",
150
+ "parts": [
151
+ {
152
+ "text": """이 이미지에 포함된 모든 한국어 텍스트를 정확하게 추출해주세요.
153
+ 다음 규칙을 따라주세요:
154
+ 1. 이미지에서 발견되는 모든 한국어 텍스트를 순서대로 추출
155
+ 2. 텍스트의 위치나 레이아웃을 최대한 보존
156
+ 3. 줄바꿈과 문단 구분을 명확히 표시
157
+ 4. 특수문자, 숫자, 영어가 포함되어 있다면 그대로 유지
158
+ 5. 읽기 어려운 부분이 있다면 [불분명] 표시
159
+
160
+ 추출된 텍스트만 반환해주세요."""
161
+ },
162
+ {
163
+ "inline_data": {
164
+ "mime_type": "image/jpeg",
165
+ "data": image_base64
166
+ }
167
+ }
168
+ ]
169
+ }],
170
+ "generation_config": {
171
+ "temperature": 0.1,
172
+ "top_p": 0.8,
173
+ "top_k": 40,
174
+ "max_output_tokens": 8192
175
+ }
176
+ }
177
+
178
+ try:
179
+ response = requests.post(url, headers=headers, json=payload, timeout=60)
180
+
181
+ if response.status_code == 401:
182
+ return "❌ 인증 오류: Access Token이 유효하지 않거나 만료되었습니다."
183
+ elif response.status_code == 403:
184
+ return "❌ 권한 오류: Vertex AI API 접근 권한이 없습니다."
185
+ elif response.status_code == 404:
186
+ return "❌ 프로젝트 ID가 올바르지 않거나 Vertex AI API가 활성화되지 않았습니다."
187
+
188
+ response.raise_for_status()
189
+
190
+ result = response.json()
191
+
192
+ if "candidates" in result and len(result["candidates"]) > 0:
193
+ content = result["candidates"][0]["content"]["parts"][0]["text"]
194
+ return content.strip()
195
+ else:
196
+ return "❌ 텍스트를 추출할 수 없습니다. 이미지에 한국어 텍스트가 포함되어 있는지 확인해주세요."
197
+
198
+ except requests.exceptions.RequestException as e:
199
+ return f"❌ API 호출 오류: {str(e)}"
200
+ except Exception as e:
201
+ return f"❌ 알 수 없는 오류: {str(e)}"
202
+
203
+ def process_image(self, image: Optional[Image.Image], api_key: str, project_id: str, api_type: str) -> Tuple[Optional[Image.Image], str]:
204
  """이미지 처리 및 OCR 수행"""
205
  if image is None:
206
  return None, "❌ 이미지를 업로드해주세요."
207
 
208
  # 인증 정보 설정
209
+ if api_type == "Google AI Studio":
210
+ if not api_key:
211
+ return image, "❌ Google AI Studio API 키를 입력해주세요."
212
+ self.api_key = api_key.strip()
213
+ else: # Vertex AI
214
+ auth_result = self.set_credentials(api_key, project_id)
215
+ if "❌" in auth_result:
216
+ return image, auth_result
217
 
218
  try:
219
+ # 이미지 크기 확인 및 조정
220
  img_byte_array = io.BytesIO()
221
+ image.save(img_byte_array, format='JPEG', quality=95)
222
  img_size_mb = len(img_byte_array.getvalue()) / (1024 * 1024)
223
 
224
+ if img_size_mb > 4: # 4MB로 제한을 낮춤
225
  # 이미지 크기가 너무 크면 리사이즈
226
+ max_dimension = 1920
227
  image.thumbnail((max_dimension, max_dimension), Image.Resampling.LANCZOS)
228
 
229
  # 이미지를 base64로 인코딩
230
  image_base64 = self.encode_image_to_base64(image)
231
 
232
+ # API 타입에 따라 호출
233
+ if api_type == "Google AI Studio":
234
+ extracted_text = self.call_gemini_api_direct(image_base64)
235
+ else:
236
+ extracted_text = self.call_vertex_ai_api(image_base64)
237
 
238
+ # 결과 반환
239
  return image, extracted_text
240
 
241
  except Exception as e:
 
267
  margin: 10px 0;
268
  }
269
 
270
+ .warning-box {
271
+ background-color: #fff3cd;
272
+ border: 1px solid #ffeaa7;
273
+ border-radius: 8px;
274
+ padding: 15px;
275
+ margin: 10px 0;
276
+ color: #856404;
 
277
  }
278
  """
279
 
280
+ with gr.Blocks(css=css, title="한국어 OCR - Gemini AI") as interface:
281
 
282
  gr.Markdown("""
283
  # 🔍 한국어 OCR 텍스트 추출기
284
+ ### Google Gemini AI를 활용한 고정밀 한국어 문자 인식
285
 
286
  이미지에서 한국어 텍스트를 정확하게 추출합니다. 문서, 간판, 손글씨 등 다양한 형태의 한국어를 인식할 수 있습니다.
287
  """, elem_classes="main-header")
288
 
289
+ # API 선택
290
+ gr.Markdown("## 🔧 API 설정")
291
+
292
+ api_type = gr.Radio(
293
+ choices=["Google AI Studio", "Vertex AI"],
294
+ value="Google AI Studio",
295
+ label="사용할 API 선택",
296
+ info="Google AI Studio는 개인 사용자용, Vertex AI는 기업용"
297
+ )
 
 
 
 
 
 
298
 
299
+ # 인증 정보 입력 섹션
300
  with gr.Row():
301
  with gr.Column(scale=2):
302
  api_key_input = gr.Textbox(
303
+ label="API / Access Token",
304
+ placeholder="Google AI Studio API 키 또는 Vertex AI Access Token",
305
  type="password",
306
  lines=1
307
  )
308
  with gr.Column(scale=1):
309
  project_id_input = gr.Textbox(
310
+ label="프로젝트 ID (Vertex AI만)",
311
  placeholder="Google Cloud 프로젝트 ID",
312
  lines=1
313
  )
314
 
315
+ # API 설정 가이드
316
+ with gr.Accordion("📖 API 설정 가이드", open=False):
317
+ gr.Markdown("""
318
+ ### Google AI Studio API (권장)
319
+ 1. [Google AI Studio](https://aistudio.google.com/)에 접속
320
+ 2. "Get API Key" 클릭
321
+ 3. API 키 생성 및 복사
322
+ 4. 위의 "API 키" 필드에 붙여넣기
323
+
324
+ ### Vertex AI API (고급 사용자용)
325
+ 1. [Google Cloud Console](https://console.cloud.google.com/)에서 프로젝트 생성
326
+ 2. Vertex AI API 활성화
327
+ 3. 서비스 계정 생성 및 키 다운로드
328
+ 4. `gcloud auth application-default login` 또는 Access Token 발급
329
+ 5. API 키와 프로젝트 ID 입력
330
+
331
+ ### ⚠️ 주의사항
332
+ - Google AI Studio는 개인 사용자에게 무료 할당량 제공
333
+ - Vertex AI는 유료 서비스로 사용량에 따라 과금
334
+ - API 키는 안전하게 보관하고 공유하지 마세요
335
+ """, elem_classes="warning-box")
336
+
337
  # 이미지 업로드 및 처리 섹션
338
  gr.Markdown("## 📤 이미지 업로드 및 텍스트 ���출")
339
 
 
367
  placeholder="추출된 텍스트가 여기에 표시됩니다...",
368
  lines=10,
369
  max_lines=20,
370
+ interactive=True,
371
  show_copy_button=True
372
  )
373
 
374
  # 이벤트 핸들러
375
  process_btn.click(
376
  fn=ocr_app.process_image,
377
+ inputs=[input_image, api_key_input, project_id_input, api_type],
378
  outputs=[output_image, extracted_text],
379
  show_progress=True
380
  )
381
 
382
+ # 사용
383
  gr.Markdown("""
384
+ ### 💡 사용
385
+
386
+ **📸 이미지 품질:**
387
+ - 선명하고 해상도가 높은 이미지 사용
388
+ - 충분한 조명과 대비
389
+ - 텍스트가 수평으로 배치된 이미지 권장
390
 
391
+ **📄 지원 형식:**
392
+ - **이미지 형식:** PNG, JPEG, WebP
393
+ - **최대 크기:** 4MB (자동 리사이즈)
394
+ - **인식 언어:** 한국어, 영어, 숫자, 특수문자
395
 
396
+ **🔒 보안:**
397
+ - API 키는 세션 동안만 임시 저장
398
+ - 이미지는 서버에 저장되지 않음
399
+ - 개인정보가 포함된 이미지 사용 주의
400
 
401
+ **⚡ 성능:**
402
+ - Google AI Studio: 빠르고 안정적 (권장)
403
+ - Vertex AI: 기업용 고급 기능
404
  """)
405
 
406
  return interface
 
412
 
413
  # 서버 실행
414
  demo.launch(
415
+ server_name="0.0.0.0",
416
+ server_port=7860,
417
+ share=True,
418
+ debug=True,
419
+ show_error=True,
420
+ inbrowser=True
421
  )