Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, File, UploadFile, HTTPException, Request | |
| from fastapi.responses import JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| import pytesseract | |
| from PIL import Image | |
| import io | |
| import os | |
| import logging | |
| # 配置日志 | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| app = FastAPI( | |
| title="OCR Text Recognition API", | |
| description="Extract text from images using Tesseract OCR", | |
| version="1.0.0" | |
| ) | |
| # CORS 配置 | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # API Key 验证(可选) | |
| API_KEY = os.environ.get("API_KEY", "") | |
| def verify_api_key(request: Request): | |
| """验证 API Key""" | |
| if not API_KEY: | |
| return True # 未设置 API Key,允许所有请求 | |
| auth_header = request.headers.get("Authorization", "") | |
| if auth_header.startswith("Bearer "): | |
| token = auth_header[7:] | |
| if token == API_KEY: | |
| return True | |
| x_api_key = request.headers.get("X-API-Key", "") | |
| if x_api_key == API_KEY: | |
| return True | |
| return False | |
| async def extract_text( | |
| request: Request, | |
| file: UploadFile = File(...), | |
| lang: str = "eng" | |
| ): | |
| """ | |
| 从图片中提取文字 | |
| 参数: | |
| - file: 图片文件(支持 PNG, JPG, JPEG, WEBP, BMP, TIFF) | |
| - lang: 语言代码(默认 eng,可选 chi_sim 中文简体、chi_tra 中文繁体、jpn 日语等) | |
| 返回: | |
| - text: 识别出的文字 | |
| - confidence: 置信度(0-100) | |
| """ | |
| # API Key 验证 | |
| if not verify_api_key(request): | |
| # 免费用户 IP 限制 | |
| client_ip = request.headers.get("X-Forwarded-For", request.client.host) | |
| logger.warning(f"Unauthorized access from {client_ip}") | |
| raise HTTPException(status_code=401, detail="Invalid API Key. Please subscribe to use this API.") | |
| # 检查文件类型 | |
| allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp", "image/tiff"] | |
| if file.content_type not in allowed_types: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"Unsupported file type: {file.content_type}. Supported: PNG, JPG, JPEG, WEBP, BMP, TIFF" | |
| ) | |
| try: | |
| # 读取图片 | |
| content = await file.read() | |
| image = Image.open(io.BytesIO(content)) | |
| # 转换为 RGB(如果需要) | |
| if image.mode in ("RGBA", "P"): | |
| image = image.convert("RGB") | |
| # OCR 识别 | |
| data = pytesseract.image_to_data(image, lang=lang, output_type=pytesseract.Output.DICT) | |
| # 提取文字和置信度 | |
| text_parts = [] | |
| confidences = [] | |
| for i, text in enumerate(data["text"]): | |
| if text.strip(): # 只保留非空文字 | |
| text_parts.append(text) | |
| confidences.append(data["conf"][i]) | |
| # 合并文字 | |
| full_text = " ".join(text_parts) | |
| # 计算平均置信度 | |
| avg_confidence = sum(confidences) / len(confidences) if confidences else 0 | |
| return JSONResponse({ | |
| "success": True, | |
| "text": full_text, | |
| "confidence": round(avg_confidence, 2), | |
| "language": lang, | |
| "word_count": len(text_parts) | |
| }) | |
| except pytesseract.TesseractError as e: | |
| logger.error(f"Tesseract error: {str(e)}") | |
| raise HTTPException(status_code=400, detail=f"OCR Error: {str(e)}") | |
| except Exception as e: | |
| logger.error(f"Error processing image: {str(e)}") | |
| raise HTTPException(status_code=500, detail=f"Error processing image: {str(e)}") | |
| async def extract_text_detailed( | |
| request: Request, | |
| file: UploadFile = File(...), | |
| lang: str = "eng" | |
| ): | |
| """ | |
| 从图片中提取文字(详细版,包含每个文字的位置信息) | |
| 参数: | |
| - file: 图片文件 | |
| - lang: 语言代码 | |
| 返回: | |
| - text: 完整文字 | |
| - words: 每个词的详细信息(文字、位置、置信度) | |
| - confidence: 平均置信度 | |
| """ | |
| # API Key 验证 | |
| if not verify_api_key(request): | |
| raise HTTPException(status_code=401, detail="Invalid API Key. Please subscribe to use this API.") | |
| # 检查文件类型 | |
| allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp", "image/bmp", "image/tiff"] | |
| if file.content_type not in allowed_types: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"Unsupported file type: {file.content_type}" | |
| ) | |
| try: | |
| # 读取图片 | |
| content = await file.read() | |
| image = Image.open(io.BytesIO(content)) | |
| if image.mode in ("RGBA", "P"): | |
| image = image.convert("RGB") | |
| # OCR 识别 | |
| data = pytesseract.image_to_data(image, lang=lang, output_type=pytesseract.Output.DICT) | |
| # 提取详细文字信息 | |
| words = [] | |
| confidences = [] | |
| for i, text in enumerate(data["text"]): | |
| if text.strip(): | |
| words.append({ | |
| "text": text, | |
| "confidence": data["conf"][i], | |
| "position": { | |
| "x": data["left"][i], | |
| "y": data["top"][i], | |
| "width": data["width"][i], | |
| "height": data["height"][i] | |
| } | |
| }) | |
| confidences.append(data["conf"][i]) | |
| full_text = " ".join([w["text"] for w in words]) | |
| avg_confidence = sum(confidences) / len(confidences) if confidences else 0 | |
| return JSONResponse({ | |
| "success": True, | |
| "text": full_text, | |
| "words": words, | |
| "confidence": round(avg_confidence, 2), | |
| "language": lang, | |
| "word_count": len(words) | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error: {str(e)}") | |
| raise HTTPException(status_code=500, detail=f"Error: {str(e)}") | |
| async def root(): | |
| """API 信息""" | |
| return { | |
| "name": "OCR Text Recognition API", | |
| "version": "1.0.0", | |
| "description": "Extract text from images using Tesseract OCR", | |
| "endpoints": { | |
| "/ocr": "Extract text from image (POST)", | |
| "/ocr/detailed": "Extract text with position details (POST)" | |
| }, | |
| "supported_languages": { | |
| "eng": "English", | |
| "chi_sim": "Chinese Simplified", | |
| "chi_tra": "Chinese Traditional", | |
| "jpn": "Japanese", | |
| "kor": "Korean", | |
| "fra": "French", | |
| "deu": "German", | |
| "spa": "Spanish", | |
| "rus": "Russian", | |
| "ara": "Arabic" | |
| } | |
| } | |
| async def health(): | |
| """健康检查""" | |
| return {"status": "ok"} | |