feat(omr): 新增基于OCR的试卷ID识别功能

- 集成Tess4J实现OCR识别,新增analyzeExamWordsId方法提取试卷ID
- 对试卷图片左上角区域进行裁剪和预处理以提升识别准确率
- 添加Mat到BufferedImage的转换辅助方法,支持OCR读取
- 在测试用例中增加对OCR识别功能的集成测试
- 修改配置文件添加OCR数据路径,完善依赖引入OpenCV和Tess4J库
This commit is contained in:
lbw
2025-12-12 17:04:02 +08:00
parent e0258c7ddf
commit e729ddc829
7 changed files with 107 additions and 1 deletions

View File

@@ -4,15 +4,23 @@ import com.yinlihupo.enlish.service.constant.ExamWordsConstant;
import com.yinlihupo.enlish.service.model.bo.CoordinatesXY;
import com.yinlihupo.enlish.service.model.bo.Word;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.tess4j.ITesseract;
import net.sourceforge.tess4j.Tesseract;
import net.sourceforge.tess4j.TesseractException;
import nu.pattern.OpenCV;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.opencv.core.*;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.Imgproc;
import org.springframework.beans.factory.annotation.Value;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Slf4j
public class PngUtil {
@@ -184,6 +192,82 @@ public class PngUtil {
}
}
public static Integer analyzeExamWordsId(String imagePath, String tessdataPath, List<CoordinatesXY> coordinatesXIES) {
// 1. 读取图片
Mat src = Imgcodecs.imread(imagePath);
if (src.empty()) {
System.out.println("无法加载图片");
return 0;
}
// 2. 截取左上角区域 (ROI)
// 根据图片大概估算:从 (0,0) 开始,宽约 300像素高约 80像素
// 你可以根据实际情况调整这个范围
CoordinatesXY left = coordinatesXIES.get(0);
Rect roiRect = new Rect(0, 0, left.getX(), left.getY());
Mat roi = new Mat(src, roiRect);
// 3. 图像预处理 (提高 OCR 准确率)
// 3.1 转为灰度图
Mat gray = new Mat();
Imgproc.cvtColor(roi, gray, Imgproc.COLOR_BGR2GRAY);
// 3.2 二值化 (Thresholding)
// 使用 OTSU 算法自动寻找最佳阈值,或者手动指定阈值
Mat binary = new Mat();
Imgproc.threshold(gray, binary, 0, 255, Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU);
// 可选:保存预处理后的图片查看效果
Imgcodecs.imwrite("debug_roi.jpg", binary);
// 4. 将 OpenCV Mat 转换为 BufferedImage (供 Tess4J 使用)
BufferedImage processedImage = matToBufferedImage(binary);
// 5. 使用 Tesseract 进行 OCR 识别
ITesseract instance = new Tesseract();
instance.setDatapath(tessdataPath);
instance.setLanguage("eng");
try {
String result = instance.doOCR(processedImage);
System.out.println("OCR 识别原始内容: \n" + result);
// 6. 使用正则表达式提取 ID
// 匹配 "Assessment_id" 后面的数字
Pattern pattern = Pattern.compile("id[:\\s_]+(\\d+)");
Matcher matcher = pattern.matcher(result);
if (matcher.find()) {
String id = matcher.group(1);
System.out.println("-------------------------");
System.out.println("成功提取 ID: " + id);
System.out.println("-------------------------");
return Integer.parseInt(id);
} else {
System.out.println("未找到匹配的 ID 格式");
}
} catch (TesseractException e) {
System.err.println("OCR 识别出错: " + e.getMessage());
}
return 0;
}
// 辅助方法Mat 转 BufferedImage
public static BufferedImage matToBufferedImage(Mat m) {
int type = BufferedImage.TYPE_BYTE_GRAY;
if (m.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
int bufferSize = m.channels() * m.cols() * m.rows();
byte[] b = new byte[bufferSize];
m.get(0, 0, b); // 获取所有像素
BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);
final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(b, 0, targetPixels, 0, b.length);
return image;
}
private static @NonNull List<CoordinatesXY> getCoordinatesXIES(List<CoordinatesXY> list, int height) {
List<CoordinatesXY> ans = new ArrayList<>();
CoordinatesXY left = new CoordinatesXY();

View File

@@ -24,5 +24,6 @@ spring:
templates:
word: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\templates\assessment_v5.docx
count: 100
data: C:\project\tess
tmp:
png: C:\project\java\enlish_edu\enlish\enlish-service\src\main\resources\tmp\png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 273 KiB

After

Width:  |  Height:  |  Size: 450 KiB

View File

@@ -6,6 +6,7 @@ import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import nu.pattern.OpenCV;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Arrays;
@@ -15,6 +16,9 @@ import java.util.List;
@Slf4j
public class TestOmr {
@Value("${templates.data}")
private String tessdataPath;
@Test
public void testOmr(){
OpenCV.loadLocally();
@@ -33,5 +37,11 @@ public class TestOmr {
}
@Test
public void testInteger(){
String filePath = "C:\\project\\java\\enlish_edu\\enlish\\enlish-service\\src\\main\\resources\\templates\\p3.png";
List<CoordinatesXY> coordinatesXIES = PngUtil.analysisXY(filePath);
Integer examWordsId = PngUtil.analyzeExamWordsId(filePath, tessdataPath, coordinatesXIES);
log.info("examWordsId:{}", examWordsId);
}
}