feat(omr): 新增基于OCR的试卷ID识别功能
- 集成Tess4J实现OCR识别,新增analyzeExamWordsId方法提取试卷ID - 对试卷图片左上角区域进行裁剪和预处理以提升识别准确率 - 添加Mat到BufferedImage的转换辅助方法,支持OCR读取 - 在测试用例中增加对OCR识别功能的集成测试 - 修改配置文件添加OCR数据路径,完善依赖引入OpenCV和Tess4J库
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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 |
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user