├── 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 | 
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 | 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 | 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 | 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 | 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 | 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 | 易语言
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 |
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 --------------------------------------------------------------------------------