NetCore Nuget中,无论是PaddleOCRSharp或者Sdcb.PaddleOCR都可以进行调用,不过都需要做跨平台支持,真心话研究起来都费劲
以下使用docker部署python接口,NetCore直接调用接口即可,不用考虑各种环境配置
Dockerfile docker部署 requirements.txt 依赖 ocr_service.py 主程序 inference/ 模型文件,可使用训练的模型替换det/rec ppocr_keys.txt 字典文件
project/
├── Dockerfile
├── requirements.txt
├── ocr_service.py
└── inference/
├── ch_PP-OCRv4_det_infer/
├── ch_PP-OCRv4_rec_infer/
└── ppocr_keys.txt
# 使用 Python 3.9 基础镜像
FROM python:3.9.13
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev \
libgl1-mesa-glx \
&& rm -rf /var/lib/apt/lists/*
# 复制需要的文件
COPY requirements.txt .
COPY ocr_service.py .
COPY inference/ ./inference/
# 安装 Python 依赖
RUN python -m pip install --upgrade pip -i https://mirrors.aliyun.com/pypi/simple/
# 显示 requirements.txt 内容(调试用)
RUN cat requirements.txt
# 安装依赖,添加 -v 参数查看详细输出
RUN pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ -v
# 暴露端口
EXPOSE 8944
# 启动命令
CMD ["python", "ocr_service.py"]
python运行需要依赖环境
fastapi==0.109.2
uvicorn==0.27.1
paddlepaddle==2.5.2
paddleocr==2.7.0.3
opencv-python-headless==4.9.0.80
python-multipart==0.0.9
pydantic==2.6.1
numpy==1.26.3
from fastapi import FastAPI, File, UploadFile, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from paddleocr import PaddleOCR
import uvicorn
from pydantic import BaseModel, validator
import base64
import numpy as np
import cv2
from io import BytesIO
from pydantic import BaseModel
from typing import List
import logging
import time
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('ocr_service.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
app = FastAPI()
logger.info("Starting OCR service...")
# 初始化OCR模型
try:
logger.info("Initializing PaddleOCR model...")
ocr = PaddleOCR(
det_model_dir='./inference/ch_PP-OCRv4_det_infer/',
rec_model_dir='./inference/ch_PP-OCRv4_rec_infer/',
use_angle_cls=True,
lang='ch',
rec_char_dict_path='./inference/ppocr_keys.txt', # 添加自定义字典文件路径
enable_mkldnn=True, # 启用 MKL-DNN 加速
cpu_threads=8, # 线程
rec_batch_num=10 # 批处理大小
)
logger.info("PaddleOCR model initialized successfully")
except Exception as e:
logger.error(f"Failed to initialize PaddleOCR model: {str(e)}")
raise
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
return JSONResponse(
status_code=400,
content={
"code": 400,
"message": "Invalid request format",
"data": None,
"errors": [
{
"loc": err["loc"],
"msg": err["msg"],
"type": err["type"]
} for err in exc.errors()
]
}
)
class ImageData(BaseModel):
base64: str
order: int = 0
@validator('base64')
def validate_base64(cls, v):
try:
# 移除可能存在的data URI前缀和多余的换行符
if ',' in v:
v = v.split(',')[1]
v = v.strip()
# 尝试解码base64字符串
base64.b64decode(v)
return v
except Exception as e:
raise ValueError(f'Invalid base64 string: {str(e)}')
class OcrRequest(BaseModel):
images: List[ImageData]
@app.post("/ocr")
async def ocr_process(request: OcrRequest):
start_time = time.time()
logger.info(f"Received OCR request with {len(request.images)} images")
try:
results = []
for i, img_data in enumerate(request.images):
logger.info(f"Processing image {i+1}/{len(request.images)}, order: {img_data.order}")
# 解码base64图片
try:
# 清理和解码base64图片
clean_base64 = img_data.base64.strip()
if ',' in clean_base64:
clean_base64 = clean_base64.split(',')[1]
img_bytes = base64.b64decode(clean_base64)
nparr = np.frombuffer(img_bytes, np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if img is None:
raise ValueError(f"Failed to decode image {i+1}")
logger.info(f"Image {i+1} decoded successfully, shape: {img.shape}")
except Exception as e:
logger.error(f"Failed to decode image {i+1}: {str(e)}")
return {
"code": 400,
"message": f"Failed to decode image {i+1}: {str(e)}",
"data": None,
"processTime": f"{time.time() - start_time:.2f}s"
}
# OCR识别
try:
result = ocr.ocr(img, cls=True)
logger.info(f"OCR completed for image {i+1}, found {len(result[0]) if result and result[0] else 0} text regions")
except Exception as e:
logger.error(f"OCR failed for image {i+1}: {str(e)}")
return {
"code": 500,
"message": f"OCR processing failed for image {i+1}: {str(e)}",
"data": None,
"processTime": f"{time.time() - start_time:.2f}s"
}
# 处理结果
texts = []
pure_text = []
if result and result[0]: # 添加空结果检查
for line in result[0]: # 获取第一个元素,因为PaddleOCR返回的是列表的列表
box_points = []
for point in line[0]: # line[0] 包含坐标点 [[x1,y1], [x2,y2], [x3,y3], [x4,y4]]
box_points.append({
"X": int(point[0]), # 转换为整数
"Y": int(point[1])
})
text = line[1][0]
pure_text.append(text)
confidence = float(line[1][1])
texts.append({
"Content": text,
"Score": confidence,
"BoundingPolygon": box_points
})
results.append({
"Order": img_data.order,
"TextBlocks": texts,
"PureText": "\n".join(pure_text)
})
logger.info(f"Image {i+1} processed, found {len(texts)} text items")
processTime = time.time() - start_time
logger.info(f"Request completed successfully in {processTime:.2f} seconds")
return {
"code": 200,
"message": "success",
"data": results,
"processTime": f"{processTime:.2f}s"
}
except Exception as e:
logger.error(f"Error processing request: {str(e)}")
return {
"code": 500,
"message": str(e),
"data": None,
"processTime": f"{time.time() - start_time:.2f}s"
}
# 测试访问接口
@app.get("/health")
async def health_check():
return {"status": "ok", "timestamp": time.time()}
# 8078接口,可自行修改
if __name__ == "__main__":
logger.info("Starting FastAPI server on port 8078...")
uvicorn.run(app, host="0.0.0.0", port=8078)
文件夹上传至/home/PaddleOCRApi,运行镜像时可以挂载到/app目录,修改python或其他文件后, docker restart paddleocr_service即可
docker build -t paddleocrservice .
docker run -d \
--name paddleocr_service \
-p 8078:8078 \
-v /home/PaddleOCRApi:/app \
paddleocrservice
打开apifox,输入接口地址http://localhost:8078/health,选择GET,设置Content-Type: application/json,发起请求
{
"status": "ok",
"timestamp": 1736147932.54789
}
curl --location --request GET 'http://10.172.100.144:8078/health' \
--header 'User-Agent: Apifox/1.0.0 (https://apifox.com)' \
--header 'Content-Type: application/json' \
--header 'Accept: */*' \
--header 'Host: 10.172.100.144:8078' \
--header 'Connection: keep-alive'
输入接口地址http://localhost:8078/ocr,选择POST,设置Content-Type: application/json,设置Body→Json,发起请求 入参结构,base64为图片的base64编码,order为图片的序号
{
"images": [
{
"base64": "", #图片的base64位编码,例iVBORw0KGgoAAAANSUhEUgAABvkAA.....CAAAAAElFTkSuQmCC
"order": 0
}
]
}
出参结构,部分,TextBlocks包含坐标,识别内容和识别可信度,PureText为识别的文本内容
{
"code": 200,
"message": "success",
"data": [
{
"Order": 0,
"TextBlocks": [
{
"Content": "Invoice",
"Score": 0.9983231425285339,
"BoundingPolygon": [
{
"X": 833,
"Y": 102
},
{
"X": 957,
"Y": 108
},
{
"X": 955,
"Y": 148
},
{
"X": 831,
"Y": 141
}
]
}
],
"PureText": "Invoice\nPAYABLE"
}
],
"processTime": "5.22s"
}
string _webUrl = Db.Queryable<BaseCode>().Where(x => x.CodeType == "AIOCR" && x.Code == "OCRUrl").First().CodeName;
var client = new RestClient(_webUrl);
var request = new RestRequest(Method.POST);
request.AddHeader("content-type", "application/json");
PaddleOcrResquest paddle = new PaddleOcrResquest();
List<PaddleOcrImages> paddles = new List<PaddleOcrImages>();
paddles.Add(new PaddleOcrImages { order = 0, base64 = base64 });
paddle.images = paddles;
PaddleOcrResponseResult paddleOcr = new PaddleOcrResponseResult();
request.AddJsonBody(paddle);
try
{
var response = client.Execute(request);
if (response.IsSuccessful)
{
paddleOcr = JsonConvert.DeserializeObject<PaddleOcrResponseResult>(response.Content);
if (paddleOcr.Code != "200")
{
return ("解析失败!", new TruckInvoiceDto());
}
}
else
{
return ("解析失败!", new TruckInvoiceDto());
}
}
catch (Exception ex)
{
return (ex.Message, new TruckInvoiceDto());
}
public class PaddleOcrResponseResult
{
public List<PaddleOcrResult> Data { get; set; } = new List<PaddleOcrResult>();
public string Code { get; set; }
public string Message { get; set; }
public string ProcessTime { get; set; }
}
public class PaddleOcrResult
{
public List<TextBlock> TextBlocks { get; set; } = new List<TextBlock>();
public string Order { get; set; }
public string PureText { get; set; }
}
public class TextBlock
{
public List<OCRPoint> BoundingPolygon { get; set; } = new List<OCRPoint>();
public string Content { get; set; }
public string Score { get; set; }
}
public class OCRPoint
{
public int X;
public int Y;
}
public class PaddleOcrResquest
{
public List<PaddleOcrImages> images { get; set; }
}
public class PaddleOcrImages
{
public string base64 { get; set; }
public int order { get; set; }
}
python部署接口后方便调用,如接口访问速度较慢,可以考虑使用多线程,PaddleOCr的yml文件对象都可以进行参数设置,如下位置
PaddleOCR(
det_model_dir='./inference/ch_PP-OCRv4_det_infer/',
rec_model_dir='./inference/ch_PP-OCRv4_rec_infer/',
use_angle_cls=True,
lang='ch',
rec_char_dict_path='./inference/ppocr_keys.txt', # 添加自定义字典文件路径
enable_mkldnn=True, # 启用 MKL-DNN 加速
cpu_threads=8, # 线程
rec_batch_num=10 # 批处理大小
)
可参考目录中文件,如有问题,欢迎留言