jerrynnms commited on
Commit
1632676
·
verified ·
1 Parent(s): 63da521

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -26
app.py CHANGED
@@ -27,7 +27,7 @@ from typing import Optional, List
27
  from fastapi import FastAPI, HTTPException, File, UploadFile
28
  from fastapi.middleware.cors import CORSMiddleware
29
  from fastapi.staticfiles import StaticFiles
30
- from fastapi.responses import FileResponse
31
  from pydantic import BaseModel
32
 
33
  from firebase_admin import credentials, firestore
@@ -74,7 +74,7 @@ except Exception as e:
74
 
75
 
76
  # ──────────────────────────────────────────────────────────────────────────
77
- # 載入模型
78
  model_path = "/tmp/model.pth"
79
  model_url = "https://huggingface.co/jerrynnms/scam-model/resolve/main/model.pth"
80
  if not os.path.exists(model_path):
@@ -82,6 +82,7 @@ if not os.path.exists(model_path):
82
  if response.status_code == 200:
83
  with open(model_path, "wb") as f:
84
  f.write(response.content)
 
85
  else:
86
  raise FileNotFoundError("❌ 無法從 Hugging Face 下載 model.pth")
87
 
@@ -108,6 +109,9 @@ class TextAnalysisResponse(BaseModel):
108
 
109
  @app.post("/predict", response_model=TextAnalysisResponse)
110
  async def analyze_text_api(request: TextAnalysisRequest):
 
 
 
111
  try:
112
  tz = pytz.timezone("Asia/Taipei")
113
  now = datetime.now(tz)
@@ -127,7 +131,8 @@ async def analyze_text_api(request: TextAnalysisRequest):
127
  }
128
  try:
129
  db.collection(collection).document(doc_id).set(record)
130
- except:
 
131
  pass
132
 
133
  return TextAnalysisResponse(
@@ -143,6 +148,9 @@ async def analyze_text_api(request: TextAnalysisRequest):
143
 
144
  @app.post("/feedback")
145
  async def save_user_feedback(feedback: dict):
 
 
 
146
  try:
147
  tz = pytz.timezone("Asia/Taipei")
148
  timestamp_str = datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
@@ -150,7 +158,7 @@ async def save_user_feedback(feedback: dict):
150
  feedback["used_in_training"] = False
151
  try:
152
  db.collection("user_feedback").add(feedback)
153
- except:
154
  pass
155
  return {"message": "✅ 已記錄使用者回饋"}
156
  except Exception as e:
@@ -160,41 +168,48 @@ async def save_user_feedback(feedback: dict):
160
  # ──────────────────────────────────────────────────────────────────────────
161
  # 強化 OCR 前處理 + 附帶 Debug 圖輸出
162
  def preprocess_image_for_ocr(pil_image: Image.Image) -> Image.Image:
163
- # PIL → NumPy (RGB->BGR)
 
 
 
 
 
 
 
 
 
164
  img = np.array(pil_image.convert("RGB"))[:, :, ::-1]
165
 
166
- # 灰階 + CLAHE
167
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
168
  clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
169
  enhanced = clahe.apply(gray)
170
-
171
- # Save debug file: CLAHE 增強後的灰階
172
  Image.fromarray(enhanced).save("/tmp/debug_clahe.png")
173
 
174
- # HSV 色彩分離 (過濾橘色背景)
175
  hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
176
  lower_orange = np.array([5, 100, 100])
177
  upper_orange = np.array([20, 255, 255])
178
  mask_orange = cv2.inRange(hsv, lower_orange, upper_orange)
179
- # Save debug file: 橘色 mask
180
  Image.fromarray(mask_orange).save("/tmp/debug_mask_orange.png")
181
 
 
182
  filtered = enhanced.copy()
183
  filtered[mask_orange > 0] = 255
184
-
185
- # Save debug file: 過濾橘色後的灰階
186
  Image.fromarray(filtered).save("/tmp/debug_filtered.png")
187
 
188
- # 固定閾值反向二值化
189
  _, thresh = cv2.threshold(filtered, 200, 255, cv2.THRESH_BINARY_INV)
190
- # Save debug file: 二值化後
191
  Image.fromarray(thresh).save("/tmp/debug_thresh.png")
192
 
193
- # 放大3倍 & GaussianBlur
194
  scaled = cv2.resize(thresh, None, fx=3.0, fy=3.0, interpolation=cv2.INTER_CUBIC)
195
  smoothed = cv2.GaussianBlur(scaled, (5, 5), 0)
196
-
197
- # Save final debug processed image
198
  Image.fromarray(smoothed).save("/tmp/debug_processed.png")
199
 
200
  return Image.fromarray(smoothed)
@@ -204,38 +219,56 @@ def preprocess_image_for_ocr(pil_image: Image.Image) -> Image.Image:
204
  @app.post("/analyze-image")
205
  async def analyze_uploaded_image(file: UploadFile = File(...)):
206
  """
207
- 圖片上傳並進行 OCR 辨識,擷取文字後再用 BERT 模型做詐騙分析。
208
- 同時會把每個關鍵步驟的影像存到 /tmp/debug_*.png 供檢查。
 
 
 
 
209
  """
 
 
 
210
  try:
 
211
  image_bytes = await file.read()
212
- image = Image.open(io.BytesIO(image_bytes))
 
 
213
 
214
- # 強化前處理 (並出 debug )
215
- processed_image = preprocess_image_for_ocr(image)
216
 
 
217
  custom_config = r"-l chi_tra+eng --oem 3 --psm 6"
218
  extracted_text = pytesseract.image_to_string(
219
  processed_image,
220
  config=custom_config
221
  ).strip()
 
222
 
 
223
  if not extracted_text:
224
- return {
225
  "extracted_text": "",
226
  "analysis_result": {
227
  "status": "無法辨識",
228
  "confidence": 0.0,
229
  "suspicious_keywords": ["無法擷取分析結果"]
230
  }
231
- }
232
 
 
233
  result = bert_analyze_text(extracted_text)
234
- return {
235
  "extracted_text": extracted_text,
236
  "analysis_result": result
237
- }
 
238
  except Exception as e:
 
 
 
239
  raise HTTPException(status_code=500, detail=f"圖片辨識失敗:{str(e)}")
240
 
241
 
 
27
  from fastapi import FastAPI, HTTPException, File, UploadFile
28
  from fastapi.middleware.cors import CORSMiddleware
29
  from fastapi.staticfiles import StaticFiles
30
+ from fastapi.responses import FileResponse, JSONResponse
31
  from pydantic import BaseModel
32
 
33
  from firebase_admin import credentials, firestore
 
74
 
75
 
76
  # ──────────────────────────────────────────────────────────────────────────
77
+ # 載入 BERT+LSTM+CNN 模型
78
  model_path = "/tmp/model.pth"
79
  model_url = "https://huggingface.co/jerrynnms/scam-model/resolve/main/model.pth"
80
  if not os.path.exists(model_path):
 
82
  if response.status_code == 200:
83
  with open(model_path, "wb") as f:
84
  f.write(response.content)
85
+ print("✅ 模型下載完成")
86
  else:
87
  raise FileNotFoundError("❌ 無法從 Hugging Face 下載 model.pth")
88
 
 
109
 
110
  @app.post("/predict", response_model=TextAnalysisResponse)
111
  async def analyze_text_api(request: TextAnalysisRequest):
112
+ """
113
+ 純文字輸入分析:使用 BERT 模型判斷詐騙與否,並取得可疑關鍵詞
114
+ """
115
  try:
116
  tz = pytz.timezone("Asia/Taipei")
117
  now = datetime.now(tz)
 
131
  }
132
  try:
133
  db.collection(collection).document(doc_id).set(record)
134
+ except Exception:
135
+ # 如果 Firestore 無法寫入,也不影響回傳結果
136
  pass
137
 
138
  return TextAnalysisResponse(
 
148
 
149
  @app.post("/feedback")
150
  async def save_user_feedback(feedback: dict):
151
+ """
152
+ 使用者回饋:將回饋資料寫入 Firestore
153
+ """
154
  try:
155
  tz = pytz.timezone("Asia/Taipei")
156
  timestamp_str = datetime.now(tz).strftime("%Y-%m-%d %H:%M:%S")
 
158
  feedback["used_in_training"] = False
159
  try:
160
  db.collection("user_feedback").add(feedback)
161
+ except Exception:
162
  pass
163
  return {"message": "✅ 已記錄使用者回饋"}
164
  except Exception as e:
 
168
  # ──────────────────────────────────────────────────────────────────────────
169
  # 強化 OCR 前處理 + 附帶 Debug 圖輸出
170
  def preprocess_image_for_ocr(pil_image: Image.Image) -> Image.Image:
171
+ """
172
+ 前處理流程:
173
+ 1. PIL Image → NumPy BGR
174
+ 2. 灰階 + CLAHE(對比度增強)
175
+ 3. 橘色背景遮罩 → 將背景橘色轉為白色
176
+ 4. 固定閾值反向二值化
177
+ 5. 放大 & GaussianBlur 平滑
178
+ 中間各步驟會將影像存到 /tmp/debug_*.png,方便除錯
179
+ """
180
+ # 1. PIL → NumPy (RGB->BGR)
181
  img = np.array(pil_image.convert("RGB"))[:, :, ::-1]
182
 
183
+ # 2. 灰階 + CLAHE
184
  gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
185
  clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
186
  enhanced = clahe.apply(gray)
187
+ # Debug: CLAHE 增強後的灰階
 
188
  Image.fromarray(enhanced).save("/tmp/debug_clahe.png")
189
 
190
+ # 3. HSV 色彩分離 (過濾橘色背景)
191
  hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
192
  lower_orange = np.array([5, 100, 100])
193
  upper_orange = np.array([20, 255, 255])
194
  mask_orange = cv2.inRange(hsv, lower_orange, upper_orange)
195
+ # Debug: 橘色 mask
196
  Image.fromarray(mask_orange).save("/tmp/debug_mask_orange.png")
197
 
198
+ # 將 mask 範圍內的像素設為白色(255),其餘保留灰階
199
  filtered = enhanced.copy()
200
  filtered[mask_orange > 0] = 255
201
+ # Debug: 過濾橘色後的灰階
 
202
  Image.fromarray(filtered).save("/tmp/debug_filtered.png")
203
 
204
+ # 4. 固定閾值反向二值化 (threshold 200)
205
  _, thresh = cv2.threshold(filtered, 200, 255, cv2.THRESH_BINARY_INV)
206
+ # Debug: 二值化後
207
  Image.fromarray(thresh).save("/tmp/debug_thresh.png")
208
 
209
+ # 5. 放大 3 倍 & GaussianBlur 平滑
210
  scaled = cv2.resize(thresh, None, fx=3.0, fy=3.0, interpolation=cv2.INTER_CUBIC)
211
  smoothed = cv2.GaussianBlur(scaled, (5, 5), 0)
212
+ # Debug: 最終前處理結果
 
213
  Image.fromarray(smoothed).save("/tmp/debug_processed.png")
214
 
215
  return Image.fromarray(smoothed)
 
219
  @app.post("/analyze-image")
220
  async def analyze_uploaded_image(file: UploadFile = File(...)):
221
  """
222
+ 圖片上傳並進行 OCR 辨識
223
+ 1. 讀取 UploadFile → PIL Image
224
+ 2. 呼叫 preprocess_image_for_ocr 進行前處理 (並輸出 debug)
225
+ 3. 用 pytesseract 擷取文字
226
+ 4. 若擷取到文字,送給 BERT 做詐騙判斷
227
+ 5. 回傳 JSON 包含 extracted_text 與 analysis_result
228
  """
229
+ # 1) 確認收到檔案
230
+ print("🔍 [DEBUG] 收到 analyze-image,檔名 =", file.filename)
231
+
232
  try:
233
+ # 2) 讀取圖片 bytes,再轉成 PIL Image
234
  image_bytes = await file.read()
235
+ print("🔍 [DEBUG] 圖片 bytes 長度 =", len(image_bytes))
236
+ pil_img = Image.open(io.BytesIO(image_bytes))
237
+ print("🔍 [DEBUG] PIL 成功開啟圖片,格式 =", pil_img.format, "大小 =", pil_img.size)
238
 
239
+ # 3) 強化前處理 (並出 debug 影像)
240
+ processed_image = preprocess_image_for_ocr(pil_img)
241
 
242
+ # 4) Tesseract OCR
243
  custom_config = r"-l chi_tra+eng --oem 3 --psm 6"
244
  extracted_text = pytesseract.image_to_string(
245
  processed_image,
246
  config=custom_config
247
  ).strip()
248
+ print("🔍 [DEBUG] Tesseract 擷取文字 =", repr(extracted_text))
249
 
250
+ # 5) 如果沒有擷取到任何文字
251
  if not extracted_text:
252
+ return JSONResponse({
253
  "extracted_text": "",
254
  "analysis_result": {
255
  "status": "無法辨識",
256
  "confidence": 0.0,
257
  "suspicious_keywords": ["無法擷取分析結果"]
258
  }
259
+ })
260
 
261
+ # 6) 擷取到文字後,呼叫 BERT 模型做詐騙判斷
262
  result = bert_analyze_text(extracted_text)
263
+ return JSONResponse({
264
  "extracted_text": extracted_text,
265
  "analysis_result": result
266
+ })
267
+
268
  except Exception as e:
269
+ # 印出詳細錯誤堆疊
270
+ import traceback
271
+ traceback.print_exc()
272
  raise HTTPException(status_code=500, detail=f"圖片辨識失敗:{str(e)}")
273
 
274