2026 山东大学软件学院项目实训博客(七)OCR图像识别功能开发
一、实训时间
2026年5月29日 - 2026年6月7日
二、阶段工作目标
完成OCR图像识别功能模块的全流程开发,实现图片上传、图像预处理、AI模型调用、文字识别结果展示的完整闭环。本次OCR功能模块开发旨在打造一个完整的文档图像识别系统,支持用户上传古籍、碑帖、书法等图片文档,通过火山引擎豆包AI视觉模型自动识别文字内容,并生成原文、大白话翻译、生词解释、背景介绍等结构化结果。
三、核心技术选型
|
技术项 |
选型 |
说明 |
|---|---|---|
|
AI模型 |
豆包Seed 2.0 Lite |
火山引擎视觉模型,支持图片直接输入 |
|
API协议 |
OpenAI兼容 Chat Completions API |
标准messages格式,image_url传图 |
|
图像处理 |
Java ImageIO + JPEG压缩 |
400px宽度,30%质量,控制在30-80KB |
|
异步架构 |
CompletableFuture + 前端轮询 |
上传立即返回,后台异步处理 |
四、数据库设计
创建ocr_records表存储识别记录,所有文本字段使用TEXT类型支持长文本:
CREATE TABLE ocr_records ( id BIGINT PRIMARY KEY AUTO_INCREMENT, guest_id INT, file_name VARCHAR(255), original_file_name VARCHAR(255), file_path VARCHAR(500), file_type VARCHAR(10), file_size BIGINT, original_text TEXT, modern_text TEXT, modern_text_translation TEXT, vocabulary TEXT, background TEXT, ocr_raw_result TEXT, ocr_model VARCHAR(100), ocr_status VARCHAR(50), error_message TEXT, created_at DATETIME, updated_at DATETIME, FOREIGN KEY (guest_id) REFERENCES guest(guest_id) );
五、完整API接口设计
|
接口 |
方法 |
路径 |
说明 |
|---|---|---|---|
|
上传识别 |
POST |
/api/ocr/upload |
上传图片,异步处理,立即返回记录ID |
|
查询状态 |
POST |
/api/ocr/status |
前端轮询,获取处理进度和结果 |
|
记录列表 |
POST |
/api/ocr/list |
获取当前用户的识别记录 |
|
记录详情 |
POST |
/api/ocr/detail |
获取单条记录完整信息 |
|
编辑记录 |
POST |
/api/ocr/edit |
手动修改识别结果 |
|
删除记录 |
POST |
/api/ocr/delete |
删除记录及对应文件 |
|
重新识别 |
POST |
/api/ocr/re-ocr |
对失败或已有记录重新OCR |
|
获取图片 |
GET |
/api/ocr/image/{id} |
返回原始图片二进制数据 |
|
模型列表 |
POST |
/api/ocr/models |
获取可用AI模型列表 |
六、后端OCR功能模块开发
(一)实体类与数据层(Model层)
OCRRecord.java
核心实体类,使用JPA注解自动映射数据库表。关键设计:
-
guest字段关联Guest实体,通过@ManyToOne建立多对一关系,一条用户可拥有多条OCR记录
-
所有文本字段使用@Column(columnDefinition = "TEXT")声明为TEXT类型,支持存储千字以上的古文内容
-
ocrStatus字段设计四种状态流转:PROCESSING(识别原文中)→ ORIGINAL_READY(翻译生成中)→ COMPLETED(完成)/ FAILED(失败)
@Entity @Table(name = "ocr_records") public class OCRRecord { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "guest_id") @JsonIgnore private Guest guest; @Column(columnDefinition = "TEXT") private String originalText; @Column(columnDefinition = "TEXT") private String modernText; @Column(columnDefinition = "TEXT") private String vocabulary; @Column(columnDefinition = "TEXT") private String background; @Column(name = "ocr_status") private String ocrStatus; }
(二)配置文件与模型管理(Config层)
OCRModelConfig.java
使用@ConfigurationProperties自动绑定yml配置,支持多模型列表管理:
@Component @ConfigurationProperties(prefix = "ocr.api") public class OCRModelConfig { private String url = "https://ark.cn-beijing.volces.com/api/v3/chat/completions"; private String key; private String defaultModel = "doubao-seed-2-0-mini-260428"; private List<ModelInfo> models = new ArrayList<>(); }
(三)图像预处理服务(Service层)
图片压缩算法
200KB原图 → 30-60KB压缩图,减少约70%体积:
private byte[] compressImage(byte[] imageBytes, String mimeType) throws IOException { if (mimeType.contains("pdf")) return imageBytes; if (imageBytes.length < 30 * 1024) return imageBytes; BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes)); int maxWidth = 400; if (image.getWidth() > maxWidth) { double ratio = (double) maxWidth / image.getWidth(); int h = (int) (image.getHeight() * ratio); Image scaled = image.getScaledInstance(maxWidth, h, Image.SCALE_SMOOTH); BufferedImage ni = new BufferedImage(maxWidth, h, BufferedImage.TYPE_INT_RGB); ni.getGraphics().drawImage(scaled, 0, 0, null); image = ni; } ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next(); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(0.3f); }
(四)两步式AI识别流程
第一步:识别原文
private String step1Recognize(String dataUrl, String model) throws IOException { JSONArray messages = new JSONArray(); JSONObject sys = new JSONObject(); sys.put("role", "system"); sys.put("content", "直接输出图片中的文字内容,保留原文的断句和换行,不要任何解释。竖排文字请按从右到左、从上到下顺序输出。"); messages.add(sys); JSONObject user = new JSONObject(); user.put("role", "user"); JSONArray content = new JSONArray(); JSONObject img = new JSONObject(); img.put("type", "image_url"); JSONObject url = new JSONObject(); url.put("url", dataUrl); img.put("image_url", url); content.add(img); user.put("content", content); messages.add(user); }
第二步:翻译+标点+背景+生词
private OCRResult step2Translate(String originalText, String model) throws IOException { String prompt = "根据以下古文原文,返回JSON(只返回JSON,不要其他):\n" + "原文:" + originalText + "\n" + "格式:{\"modernText\":\"用大白话翻译\",\"originalTextWithPunctuation\":\"给原文加上标点符号\",\"background\":\"背景介绍\",\"vocabulary\":[{\"word\":\"词\",\"pinyin\":\"拼音\",\"meaning\":\"释义\"}]}"; }
(五)异步处理架构(Controller层)
异步上传接口
@PostMapping("/upload") public DataResponse uploadOCR(@RequestParam("file") MultipartFile file) { OCRRecord record = new OCRRecord(); record.setOcrStatus("PROCESSING"); ocrRepository.save(record); final Long recordId = record.getId(); CompletableFuture.runAsync(() -> processOCRAsync(recordId, filePath)); Map<String, Object> result = new HashMap<>(); result.put("id", recordId); result.put("status", "PROCESSING"); return CommonMethod.getReturnData(result); }
完整数据流
📊 用户选择图片 → 前端handleUpload() → POST /api/ocr/upload → 后端保存文件、创建记录(PROCESSING) → 立即返回ID → 前端启动定时器每1.5秒轮询 → 后端异步完成第一步更新ORIGINAL_READY → 异步完成第二步更新COMPLETED → 前端停止轮询
七、前端OCR页面开发
(一)前端服务层(OCRServ.ts)
export interface OCRRecord { id: number; originalFileName: string; fileType: string; fileSize: number; imageUrl: string; originalText: string; modernText: string; background: string; vocabulary: string; ocrStatus: string; } export async function uploadOCR(params: { file: File }): Promise<{ id: number }> { const fd = new FormData(); fd.append('file', params.file); const res = await uploadRequest("/api/ocr/upload", fd); return res.data; }
(二)上传与进度展示
async function handleUpload() { const file = uploadForm.value.file; const { id } = await uploadOCR({ file }); currentOCR.value = { id, originalFileName: file.name, imageUrl: '/api/ocr/image/' + id, ocrStatus: 'PROCESSING', } as OCRRecord; showViewDialog.value = true; startPolling(id); }
(三)轮询状态更新
function startPolling(recordId: number) { pollTimer = setInterval(async () => { const record = await getOCRStatus(recordId); if (record.ocrStatus === 'PROCESSING') { uploadProgress.value = Math.min(uploadProgress.value + 2, 45); uploadMessage.value = '识别原文中...'; } else if (record.ocrStatus === 'ORIGINAL_READY') { uploadProgress.value = 60; uploadMessage.value = '翻译生成中...'; } else if (record.ocrStatus === 'COMPLETED') { uploadProgress.value = 100; uploadMessage.value = '完成!'; clearInterval(pollTimer); } }, 1500); }
八、关键技术问题解决
|
问题 |
原因 |
解决方案 |
|---|---|---|
|
API 401认证失败 |
@ConfigurationProperties(prefix = "ocr")与yml的ocr.api不匹配,key读取为null |
改为prefix = "ocr.api",确保与yml层级一致 |
|
图片无法显示 |
Spring Security拦截了GET /api/ocr/image/{id} |
添加.antMatchers(HttpMethod.GET, "/api/ocr/image/**").permitAll() |
|
图片过大导致超时 |
原图2-5MB,Base64编码后更大 |
压缩至400px/30%质量,控制30-80KB |
|
单步识别等待太久 |
一次请求要等原文+翻译全部完成 |
拆分为两步:先原文后翻译,用户先看到原文 |
|
Guest为null报错 |
未登录状态下上传,guest字段为null |
添加多重null检查,guest为null时允许上传 |
|
模型返回JSON格式不稳定 |
AI有时返回markdown包裹的JSON |
清理 |
|
Cannot read null错误 |
resetUploadForm()清空file引用 |
上传前保存局部变量const file = uploadForm.value.file |
九、阶段项目成果
(一)功能成果
-
完整OCR识别流程:支持图片上传、图像预处理、AI识别、翻译生成的完整闭环
-
两步式异步识别:先展示原文再展示翻译,用户感知等待时间大幅缩短
-
图片智能压缩:200KB→30-60KB,API传输时间减少70%
-
实时状态展示:轮询机制让用户清晰感知处理进度
-
结构化输出:自动生成原文、翻译、生词、背景四大模块
(二)技术成果
-
搭建豆包AI视觉模型对接架构,实现OpenAI兼容协议的标准化调用
-
实现CompletableFuture异步处理+前端轮询的非阻塞架构
-
完成Java ImageIO图片压缩算法优化,平衡识别精度与传输速度
-
设计完整的数据库表结构与RESTful API接口体系



十、阶段总结
在5月29日至6月7日的实训周期内,我聚焦OCR图像识别功能开发核心目标,完成了从后端服务到前端页面的全流程开发。通过两步式异步识别架构、智能图片压缩算法、实时轮询状态展示等技术手段,有效解决了AI识别等待时间长、图片传输慢等关键问题。系统已支持古籍、碑帖、书法等各类图片文档的上传识别,能够自动生成原文、大白话翻译、生词解释、背景介绍等结构化结果,圆满完成本阶段既定工作目标。
十一、后续计划
对OCR识别功能进行全面优化,提升识别速度、完善提示词策略、优化前端展示效果、修复遗留Bug,使OCR功能达到较好结果。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)