This commit is contained in:
2026-08-07 17:46:23 +08:00
parent 98ac9840c2
commit 645c483649
22 changed files with 370 additions and 29 deletions
@@ -1,16 +1,22 @@
package com.baiyue.integration.dto.ocr;
import java.math.BigDecimal;
/**
* 百度 OCR 返回的文字候选及其识别框。
*/
public record OcrCandidate(
String text,
Double confidence,
BigDecimal confidence,
Integer left,
Integer top,
Integer width,
Integer height
) {
public OcrCandidate {
confidence = OcrConfidence.normalize(confidence);
}
public long area() {
if (width == null || height == null || width <= 0 || height <= 0) {
return 0;
@@ -0,0 +1,27 @@
package com.baiyue.integration.dto.ocr;
import java.math.BigDecimal;
import java.math.RoundingMode;
/** Canonical confidence representation shared by OCR response models. */
public final class OcrConfidence {
private static final int SCALE = 5;
private static final BigDecimal ZERO = BigDecimal.ZERO.setScale(SCALE);
private static final BigDecimal ONE = BigDecimal.ONE.setScale(SCALE);
private OcrConfidence() {
}
public static BigDecimal normalize(BigDecimal value) {
if (value == null) {
return null;
}
if (value.compareTo(BigDecimal.ZERO) < 0) {
return ZERO;
}
if (value.compareTo(BigDecimal.ONE) > 0) {
return ONE;
}
return value.setScale(SCALE, RoundingMode.HALF_UP);
}
}
@@ -12,10 +12,15 @@ public record WeightOcrImageResult(
String rawResponseFile,
Status status,
BigDecimal weight,
Double confidence,
BigDecimal confidence,
List<OcrCandidate> candidates,
String ocrRawText,
String error
) {
public WeightOcrImageResult {
confidence = OcrConfidence.normalize(confidence);
}
public enum Status {
RECOGNIZED,
NO_WEIGHT_FOUND,
@@ -12,6 +12,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
@@ -163,7 +164,7 @@ public class BaiduMeterClient {
JsonNode location = item.path("location");
candidates.add(new OcrCandidate(
item.path("words").asText(),
nullableDouble(probability.get("average")),
nullableDecimal(probability.get("average")),
nullableInt(location.get("left")),
nullableInt(location.get("top")),
nullableInt(location.get("width")),
@@ -195,8 +196,8 @@ public class BaiduMeterClient {
return URLEncoder.encode(value, StandardCharsets.UTF_8);
}
private static Double nullableDouble(JsonNode node) {
return node == null || !node.isNumber() ? null : node.doubleValue();
private static BigDecimal nullableDecimal(JsonNode node) {
return node == null || !node.isNumber() ? null : node.decimalValue();
}
private static Integer nullableInt(JsonNode node) {
@@ -25,7 +25,8 @@ public class WeightCandidateSelector {
.flatMap(Optional::stream)
.max(Comparator
.comparingLong((SelectedWeight selected) -> selected.candidate().area())
.thenComparing(selected -> selected.confidence() == null ? -1 : selected.confidence()));
.thenComparing(selected -> selected.confidence() == null
? BigDecimal.valueOf(-1) : selected.confidence()));
}
private Optional<SelectedWeight> toSelectedWeight(OcrCandidate candidate) {
@@ -55,6 +56,6 @@ public class WeightCandidateSelector {
.replace('-', '-');
}
public record SelectedWeight(BigDecimal weight, Double confidence, OcrCandidate candidate) {
public record SelectedWeight(BigDecimal weight, BigDecimal confidence, OcrCandidate candidate) {
}
}
@@ -34,6 +34,7 @@ import java.util.stream.Stream;
@Service
public class WeightOcrBatchService {
private static final Set<String> SUPPORTED_EXTENSIONS = Set.of("jpg", "jpeg", "png", "bmp");
private static final int RAW_TEXT_MAX_LENGTH = 1000;
@Resource private IntegrationProperties properties;
@Resource private WeightOcrStoragePolicy storagePolicy;
@@ -113,7 +114,7 @@ public class WeightOcrBatchService {
return new WeightOcrImageResult(
source.toString(), outputRelative(paths, cropped), outputRelative(paths, rawResponse),
Status.RECOGNIZED, selected.weight(), selected.confidence(),
response.candidates(), null);
response.candidates(), summarizeCandidates(response.candidates()), null);
}
private WeightOcrImageResult noWeight(Path source, BatchPaths paths, Path cropped,
@@ -121,6 +122,7 @@ public class WeightOcrBatchService {
return new WeightOcrImageResult(
source.toString(), outputRelative(paths, cropped), outputRelative(paths, rawResponse),
Status.NO_WEIGHT_FOUND, null, null, response.candidates(),
summarizeCandidates(response.candidates()),
"百度响应中没有符合重量格式的候选值");
}
@@ -129,7 +131,33 @@ public class WeightOcrBatchService {
return new WeightOcrImageResult(
source.toString(), Files.exists(cropped) ? outputRelative(paths, cropped) : null,
Files.exists(rawResponse) ? outputRelative(paths, rawResponse) : null,
Status.FAILED, null, null, List.of(), error);
Status.FAILED, null, null, List.of(), null, error);
}
private String summarizeCandidates(List<com.baiyue.integration.dto.ocr.OcrCandidate> candidates) {
if (candidates == null || candidates.isEmpty()) {
return null;
}
StringBuilder summary = new StringBuilder();
for (var candidate : candidates) {
if (candidate == null || candidate.text() == null) {
continue;
}
String text = candidate.text().strip().replaceAll("[\\p{Cc}\\p{Cf}]+", " ");
if (text.isBlank()) {
continue;
}
int separatorLength = summary.isEmpty() ? 0 : 1;
int remaining = RAW_TEXT_MAX_LENGTH - summary.length() - separatorLength;
if (remaining <= 0) {
break;
}
if (separatorLength > 0) {
summary.append('\n');
}
summary.append(text, 0, Math.min(text.length(), remaining));
}
return summary.isEmpty() ? null : summary.toString();
}
private WeightOcrBatchResult buildResult(String batchId, Instant startedAt, BatchPaths paths,
@@ -56,7 +56,9 @@ public class WeightOcrSingleImageService {
if (batch.results() == null || batch.results().size() != 1) {
throw new BizException(ResultCode.INTERNAL_ERROR, "OCR单图识别结果数量异常");
}
return batch.results().get(0);
WeightOcrImageResult result = batch.results().get(0);
return new WeightOcrImageResult(null, null, null, result.status(), result.weight(),
result.confidence(), java.util.List.of(), result.ocrRawText(), result.error());
} finally {
deleteRequestDirectory(requestDirectory);
}