From 6e75ca95fe8aeea7ebe020056fad623a40d9d7ef Mon Sep 17 00:00:00 2001 From: litongjava Date: Sat, 9 Dec 2023 16:50:14 -1000 Subject: [PATCH 1/6] Refactoring Code to MainForm --- ...53\351\200\237\344\270\212\346\211\213.md" | 2 +- pom.xml | 2 +- src/main/java/com/luooqi/ocr/OcrApp.java | 67 +++++++++++++++ .../luooqi/ocr/local/PaddlePaddleOCRV4.java | 5 ++ .../com/luooqi/ocr/snap/ScreenCapture.java | 52 ++++++------ .../java/com/luooqi/ocr/utils/CommUtils.java | 83 ++++++++++--------- .../luooqi/ocr/utils/GlobalKeyListener.java | 7 +- .../{MainFm.java => windows/MainForm.java} | 45 ++++------ 8 files changed, 166 insertions(+), 97 deletions(-) create mode 100644 src/main/java/com/luooqi/ocr/OcrApp.java rename src/main/java/com/luooqi/ocr/{MainFm.java => windows/MainForm.java} (89%) diff --git "a/docs/cn/1 \345\277\253\351\200\237\344\270\212\346\211\213/1.0 \345\277\253\351\200\237\344\270\212\346\211\213.md" "b/docs/cn/1 \345\277\253\351\200\237\344\270\212\346\211\213/1.0 \345\277\253\351\200\237\344\270\212\346\211\213.md" index 7fb5562..8d9a077 100644 --- "a/docs/cn/1 \345\277\253\351\200\237\344\270\212\346\211\213/1.0 \345\277\253\351\200\237\344\270\212\346\211\213.md" +++ "b/docs/cn/1 \345\277\253\351\200\237\344\270\212\346\211\213/1.0 \345\277\253\351\200\237\344\270\212\346\211\213.md" @@ -92,7 +92,7 @@ mvn clean package -DskipTests 以下是一个基本的 `jpackage` 命令示例,用于将 JavaFX JAR 打包为 `.exe` 文件: ```bash -jpackage --type exe --input target/ --main-jar tool-ocr-1.2.6.jar --name tree-hole-ocr --main-class com.luooqi.ocr.MainFm +jpackage --type exe --input target/ --main-jar tool-ocr-1.2.6.jar --name tree-hole-ocr --main-class com.luooqi.ocr.OcrApp ``` 其中: diff --git a/pom.xml b/pom.xml index ba47252..966706f 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ 1.8 ${java.version} ${java.version} - com.luooqi.ocr.MainFm + com.luooqi.ocr.OcrApp 1.18.20 1.2.3 0.25.0 diff --git a/src/main/java/com/luooqi/ocr/OcrApp.java b/src/main/java/com/luooqi/ocr/OcrApp.java new file mode 100644 index 0000000..8c63775 --- /dev/null +++ b/src/main/java/com/luooqi/ocr/OcrApp.java @@ -0,0 +1,67 @@ +package com.luooqi.ocr; + +import cn.hutool.core.util.StrUtil; +import cn.hutool.log.StaticLog; +import com.luooqi.ocr.config.InitConfig; +import com.luooqi.ocr.controller.ProcessController; +import com.luooqi.ocr.local.PaddlePaddleOCRV4; +import com.luooqi.ocr.model.CaptureInfo; +import com.luooqi.ocr.model.StageInfo; +import com.luooqi.ocr.snap.ScreenCapture; +import com.luooqi.ocr.utils.CommUtils; +import com.luooqi.ocr.utils.OcrUtils; +import com.luooqi.ocr.windows.MainForm; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.beans.property.SimpleStringProperty; +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.TextArea; +import javafx.scene.control.ToolBar; +import javafx.scene.input.Clipboard; +import javafx.scene.input.DataFormat; +import javafx.scene.layout.*; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.scene.text.FontPosture; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import lombok.extern.slf4j.Slf4j; +import org.jnativehook.GlobalScreen; +import org.slf4j.LoggerFactory; +import sun.applet.Main; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class OcrApp extends Application { + + public static void main(String[] args) { + launch(args); + } + + @Override + public void init() throws Exception { + super.init(); + InitConfig.init(); + } + + + @Override + public void start(Stage primaryStage) { + MainForm mainForm = new MainForm(); + mainForm.init(primaryStage); + primaryStage.show(); + + } + + @Override + public void stop() throws Exception { + GlobalScreen.unregisterNativeHook(); + PaddlePaddleOCRV4.INSTANCE.close(); + } +} \ No newline at end of file diff --git a/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java b/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java index 33b9c98..e61b1cf 100644 --- a/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java +++ b/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java @@ -105,4 +105,9 @@ public String ocr(File imageFile) throws Exception { } return fullText.toString(); } + + public void close() { + detector.close(); + recognizer.close(); + } } diff --git a/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java b/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java index 0a64f11..4369dec 100644 --- a/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java +++ b/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java @@ -3,9 +3,10 @@ import cn.hutool.core.swing.ScreenUtil; import cn.hutool.log.StaticLog; -import com.luooqi.ocr.MainFm; +import com.luooqi.ocr.OcrApp; import com.luooqi.ocr.model.CaptureInfo; import com.luooqi.ocr.utils.CommUtils; +import com.luooqi.ocr.windows.MainForm; import javafx.animation.AnimationTimer; import javafx.application.Platform; import javafx.embed.swing.SwingFXUtils; @@ -22,6 +23,7 @@ import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.stage.Stage; +import sun.applet.Main; import java.awt.*; import java.awt.image.BufferedImage; @@ -180,8 +182,7 @@ public ScreenCapture(Stage mainStage) { mainCanvas.setOnMouseDragged(m -> { if (m.getButton() == MouseButton.PRIMARY) { - if (m.getScreenX() >= CaptureInfo.ScreenMinX && - m.getScreenX() <= CaptureInfo.ScreenMaxX) { + if (m.getScreenX() >= CaptureInfo.ScreenMinX && m.getScreenX() <= CaptureInfo.ScreenMaxX) { data.mouseXNow = (int) m.getX(); } else if (m.getScreenX() > CaptureInfo.ScreenMaxX) { data.mouseXNow = CaptureInfo.ScreenWidth; @@ -311,8 +312,7 @@ private void addKeyHandlers() { } }); - data.anyPressed.addListener((obs, wasPressed, isNowPressed) -> - { + data.anyPressed.addListener((obs, wasPressed, isNowPressed) -> { if (isNowPressed) { yPressedAnimation.start(); } else { @@ -355,22 +355,23 @@ private void repaintCanvas() { // smart calculation of where the mouse has been dragged data.rectWidth = (data.mouseXNow > data.mouseXPressed) ? data.mouseXNow - data.mouseXPressed // RIGHT - : data.mouseXPressed - data.mouseXNow // LEFT + : data.mouseXPressed - data.mouseXNow // LEFT ; data.rectHeight = (data.mouseYNow > data.mouseYPressed) ? data.mouseYNow - data.mouseYPressed // DOWN - : data.mouseYPressed - data.mouseYNow // UP + : data.mouseYPressed - data.mouseYNow // UP ; data.rectUpperLeftX = // -------->UPPER_LEFT_X - (data.mouseXNow > data.mouseXPressed) ? data.mouseXPressed // RIGHT - : data.mouseXNow// LEFT + (data.mouseXNow > data.mouseXPressed) ? data.mouseXPressed // RIGHT + : data.mouseXNow// LEFT ; data.rectUpperLeftY = // -------->UPPER_LEFT_Y - (data.mouseYNow > data.mouseYPressed) ? data.mouseYPressed // DOWN - : data.mouseYNow // UP + (data.mouseYNow > data.mouseYPressed) ? data.mouseYPressed // DOWN + : data.mouseYNow // UP ; - gc.strokeRect(data.rectUpperLeftX - 1.00, data.rectUpperLeftY - 1.00, data.rectWidth + 2.00, data.rectHeight + 2.00); + gc.strokeRect(data.rectUpperLeftX - 1.00, data.rectUpperLeftY - 1.00, data.rectWidth + 2.00, + data.rectHeight + 2.00); gc.clearRect(data.rectUpperLeftX, data.rectUpperLeftY, data.rectWidth, data.rectHeight); // draw the text @@ -378,10 +379,11 @@ private void repaintCanvas() { double middle = data.rectUpperLeftX + data.rectWidth / 2.00; gc.setLineWidth(1); gc.setFill(Color.FIREBRICK); - gc.fillRect(middle - 77, data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 2 : data.rectUpperLeftY - 18.00, 100, 18); + gc.fillRect(middle - 77, data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 2 : data.rectUpperLeftY - 18.00, 100, + 18); gc.setFill(Color.WHITE); gc.fillText(data.rectWidth + " * " + data.rectHeight, middle - 77 + 9, - data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 17.00 : data.rectUpperLeftY - 4.00); + data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 17.00 : data.rectUpperLeftY - 4.00); } } @@ -398,16 +400,16 @@ private void selectWholeScreen() { public void prepareForCapture() { isSnapping = true; - MainFm.stage.setOpacity(0.0f); + MainForm.stage.setOpacity(0.0f); Platform.runLater(() -> { - Rectangle rectangle = CommUtils.getDisplayScreen(MainFm.stage); + Rectangle rectangle = CommUtils.getDisplayScreen(MainForm.stage); data.reset(); CaptureInfo.ScreenMinX = rectangle.x; CaptureInfo.ScreenMaxX = rectangle.x + rectangle.width; CaptureInfo.ScreenWidth = rectangle.width; CaptureInfo.ScreenHeight = rectangle.height; BufferedImage bufferedImage = ScreenUtil.captureScreen(rectangle); - //bufferedImage = Scalr.resize(bufferedImage, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, CaptureInfo.ScreenWidth * 2, CaptureInfo.ScreenHeight * 2); + // bufferedImage = Scalr.resize(bufferedImage, Scalr.Method.QUALITY, Scalr.Mode.AUTOMATIC, CaptureInfo.ScreenWidth * 2, CaptureInfo.ScreenHeight * 2); WritableImage fxImage = SwingFXUtils.toFXImage(bufferedImage, null); deActivateAllKeys(); scene.setRoot(new Pane()); @@ -417,9 +419,9 @@ public void prepareForCapture() { mainCanvas.setHeight(CaptureInfo.ScreenHeight); mainCanvas.setCursor(Cursor.CROSSHAIR); initGraphContent(); - rootPane.setBackground(new Background(new BackgroundImage(fxImage, - BackgroundRepeat.NO_REPEAT, BackgroundRepeat.NO_REPEAT, - BackgroundPosition.CENTER, new BackgroundSize(CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, false, false, true, true)))); + rootPane.setBackground(new Background(new BackgroundImage(fxImage, BackgroundRepeat.NO_REPEAT, + BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER, + new BackgroundSize(CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, false, false, true, true)))); repaintCanvas(); stage.setScene(scene); stage.setFullScreenExitHint(""); @@ -438,19 +440,21 @@ private void prepareImage() { BufferedImage image; try { mainCanvas.setDisable(true); - image = new Robot().createScreenCapture(new Rectangle(data.rectUpperLeftX + CaptureInfo.ScreenMinX, data.rectUpperLeftY + (int) CommUtils.getCrtScreen(stage).getVisualBounds().getMinY(), data.rectWidth, data.rectHeight)); + image = new Robot().createScreenCapture(new Rectangle(data.rectUpperLeftX + CaptureInfo.ScreenMinX, + data.rectUpperLeftY + (int) CommUtils.getCrtScreen(stage).getVisualBounds().getMinY(), data.rectWidth, + data.rectHeight)); } catch (AWTException ex) { StaticLog.error(ex); return; } finally { mainCanvas.setDisable(false); - MainFm.restore(false); + MainForm.restore(false); } - MainFm.doOcr(image); + MainForm.doOcr(image); } public void cancelSnap() { deActivateAllKeys(); - MainFm.restore(true); + MainForm.restore(true); } } diff --git a/src/main/java/com/luooqi/ocr/utils/CommUtils.java b/src/main/java/com/luooqi/ocr/utils/CommUtils.java index 9431c13..7f36730 100644 --- a/src/main/java/com/luooqi/ocr/utils/CommUtils.java +++ b/src/main/java/com/luooqi/ocr/utils/CommUtils.java @@ -1,18 +1,44 @@ package com.luooqi.ocr.utils; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.MemoryCacheImageOutputStream; +import javax.swing.ImageIcon; + +import com.luooqi.ocr.OcrApp; +import com.luooqi.ocr.model.TextBlock; + import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.http.HttpRequest; import cn.hutool.http.HttpResponse; import cn.hutool.http.HttpUtil; import cn.hutool.log.StaticLog; -import com.luooqi.ocr.MainFm; -import com.luooqi.ocr.model.TextBlock; import javafx.geometry.Insets; import javafx.geometry.Orientation; import javafx.geometry.Rectangle2D; import javafx.scene.control.Button; -import javafx.scene.control.*; +import javafx.scene.control.ButtonBase; +import javafx.scene.control.Separator; +import javafx.scene.control.ToggleButton; +import javafx.scene.control.ToggleGroup; +import javafx.scene.control.Tooltip; import javafx.scene.layout.Background; import javafx.scene.layout.BackgroundFill; import javafx.scene.layout.CornerRadii; @@ -21,31 +47,12 @@ import javafx.stage.Screen; import javafx.stage.Stage; -import javax.imageio.IIOImage; -import javax.imageio.ImageIO; -import javax.imageio.ImageWriteParam; -import javax.imageio.ImageWriter; -import javax.imageio.stream.MemoryCacheImageOutputStream; -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.lang.reflect.Method; -import java.net.URL; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - public class CommUtils { public static final Paint MASK_COLOR = Color.rgb(0, 0, 0, 0.4); public static final int BUTTON_SIZE = 28; - public static Background BG_TRANSPARENT = new Background(new BackgroundFill(Color.TRANSPARENT, - CornerRadii.EMPTY, Insets.EMPTY)); + public static Background BG_TRANSPARENT = new Background( + new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)); private static Pattern NORMAL_CHAR = Pattern.compile("[\\u4e00-\\u9fa5\\w、-,/|_]"); public static Separator SEPARATOR = new Separator(Orientation.VERTICAL); private static final float IMAGE_QUALITY = 0.5f; @@ -97,7 +104,7 @@ static String combineTextBlocks(List textBlocks, boolean isEng) { int maxX = -1; double maxAngle = -100; for (TextBlock textBlock : textBlocks) { - //System.out.println(textBlock.getAngle()+ "\t" + textBlock.getFontSize()); + // System.out.println(textBlock.getAngle()+ "\t" + textBlock.getFontSize()); if (textBlock.getTopLeft().x < minX) { minX = textBlock.getTopLeft().x; minBlock = textBlock; @@ -138,10 +145,8 @@ static String combineTextBlocks(List textBlocks, boolean isEng) { continue; } String endTxt = blockTxt.substring(blockTxt.length() - 1); - if (maxX - lastBlock.getTopRight().x >= CHAR_WIDTH * 2 || - !NORMAL_CHAR.matcher(endTxt).find() || - (NORMAL_CHAR.matcher(endTxt).find() && - (firstBlock.getTopLeft().x - minX) > CHAR_WIDTH * 2)) { + if (maxX - lastBlock.getTopRight().x >= CHAR_WIDTH * 2 || !NORMAL_CHAR.matcher(endTxt).find() + || (NORMAL_CHAR.matcher(endTxt).find() && (firstBlock.getTopLeft().x - minX) > CHAR_WIDTH * 2)) { sb.append("\n"); for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) { if (i % 2 == 0) { @@ -149,7 +154,8 @@ static String combineTextBlocks(List textBlocks, boolean isEng) { } } } else { - if (CharUtil.isLetterOrNumber(endTxt.charAt(0)) && CharUtil.isLetterOrNumber(firstBlock.getText().charAt(0))) { + if (CharUtil.isLetterOrNumber(endTxt.charAt(0)) + && CharUtil.isLetterOrNumber(firstBlock.getText().charAt(0))) { sb.append(" "); } } @@ -165,8 +171,8 @@ static String combineTextBlocks(List textBlocks, boolean isEng) { TextBlock text = line.get(i); String ocrText = text.getText(); if (i > 0) { - for (int a = 0, ln = (text.getTopLeft().x - line.get(i - 1).getTopRight().x) / (CHAR_WIDTH * 2); - a < ln; a++) { + for (int a = 0, + ln = (text.getTopLeft().x - line.get(i - 1).getTopRight().x) / (CHAR_WIDTH * 2); a < ln; a++) { sb.append(" "); } } @@ -252,15 +258,15 @@ private static void initButton(ButtonBase button, String id, int size, Runnable public static void initStage(Stage stage) { try { if (CommUtils.IS_MAC_OS) { - URL iconURL = MainFm.class.getResource("/img/logo.png"); + URL iconURL = OcrApp.class.getResource("/img/logo.png"); java.awt.Image image = new ImageIcon(iconURL).getImage(); Class appleApp = Class.forName("com.apple.eawt.Application"); - //noinspection unchecked + // noinspection unchecked Method getApplication = appleApp.getMethod("getApplication"); Object application = getApplication.invoke(appleApp); Class[] params = new Class[1]; params[0] = java.awt.Image.class; - //noinspection unchecked + // noinspection unchecked Method setDockIconImage = appleApp.getMethod("setDockIconImage", params); setDockIconImage.invoke(application, image); } @@ -268,7 +274,7 @@ public static void initStage(Stage stage) { StaticLog.error(e); } stage.setTitle("树洞OCR文字识别"); - stage.getIcons().add(new javafx.scene.image.Image(MainFm.class.getResource("/img/logo.png").toExternalForm())); + stage.getIcons().add(new javafx.scene.image.Image(OcrApp.class.getResource("/img/logo.png").toExternalForm())); } private static final Pattern SCALE_PATTERN = Pattern.compile("renderScale:([\\d.]+)"); @@ -276,9 +282,8 @@ public static void initStage(Stage stage) { public static Rectangle getDisplayScreen(Stage stage) { Screen crtScreen = getCrtScreen(stage); Rectangle2D rectangle2D = crtScreen.getBounds(); - return new Rectangle((int) rectangle2D.getMinX(), (int) rectangle2D.getMinY(), - (int) rectangle2D.getWidth(), - (int) rectangle2D.getHeight()); + return new Rectangle((int) rectangle2D.getMinX(), (int) rectangle2D.getMinY(), (int) rectangle2D.getWidth(), + (int) rectangle2D.getHeight()); } public static float getScale(Stage stage) { diff --git a/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java b/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java index 26671b6..f64a1cb 100644 --- a/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java +++ b/src/main/java/com/luooqi/ocr/utils/GlobalKeyListener.java @@ -1,8 +1,9 @@ package com.luooqi.ocr.utils; import cn.hutool.log.StaticLog; -import com.luooqi.ocr.MainFm; +import com.luooqi.ocr.OcrApp; import com.luooqi.ocr.snap.ScreenCapture; +import com.luooqi.ocr.windows.MainForm; import org.jnativehook.NativeInputEvent; import org.jnativehook.keyboard.NativeKeyEvent; import org.jnativehook.keyboard.NativeKeyListener; @@ -19,11 +20,11 @@ public void nativeKeyTyped(NativeKeyEvent nativeKeyEvent) { public void nativeKeyPressed(NativeKeyEvent e) { if (e.getKeyCode() == NativeKeyEvent.VC_F4) { preventEvent(e); - MainFm.screenShotOcr(); + MainForm.screenShotOcr(); } else if (e.getKeyCode() == NativeKeyEvent.VC_ESCAPE) { if (ScreenCapture.isSnapping) { preventEvent(e); - MainFm.cancelSnap(); + MainForm.cancelSnap(); } } } diff --git a/src/main/java/com/luooqi/ocr/MainFm.java b/src/main/java/com/luooqi/ocr/windows/MainForm.java similarity index 89% rename from src/main/java/com/luooqi/ocr/MainFm.java rename to src/main/java/com/luooqi/ocr/windows/MainForm.java index ca26739..63acf95 100644 --- a/src/main/java/com/luooqi/ocr/MainFm.java +++ b/src/main/java/com/luooqi/ocr/windows/MainForm.java @@ -1,8 +1,8 @@ -package com.luooqi.ocr; +package com.luooqi.ocr.windows; -import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; +import com.luooqi.ocr.OcrApp; import com.luooqi.ocr.config.InitConfig; import com.luooqi.ocr.controller.ProcessController; import com.luooqi.ocr.model.CaptureInfo; @@ -10,7 +10,6 @@ import com.luooqi.ocr.snap.ScreenCapture; import com.luooqi.ocr.utils.CommUtils; import com.luooqi.ocr.utils.OcrUtils; -import javafx.application.Application; import javafx.application.Platform; import javafx.beans.property.SimpleStringProperty; import javafx.geometry.Insets; @@ -32,20 +31,14 @@ import java.awt.image.BufferedImage; import java.io.File; -import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; -import static javafx.application.Platform.runLater; - +/** + * Created by litonglinux@qq.com on 12/9/2023_4:40 PM + */ @Slf4j -public class MainFm extends Application { - - public static void main(String[] args) { - InitConfig.init(); - launch(args); - } - +public class MainForm { private static StageInfo stageInfo; public static Stage stage; private static Scene mainScene; @@ -55,8 +48,8 @@ public static void main(String[] args) { //private static boolean isSegment = true; //private static String ocrText = ""; - @Override - public void start(Stage primaryStage) { + public void init(Stage primaryStage) { + log.info("primaryStage:{}", primaryStage); stage = primaryStage; setAutoResize(); @@ -76,8 +69,8 @@ public void start(Stage primaryStage) { // }); HBox topBar = new HBox( - CommUtils.createButton("snapBtn", MainFm::screenShotOcr, "截图"), - CommUtils.createButton("openImageBtn", MainFm::openImageOcr, "打开"), + CommUtils.createButton("snapBtn", MainForm::screenShotOcr, "截图"), + CommUtils.createButton("openImageBtn", this::openImageOcr, "打开"), CommUtils.createButton("copyBtn", this::copyText, "复制"), CommUtils.createButton("pasteBtn", this::pasteText, "粘贴"), CommUtils.createButton("clearBtn", this::clearText, "清空"), @@ -112,8 +105,6 @@ public void start(Stage primaryStage) { CommUtils.initStage(primaryStage); mainScene = new Scene(root, 670, 470); stage.setScene(mainScene); - stage.show(); -// InitConfig.after(); } private void setAutoResize() { @@ -134,10 +125,6 @@ private void wrapText() { textArea.setWrapText(!textArea.isWrapText()); } - @Override - public void stop() throws Exception { - GlobalScreen.unregisterNativeHook(); - } private void clearText() { textArea.setText(""); @@ -170,13 +157,13 @@ public static void screenShotOcr() { stageInfo.setWidth(stage.getWidth()); stageInfo.setHeight(stage.getHeight()); stageInfo.setFullScreenState(stage.isFullScreen()); - runLater(screenCapture::prepareForCapture); + Platform.runLater(screenCapture::prepareForCapture); } /** * 打开图片 */ - private static void openImageOcr() { + private void openImageOcr() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Please Select Image File"); fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg")); @@ -186,7 +173,7 @@ private static void openImageOcr() { } stageInfo = new StageInfo(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight(), stage.isFullScreen()); - MainFm.stage.close(); + stage.close(); try { //BufferedImage image = ImageIO.read(selectedFile); doOcr(selectedFile); @@ -197,7 +184,7 @@ private static void openImageOcr() { public static void cancelSnap() { - runLater(screenCapture::cancelSnap); + Platform.runLater(screenCapture::cancelSnap); } public static void doOcr(BufferedImage image) { @@ -225,7 +212,7 @@ public static void doOcr(BufferedImage image) { } public static void doOcr(File selectedFile) { - org.slf4j.Logger log = LoggerFactory.getLogger(MainFm.class); + org.slf4j.Logger log = LoggerFactory.getLogger(OcrApp.class); processController.setX(CaptureInfo.ScreenMinX + (CaptureInfo.ScreenWidth - 300) / 2); processController.setY(250); processController.show(); @@ -266,4 +253,4 @@ public static void restore(boolean focus) { stage.setOpacity(0.0f); } } -} \ No newline at end of file +} From c3930c2185faff820ecf0bfb9993813c51ed2c69 Mon Sep 17 00:00:00 2001 From: litongjava Date: Sat, 9 Dec 2023 17:27:28 -1000 Subject: [PATCH 2/6] remove state.close when openfile --- src/main/java/com/luooqi/ocr/windows/MainForm.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/luooqi/ocr/windows/MainForm.java b/src/main/java/com/luooqi/ocr/windows/MainForm.java index 63acf95..a04a064 100644 --- a/src/main/java/com/luooqi/ocr/windows/MainForm.java +++ b/src/main/java/com/luooqi/ocr/windows/MainForm.java @@ -173,7 +173,7 @@ private void openImageOcr() { } stageInfo = new StageInfo(stage.getX(), stage.getY(), stage.getWidth(), stage.getHeight(), stage.isFullScreen()); - stage.close(); + try { //BufferedImage image = ImageIO.read(selectedFile); doOcr(selectedFile); From 44e01f3ba28bc0bc49cced8d0e88e59449e7ecc9 Mon Sep 17 00:00:00 2001 From: litongjava Date: Sat, 9 Dec 2023 17:44:18 -1000 Subject: [PATCH 3/6] change to use the GlobalThreadPool --- src/main/java/com/luooqi/ocr/OcrApp.java | 3 +++ .../java/com/luooqi/ocr/windows/MainForm.java | 19 ++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/luooqi/ocr/OcrApp.java b/src/main/java/com/luooqi/ocr/OcrApp.java index 8c63775..e2f5d30 100644 --- a/src/main/java/com/luooqi/ocr/OcrApp.java +++ b/src/main/java/com/luooqi/ocr/OcrApp.java @@ -1,5 +1,6 @@ package com.luooqi.ocr; +import cn.hutool.core.thread.GlobalThreadPool; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; import com.luooqi.ocr.config.InitConfig; @@ -61,7 +62,9 @@ public void start(Stage primaryStage) { @Override public void stop() throws Exception { + log.info("close"); GlobalScreen.unregisterNativeHook(); PaddlePaddleOCRV4.INSTANCE.close(); + GlobalThreadPool.shutdown(true); } } \ No newline at end of file diff --git a/src/main/java/com/luooqi/ocr/windows/MainForm.java b/src/main/java/com/luooqi/ocr/windows/MainForm.java index a04a064..bbc95d8 100644 --- a/src/main/java/com/luooqi/ocr/windows/MainForm.java +++ b/src/main/java/com/luooqi/ocr/windows/MainForm.java @@ -1,5 +1,6 @@ package com.luooqi.ocr.windows; +import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; import com.luooqi.ocr.OcrApp; @@ -42,6 +43,12 @@ public class MainForm { private static StageInfo stageInfo; public static Stage stage; private static Scene mainScene; + + @Override + public int hashCode() { + return super.hashCode(); + } + private static ScreenCapture screenCapture; private static ProcessController processController; private static TextArea textArea; @@ -191,7 +198,8 @@ public static void doOcr(BufferedImage image) { processController.setX(CaptureInfo.ScreenMinX + (CaptureInfo.ScreenWidth - 300) / 2); processController.setY(250); processController.show(); - Thread ocrThread = new Thread(() -> { + + ThreadUtil.execute(() -> { byte[] bytes = CommUtils.imageToBytes(image); String text = null; try { @@ -207,16 +215,13 @@ public static void doOcr(BufferedImage image) { restore(true); }); }); - ocrThread.setDaemon(false); - ocrThread.start(); } public static void doOcr(File selectedFile) { - org.slf4j.Logger log = LoggerFactory.getLogger(OcrApp.class); processController.setX(CaptureInfo.ScreenMinX + (CaptureInfo.ScreenWidth - 300) / 2); processController.setY(250); processController.show(); - Thread ocrThread = new Thread(() -> { + ThreadUtil.execute(() -> { String text = null; try { text = OcrUtils.recImgLocal(selectedFile); @@ -224,18 +229,14 @@ public static void doOcr(File selectedFile) { text = e.getMessage(); e.printStackTrace(); } - //log.info("识别结果:{}", text); String finalText = text; Platform.runLater(() -> { processController.close(); textArea.setText(finalText); - restore(true); }); }); - ocrThread.setDaemon(false); - ocrThread.start(); } public static void restore(boolean focus) { From d02ed481b666e87c79144ad453746ba7aefbd060 Mon Sep 17 00:00:00 2001 From: litongjava Date: Sat, 9 Dec 2023 19:23:38 -1000 Subject: [PATCH 4/6] support ocr pdf file --- pom.xml | 36 +++++---- readme.md | 1 + .../v4/detection/OCRDetectionTranslator.java | 30 +++++--- .../ocr/v4/recognition/OcrV4Recognition.java | 3 + src/main/java/com/luooqi/ocr/OcrApp.java | 30 -------- .../luooqi/ocr/constants/ImagesConstants.java | 8 ++ .../java/com/luooqi/ocr/local/LocalOCR.java | 49 ------------ .../luooqi/ocr/local/PaddlePaddleOCRV4.java | 8 ++ .../com/luooqi/ocr/snap/ScreenCapture.java | 28 ++++--- .../luooqi/ocr/utils/BufferedImageUtils.java | 28 +++++++ .../java/com/luooqi/ocr/utils/CommUtils.java | 18 +++-- .../java/com/luooqi/ocr/utils/OcrUtils.java | 46 +++++++++++- .../java/com/luooqi/ocr/windows/MainForm.java | 75 ++++++++++++------- .../com/luooqi/ocr/utils/OcrUtilsTest.java | 51 ++++++++----- .../java/com/luooqi/ocr/utils/PdfTest.java | 28 +++++++ 15 files changed, 260 insertions(+), 179 deletions(-) create mode 100644 src/main/java/com/luooqi/ocr/constants/ImagesConstants.java delete mode 100644 src/main/java/com/luooqi/ocr/local/LocalOCR.java create mode 100644 src/main/java/com/luooqi/ocr/utils/BufferedImageUtils.java create mode 100644 src/test/java/com/luooqi/ocr/utils/PdfTest.java diff --git a/pom.xml b/pom.xml index 966706f..2bcb107 100644 --- a/pom.xml +++ b/pom.xml @@ -39,6 +39,12 @@ 4.2 + + org.apache.pdfbox + pdfbox + 2.0.24 + + org.projectlombok lombok @@ -96,21 +102,21 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/readme.md b/readme.md index d092be0..25bcab0 100644 --- a/readme.md +++ b/readme.md @@ -49,6 +49,7 @@ mvn jfx:native -DskipTests -f pom.xml ![2](readme_files/2.jpg) ## TODO +- [x] PDF识别 - [x] 图片文字识别 - [x] 识别结果文本对齐(暂未实现多分栏) - [x] 全屏模式下截图 diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OCRDetectionTranslator.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OCRDetectionTranslator.java index bb0c944..ee59fdb 100644 --- a/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OCRDetectionTranslator.java +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/detection/OCRDetectionTranslator.java @@ -104,18 +104,20 @@ public NDList processOutput(TranslatorContext ctx, NDList list) { srcMat.release(); } + NDList dt_boxes = null; NDArray boxes = boxes_from_bitmap(manager, pred, newMask); + if (boxes != null) { + //boxes[:, :, 0] = boxes[:, :, 0] / ratio_w + NDArray boxes1 = boxes.get(":, :, 0").div(ratio_w); + boxes.set(new NDIndex(":, :, 0"), boxes1); + //boxes[:, :, 1] = boxes[:, :, 1] / ratio_h + NDArray boxes2 = boxes.get(":, :, 1").div(ratio_h); + boxes.set(new NDIndex(":, :, 1"), boxes2); - //boxes[:, :, 0] = boxes[:, :, 0] / ratio_w - NDArray boxes1 = boxes.get(":, :, 0").div(ratio_w); - boxes.set(new NDIndex(":, :, 0"), boxes1); - //boxes[:, :, 1] = boxes[:, :, 1] / ratio_h - NDArray boxes2 = boxes.get(":, :, 1").div(ratio_h); - boxes.set(new NDIndex(":, :, 1"), boxes2); + dt_boxes = this.filter_tag_det_res(boxes); - NDList dt_boxes = this.filter_tag_det_res(boxes); - - dt_boxes.detach(); + dt_boxes.detach(); + } // release Mat newMask.release(); @@ -255,12 +257,18 @@ private NDArray boxes_from_bitmap(NDManager manager, NDArray pred, Mat bitmap) { newContour.release(); } - NDArray boxes = NDArrays.stack(boxList); - // release hierarchy.release(); + NDArray boxes = null; + if (boxList.size() > 0) { + boxes = NDArrays.stack(boxList); + return boxes; + } + return boxes; + + } /** diff --git a/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/OcrV4Recognition.java b/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/OcrV4Recognition.java index 9ad7135..9ce8df4 100644 --- a/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/OcrV4Recognition.java +++ b/src/main/java/com/litongjava/djl/paddle/ocr/v4/recognition/OcrV4Recognition.java @@ -64,6 +64,9 @@ public List predict(NDManager manager, Image image, Predictor detector, Predictor recognizer) throws TranslateException { NDList boxes = detector.predict(image); + if (boxes == null) { + return null; + } // 交给 NDManager自动管理内存 // attach to manager for automatic memory management boxes.attach(manager); diff --git a/src/main/java/com/luooqi/ocr/OcrApp.java b/src/main/java/com/luooqi/ocr/OcrApp.java index e2f5d30..7a82e10 100644 --- a/src/main/java/com/luooqi/ocr/OcrApp.java +++ b/src/main/java/com/luooqi/ocr/OcrApp.java @@ -1,42 +1,13 @@ package com.luooqi.ocr; import cn.hutool.core.thread.GlobalThreadPool; -import cn.hutool.core.util.StrUtil; -import cn.hutool.log.StaticLog; import com.luooqi.ocr.config.InitConfig; -import com.luooqi.ocr.controller.ProcessController; import com.luooqi.ocr.local.PaddlePaddleOCRV4; -import com.luooqi.ocr.model.CaptureInfo; -import com.luooqi.ocr.model.StageInfo; -import com.luooqi.ocr.snap.ScreenCapture; -import com.luooqi.ocr.utils.CommUtils; -import com.luooqi.ocr.utils.OcrUtils; import com.luooqi.ocr.windows.MainForm; import javafx.application.Application; -import javafx.application.Platform; -import javafx.beans.property.SimpleStringProperty; -import javafx.geometry.Insets; -import javafx.scene.Scene; -import javafx.scene.control.Label; -import javafx.scene.control.TextArea; -import javafx.scene.control.ToolBar; -import javafx.scene.input.Clipboard; -import javafx.scene.input.DataFormat; -import javafx.scene.layout.*; -import javafx.scene.paint.Color; -import javafx.scene.text.Font; -import javafx.scene.text.FontPosture; -import javafx.stage.FileChooser; import javafx.stage.Stage; import lombok.extern.slf4j.Slf4j; import org.jnativehook.GlobalScreen; -import org.slf4j.LoggerFactory; -import sun.applet.Main; - -import java.awt.image.BufferedImage; -import java.io.File; -import java.util.HashMap; -import java.util.Map; @Slf4j public class OcrApp extends Application { @@ -57,7 +28,6 @@ public void start(Stage primaryStage) { MainForm mainForm = new MainForm(); mainForm.init(primaryStage); primaryStage.show(); - } @Override diff --git a/src/main/java/com/luooqi/ocr/constants/ImagesConstants.java b/src/main/java/com/luooqi/ocr/constants/ImagesConstants.java new file mode 100644 index 0000000..4d15e88 --- /dev/null +++ b/src/main/java/com/luooqi/ocr/constants/ImagesConstants.java @@ -0,0 +1,8 @@ +package com.luooqi.ocr.constants; + +/** + * Created by litonglinux@qq.com on 12/9/2023_7:14 PM + */ +public class ImagesConstants { + public static final String LOGO = "img/logo.png"; +} diff --git a/src/main/java/com/luooqi/ocr/local/LocalOCR.java b/src/main/java/com/luooqi/ocr/local/LocalOCR.java deleted file mode 100644 index f7690c5..0000000 --- a/src/main/java/com/luooqi/ocr/local/LocalOCR.java +++ /dev/null @@ -1,49 +0,0 @@ -//package com.luooqi.ocr.local; -// -//import cn.hutool.log.StaticLog; -//import com.benjaminwan.ocrlibrary.OcrEngine; -//import com.litongjava.jfinal.aop.Aop; -//import com.litongjava.project.config.ConfigKeys; -//import com.litongjava.project.config.ProjectConfig; -//import com.luooqi.ocr.utils.LibraryUtils; -// -//public enum LocalOCR { -// INSTANCE; -// -// private final OcrEngine ocrEngine; -// -// LocalOCR() { -// ProjectConfig projectConfig = Aop.get(ProjectConfig.class); -// String libPath = projectConfig.getStr(ConfigKeys.libPath); -// -// String modelsDir = projectConfig.getStr(ConfigKeys.modelsDir); -// String detName = projectConfig.getStr(ConfigKeys.detName); -// String clsName = projectConfig.getStr(ConfigKeys.clsName); -// String recName = projectConfig.getStr(ConfigKeys.recName); -// String keysName = projectConfig.getStr(ConfigKeys.keysName); -// -// LibraryUtils.addLibary(libPath); -// -// this.ocrEngine = new OcrEngine(); -// StaticLog.info("version=" + ocrEngine.getVersion()); -// ocrEngine.setNumThread(4); -// //------- init Logger ------- -// ocrEngine.initLogger(true, false, false); -// //ocrEngine.enableResultText(""); -// ocrEngine.setGpuIndex(-1); -// boolean initModelsRet = ocrEngine.initModels(modelsDir, detName, clsName, recName, keysName); -// if (!initModelsRet) { -// StaticLog.error("Error in models initialization, please check the models/keys path!"); -// return; -// } -// StaticLog.info("padding(%d) boxScoreThresh(%f) boxThresh(%f) unClipRatio(%f) doAngle(%b) mostAngle(%b)", ocrEngine.getPadding(), ocrEngine.getBoxScoreThresh(), ocrEngine.getBoxThresh(), ocrEngine.getUnClipRatio(), ocrEngine.getDoAngle(), ocrEngine.getMostAngle()); -// } -// -// public OcrEngine getOcrEngine() { -// return ocrEngine; -// } -// -// public void useGpu(Boolean isUse) { -// this.ocrEngine.setGpuIndex(isUse ? 0 : -1); -// } -//} \ No newline at end of file diff --git a/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java b/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java index e61b1cf..e0a860f 100644 --- a/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java +++ b/src/main/java/com/luooqi/ocr/local/PaddlePaddleOCRV4.java @@ -9,6 +9,7 @@ import ai.djl.repository.zoo.ModelNotFoundException; import ai.djl.repository.zoo.ModelZoo; import ai.djl.repository.zoo.ZooModel; +import ai.djl.translate.TranslateException; import com.litongjava.djl.paddle.ocr.v4.common.RotatedBox; import com.litongjava.djl.paddle.ocr.v4.common.RotatedBoxCompX; import com.litongjava.djl.paddle.ocr.v4.detection.OcrV4Detection; @@ -62,7 +63,14 @@ public void init() { public String ocr(File imageFile) throws Exception { Path path = imageFile.toPath(); Image image = OpenCVImageFactory.getInstance().fromFile(path); + return ocr(image); + } + + public String ocr(Image image) throws Exception { List detections = recognition.predict(manager, image, detector, recognizer); + if (detections == null) { + return null; + } List initList = new ArrayList<>(); for (RotatedBox result : detections) { diff --git a/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java b/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java index 4369dec..0afbe19 100644 --- a/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java +++ b/src/main/java/com/luooqi/ocr/snap/ScreenCapture.java @@ -3,7 +3,6 @@ import cn.hutool.core.swing.ScreenUtil; import cn.hutool.log.StaticLog; -import com.luooqi.ocr.OcrApp; import com.luooqi.ocr.model.CaptureInfo; import com.luooqi.ocr.utils.CommUtils; import com.luooqi.ocr.windows.MainForm; @@ -23,7 +22,6 @@ import javafx.scene.text.Font; import javafx.scene.text.FontWeight; import javafx.stage.Stage; -import sun.applet.Main; import java.awt.*; import java.awt.image.BufferedImage; @@ -355,23 +353,23 @@ private void repaintCanvas() { // smart calculation of where the mouse has been dragged data.rectWidth = (data.mouseXNow > data.mouseXPressed) ? data.mouseXNow - data.mouseXPressed // RIGHT - : data.mouseXPressed - data.mouseXNow // LEFT + : data.mouseXPressed - data.mouseXNow // LEFT ; data.rectHeight = (data.mouseYNow > data.mouseYPressed) ? data.mouseYNow - data.mouseYPressed // DOWN - : data.mouseYPressed - data.mouseYNow // UP + : data.mouseYPressed - data.mouseYNow // UP ; data.rectUpperLeftX = // -------->UPPER_LEFT_X - (data.mouseXNow > data.mouseXPressed) ? data.mouseXPressed // RIGHT - : data.mouseXNow// LEFT + (data.mouseXNow > data.mouseXPressed) ? data.mouseXPressed // RIGHT + : data.mouseXNow// LEFT ; data.rectUpperLeftY = // -------->UPPER_LEFT_Y - (data.mouseYNow > data.mouseYPressed) ? data.mouseYPressed // DOWN - : data.mouseYNow // UP + (data.mouseYNow > data.mouseYPressed) ? data.mouseYPressed // DOWN + : data.mouseYNow // UP ; gc.strokeRect(data.rectUpperLeftX - 1.00, data.rectUpperLeftY - 1.00, data.rectWidth + 2.00, - data.rectHeight + 2.00); + data.rectHeight + 2.00); gc.clearRect(data.rectUpperLeftX, data.rectUpperLeftY, data.rectWidth, data.rectHeight); // draw the text @@ -380,10 +378,10 @@ private void repaintCanvas() { gc.setLineWidth(1); gc.setFill(Color.FIREBRICK); gc.fillRect(middle - 77, data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 2 : data.rectUpperLeftY - 18.00, 100, - 18); + 18); gc.setFill(Color.WHITE); gc.fillText(data.rectWidth + " * " + data.rectHeight, middle - 77 + 9, - data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 17.00 : data.rectUpperLeftY - 4.00); + data.rectUpperLeftY < 50 ? data.rectUpperLeftY + 17.00 : data.rectUpperLeftY - 4.00); } } @@ -420,8 +418,8 @@ public void prepareForCapture() { mainCanvas.setCursor(Cursor.CROSSHAIR); initGraphContent(); rootPane.setBackground(new Background(new BackgroundImage(fxImage, BackgroundRepeat.NO_REPEAT, - BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER, - new BackgroundSize(CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, false, false, true, true)))); + BackgroundRepeat.NO_REPEAT, BackgroundPosition.CENTER, + new BackgroundSize(CaptureInfo.ScreenWidth, CaptureInfo.ScreenHeight, false, false, true, true)))); repaintCanvas(); stage.setScene(scene); stage.setFullScreenExitHint(""); @@ -441,8 +439,8 @@ private void prepareImage() { try { mainCanvas.setDisable(true); image = new Robot().createScreenCapture(new Rectangle(data.rectUpperLeftX + CaptureInfo.ScreenMinX, - data.rectUpperLeftY + (int) CommUtils.getCrtScreen(stage).getVisualBounds().getMinY(), data.rectWidth, - data.rectHeight)); + data.rectUpperLeftY + (int) CommUtils.getCrtScreen(stage).getVisualBounds().getMinY(), data.rectWidth, + data.rectHeight)); } catch (AWTException ex) { StaticLog.error(ex); return; diff --git a/src/main/java/com/luooqi/ocr/utils/BufferedImageUtils.java b/src/main/java/com/luooqi/ocr/utils/BufferedImageUtils.java new file mode 100644 index 0000000..61a4d23 --- /dev/null +++ b/src/main/java/com/luooqi/ocr/utils/BufferedImageUtils.java @@ -0,0 +1,28 @@ +package com.luooqi.ocr.utils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Created by litonglinux@qq.com on 12/9/2023_6:28 PM + */ +public class BufferedImageUtils { + public static InputStream toInputStream(BufferedImage bufferedImage) { + // 将BufferedImage写入到一个ByteArrayOutputStream + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + ImageIO.write(bufferedImage, "png", baos); // 选择合适的格式,如 "png" 或 "jpg" + } catch (IOException e) { + e.printStackTrace(); + } + + // 使用输出流的字节数组来创建一个InputStream + byte[] imageBytes = baos.toByteArray(); + InputStream inputStream = new ByteArrayInputStream(imageBytes); + return inputStream; + } +} diff --git a/src/main/java/com/luooqi/ocr/utils/CommUtils.java b/src/main/java/com/luooqi/ocr/utils/CommUtils.java index 7f36730..ea260c8 100644 --- a/src/main/java/com/luooqi/ocr/utils/CommUtils.java +++ b/src/main/java/com/luooqi/ocr/utils/CommUtils.java @@ -21,7 +21,9 @@ import javax.imageio.stream.MemoryCacheImageOutputStream; import javax.swing.ImageIcon; +import cn.hutool.core.util.ClassUtil; import com.luooqi.ocr.OcrApp; +import com.luooqi.ocr.constants.ImagesConstants; import com.luooqi.ocr.model.TextBlock; import cn.hutool.core.util.CharUtil; @@ -52,7 +54,7 @@ public class CommUtils { public static final Paint MASK_COLOR = Color.rgb(0, 0, 0, 0.4); public static final int BUTTON_SIZE = 28; public static Background BG_TRANSPARENT = new Background( - new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)); + new BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)); private static Pattern NORMAL_CHAR = Pattern.compile("[\\u4e00-\\u9fa5\\w、-,/|_]"); public static Separator SEPARATOR = new Separator(Orientation.VERTICAL); private static final float IMAGE_QUALITY = 0.5f; @@ -146,7 +148,7 @@ static String combineTextBlocks(List textBlocks, boolean isEng) { } String endTxt = blockTxt.substring(blockTxt.length() - 1); if (maxX - lastBlock.getTopRight().x >= CHAR_WIDTH * 2 || !NORMAL_CHAR.matcher(endTxt).find() - || (NORMAL_CHAR.matcher(endTxt).find() && (firstBlock.getTopLeft().x - minX) > CHAR_WIDTH * 2)) { + || (NORMAL_CHAR.matcher(endTxt).find() && (firstBlock.getTopLeft().x - minX) > CHAR_WIDTH * 2)) { sb.append("\n"); for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) { if (i % 2 == 0) { @@ -155,7 +157,7 @@ static String combineTextBlocks(List textBlocks, boolean isEng) { } } else { if (CharUtil.isLetterOrNumber(endTxt.charAt(0)) - && CharUtil.isLetterOrNumber(firstBlock.getText().charAt(0))) { + && CharUtil.isLetterOrNumber(firstBlock.getText().charAt(0))) { sb.append(" "); } } @@ -172,7 +174,7 @@ static String combineTextBlocks(List textBlocks, boolean isEng) { String ocrText = text.getText(); if (i > 0) { for (int a = 0, - ln = (text.getTopLeft().x - line.get(i - 1).getTopRight().x) / (CHAR_WIDTH * 2); a < ln; a++) { + ln = (text.getTopLeft().x - line.get(i - 1).getTopRight().x) / (CHAR_WIDTH * 2); a < ln; a++) { sb.append(" "); } } @@ -256,9 +258,10 @@ private static void initButton(ButtonBase button, String id, int size, Runnable } public static void initStage(Stage stage) { + try { if (CommUtils.IS_MAC_OS) { - URL iconURL = OcrApp.class.getResource("/img/logo.png"); + URL iconURL = ClassUtil.getClassLoader().getResource(ImagesConstants.LOGO); java.awt.Image image = new ImageIcon(iconURL).getImage(); Class appleApp = Class.forName("com.apple.eawt.Application"); // noinspection unchecked @@ -274,7 +277,8 @@ public static void initStage(Stage stage) { StaticLog.error(e); } stage.setTitle("树洞OCR文字识别"); - stage.getIcons().add(new javafx.scene.image.Image(OcrApp.class.getResource("/img/logo.png").toExternalForm())); + URL iconURL = ClassUtil.getClassLoader().getResource(ImagesConstants.LOGO); + stage.getIcons().add(new javafx.scene.image.Image(iconURL.toExternalForm())); } private static final Pattern SCALE_PATTERN = Pattern.compile("renderScale:([\\d.]+)"); @@ -283,7 +287,7 @@ public static Rectangle getDisplayScreen(Stage stage) { Screen crtScreen = getCrtScreen(stage); Rectangle2D rectangle2D = crtScreen.getBounds(); return new Rectangle((int) rectangle2D.getMinX(), (int) rectangle2D.getMinY(), (int) rectangle2D.getWidth(), - (int) rectangle2D.getHeight()); + (int) rectangle2D.getHeight()); } public static float getScale(Stage stage) { diff --git a/src/main/java/com/luooqi/ocr/utils/OcrUtils.java b/src/main/java/com/luooqi/ocr/utils/OcrUtils.java index 6387134..85264de 100644 --- a/src/main/java/com/luooqi/ocr/utils/OcrUtils.java +++ b/src/main/java/com/luooqi/ocr/utils/OcrUtils.java @@ -1,9 +1,12 @@ package com.luooqi.ocr.utils; +import ai.djl.modality.cv.Image; +import ai.djl.opencv.OpenCVImageFactory; import cn.hutool.core.codec.Base64; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.UUID; import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.HashUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.URLUtil; import cn.hutool.crypto.SecureUtil; @@ -17,9 +20,16 @@ import com.benjaminwan.ocrlibrary.OcrResult; import com.luooqi.ocr.local.PaddlePaddleOCRV4; import com.luooqi.ocr.model.TextBlock; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.rendering.PDFRenderer; +import javax.imageio.ImageIO; import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferInt; import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; import java.util.List; import java.util.*; @@ -36,12 +46,13 @@ public static String recImgLocal(byte[] imgData) { return recImgLocal(file); } + public static String recImgLocal(BufferedImage image) { + byte[] bytes = CommUtils.imageToBytes(image); + return recImgLocal(bytes); + } + public static String recImgLocal(File file) { if (file.exists()) { -// OcrEngine ocrEngine = LocalOCR.INSTANCE.getOcrEngine(); -// OcrResult ocrResult = ocrEngine.detect(file.getAbsolutePath()); -// return extractLocalResult(ocrResult); - //替换为PaddlePaddleOCRV4 try { return PaddlePaddleOCRV4.INSTANCE.ocr(file); } catch (Exception e) { @@ -53,6 +64,33 @@ public static String recImgLocal(File file) { } + public static String recPdfLocal(File pdfFile) { + if (pdfFile.exists()) { + try (PDDocument document = PDDocument.load(pdfFile)) { + PDFRenderer renderer = new PDFRenderer(document); + List ocrResults = new ArrayList<>(); + + for (int i = 0; i < document.getNumberOfPages(); ++i) { + BufferedImage bufferedImage = renderer.renderImageWithDPI(i, 300); + long hashCode = HashUtil.hfHash(pdfFile.getName()); + String filename = "temp_" + hashCode + "_" + i + ".png"; + FileOutputStream fileOutputStream = new FileOutputStream(filename); + ImageIO.write(bufferedImage, "png", fileOutputStream); // 选择合适的格式,如 "png" 或 "jpg" + String text = recImgLocal(new File(filename)); + ocrResults.add(text); + } + // 将所有页面的OCR结果合并为一个字符串 + return String.join("\n", ocrResults); + + } catch (Exception e) { + e.printStackTrace(); + } + return "文件不存在"; + } + return null; + } + + public static String ocrImg(byte[] imgData) { int i = Math.abs(UUID.randomUUID().hashCode()) % 4; StaticLog.info("OCR Engine: " + i); diff --git a/src/main/java/com/luooqi/ocr/windows/MainForm.java b/src/main/java/com/luooqi/ocr/windows/MainForm.java index bbc95d8..23f9519 100644 --- a/src/main/java/com/luooqi/ocr/windows/MainForm.java +++ b/src/main/java/com/luooqi/ocr/windows/MainForm.java @@ -1,9 +1,9 @@ package com.luooqi.ocr.windows; +import cn.hutool.core.io.FileTypeUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.log.StaticLog; -import com.luooqi.ocr.OcrApp; import com.luooqi.ocr.config.InitConfig; import com.luooqi.ocr.controller.ProcessController; import com.luooqi.ocr.model.CaptureInfo; @@ -27,8 +27,6 @@ import javafx.stage.FileChooser; import javafx.stage.Stage; import lombok.extern.slf4j.Slf4j; -import org.jnativehook.GlobalScreen; -import org.slf4j.LoggerFactory; import java.awt.image.BufferedImage; import java.io.File; @@ -75,26 +73,31 @@ public void init(Stage primaryStage) { // isSegment = newValue.getUserData().toString().equals("segmentBtn"); // }); - HBox topBar = new HBox( - CommUtils.createButton("snapBtn", MainForm::screenShotOcr, "截图"), - CommUtils.createButton("openImageBtn", this::openImageOcr, "打开"), - CommUtils.createButton("copyBtn", this::copyText, "复制"), - CommUtils.createButton("pasteBtn", this::pasteText, "粘贴"), - CommUtils.createButton("clearBtn", this::clearText, "清空"), - CommUtils.createButton("wrapBtn", this::wrapText, "换行") - //CommUtils.SEPARATOR, resetBtn, segmentBtn + HBox topBar = getTopBar(); + textArea = getCenter(); + ToolBar footerBar = getFooterBar(); + BorderPane root = new BorderPane(); + root.setTop(topBar); + root.setCenter(textArea); + root.setBottom(footerBar); + root.getStylesheets().addAll( + getClass().getResource("/css/main.css").toExternalForm() ); - topBar.setId("topBar"); - topBar.setMinHeight(40); - topBar.setSpacing(8); - topBar.setPadding(new Insets(6, 8, 6, 8)); + CommUtils.initStage(primaryStage); + mainScene = new Scene(root, 670, 470); + stage.setScene(mainScene); + } - textArea = new TextArea(); + private TextArea getCenter() { + TextArea textArea = new TextArea(); textArea.setId("ocrTextArea"); textArea.setWrapText(true); textArea.setBorder(new Border(new BorderStroke(Color.DARKGRAY, BorderStrokeStyle.SOLID, CornerRadii.EMPTY, BorderWidths.DEFAULT))); textArea.setFont(Font.font("Arial", FontPosture.REGULAR, 14)); + return textArea; + } + private ToolBar getFooterBar() { ToolBar footerBar = new ToolBar(); footerBar.setId("statsToolbar"); Label statsLabel = new Label(); @@ -102,16 +105,24 @@ public void init(Stage primaryStage) { textArea.textProperty().addListener((observable, oldValue, newValue) -> statsProperty.set("总字数:" + newValue.replaceAll(CommUtils.SPECIAL_CHARS, "").length())); statsLabel.textProperty().bind(statsProperty); footerBar.getItems().add(statsLabel); - BorderPane root = new BorderPane(); - root.setTop(topBar); - root.setCenter(textArea); - root.setBottom(footerBar); - root.getStylesheets().addAll( - getClass().getResource("/css/main.css").toExternalForm() + return footerBar; + } + + private HBox getTopBar() { + HBox topBar = new HBox( + CommUtils.createButton("snapBtn", MainForm::screenShotOcr, "截图"), + CommUtils.createButton("openImageBtn", this::openImageOcr, "打开"), + CommUtils.createButton("copyBtn", this::copyText, "复制"), + CommUtils.createButton("pasteBtn", this::pasteText, "粘贴"), + CommUtils.createButton("clearBtn", this::clearText, "清空"), + CommUtils.createButton("wrapBtn", this::wrapText, "换行") + //CommUtils.SEPARATOR, resetBtn, segmentBtn ); - CommUtils.initStage(primaryStage); - mainScene = new Scene(root, 670, 470); - stage.setScene(mainScene); + topBar.setId("topBar"); + topBar.setMinHeight(40); + topBar.setSpacing(8); + topBar.setPadding(new Insets(6, 8, 6, 8)); + return topBar; } private void setAutoResize() { @@ -173,7 +184,8 @@ public static void screenShotOcr() { private void openImageOcr() { FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Please Select Image File"); - fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Image Files", "*.png", "*.jpg")); + String[] extensions = {"*.png", "*.jpg", "*.pdf", "*.PDF"}; + fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Image Files", extensions)); File selectedFile = fileChooser.showOpenDialog(stage); if (selectedFile == null || !selectedFile.isFile()) { return; @@ -200,10 +212,9 @@ public static void doOcr(BufferedImage image) { processController.show(); ThreadUtil.execute(() -> { - byte[] bytes = CommUtils.imageToBytes(image); String text = null; try { - text = OcrUtils.recImgLocal(bytes); + text = OcrUtils.recImgLocal(image); } catch (Exception e) { text = e.getMessage(); } @@ -224,7 +235,13 @@ public static void doOcr(File selectedFile) { ThreadUtil.execute(() -> { String text = null; try { - text = OcrUtils.recImgLocal(selectedFile); + String fileType = FileTypeUtil.getType(selectedFile); + if ("pdf".equalsIgnoreCase(fileType)) { + text = OcrUtils.recPdfLocal(selectedFile); + } else { + text = OcrUtils.recImgLocal(selectedFile); + } + } catch (Exception e) { text = e.getMessage(); e.printStackTrace(); diff --git a/src/test/java/com/luooqi/ocr/utils/OcrUtilsTest.java b/src/test/java/com/luooqi/ocr/utils/OcrUtilsTest.java index 192144d..478b01a 100644 --- a/src/test/java/com/luooqi/ocr/utils/OcrUtilsTest.java +++ b/src/test/java/com/luooqi/ocr/utils/OcrUtilsTest.java @@ -10,6 +10,7 @@ import java.awt.*; import java.awt.geom.AffineTransform; +import java.io.File; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -17,23 +18,35 @@ public class OcrUtilsTest { - String html = "{\"result\":[{\"groupID\":0,\"content\":\"目前,RFID(radio frequency identification\\n\",\"frame\":[\"537,11\",\"935,11\",\"935,41\",\"537,41\"]},{\"groupID\":1,\"content\":\"0引言\\n\",\"frame\":[\"23,22\",\"129,22\",\"129,54\",\"23,54\"]},{\"groupID\":2,\"content\":\"devices)技术、网络化信息管理及数据库技术在农\\n\",\"frame\":[\"496,43\",\"931,43\",\"931,65\",\"496,65\"]},{\"groupID\":3,\"content\":\"近年来,关于遗传育种的研究已从传统的常规\\n\",\"frame\":[\"64,68\",\"461,68\",\"461,93\",\"64,93\"]},{\"groupID\":2,\"content\":\"业领域的应用较广泛,三者相结合主要应用于农田\\n\",\"frame\":[\"496,68\",\"931,68\",\"931,93\",\"496,93\"]},{\"groupID\":2,\"content\":\"信息采集[9-12]、食品存储监测及安全跟踪[13-19]、畜\\n\",\"frame\":[\"499,94\",\"931,94\",\"931,121\",\"499,121\"]},{\"groupID\":3,\"content\":\"育种技术进入依靠生物技术育种阶段,研究内容也\\n\",\"frame\":[\"28,94\",\"460,94\",\"460,121\",\"28,121\"]},{\"groupID\":2,\"content\":\"禽养殖监测[20-2]等方面。关于采用编码的方法结合\\n\",\"frame\":[\"499,125\",\"931,125\",\"931,149\",\"499,149\"]},{\"groupID\":3,\"content\":\"从单个基因的测序转为有计划、大规模地检测水稻\\n\",\"frame\":[\"25,125\",\"460,125\",\"460,148\",\"25,148\"]},{\"groupID\":3,\"content\":\"等重要生物体的基因图谱。要实现水稻分子遗传育\\n\",\"frame\":[\"25,150\",\"460,150\",\"460,175\",\"25,175\"]},{\"groupID\":2,\"content\":\"数据库技术对农业中的动、植物进行标识、溯源和\\n\",\"frame\":[\"497,155\",\"929,155\",\"929,177\",\"497,177\"]},{\"groupID\":2,\"content\":\"建模方面的研究也有报道[242]。其在农作物育种方\\n\",\"frame\":[\"500,178\",\"929,178\",\"929,203\",\"500,203\"]},{\"groupID\":3,\"content\":\"种的目标,首要任务是了解、选择群体细胞的生理\\n\",\"frame\":[\"25,181\",\"460,181\",\"460,204\",\"25,204\"]},{\"groupID\":3,\"content\":\"生化特性以及与之相对应的表型现象,也就是要确\\n\",\"frame\":[\"25,207\",\"461,207\",\"461,232\",\"25,232\"]},{\"groupID\":2,\"content\":\"面的研究则主要涉及育种参数统计分析,育种遗传\\n\",\"frame\":[\"496,211\",\"931,211\",\"931,235\",\"496,235\"]},{\"groupID\":2,\"content\":\"过程的计算机数学模型,农作物育种专家系统,种\\n\",\"frame\":[\"496,237\",\"931,237\",\"931,262\",\"496,262\"]},{\"groupID\":3,\"content\":\"定植物群体实际基因或基因片段的表达与表型现\\n\",\"frame\":[\"25,237\",\"461,237\",\"461,262\",\"25,262\"]},{\"groupID\":3,\"content\":\"象的内在关联程度1一]。高通量的水稻种植试验成为\\n\",\"frame\":[\"25,263\",\"460,263\",\"460,288\",\"25,288\"]},{\"groupID\":2,\"content\":\"质资源数据库和育种信息管理等方面。由于长期以\\n\",\"frame\":[\"499,267\",\"931,267\",\"931,291\",\"499,291\"]},{\"groupID\":2,\"content\":\"来农作物育种信息采用人工方式记录管理,在育种\\n\",\"frame\":[\"499,293\",\"931,293\",\"931,318\",\"499,318\"]},{\"groupID\":3,\"content\":\"表型现象验证的重要手段。大规模、高效率的分子\\n\",\"frame\":[\"25,293\",\"461,293\",\"461,318\",\"25,318\"]},{\"groupID\":3,\"content\":\"遗传育种技术试验平台是实现高通量分子遗传育\\n\",\"frame\":[\"25,319\",\"461,319\",\"461,345\",\"25,345\"]},{\"groupID\":2,\"content\":\"试验量不大时,信息管理需求方面的矛眉并未突显,\\n\",\"frame\":[\"496,324\",\"928,324\",\"928,347\",\"496,347\"]},{\"groupID\":2,\"content\":\"因此国内关于农作物育种信息管理和数据库方面的\\n\",\"frame\":[\"496,349\",\"931,349\",\"931,374\",\"496,374\"]},{\"groupID\":3,\"content\":\"种试验的关键环节。温、湿度自动调节的现代化温\\n\",\"frame\":[\"25,350\",\"460,350\",\"460,373\",\"25,373\"]},{\"groupID\":3,\"content\":\"室以及全自动化的水稻种植、栽培、输送、检测试\\n\",\"frame\":[\"25,377\",\"460,377\",\"460,400\",\"25,400\"]},{\"groupID\":2,\"content\":\"研究报导较少[283]。随着分子遗传育种试验的中信\\n\",\"frame\":[\"496,376\",\"931,376\",\"931,404\",\"496,404\"]},{\"groupID\":2,\"content\":\"息量的倍增,如何运用计算机技术对育种试验信息\\n\",\"frame\":[\"499,406\",\"931,406\",\"931,431\",\"499,431\"]},{\"groupID\":3,\"content\":\"验环境的建立是保证高通重分子遗传育种试验完\\n\",\"frame\":[\"25,407\",\"460,407\",\"460,430\",\"25,430\"]},{\"groupID\":3,\"content\":\"成的基础条件。\\n\",\"frame\":[\"25,433\",\"154,433\",\"154,456\",\"25,456\"]},{\"groupID\":2,\"content\":\"进行科学、高效的管理成为一个亟待解决的问题。\\n\",\"frame\":[\"498,435\",\"914,435\",\"914,459\",\"498,459\"]},{\"groupID\":4,\"content\":\"本文以高通量水稻种植试验为研究对象,在已\\n\",\"frame\":[\"535,462\",\"931,462\",\"931,487\",\"535,487\"]},{\"groupID\":5,\"content\":\"修订日期:2014-02-26\\n\",\"frame\":[\"197,487\",\"336,487\",\"336,504\",\"197,504\"]},{\"groupID\":6,\"content\":\"收稿日期:2013-04-13\\n\",\"frame\":[\"27,487\",\"164,487\",\"164,504\",\"27,504\"]},{\"groupID\":4,\"content\":\"有的温室水稻盆栽自动化输送设备基础上,结合\\n\",\"frame\":[\"499,489\",\"931,489\",\"931,517\",\"499,517\"]},{\"groupID\":7,\"content\":\"基金项目:中央高校基本科研业务费专项资金资助(2013PY052):国\\n\",\"frame\":[\"27,511\",\"461,511\",\"461,527\",\"27,527\"]},{\"groupID\":4,\"content\":\"RFID技术、网络化信息管理及数据库技术,研究\\n\",\"frame\":[\"496,519\",\"934,519\",\"934,542\",\"496,542\"]},{\"groupID\":7,\"content\":\"家自然科学基金(61007058)\\n\",\"frame\":[\"27,530\",\"204,530\",\"204,549\",\"27,549\"]},{\"groupID\":4,\"content\":\"水稻的遗传育种试验中种植试验信息的管理方案,\\n\",\"frame\":[\"499,545\",\"922,545\",\"922,570\",\"499,570\"]},{\"groupID\":8,\"content\":\"作者简介:高云(1974一),女(汉),湖北鄂州,副教授,硕士,农\\n\",\"frame\":[\"27,554\",\"461,554\",\"461,570\",\"27,570\"]},{\"groupID\":8,\"content\":\"业智能检测与控制。武汉湖北省武汉市洪山区华中农业大学工学院,\\n\",\"frame\":[\"26,573\",\"455,573\",\"455,594\",\"26,594\"]},{\"groupID\":4,\"content\":\"针对水稻的突变体库的特点,提出了水稻种植试验\\n\",\"frame\":[\"496,576\",\"931,576\",\"931,599\",\"496,599\"]},{\"groupID\":9,\"content\":\"430070。Email:angelclouder@hotmail.com\\n\",\"frame\":[\"23,595\",\"284,595\",\"284,616\",\"23,616\"]},{\"groupID\":4,\"content\":\"家系可追溯的数据管理方法,实现水稻家系的自动\\n\",\"frame\":[\"499,603\",\"931,603\",\"931,626\",\"499,626\"]},{\"groupID\":10,\"content\":\"中国农业工程学会会员(E041700006M)\\n\",\"frame\":[\"27,621\",\"274,621\",\"274,637\",\"27,637\"]},{\"groupID\":4,\"content\":\"追溯和家系树的自动生成。该研究有效解决了高通\\n\",\"frame\":[\"496,632\",\"931,632\",\"931,655\",\"496,655\"]},{\"groupID\":11,\"content\":\"米通信作者:李小昱(1953一),女(汉),陕西西安,教授,博士生导\\n\",\"frame\":[\"26,639\",\"460,639\",\"460,660\",\"26,660\"]},{\"groupID\":4,\"content\":\"量下育种种植试验的信息量大、管理复杂的问题,\\n\",\"frame\":[\"499,658\",\"922,658\",\"922,682\",\"499,682\"]},{\"groupID\":11,\"content\":\"师,智能化检测技术。武汉湖北省武汉市洪山区华中农业大学工学院,\\n\",\"frame\":[\"25,664\",\"458,664\",\"458,680\",\"25,680\"]},{\"groupID\":12,\"content\":\"430070。Email:lixiaoyu@mail.hzau.edu.cn\\n\",\"frame\":[\"24,686\",\"280,686\",\"280,703\",\"24,703\"]},{\"groupID\":4,\"content\":\"提高了试验效率。对于其他与水稻相类似农作物育\\n\",\"frame\":[\"499,687\",\"931,687\",\"931,712\",\"499,712\"]},{\"groupID\":13,\"content\":\"中国农业工程学会高级会员(E041200068S)\\n\",\"frame\":[\"27,707\",\"297,707\",\"297,723\",\"27,723\"]}],\"success\":1,\"zly\":\"zly\",\"ocr_time\":1584.014892578125,\"id\":\"2274054490548600832\",\"lang\":\"zh-Chs\",\"direction\":0}"; - - @Test - public void sogouMobileOcr() { - - } - - private Point frameToPoint(String text){ - String[] arr = text.split(","); - return new Point(Integer.valueOf(arr[0].trim()), Integer.valueOf(arr[1].trim())); - } - - @Test - public void sogouWebOcr() { - GraphicsConfiguration asdf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); - AffineTransform asfd2 = asdf.getDefaultTransform(); - double scaleX = asfd2.getScaleX(); - double scaleY = asfd2.getScaleY(); - } + String html = "{\"result\":[{\"groupID\":0,\"content\":\"目前,RFID(radio frequency identification\\n\",\"frame\":[\"537,11\",\"935,11\",\"935,41\",\"537,41\"]},{\"groupID\":1,\"content\":\"0引言\\n\",\"frame\":[\"23,22\",\"129,22\",\"129,54\",\"23,54\"]},{\"groupID\":2,\"content\":\"devices)技术、网络化信息管理及数据库技术在农\\n\",\"frame\":[\"496,43\",\"931,43\",\"931,65\",\"496,65\"]},{\"groupID\":3,\"content\":\"近年来,关于遗传育种的研究已从传统的常规\\n\",\"frame\":[\"64,68\",\"461,68\",\"461,93\",\"64,93\"]},{\"groupID\":2,\"content\":\"业领域的应用较广泛,三者相结合主要应用于农田\\n\",\"frame\":[\"496,68\",\"931,68\",\"931,93\",\"496,93\"]},{\"groupID\":2,\"content\":\"信息采集[9-12]、食品存储监测及安全跟踪[13-19]、畜\\n\",\"frame\":[\"499,94\",\"931,94\",\"931,121\",\"499,121\"]},{\"groupID\":3,\"content\":\"育种技术进入依靠生物技术育种阶段,研究内容也\\n\",\"frame\":[\"28,94\",\"460,94\",\"460,121\",\"28,121\"]},{\"groupID\":2,\"content\":\"禽养殖监测[20-2]等方面。关于采用编码的方法结合\\n\",\"frame\":[\"499,125\",\"931,125\",\"931,149\",\"499,149\"]},{\"groupID\":3,\"content\":\"从单个基因的测序转为有计划、大规模地检测水稻\\n\",\"frame\":[\"25,125\",\"460,125\",\"460,148\",\"25,148\"]},{\"groupID\":3,\"content\":\"等重要生物体的基因图谱。要实现水稻分子遗传育\\n\",\"frame\":[\"25,150\",\"460,150\",\"460,175\",\"25,175\"]},{\"groupID\":2,\"content\":\"数据库技术对农业中的动、植物进行标识、溯源和\\n\",\"frame\":[\"497,155\",\"929,155\",\"929,177\",\"497,177\"]},{\"groupID\":2,\"content\":\"建模方面的研究也有报道[242]。其在农作物育种方\\n\",\"frame\":[\"500,178\",\"929,178\",\"929,203\",\"500,203\"]},{\"groupID\":3,\"content\":\"种的目标,首要任务是了解、选择群体细胞的生理\\n\",\"frame\":[\"25,181\",\"460,181\",\"460,204\",\"25,204\"]},{\"groupID\":3,\"content\":\"生化特性以及与之相对应的表型现象,也就是要确\\n\",\"frame\":[\"25,207\",\"461,207\",\"461,232\",\"25,232\"]},{\"groupID\":2,\"content\":\"面的研究则主要涉及育种参数统计分析,育种遗传\\n\",\"frame\":[\"496,211\",\"931,211\",\"931,235\",\"496,235\"]},{\"groupID\":2,\"content\":\"过程的计算机数学模型,农作物育种专家系统,种\\n\",\"frame\":[\"496,237\",\"931,237\",\"931,262\",\"496,262\"]},{\"groupID\":3,\"content\":\"定植物群体实际基因或基因片段的表达与表型现\\n\",\"frame\":[\"25,237\",\"461,237\",\"461,262\",\"25,262\"]},{\"groupID\":3,\"content\":\"象的内在关联程度1一]。高通量的水稻种植试验成为\\n\",\"frame\":[\"25,263\",\"460,263\",\"460,288\",\"25,288\"]},{\"groupID\":2,\"content\":\"质资源数据库和育种信息管理等方面。由于长期以\\n\",\"frame\":[\"499,267\",\"931,267\",\"931,291\",\"499,291\"]},{\"groupID\":2,\"content\":\"来农作物育种信息采用人工方式记录管理,在育种\\n\",\"frame\":[\"499,293\",\"931,293\",\"931,318\",\"499,318\"]},{\"groupID\":3,\"content\":\"表型现象验证的重要手段。大规模、高效率的分子\\n\",\"frame\":[\"25,293\",\"461,293\",\"461,318\",\"25,318\"]},{\"groupID\":3,\"content\":\"遗传育种技术试验平台是实现高通量分子遗传育\\n\",\"frame\":[\"25,319\",\"461,319\",\"461,345\",\"25,345\"]},{\"groupID\":2,\"content\":\"试验量不大时,信息管理需求方面的矛眉并未突显,\\n\",\"frame\":[\"496,324\",\"928,324\",\"928,347\",\"496,347\"]},{\"groupID\":2,\"content\":\"因此国内关于农作物育种信息管理和数据库方面的\\n\",\"frame\":[\"496,349\",\"931,349\",\"931,374\",\"496,374\"]},{\"groupID\":3,\"content\":\"种试验的关键环节。温、湿度自动调节的现代化温\\n\",\"frame\":[\"25,350\",\"460,350\",\"460,373\",\"25,373\"]},{\"groupID\":3,\"content\":\"室以及全自动化的水稻种植、栽培、输送、检测试\\n\",\"frame\":[\"25,377\",\"460,377\",\"460,400\",\"25,400\"]},{\"groupID\":2,\"content\":\"研究报导较少[283]。随着分子遗传育种试验的中信\\n\",\"frame\":[\"496,376\",\"931,376\",\"931,404\",\"496,404\"]},{\"groupID\":2,\"content\":\"息量的倍增,如何运用计算机技术对育种试验信息\\n\",\"frame\":[\"499,406\",\"931,406\",\"931,431\",\"499,431\"]},{\"groupID\":3,\"content\":\"验环境的建立是保证高通重分子遗传育种试验完\\n\",\"frame\":[\"25,407\",\"460,407\",\"460,430\",\"25,430\"]},{\"groupID\":3,\"content\":\"成的基础条件。\\n\",\"frame\":[\"25,433\",\"154,433\",\"154,456\",\"25,456\"]},{\"groupID\":2,\"content\":\"进行科学、高效的管理成为一个亟待解决的问题。\\n\",\"frame\":[\"498,435\",\"914,435\",\"914,459\",\"498,459\"]},{\"groupID\":4,\"content\":\"本文以高通量水稻种植试验为研究对象,在已\\n\",\"frame\":[\"535,462\",\"931,462\",\"931,487\",\"535,487\"]},{\"groupID\":5,\"content\":\"修订日期:2014-02-26\\n\",\"frame\":[\"197,487\",\"336,487\",\"336,504\",\"197,504\"]},{\"groupID\":6,\"content\":\"收稿日期:2013-04-13\\n\",\"frame\":[\"27,487\",\"164,487\",\"164,504\",\"27,504\"]},{\"groupID\":4,\"content\":\"有的温室水稻盆栽自动化输送设备基础上,结合\\n\",\"frame\":[\"499,489\",\"931,489\",\"931,517\",\"499,517\"]},{\"groupID\":7,\"content\":\"基金项目:中央高校基本科研业务费专项资金资助(2013PY052):国\\n\",\"frame\":[\"27,511\",\"461,511\",\"461,527\",\"27,527\"]},{\"groupID\":4,\"content\":\"RFID技术、网络化信息管理及数据库技术,研究\\n\",\"frame\":[\"496,519\",\"934,519\",\"934,542\",\"496,542\"]},{\"groupID\":7,\"content\":\"家自然科学基金(61007058)\\n\",\"frame\":[\"27,530\",\"204,530\",\"204,549\",\"27,549\"]},{\"groupID\":4,\"content\":\"水稻的遗传育种试验中种植试验信息的管理方案,\\n\",\"frame\":[\"499,545\",\"922,545\",\"922,570\",\"499,570\"]},{\"groupID\":8,\"content\":\"作者简介:高云(1974一),女(汉),湖北鄂州,副教授,硕士,农\\n\",\"frame\":[\"27,554\",\"461,554\",\"461,570\",\"27,570\"]},{\"groupID\":8,\"content\":\"业智能检测与控制。武汉湖北省武汉市洪山区华中农业大学工学院,\\n\",\"frame\":[\"26,573\",\"455,573\",\"455,594\",\"26,594\"]},{\"groupID\":4,\"content\":\"针对水稻的突变体库的特点,提出了水稻种植试验\\n\",\"frame\":[\"496,576\",\"931,576\",\"931,599\",\"496,599\"]},{\"groupID\":9,\"content\":\"430070。Email:angelclouder@hotmail.com\\n\",\"frame\":[\"23,595\",\"284,595\",\"284,616\",\"23,616\"]},{\"groupID\":4,\"content\":\"家系可追溯的数据管理方法,实现水稻家系的自动\\n\",\"frame\":[\"499,603\",\"931,603\",\"931,626\",\"499,626\"]},{\"groupID\":10,\"content\":\"中国农业工程学会会员(E041700006M)\\n\",\"frame\":[\"27,621\",\"274,621\",\"274,637\",\"27,637\"]},{\"groupID\":4,\"content\":\"追溯和家系树的自动生成。该研究有效解决了高通\\n\",\"frame\":[\"496,632\",\"931,632\",\"931,655\",\"496,655\"]},{\"groupID\":11,\"content\":\"米通信作者:李小昱(1953一),女(汉),陕西西安,教授,博士生导\\n\",\"frame\":[\"26,639\",\"460,639\",\"460,660\",\"26,660\"]},{\"groupID\":4,\"content\":\"量下育种种植试验的信息量大、管理复杂的问题,\\n\",\"frame\":[\"499,658\",\"922,658\",\"922,682\",\"499,682\"]},{\"groupID\":11,\"content\":\"师,智能化检测技术。武汉湖北省武汉市洪山区华中农业大学工学院,\\n\",\"frame\":[\"25,664\",\"458,664\",\"458,680\",\"25,680\"]},{\"groupID\":12,\"content\":\"430070。Email:lixiaoyu@mail.hzau.edu.cn\\n\",\"frame\":[\"24,686\",\"280,686\",\"280,703\",\"24,703\"]},{\"groupID\":4,\"content\":\"提高了试验效率。对于其他与水稻相类似农作物育\\n\",\"frame\":[\"499,687\",\"931,687\",\"931,712\",\"499,712\"]},{\"groupID\":13,\"content\":\"中国农业工程学会高级会员(E041200068S)\\n\",\"frame\":[\"27,707\",\"297,707\",\"297,723\",\"27,723\"]}],\"success\":1,\"zly\":\"zly\",\"ocr_time\":1584.014892578125,\"id\":\"2274054490548600832\",\"lang\":\"zh-Chs\",\"direction\":0}"; + + @Test + public void sogouMobileOcr() { + + } + + private Point frameToPoint(String text) { + String[] arr = text.split(","); + return new Point(Integer.valueOf(arr[0].trim()), Integer.valueOf(arr[1].trim())); + } + + @Test + public void sogouWebOcr() { + GraphicsConfiguration asdf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); + AffineTransform asfd2 = asdf.getDefaultTransform(); + double scaleX = asfd2.getScaleX(); + double scaleY = asfd2.getScaleY(); + } + + @Test + public void recPdfLocal() { + File file = new File("F:\\document\\dev-docs\\24.Internet_of_things\\02_C++\\2.1 面向C++模板库应用开发\\01 第一章C++.pdf"); + String s = OcrUtils.recPdfLocal(file); + System.out.println(s); + } + + @Test + public void recImageLocal(){ + OcrUtils.recImgLocal(new File("temp_1010298_4.png")); + } } diff --git a/src/test/java/com/luooqi/ocr/utils/PdfTest.java b/src/test/java/com/luooqi/ocr/utils/PdfTest.java new file mode 100644 index 0000000..ea0af09 --- /dev/null +++ b/src/test/java/com/luooqi/ocr/utils/PdfTest.java @@ -0,0 +1,28 @@ +package com.luooqi.ocr.utils; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.rendering.PDFRenderer; +import org.junit.Test; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class PdfTest { + + @Test + public void extraImageFromPdf() throws IOException { + File pdfFile = new File("F:\\document\\dev-docs\\24.Internet_of_things\\02_C++\\2.1 面向C++模板库应用开发\\01 第一章C++.pdf"); + try (PDDocument document = PDDocument.load(pdfFile)) { + PDFRenderer renderer = new PDFRenderer(document); + + for (int i = 0; i < document.getNumberOfPages(); ++i) { + BufferedImage bufferedImage = renderer.renderImageWithDPI(i, 300); + FileOutputStream fileOutputStream = new FileOutputStream(i + ".png"); + ImageIO.write(bufferedImage, "png", fileOutputStream); // 选择合适的格式,如 "png" 或 "jpg" + } + } + } +} From cacd3c9783addf0853327259a1cd8f0650f7fa2c Mon Sep 17 00:00:00 2001 From: litongjava Date: Sat, 9 Dec 2023 19:30:36 -1000 Subject: [PATCH 5/6] add open source address --- readme.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/readme.md b/readme.md index 25bcab0..2c15e9d 100644 --- a/readme.md +++ b/readme.md @@ -8,6 +8,9 @@ - onnx - paddle ocr - opencv + +## 开源地址 +[gitee](https://gitee.com/ppnt/tools-ocr) | [github](https://github.com/litongjava/tools-ocr) ## 安装 > - **安装路径请勿包含中文字符**; > - 本程序使用 JavaFX 开发,提供的安装包中已经包含了Java From 6d57ebaff53598f1acfd9c5d6d6a577e3267bb5e Mon Sep 17 00:00:00 2001 From: litongjava Date: Sat, 9 Dec 2023 19:30:58 -1000 Subject: [PATCH 6/6] release to v2.2.9 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 2bcb107..ce322a4 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.luooqi tools-ocr - 2.2.8 + 2.2.9 UTF-8