├── Dockerfile ├── README.md ├── app ├── __init__.py ├── main.py ├── models.py └── services.py ├── docker-compose.yml ├── requirements.txt └── tests └── test_main.py /Dockerfile: -------------------------------------------------------------------------------- 1 | # 使用官方 Python 运行时作为父镜像 2 | FROM python:3.9-slim 3 | 4 | # 设置工作目录 5 | WORKDIR /app 6 | 7 | # 将当前目录内容复制到容器的 /app 中 8 | COPY . /app 9 | 10 | # 安装项目依赖 11 | RUN pip install --no-cache-dir -r requirements.txt 12 | 13 | # 暴露端口 8000 14 | EXPOSE 8000 15 | 16 | # 运行应用 17 | CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 DdddOcr API 2 | 3 | ![DdddOcr Logo](https://cdn.wenanzhe.com/img/logo.png!/crop/700x500a400a500) 4 | 5 | > 基于 FastAPI 和 DdddOcr 的高性能 OCR API 服务,提供图像文字识别、滑动验证码匹配和目标检测功能。 6 | > 7 | > [自营各类GPT聚合平台](https://juxiangyun.com) 8 | 9 | ## 📋 目录 10 | 11 | - [系统要求](#-系统要求) 12 | - [安装和启动](#-安装和启动) 13 | - [API 端点](#-api-端点) 14 | - [API 调用示例](#-api-调用示例) 15 | - [注意事项](#-注意事项) 16 | - [故障排除](#-故障排除) 17 | - [许可证](#-许可证) 18 | 19 | ## 💻 系统要求 20 | 21 | | 组件 | 版本 | 22 | |------|------| 23 | | 操作系统 | Linux(推荐 Ubuntu 20.04 LTS 或更高版本)| 24 | | Docker | 20.10 或更高 | 25 | | Docker Compose | 1.29 或更高 | 26 | 27 | ## 🚀 安装和启动 28 | 29 | 1. **克隆仓库** 30 | ```bash 31 | git clone https://github.com/your-repo/ddddocr-api.git 32 | cd ddddocr-api 33 | ``` 34 | 35 | 2. **启动服务** 36 | 37 | 有三种方式可以启动应用: 38 | 39 | a. 使用 docker启动: 40 | 1. 构建 Docker 镜像 [一键docker环境服务器购买,可一元试用](https://www.rainyun.com/ddddocr_) 41 | 2. 打包镜像 42 | ```bash 43 | docker build -t ddddocr-api . 44 | ``` 45 | 3. 启动镜像 46 | ```bash 47 | docker run -d -p 8000:8000 --name ddddocr-api-container ddddocr-api 48 | ``` 49 | 50 | b. 使用 python 命令直接运行: 51 | ```bash 52 | python app/main.py 53 | ``` 54 | 55 | b. 使用 uvicorn(支持热重载,适合开发): 56 | ```bash 57 | uvicorn app.main:app --reload 58 | ``` 59 | 60 | 61 | 3. **验证服务** 62 | ```bash 63 | curl http://localhost:8000/docs 64 | ``` 65 | > 如果成功,您将看到 Swagger UI 文档页面。 66 | 67 | 4. **停止服务** 68 | 69 | - 如果使用 Docker: 70 | ```bash 71 | docker stop ddddocr-api-container 72 | ``` 73 | 74 | - 如果使用 Docker Compose: 75 | ```bash 76 | docker-compose down 77 | ``` 78 | 79 | 5. **查看日志** 80 | 81 | - 如果使用 Docker: 82 | ```bash 83 | docker logs ddddocr-api-container 84 | ``` 85 | 86 | - 如果使用 Docker Compose: 87 | ```bash 88 | docker-compose logs 89 | ``` 90 | 91 | ## 🔌 API 端点 92 | 93 | ### 1. OCR 识别 94 | 95 | 🔗 **端点**:`POST /ocr` 96 | 97 | | 参数 | 类型 | 描述 | 98 | |------|------|------| 99 | | `file` | File | 图片文件(可选) | 100 | | `image` | String | Base64 编码的图片字符串(可选) | 101 | | `probability` | Boolean | 是否返回概率(默认:false) | 102 | | `charsets` | String | 字符集(可选) | 103 | | `png_fix` | Boolean | 是否进行 PNG 修复(默认:false) | 104 | 105 | ### 2. 滑动验证码匹配 106 | 107 | 🔗 **端点**:`POST /slide_match` 108 | 109 | | 参数 | 类型 | 描述 | 110 | |-------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------| 111 | | `target_file` | File | 目标图片文件(可选)需要与target字段同时使用 | 112 | | `target` | String | Base64 编码的目标图片字符串(可选) 需要与target_file字段同时使用 | 113 | | `background_file` | File | 背景图片文件(可选) 需要与background字段同时使用 | 114 | | `background` | String | Base64 编码的背景图片字符串(可选) 需要与background_file字段同时使用 | 115 | | `simple_target` | Boolean | 是否使用简单目标(默认:false) | 116 | || | `target_file`和`target` 为一组字段,`background_file`和`background` 为一组字段, 两组字段不可同时使用,同时使用则仅一组会生效 | 117 | 118 | 119 | ### 3. 目标检测 120 | 121 | 🔗 **端点**:`POST /detection` 122 | 123 | | 参数 | 类型 | 描述 | 124 | |------|------|------| 125 | | `file` | File | 图片文件(可选) | 126 | | `image` | String | Base64 编码的图片字符串(可选) | 127 | 128 | ## 📘 API 调用示例 129 | 130 |
131 | Python 132 | 133 | ```python 134 | import requests 135 | import base64 136 | 137 | url = "http://localhost:8000/ocr" 138 | image_path = "path/to/your/image.jpg" 139 | 140 | with open(image_path, "rb") as image_file: 141 | encoded_string = base64.b64encode(image_file.read()).decode('utf-8') 142 | 143 | data = { 144 | "image": encoded_string, 145 | "probability": False, 146 | "png_fix": False 147 | } 148 | 149 | response = requests.post(url, data=data) 150 | print(response.json()) 151 | ``` 152 |
153 |
154 | Node.js 155 | 156 | ```javascript 157 | const axios = require('axios'); 158 | const fs = require('fs'); 159 | 160 | const url = 'http://localhost:8000/ocr'; 161 | const imagePath = 'path/to/your/image.jpg'; 162 | 163 | const imageBuffer = fs.readFileSync(imagePath); 164 | const base64Image = imageBuffer.toString('base64'); 165 | 166 | const data = { 167 | image: base64Image, 168 | probability: false, 169 | png_fix: false 170 | }; 171 | 172 | axios.post(url, data) 173 | .then(response => { 174 | console.log(response.data); 175 | }) 176 | .catch(error => { 177 | console.error('Error:', error); 178 | }); 179 | ``` 180 |
181 | 182 |
183 | C# 184 | 185 | ```csharp 186 | using System; 187 | using System.Net.Http; 188 | using System.IO; 189 | using System.Threading.Tasks; 190 | 191 | class Program 192 | { 193 | static async Task Main(string[] args) 194 | { 195 | var url = "http://localhost:8000/ocr"; 196 | var imagePath = "path/to/your/image.jpg"; 197 | 198 | var imageBytes = File.ReadAllBytes(imagePath); 199 | var base64Image = Convert.ToBase64String(imageBytes); 200 | 201 | var client = new HttpClient(); 202 | var content = new MultipartFormDataContent(); 203 | content.Add(new StringContent(base64Image), "image"); 204 | content.Add(new StringContent("false"), "probability"); 205 | content.Add(new StringContent("false"), "png_fix"); 206 | 207 | var response = await client.PostAsync(url, content); 208 | var result = await response.Content.ReadAsStringAsync(); 209 | Console.WriteLine(result); 210 | } 211 | } 212 | ``` 213 |
214 | 215 |
216 | PHP 217 | 218 | ```php 219 | $imageData, 228 | 'probability' => 'false', 229 | 'png_fix' => 'false' 230 | ); 231 | 232 | $options = array( 233 | 'http' => array( 234 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n", 235 | 'method' => 'POST', 236 | 'content' => http_build_query($data) 237 | ) 238 | ); 239 | 240 | $context = stream_context_create($options); 241 | $result = file_get_contents($url, false, $context); 242 | 243 | echo $result; 244 | ?> 245 | ``` 246 |
247 | 248 |
249 | Go 250 | 251 | ```go 252 | package main 253 | 254 | import ( 255 | "bytes" 256 | "encoding/base64" 257 | "encoding/json" 258 | "fmt" 259 | "io/ioutil" 260 | "net/http" 261 | "net/url" 262 | ) 263 | 264 | func main() { 265 | apiURL := "http://localhost:8000/ocr" 266 | imagePath := "path/to/your/image.jpg" 267 | 268 | imageData, err := ioutil.ReadFile(imagePath) 269 | if err != nil { 270 | panic(err) 271 | } 272 | 273 | base64Image := base64.StdEncoding.EncodeToString(imageData) 274 | 275 | data := url.Values{} 276 | data.Set("image", base64Image) 277 | data.Set("probability", "false") 278 | data.Set("png_fix", "false") 279 | 280 | resp, err := http.PostForm(apiURL, data) 281 | if err != nil { 282 | panic(err) 283 | } 284 | defer resp.Body.Close() 285 | 286 | body, err := ioutil.ReadAll(resp.Body) 287 | if err != nil { 288 | panic(err) 289 | } 290 | 291 | fmt.Println(string(body)) 292 | } 293 | ``` 294 |
295 | 296 |
297 | 易语言 298 | 299 | ```易语言 300 | .版本 2 301 | 302 | .程序集 调用OCR接口 303 | 304 | .子程序 主函数, 整数型 305 | .局部变量 请求头, QQ.HttpHeaders 306 | .局部变量 请求内容, QQ.HttpMultiData 307 | .局部变量 图片路径, 文本型 308 | .局部变量 图片数据, 字节集 309 | .局部变量 HTTP, QQ.Http 310 | 311 | 图片路径 = "path/to/your/image.jpg" 312 | 图片数据 = 读入文件 (图片路径) 313 | 314 | 请求头.添加 ("Content-Type", "application/x-www-form-urlencoded") 315 | 316 | 请求内容.添加文本 ("image", 到Base64 (图片数据)) 317 | 请求内容.添加文本 ("probability", "false") 318 | 请求内容.添加文本 ("png_fix", "false") 319 | 320 | HTTP.发送POST请求 ("http://localhost:8000/ocr", 请求内容, 请求头) 321 | 322 | 调试输出 (HTTP.获取返回文本()) 323 | 324 | 返回 (0) 325 | ``` 326 |
327 | 328 | > **注意**:使用示例前,请确保安装了必要的依赖库,并根据实际环境修改服务器地址和图片路径。 329 | 330 | ## ⚠️ 注意事项 331 | 332 | - 确保防火墙允许访问 8000 端口。 333 | - 生产环境建议配置 HTTPS 和适当的身份验证机制。 334 | - 定期更新 Docker 镜像以获取最新的安全补丁和功能更新。 335 | 336 | ## 🔧 故障排除 337 | 338 | 遇到问题?请检查以下几点: 339 | 340 | 1. 确保 Docker 服务正在运行。 341 | 2. 检查容器日志: 342 | ```bash 343 | docker logs ddddocr-api-container 344 | ``` 345 | 3. 确保没有其他服务占用 8000 端口。 346 | 347 | > 如果问题仍然存在,请提交 issue 到本项目的 GitHub 仓库。 348 | 349 | ## 📄 许可证 350 | 351 | 本项目采用 MIT 许可证。详情请参见 [LICENSE](LICENSE) 文件。 352 | 353 | --- 354 | 355 |

356 | Made with ❤️ by sml2h3 357 |

358 | -------------------------------------------------------------------------------- /app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sml2h3/ddddocr-fastapi/a40a6b96d7259c2f5cd544963d147cbd40be344d/app/__init__.py -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | from fastapi import FastAPI, File, UploadFile, HTTPException, Form 3 | from typing import Optional, Union 4 | import base64 5 | from .models import OCRRequest, SlideMatchRequest, DetectionRequest, APIResponse 6 | from .services import ocr_service 7 | 8 | app = FastAPI() 9 | 10 | from starlette.datastructures import UploadFile as StarletteUploadFile 11 | 12 | 13 | async def decode_image(image: Union[UploadFile, StarletteUploadFile, str, None]) -> bytes: 14 | if image is None: 15 | raise HTTPException(status_code=400, detail="No image provided") 16 | 17 | if isinstance(image, (UploadFile, StarletteUploadFile)): 18 | return await image.read() 19 | elif isinstance(image, str): 20 | try: 21 | # 检查是否是 base64 编码的图片 22 | if image.startswith(('data:image/', 'data:application/')): 23 | # 移除 MIME 类型前缀 24 | image = image.split(',')[1] 25 | return base64.b64decode(image) 26 | except: 27 | raise HTTPException(status_code=400, detail="Invalid base64 string") 28 | else: 29 | raise HTTPException(status_code=400, detail="Invalid image input") 30 | 31 | 32 | @app.post("/ocr", response_model=APIResponse) 33 | async def ocr_endpoint( 34 | file: Optional[UploadFile] = File(None), 35 | image: Optional[str] = Form(None), 36 | probability: bool = Form(False), 37 | charsets: Optional[str] = Form(None), 38 | png_fix: bool = Form(False) 39 | ): 40 | try: 41 | if file is None and image is None: 42 | return APIResponse(code=400, message="Either file or image must be provided") 43 | 44 | image_bytes = await decode_image(file or image) 45 | result = ocr_service.ocr_classification(image_bytes, probability, charsets, png_fix) 46 | return APIResponse(code=200, message="Success", data=result) 47 | except Exception as e: 48 | return APIResponse(code=500, message=str(e)) 49 | 50 | 51 | @app.post("/slide_match", response_model=APIResponse) 52 | async def slide_match_endpoint( 53 | target_file: Optional[UploadFile] = File(None), 54 | background_file: Optional[UploadFile] = File(None), 55 | target: Optional[str] = Form(None), 56 | background: Optional[str] = Form(None), 57 | simple_target: bool = Form(False) 58 | ): 59 | try: 60 | if (background is None and target is None) or (background_file.size == 0 and target_file.size == 0): 61 | return APIResponse(code=400, message="Both target and background must be provided") 62 | 63 | target_bytes = await decode_image(target_file or target) 64 | background_bytes = await decode_image(background_file or background) 65 | result = ocr_service.slide_match(target_bytes, background_bytes, simple_target) 66 | return APIResponse(code=200, message="Success", data=result) 67 | except Exception as e: 68 | return APIResponse(code=500, message=str(e)) 69 | 70 | 71 | @app.post("/detection", response_model=APIResponse) 72 | async def detection_endpoint( 73 | file: Optional[UploadFile] = File(None), 74 | image: Optional[str] = Form(None) 75 | ): 76 | try: 77 | if file is None and image is None: 78 | return APIResponse(code=400, message="Either file or image must be provided") 79 | 80 | image_bytes = await decode_image(file or image) 81 | bboxes = ocr_service.detection(image_bytes) 82 | return APIResponse(code=200, message="Success", data=bboxes) 83 | except Exception as e: 84 | return APIResponse(code=500, message=str(e)) 85 | 86 | 87 | if __name__ == "__main__": 88 | uvicorn.run(app, host="0.0.0.0", port=8000) 89 | -------------------------------------------------------------------------------- /app/models.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from typing import Optional, List, Union, Any 3 | 4 | 5 | class ImageInput(BaseModel): 6 | image: Optional[str] = None # For base64 string 7 | 8 | 9 | class OCRRequest(ImageInput): 10 | probability: bool = False 11 | charsets: Optional[str] = None 12 | png_fix: bool = False 13 | 14 | 15 | class OCRResponse(BaseModel): 16 | result: Union[str, dict] 17 | 18 | 19 | class SlideMatchRequest(BaseModel): 20 | target: Optional[str] = None # For base64 string 21 | background: Optional[str] = None # For base64 string 22 | simple_target: bool = False 23 | 24 | 25 | class SlideMatchResponse(BaseModel): 26 | result: List[int] 27 | 28 | 29 | class DetectionRequest(ImageInput): 30 | pass 31 | 32 | 33 | class DetectionResponse(BaseModel): 34 | bboxes: List[List[int]] 35 | 36 | 37 | class APIResponse(BaseModel): 38 | code: int 39 | message: str 40 | data: Optional[Any] = None 41 | -------------------------------------------------------------------------------- /app/services.py: -------------------------------------------------------------------------------- 1 | import ddddocr 2 | from typing import Union, List, Optional 3 | 4 | class OCRService: 5 | def __init__(self): 6 | self.ocr = ddddocr.DdddOcr() 7 | self.det = ddddocr.DdddOcr(det=True) 8 | self.slide = ddddocr.DdddOcr(det=False, ocr=False) 9 | 10 | def ocr_classification(self, image: bytes, probability: bool = False, charsets: Optional[str] = None, png_fix: bool = False) -> Union[str, dict]: 11 | if charsets: 12 | self.ocr.set_ranges(charsets) 13 | result = self.ocr.classification(image, probability=probability, png_fix=png_fix) 14 | return result 15 | 16 | def slide_match(self, target: bytes, background: bytes, simple_target: bool = False) -> List[int]: 17 | result = self.slide.slide_match(target, background, simple_target=simple_target) 18 | return result 19 | 20 | def detection(self, image: bytes) -> List[List[int]]: 21 | bboxes = self.det.detection(image) 22 | return bboxes 23 | 24 | ocr_service = OCRService() 25 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | ddddocr-api: 5 | build: . 6 | ports: 7 | - "8000:8000" 8 | volumes: 9 | - .:/app 10 | environment: 11 | - DEBUG=1 12 | restart: always 13 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi==0.68.0 2 | uvicorn==0.15.0 3 | ddddocr==1.5.5 4 | python-multipart==0.0.5 -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sml2h3/ddddocr-fastapi/a40a6b96d7259c2f5cd544963d147cbd40be344d/tests/test_main.py --------------------------------------------------------------------------------