fix short word snuggification
This commit is contained in:
parent
0d0942ad46
commit
b5523842dd
@ -13,6 +13,7 @@ import com.knecon.fforesight.service.ocr.v1.api.model.QuadPoint;
|
|||||||
import com.sun.jna.Pointer;
|
import com.sun.jna.Pointer;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.sourceforge.lept4j.Leptonica1;
|
import net.sourceforge.lept4j.Leptonica1;
|
||||||
import net.sourceforge.lept4j.Numa;
|
import net.sourceforge.lept4j.Numa;
|
||||||
@ -23,6 +24,7 @@ import net.sourceforge.lept4j.util.LeptUtils;
|
|||||||
* This class attempts to shrink the BBox of a word to match the exact height of the word. This is only attempted for horizontal or vertical words. Any askew text is left as is.
|
* This class attempts to shrink the BBox of a word to match the exact height of the word. This is only attempted for horizontal or vertical words. Any askew text is left as is.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@UtilityClass
|
||||||
public class BBoxSnuggificationService {
|
public class BBoxSnuggificationService {
|
||||||
|
|
||||||
public static final int PIXEL_COUNT_THRESHOLD = 2; // minimum active pixel count per row for shrinking to stop
|
public static final int PIXEL_COUNT_THRESHOLD = 2; // minimum active pixel count per row for shrinking to stop
|
||||||
@ -32,12 +34,13 @@ public class BBoxSnuggificationService {
|
|||||||
private enum Operation {
|
private enum Operation {
|
||||||
HORIZONTAL,
|
HORIZONTAL,
|
||||||
VERTICAL,
|
VERTICAL,
|
||||||
|
BOTH,
|
||||||
NONE
|
NONE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public static Optional<QuadPoint> snuggify(Pix pageImage, DocumentWord origin, AffineTransform resultToImageTransform) {
|
public Optional<QuadPoint> snuggify(Pix pageImage, DocumentWord origin, AffineTransform resultToImageTransform) {
|
||||||
|
|
||||||
if (pageImage == null) {
|
if (pageImage == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@ -60,11 +63,17 @@ public class BBoxSnuggificationService {
|
|||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!StrokeWidthCalculator.wordImageHasMinimumPixelDensity(wordImage)) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
Optional<Rectangle2D> snugBox = switch (operation) {
|
Optional<Rectangle2D> snugBox = switch (operation) {
|
||||||
case HORIZONTAL -> snuggifyY(wordImage, originTransformed.getBounds2D());
|
case HORIZONTAL -> snuggifyY(wordImage, originTransformed.getBounds2D());
|
||||||
case VERTICAL -> snuggifyX(wordImage, originTransformed.getBounds2D());
|
case VERTICAL -> snuggifyX(wordImage, originTransformed.getBounds2D());
|
||||||
|
case BOTH -> snuggifyBoth(wordImage, originTransformed);
|
||||||
default -> Optional.empty();
|
default -> Optional.empty();
|
||||||
};
|
};
|
||||||
|
|
||||||
LeptUtils.disposePix(wordImage);
|
LeptUtils.disposePix(wordImage);
|
||||||
|
|
||||||
AffineTransform imageToResultTransform = resultToImageTransform.createInverse();
|
AffineTransform imageToResultTransform = resultToImageTransform.createInverse();
|
||||||
@ -74,20 +83,35 @@ public class BBoxSnuggificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Operation determineOperation(DocumentWord origin, QuadPoint.Direction direction, double remainingAngle, QuadPoint originTransformed) {
|
private Optional<Rectangle2D> snuggifyBoth(Pix wordImage, QuadPoint originTransformed) {
|
||||||
|
|
||||||
|
Optional<Rectangle2D> snugY = snuggifyY(wordImage, originTransformed.getBounds2D());
|
||||||
|
Optional<Rectangle2D> snugX = snuggifyX(wordImage, originTransformed.getBounds2D());
|
||||||
|
if (snugY.isPresent() && snugX.isPresent()) {
|
||||||
|
return Optional.of(snugY.get().createIntersection(snugX.get()).getBounds2D());
|
||||||
|
} else if (snugY.isPresent()) {
|
||||||
|
return snugY;
|
||||||
|
} else {
|
||||||
|
return snugX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Operation determineOperation(DocumentWord origin, QuadPoint.Direction direction, double remainingAngle, QuadPoint originTransformed) {
|
||||||
|
|
||||||
Operation operation = Operation.NONE;
|
Operation operation = Operation.NONE;
|
||||||
if (((direction.equals(QuadPoint.Direction.RIGHT) || direction.equals(QuadPoint.Direction.LEFT)) && remainingAngle < INDIVIDUAL_ANGLE_THRESHOLD) //
|
if (((direction.equals(QuadPoint.Direction.RIGHT) || direction.equals(QuadPoint.Direction.LEFT)) && remainingAngle < INDIVIDUAL_ANGLE_THRESHOLD)) {
|
||||||
|| (origin.getContent().length() < 4 || Math.abs(originTransformed.getAngle()) < AVERAGE_ANGLE_THRESHOLD * 3)) {
|
|
||||||
operation = Operation.HORIZONTAL;
|
operation = Operation.HORIZONTAL;
|
||||||
} else if ((direction.equals(QuadPoint.Direction.UP) || direction.equals(QuadPoint.Direction.DOWN)) && remainingAngle < INDIVIDUAL_ANGLE_THRESHOLD) {
|
} else if ((direction.equals(QuadPoint.Direction.UP) || direction.equals(QuadPoint.Direction.DOWN)) && remainingAngle < INDIVIDUAL_ANGLE_THRESHOLD) {
|
||||||
operation = Operation.VERTICAL;
|
operation = Operation.VERTICAL;
|
||||||
|
} else if ((origin.getContent().length() < 4 || Math.abs(originTransformed.getAngle()) < AVERAGE_ANGLE_THRESHOLD * 3)) {
|
||||||
|
return Operation.BOTH;
|
||||||
}
|
}
|
||||||
return operation;
|
return operation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Optional<Rectangle2D> snuggifyX(Pix wordImage, Rectangle2D origin) {
|
private Optional<Rectangle2D> snuggifyX(Pix wordImage, Rectangle2D origin) {
|
||||||
|
|
||||||
Numa colCounts = Leptonica1.pixCountPixelsByColumn(wordImage);
|
Numa colCounts = Leptonica1.pixCountPixelsByColumn(wordImage);
|
||||||
int start = 0;
|
int start = 0;
|
||||||
@ -112,7 +136,7 @@ public class BBoxSnuggificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static Optional<Rectangle2D> snuggifyY(Pix wordImage, Rectangle2D origin) {
|
private Optional<Rectangle2D> snuggifyY(Pix wordImage, Rectangle2D origin) {
|
||||||
|
|
||||||
int start = 0;
|
int start = 0;
|
||||||
int end = wordImage.h - 1;
|
int end = wordImage.h - 1;
|
||||||
@ -135,7 +159,7 @@ public class BBoxSnuggificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static int pixCountPerRow(int row, Pix pix) {
|
private int pixCountPerRow(int row, Pix pix) {
|
||||||
|
|
||||||
IntBuffer result = IntBuffer.allocate(1);
|
IntBuffer result = IntBuffer.allocate(1);
|
||||||
int success = Leptonica1.pixCountPixelsInRow(pix, row, result, null);
|
int success = Leptonica1.pixCountPixelsInRow(pix, row, result, null);
|
||||||
@ -147,7 +171,7 @@ public class BBoxSnuggificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static int pixCountPerColumn(int column, Numa colCounts) {
|
private int pixCountPerColumn(int column, Numa colCounts) {
|
||||||
|
|
||||||
if (column > colCounts.n) {
|
if (column > colCounts.n) {
|
||||||
throw new IndexOutOfBoundsException("column " + column + " is out of bounds for column count " + colCounts.n);
|
throw new IndexOutOfBoundsException("column " + column + " is out of bounds for column count " + colCounts.n);
|
||||||
@ -159,7 +183,7 @@ public class BBoxSnuggificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean canBeSnuggified(DocumentPage resultPage, AffineTransform imageTransform) {
|
public boolean canBeSnuggified(DocumentPage resultPage, AffineTransform imageTransform) {
|
||||||
|
|
||||||
double averageAngle = resultPage.getWords()
|
double averageAngle = resultPage.getWords()
|
||||||
.stream()
|
.stream()
|
||||||
|
|||||||
@ -3,13 +3,9 @@ package com.knecon.fforesight.service.ocr.processor.service.imageprocessing;
|
|||||||
import static net.sourceforge.lept4j.ILeptonica.L_THIN_FG;
|
import static net.sourceforge.lept4j.ILeptonica.L_THIN_FG;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.IntBuffer;
|
import java.nio.IntBuffer;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.experimental.FieldDefaults;
|
import lombok.experimental.FieldDefaults;
|
||||||
import net.sourceforge.lept4j.Leptonica1;
|
import net.sourceforge.lept4j.Leptonica1;
|
||||||
import net.sourceforge.lept4j.Pix;
|
import net.sourceforge.lept4j.Pix;
|
||||||
@ -19,6 +15,7 @@ import net.sourceforge.lept4j.util.LeptUtils;
|
|||||||
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
@FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
|
||||||
public class StrokeWidthCalculator implements Closeable {
|
public class StrokeWidthCalculator implements Closeable {
|
||||||
|
|
||||||
|
public static final double MINIMUM_PIXEL_DENSITY = 0.05;
|
||||||
Sela thinningSel = Leptonica1.selaMakeThinSets(1, 0);
|
Sela thinningSel = Leptonica1.selaMakeThinSets(1, 0);
|
||||||
|
|
||||||
|
|
||||||
@ -46,6 +43,14 @@ public class StrokeWidthCalculator implements Closeable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static boolean wordImageHasMinimumPixelDensity(Pix wordImage) {
|
||||||
|
|
||||||
|
IntBuffer pixelCount = IntBuffer.allocate(1);
|
||||||
|
Leptonica1.pixCountPixels(wordImage, pixelCount, null);
|
||||||
|
return (double) pixelCount.get(0) / (wordImage.w * wordImage.h) >= MINIMUM_PIXEL_DENSITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean hasLargerStrokeWidth(Pix pix, double strokeWidth, double threshold) {
|
public boolean hasLargerStrokeWidth(Pix pix, double strokeWidth, double threshold) {
|
||||||
|
|
||||||
int roundedStrokeWidth = (int) Math.round(strokeWidth);
|
int roundedStrokeWidth = (int) Math.round(strokeWidth);
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package com.knecon.fforesight.service.ocr.processor.visualizations;
|
|||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.geom.Line2D;
|
import java.awt.geom.Line2D;
|
||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.nio.IntBuffer;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -37,6 +36,7 @@ import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.FontS
|
|||||||
import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.ImageProcessingPipeline;
|
import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.ImageProcessingPipeline;
|
||||||
import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.ImageProcessingSupervisor;
|
import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.ImageProcessingSupervisor;
|
||||||
import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.BBoxSnuggificationService;
|
import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.BBoxSnuggificationService;
|
||||||
|
import com.knecon.fforesight.service.ocr.processor.service.imageprocessing.StrokeWidthCalculator;
|
||||||
import com.knecon.fforesight.service.ocr.processor.visualizations.fonts.FontMetricsProvider;
|
import com.knecon.fforesight.service.ocr.processor.visualizations.fonts.FontMetricsProvider;
|
||||||
import com.knecon.fforesight.service.ocr.processor.visualizations.fonts.FontStyle;
|
import com.knecon.fforesight.service.ocr.processor.visualizations.fonts.FontStyle;
|
||||||
import com.knecon.fforesight.service.ocr.processor.visualizations.fonts.Type0FontMetricsProvider;
|
import com.knecon.fforesight.service.ocr.processor.visualizations.fonts.Type0FontMetricsProvider;
|
||||||
@ -185,10 +185,7 @@ public class WritableOcrResultFactory {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
IntBuffer pixelCount = IntBuffer.allocate(1);
|
if (StrokeWidthCalculator.wordImageHasMinimumPixelDensity(wordImage)) {
|
||||||
Leptonica1.pixCountPixels(wordImage, pixelCount, null);
|
|
||||||
|
|
||||||
if (pixelCount.get(0) > 3) {
|
|
||||||
fontStyleDetector.add(textPosition, wordImage, textPosition.getFontSizeByHeight());
|
fontStyleDetector.add(textPosition, wordImage, textPosition.getFontSizeByHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,9 @@ azure:
|
|||||||
|
|
||||||
logging.type: ${LOGGING_TYPE:CONSOLE}
|
logging.type: ${LOGGING_TYPE:CONSOLE}
|
||||||
|
|
||||||
ocrService.sendStatusUpdates: false
|
ocrService:
|
||||||
|
sendStatusUpdates: false
|
||||||
|
debug: true
|
||||||
|
|
||||||
management:
|
management:
|
||||||
endpoint:
|
endpoint:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user