系统架构设计
- 前端:Java应用(可以是Spring Boot微服务)
- OCR处理:Python程序(使用PaddleOCR/Tesseract等OCR库)
- 通信方式:Java通过ProcessBuilder调用Python脚本并传递图像数据
ocr-system/
├── java-service/
│ ├── src/
│ │ └── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── OcrApplication.java
│ │ │ ├── controller/
│ │ │ │ └── OcrController.java
│ │ │ └── service/
│ │ │ └── OcrService.java
│ │ └── resources/
│ │ └── application.properties
│ └── pom.xml
└── python-ocr/
├── ocr_processor.py
├── requirements.txt
└── test_images/
扩展方案:
- REST API:将Python OCR服务部署为独立的HTTP服务
- gRPC:使用gRPC实现更高效的跨语言通信
- Jython:直接在JVM中运行Python代码(但可能不支持所有Python库)
- Py4J:Python和Java互调的另一选择
实现步骤
①、Python OCR服务实现ocr_processor.py
# pip install paddleocr opencv-python numpy
import sys
import json
import base64
import numpy as np
import cv2
from paddleocr import PaddleOCR
#初始化OCR模型(可以按需选择语言等参数)
ocr = PaddleOCR(use_angle_cls=True,lang="ch")
def process_image(image_bytes):
"""处理图像并返回OCR结果"""
try:
#将字节数据转换为numpy数组
nparr = np.frombuffer(image_bytes,np.uint8)
img = cv2.imdecode(nparr,cv2.IMREAD_COLOR)
#执行OCR
result = ocr.ocr(img,cls=True)
#格式化结果
formatted = []
if result and result[0]:
for line in result[0]:
if line and len(line) >= 2:
text = line[1][0]
confidence = float(line[1][1])
formatted.append({
"text":text,
"confidence":confidence
"position":line[0]
})
return {"success":True,"result":formatted}
except Exception as e:
return {"success": False,"error":str(e)}
def main():
"""主函数,从标砖输入读取数据"""
try:
#从标准输入读取Base64编码的图像数据
input_data = sys.stdin.read()
data = json.loads(input_data)
# 解码图像
image_bytes = base64.b64decode(data["image"])
#处理图像
result = process_image(image_bytes)
#输出结果
print(json.dumps(result))
except Exception as e:
print(json.dumps({"success":False,"error":str(e)}))
if __name__ == "__main__":
main()
②、Java服务实现,创建Spring Boot项目并添加OCR服务调用类
@Service
public class OcrService {
private static final String PYTHON_SCRIPT_PATH = "path/to/ocr_processor.py";
/**
* 调用Python OCR处理图像
* @param imageBytes 图像字节数组
* @return OCR结果
*/
public OcrResult processImage(byte[] imageBytes) {
try {
// 准备输入数据
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
String inputJson = String.format("{\"image\": \"%s\"}", base64Image);
// 构建Python命令
ProcessBuilder pb = new ProcessBuilder("python3", PYTHON_SCRIPT_PATH);
pb.redirectErrorStream(true);
// 启动进程
Process process = pb.start();
// 写入输入数据
try (OutputStream os = process.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8))) {
writer.println(inputJson);
}
// 读取输出
String output;
try (InputStream is = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
output = reader.lines().collect(Collectors.joining("\n"));
}
// 等待进程结束
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("Python script exited with code: " + exitCode);
}
// 解析结果
return parseOcrResult(output);
} catch (IOException | InterruptedException e) {
throw new RuntimeException("OCR processing failed", e);
}
}
private OcrResult parseOcrResult(String jsonOutput) {
// 这里简化处理,实际应该使用JSON库如Jackson
if (jsonOutput.contains("\"success\":true")) {
// 解析成功结果
return new OcrResult(true, extractTexts(jsonOutput), null);
} else {
// 解析错误
String error = jsonOutput.split("\"error\":\"")[1].split("\"")[0];
return new OcrResult(false, null, error);
}
}
private List<String> extractTexts(String jsonOutput) {
// 简化处理,实际应该使用JSON库
String[] parts = jsonOutput.split("\"text\":\"");
List<String> texts = new java.util.ArrayList<>();
for (int i = 1; i < parts.length; i++) {
texts.add(parts[i].split("\"")[0]);
}
return texts;
}
public static class OcrResult {
private final boolean success;
private final List<String> texts;
private final String error;
public OcrResult(boolean success, List<String> texts, String error) {
this.success = success;
this.texts = texts;
this.error = error;
}
// Getters
public boolean isSuccess() { return success; }
public List<String> getTexts() { return texts; }
public String getError() { return error; }
}
}
@RestController
@RequestMapping("/api/ocr")
public class OcrController {
@Autowired
private OcrService ocrService;
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public OcrService.OcrResult processImage(@RequestParam("file") MultipartFile file) {
try {
byte[] imageBytes = file.getBytes();
return ocrService.processImage(imageBytes);
} catch (IOException e) {
return new OcrService.OcrResult(false, null, "Failed to read image: " + e.getMessage());
}
}
}
③、构建和部署
Python端
- 将ocr_processor.py放在合适的位置
- 确保Python环境已安装所有依赖
- 测试Python脚本是否可以独立运行
Java端
- 构建Spring Boot应用
- 配置PYTHON_SCRIPT_PATH为正确的Python脚本路径
- 启动Java应用
使用Postman或者curl测试API
curl -X POST -F "file=@test.png" http://localhost:8080/api/ocr
高级优化
- 使用更高效的通信方式
上面的实现使用JSON通过标准输入输出通信,对于大图像可能效率不高。可以考虑:
- 使用临时文件传递图像
- 使用gRPC或Thrift进行进程间通信
- 使用Redis或消息队列解耦
- 错误处理和日志
增强错误处理和日志记录:
// 在OcrService中添加日志
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(OcrService.class);
// 在processImage方法中添加日志
logger.debug("Starting OCR process for image (size: {} bytes)", imageBytes.length);
// ...
logger.debug("Python script output: {}", output);
- 性能优化
保持Python OCR模型常驻内存(上面的Python脚本已经实现)
实现Java端的连接池管理多个Python进程
添加缓存层避免重复处理相同图像
- 使用更好的JSON处理
替换简单的字符串处理为Jackson等JSON库:
// 添加依赖
// Maven: com.fasterxml.jackson.core:jackson-databind
private OcrResult parseOcrResult(String jsonOutput) throws IOException {
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> resultMap = mapper.readValue(jsonOutput, new TypeReference<Map<String, Object>>() {});
if (Boolean.TRUE.equals(resultMap.get("success"))) {
List<Map<String, Object>> ocrResults = (List<Map<String, Object>>) resultMap.get("result");
List<String> texts = ocrResults.stream()
.map(r -> (String) r.get("text"))
.collect(Collectors.toList());
return new OcrResult(true, texts, null);
} else {
return new OcrResult(false, null, (String) resultMap.get("error"));
}
}