Browse Source

增加 文字识别

liuyuqi-dellpc 4 years ago
parent
commit
683fd9e009

+ 11 - 0
tools-ocr/.gitignore

@@ -0,0 +1,11 @@
+.idea/
+target/
+out/
+.git/
+
+
+*.iml
+/bin/
+/.settings
+/.classpath
+/.project

+ 102 - 0
tools-ocr/pom.xml

@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <groupId>com.luooqi</groupId>
+    <artifactId>treehole</artifactId>
+    <version>${soft.version}</version>
+
+    <properties>
+        <soft.version>1.2.0</soft.version>
+    </properties>
+
+    <dependencies>
+<!--        文件处理把包        -->
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>1.3.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.1stleg</groupId>
+            <artifactId>jnativehook</artifactId>
+            <version>2.1.0</version>
+        </dependency>
+
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+            <version>4.5.16</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.imgscalr</groupId>
+            <artifactId>imgscalr-lib</artifactId>
+            <version>4.2</version>
+        </dependency>
+
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.12</version>
+            <scope>test</scope>
+        </dependency>
+
+    </dependencies>
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.5.1</version>
+                <configuration>
+                    <encoding>UTF-8</encoding>
+                    <source>1.8</source>
+                    <target>1.8</target>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>2.10</version>
+                <executions>
+                    <execution>
+                        <id>copy-dependencies</id>
+                        <phase>package</phase>
+                        <configuration>
+                            <overWriteReleases>false</overWriteReleases>
+                            <overWriteSnapshots>false</overWriteSnapshots>
+                            <overWriteIfNewer>true</overWriteIfNewer>
+                        </configuration>
+                        <goals>
+                            <goal>copy-dependencies</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>com.zenjava</groupId>
+                <artifactId>javafx-maven-plugin</artifactId>
+                <version>8.8.3</version>
+                <configuration>
+                    <mainClass>com.luooqi.ocr.MainFm</mainClass>
+                    <bundler>dmg</bundler>
+                    <jfxAppOutputDir>${project.build.directory}/app</jfxAppOutputDir>
+                    <nativeOutputDir>${project.build.directory}/native</nativeOutputDir>
+                    <appName>treehole</appName>
+                    <verbose>true</verbose>
+                    <vendor>com.luooqi</vendor>
+                    <needShortcut>true</needShortcut>
+                    <bundleArguments>
+                        <runtime />
+                    </bundleArguments>
+                    <nativeReleaseVersion>${soft.version}</nativeReleaseVersion>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>

BIN
tools-ocr/src/main/deploy/package/macosx/treehole.icns


BIN
tools-ocr/src/main/deploy/package/windows/treehole.ico


+ 60 - 0
tools-ocr/src/main/java/com/luooqi/ocr/MainFm.java

@@ -0,0 +1,60 @@
+package com.luooqi.ocr;
+
+import cn.hutool.core.lang.UUID;
+import com.luooqi.ocr.utils.CommUtils;
+import com.luooqi.ocr.utils.OcrUtils;
+import org.apache.commons.io.FileUtils;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ * 主窗体
+ *
+ * @author liuyuqi
+ */
+public class MainFm {
+   private static String outputDirPath;
+    public static void main(String[] args) {
+        String srcDirPath = "D:/liuyuqi/fishsource/python/video-subtitle-extract/videos/大象解说《血战钢锯岭》";
+//
+        String outputDirPath = "D:/liuyuqi/fishsource/python/video-subtitle-extract/";
+
+        // 获取所有png文件
+        Collection<File> javaGbkFileCol = FileUtils.listFiles(new File(srcDirPath), new String[]{"png"}, true);
+        String res = "";
+        for (File img : javaGbkFileCol) {
+//            对每个图片识别
+            res += imgOCR(img);
+        }
+
+        saveRes(res);
+        System.out.println(res);
+    }
+    public static void saveRes(String res){
+        File file = new File(outputDirPath+Math.abs(UUID.randomUUID().hashCode())+".txt");
+        try {
+            FileUtils.writeStringToFile(file,res);
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    public static String imgOCR(File img) {
+        BufferedImage image;
+        try {
+            image = ImageIO.read(img);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return "";
+        }
+        byte[] bytes = CommUtils.imageToBytes(image);
+        int ocrType = 0;
+        ocrType = Math.abs(UUID.randomUUID().hashCode()) % 4;
+        String text = OcrUtils.ocrImg(bytes, ocrType);
+        return text;
+    }
+}

+ 91 - 0
tools-ocr/src/main/java/com/luooqi/ocr/model/CaptureInfo.java

@@ -0,0 +1,91 @@
+
+package com.luooqi.ocr.model;
+
+import cn.hutool.core.swing.ScreenUtil;
+import javafx.beans.binding.BooleanBinding;
+import javafx.beans.property.BooleanProperty;
+import javafx.beans.property.SimpleBooleanProperty;
+import javafx.scene.text.Font;
+import javafx.scene.text.FontWeight;
+
+/**
+ * @author GOXR3PLUS
+ *
+ */
+public class CaptureInfo {
+
+	/** The x pressed. */
+	public int mouseXPressed = 0;
+
+	/** The y pressed. */
+	public int mouseYPressed = 0;
+
+	/** The x now. */
+	public int mouseXNow = 0;
+
+	/** The y now. */
+	public int mouseYNow = 0;
+
+	/** The upper left X. */
+	public int rectUpperLeftX = 0;
+
+	/** The upper left Y. */
+	public int rectUpperLeftY = 0;
+
+	/** The rectangle width. */
+	public int rectWidth;
+
+	/** The rectangle height. */
+	public int rectHeight;
+
+	// ----------------
+
+	/** The font. */
+	public Font font = Font.font("", FontWeight.BOLD, 14);
+
+	// ---------------
+
+	/** The shift pressed. */
+	public BooleanProperty shiftPressed = new SimpleBooleanProperty();
+
+	/** The up pressed. */
+	public BooleanProperty upPressed = new SimpleBooleanProperty();
+
+	/** The right pressed. */
+	public BooleanProperty rightPressed = new SimpleBooleanProperty();
+
+	/** The down pressed. */
+	public BooleanProperty downPressed = new SimpleBooleanProperty();
+
+	/** The left pressed. */
+	public BooleanProperty leftPressed = new SimpleBooleanProperty();
+
+	/** The any pressed. */
+	public BooleanBinding anyPressed = upPressed.or(downPressed).or(leftPressed).or(rightPressed);
+
+	/** The hide extra features. */
+	public BooleanProperty hideExtraFeatures = new SimpleBooleanProperty();
+
+	// ------------
+
+	/** The screen width. */
+	public static int ScreenWidth = ScreenUtil.getWidth();
+
+	/** The screen height. */
+	public static int ScreenHeight = ScreenUtil.getHeight();
+
+	public static int ScreenMinX = 0;
+	public static int ScreenMaxX = 0;
+
+	public void reset(){
+		mouseXNow = 0;
+		mouseXPressed = 0;
+		mouseYNow = 0;
+		mouseYPressed = 0;
+		rectUpperLeftY = 0;
+		rectUpperLeftX = 0;
+		rectWidth = 0;
+		rectHeight = 0;
+	}
+
+}

+ 57 - 0
tools-ocr/src/main/java/com/luooqi/ocr/model/StageInfo.java

@@ -0,0 +1,57 @@
+package com.luooqi.ocr.model;
+
+public class StageInfo {
+    private double x;
+    private double y;
+    private double width;
+    private double height;
+    private boolean fullScreenState;
+
+    public StageInfo(double x, double y, double width, double height, boolean fullScreenState) {
+        this.x = x;
+        this.y = y;
+        this.width = width;
+        this.height = height;
+        this.fullScreenState = fullScreenState;
+    }
+
+    public double getX() {
+        return x;
+    }
+
+    public void setX(double x) {
+        this.x = x;
+    }
+
+    public double getY() {
+        return y;
+    }
+
+    public void setY(double y) {
+        this.y = y;
+    }
+
+    public double getWidth() {
+        return width;
+    }
+
+    public void setWidth(double width) {
+        this.width = width;
+    }
+
+    public double getHeight() {
+        return height;
+    }
+
+    public void setHeight(double height) {
+        this.height = height;
+    }
+
+    public boolean isFullScreenState() {
+        return fullScreenState;
+    }
+
+    public void setFullScreenState(boolean fullScreenState) {
+        this.fullScreenState = fullScreenState;
+    }
+}

+ 93 - 0
tools-ocr/src/main/java/com/luooqi/ocr/model/TextBlock.java

@@ -0,0 +1,93 @@
+package com.luooqi.ocr.model;
+
+import java.awt.Point;
+
+public class TextBlock {
+    private Point topLeft;
+    private Point topRight;
+    private Point bottomLeft;
+    private Point bottomRight;
+    private double angle;
+    private double fontSize;
+    private String text;
+
+    public TextBlock(){}
+
+    public TextBlock(Point topLeft, Point topRight, Point bottomLeft, Point bottomRight, String text) {
+        this.topLeft = topLeft;
+        this.topRight = topRight;
+        this.bottomLeft = bottomLeft;
+        this.bottomRight = bottomRight;
+        this.text = text;
+        calcAngle();
+    }
+
+    public Point getTopLeft() {
+        return topLeft;
+    }
+
+    public void setTopLeft(Point topLeft) {
+        this.topLeft = topLeft;
+        calcAngle();
+    }
+
+    public Point getTopRight() {
+        return topRight;
+    }
+
+    public void setTopRight(Point topRight) {
+        this.topRight = topRight;
+        calcAngle();
+    }
+
+    public Point getBottomLeft() {
+        return bottomLeft;
+    }
+
+    public void setBottomLeft(Point bottomLeft) {
+        this.bottomLeft = bottomLeft;
+        calcAngle();
+    }
+
+    public Point getBottomRight() {
+        return bottomRight;
+    }
+
+    public void setBottomRight(Point bottomRight) {
+        this.bottomRight = bottomRight;
+        calcAngle();
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    public double getFontSize() {
+        return fontSize;
+    }
+
+    private void setFontSize(double fontSize) {
+        this.fontSize = fontSize;
+    }
+
+    private void calcAngle() {
+        if (this.topLeft != null && this.bottomLeft != null) {
+            int x = this.topLeft.x - this.bottomLeft.x;
+            int y = this.bottomLeft.y - this.topLeft.y;
+            setAngle(x * 1.0 / y);
+            setFontSize(Math.sqrt(x * x + y * y));
+        }
+    }
+
+    public double getAngle() {
+        return angle;
+    }
+
+    private void setAngle(double angle) {
+        this.angle = angle;
+    }
+}

+ 295 - 0
tools-ocr/src/main/java/com/luooqi/ocr/utils/CommUtils.java

@@ -0,0 +1,295 @@
+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.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.MainFm;
+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 javafx.geometry.Insets;
+import javafx.geometry.Orientation;
+import javafx.geometry.Rectangle2D;
+import javafx.scene.control.Button;
+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;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.Paint;
+import javafx.stage.Screen;
+import javafx.stage.Stage;
+
+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));
+    private static Pattern NORMAL_CHAR = Pattern.compile("[\\u4e00-\\u9fa5\\w、-,/|_]");
+    private static final float IMAGE_QUALITY = 0.5f;
+    private static final int SAME_LINE_LIMIT = 8;
+    private static final int CHAR_WIDTH = 12;
+    public static final String STYLE_TRANSPARENT = "-fx-background-color: transparent;";
+    public static final String SPECIAL_CHARS = "[\\s`~!@#$%^&*()_\\-+=|{}':;,\\[\\].<>/?!¥…()【】‘;:”“’。,、?]+";
+    public static boolean IS_MAC_OS = false;
+    static {
+        String osName = System.getProperty("os.name", "generic").toLowerCase();
+        if ((osName.contains("mac")) || (osName.contains("darwin"))) {
+            IS_MAC_OS = true;
+        }
+    }
+
+    public static byte[] imageToBytes(BufferedImage img) {
+        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+        MemoryCacheImageOutputStream outputStream = new MemoryCacheImageOutputStream(byteArrayOutputStream);
+        try {
+            Iterator<?> iter = ImageIO.getImageWritersByFormatName("jpeg");
+            ImageWriter writer = (ImageWriter) iter.next();
+            ImageWriteParam iwp = writer.getDefaultWriteParam();
+            iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
+            iwp.setCompressionQuality(IMAGE_QUALITY);
+            writer.setOutput(outputStream);
+            IIOImage image = new IIOImage(img, null, null);
+            writer.write(null, image, iwp);
+            writer.dispose();
+            byte[] result = byteArrayOutputStream.toByteArray();
+            byteArrayOutputStream.close();
+            outputStream.close();
+            return result;
+        } catch (IOException e) {
+            StaticLog.error(e);
+            return new byte[0];
+        }
+    }
+
+    static String combineTextBlocks(List<TextBlock> textBlocks, boolean isEng) {
+        textBlocks.sort(Comparator.comparingInt(o -> o.getTopLeft().y));
+        List<List<TextBlock>> lineBlocks = new ArrayList<>();
+        int lastY = -1;
+        List<TextBlock> lineBlock = new ArrayList<>();
+        boolean sameLine = true;
+        int minX = Integer.MAX_VALUE;
+        TextBlock minBlock = null;
+        TextBlock maxBlock = null;
+        int maxX = -1;
+        double maxAngle = -100;
+        for (TextBlock textBlock : textBlocks) {
+            System.out.println(textBlock.getAngle()+ "\t" + textBlock.getFontSize());
+            if (textBlock.getTopLeft().x < minX) {
+                minX = textBlock.getTopLeft().x;
+                minBlock = textBlock;
+            }
+            if (textBlock.getTopRight().x > maxX) {
+                maxX = textBlock.getTopRight().x;
+                maxBlock = textBlock;
+            }
+            if (Math.abs(textBlock.getAngle()) > maxAngle){
+                maxAngle = Math.abs(textBlock.getAngle());
+            }
+            if (lastY == -1) {
+                lastY = textBlock.getTopLeft().y;
+            } else {
+                sameLine = textBlock.getTopLeft().y - lastY <= SAME_LINE_LIMIT;
+            }
+            if (!sameLine) {
+                lineBlock.sort(Comparator.comparingInt(o -> o.getTopLeft().x));
+                lineBlocks.add(lineBlock);
+                lineBlock = new ArrayList<>();
+                sameLine = true;
+                lastY = textBlock.getTopLeft().y;
+            }
+            lineBlock.add(textBlock);
+        }
+
+        if (maxAngle >= 0.05){
+            //todo 文本倾斜校正
+        }
+
+        if (lineBlock.size() > 0) {
+            lineBlock.sort(Comparator.comparingInt(o -> o.getTopLeft().x));
+            lineBlocks.add(lineBlock);
+        }
+        StringBuilder sb = new StringBuilder();
+        TextBlock lastBlock = null;
+        for (List<TextBlock> line : lineBlocks) {
+            TextBlock firstBlock = line.get(0);
+            if (lastBlock != null) {
+                String blockTxt = lastBlock.getText().trim();
+                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)){
+                    sb.append("\n");
+                    for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) {
+                        if (i % 2 == 0){
+                            sb.append("    ");
+                        }
+                    }
+                }
+                else{
+                    if (CharUtil.isLetterOrNumber(endTxt.charAt(0)) && CharUtil.isLetterOrNumber(firstBlock.getText().charAt(0))){
+                        sb.append(" ");
+                    }
+                }
+            }
+            else{
+                for (int i = 0, ln = (firstBlock.getTopLeft().x - minX) / CHAR_WIDTH; i < ln; i++) {
+                    if (i % 2 == 0){
+                        sb.append("    ");
+                    }
+                }
+            }
+
+            for (int i = 0; i < line.size(); i++) {
+                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++) {
+                        sb.append("  ");
+                    }
+                }
+                sb.append(ocrText);
+            }
+            lastBlock = line.get(line.size() - 1);
+        }
+        return sb.toString();
+    }
+
+    static Point frameToPoint(String text) {
+        String[] arr = text.split(",");
+        return new Point(Integer.valueOf(arr[0].trim()), Integer.valueOf(arr[1].trim()));
+    }
+
+    static String postMultiData(String url, byte[] data, String boundary) {
+        return postMultiData(url, data, boundary, "", "");
+    }
+
+    private static String postMultiData(String url, byte[] data, String boundary, String cookie, String referer) {
+        try {
+            HttpRequest request = HttpUtil.createPost(url).timeout(15000);
+            request.contentType("multipart/form-data; boundary=" + boundary);
+            request.body(data);
+            if (StrUtil.isNotBlank(referer)) {
+                request.header("Referer", referer);
+            }
+            if (StrUtil.isNotBlank(cookie)) {
+                request.cookie(cookie);
+            }
+            HttpResponse response = request.execute();
+            return WebUtils.getSafeHtml(response);
+        } catch (Exception ex) {
+            StaticLog.error(ex);
+            return null;
+        }
+    }
+
+    static byte[] mergeByte(byte[]... bytes) {
+        int length = 0;
+        for (byte[] b : bytes) {
+            length += b.length;
+        }
+        byte[] resultBytes = new byte[length];
+        int offset = 0;
+        for (byte[] arr : bytes) {
+            System.arraycopy(arr, 0, resultBytes, offset, arr.length);
+            offset += arr.length;
+        }
+        return resultBytes;
+    }
+
+    public static Button createButton(String id, Runnable action, String toolTip){
+        return createButton(id, BUTTON_SIZE, action, toolTip);
+    }
+
+    public static Button createButton(String id, int size, Runnable action, String toolTip) {
+        javafx.scene.control.Button button = new Button();
+        initButton(button, id, size, action, toolTip);
+        return button;
+    }
+
+    public static ToggleButton createToggleButton(ToggleGroup grp, String id, Runnable action, String toolTip){
+        return createToggleButton(grp, id, BUTTON_SIZE, action, toolTip);
+    }
+
+    public static ToggleButton createToggleButton(ToggleGroup grp, String id, int size, Runnable action, String toolTip) {
+        ToggleButton button = new ToggleButton();
+        button.setToggleGroup(grp);
+        initButton(button, id, size, action, toolTip);
+        return button;
+    }
+
+    private static void initButton(ButtonBase button, String id, int size, Runnable action, String toolTip) {
+        button.setId(id);
+        button.setOnAction(evt -> action.run());
+        button.setMinSize(size, size);
+        if (toolTip != null) {
+            button.setTooltip(new Tooltip(toolTip));
+        }
+    }
+
+    public static void initStage(Stage stage) {
+        try {
+            if (CommUtils.IS_MAC_OS) {
+                URL iconURL = MainFm.class.getResource("/img/logo.png");
+                java.awt.Image image = new ImageIcon(iconURL).getImage();
+                Class<?> appleApp = Class.forName("com.apple.eawt.Application");
+                //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
+                Method setDockIconImage = appleApp.getMethod("setDockIconImage", params);
+                setDockIconImage.invoke(application, image);
+            }
+        } catch (Exception e) {
+            StaticLog.error(e);
+        }
+        stage.setTitle("树洞OCR文字识别");
+        stage.getIcons().add(new javafx.scene.image.Image(MainFm.class.getResource("/img/logo.png").toExternalForm()));
+    }
+
+    public static Rectangle snapScreen(Stage stage){
+        double x = stage.getX();
+        Screen crtScreen = null;
+        for (Screen screen : Screen.getScreens()) {
+            crtScreen = screen;
+            Rectangle2D bounds = screen.getBounds();
+            if (bounds.getMaxX() > x){
+                break;
+            }
+        }
+        Rectangle2D rectangle2D = crtScreen.getBounds();
+        return new Rectangle((int)rectangle2D.getMinX (), (int)rectangle2D.getMinY(), (int) rectangle2D.getWidth(),
+                (int) rectangle2D.getHeight());
+    }
+}

+ 152 - 0
tools-ocr/src/main/java/com/luooqi/ocr/utils/OcrUtils.java

@@ -0,0 +1,152 @@
+package com.luooqi.ocr.utils;
+
+import cn.hutool.core.codec.Base64;
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.util.CharsetUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.URLUtil;
+import cn.hutool.crypto.SecureUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.luooqi.ocr.model.TextBlock;
+
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * tools-ocr
+ * Created by 何志龙 on 2019-03-22.
+ */
+public class OcrUtils {
+
+    public static String ocrImg(byte[] imgData, int ocrType) {
+        switch (ocrType){
+            case 0:
+                return bdGeneralOcr(imgData);
+            case 1:
+                return bdAccurateOcr(imgData);
+            case 2:
+                return sogouMobileOcr(imgData);
+            default:
+                return sogouWebOcr(imgData);
+        }
+    }
+
+    private static String bdGeneralOcr(byte[] imgData){
+        return bdBaseOcr(imgData, "general_location");
+    }
+
+    private static String bdAccurateOcr(byte[] imgData){
+        return bdBaseOcr(imgData, "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate");
+    }
+
+    private static String bdBaseOcr(byte[] imgData, String type){
+        String[] urlArr = new String[]{"http://ai.baidu.com/tech/ocr/general", "http://ai.baidu.com/index/seccode?action=show"};
+        StringBuilder cookie = new StringBuilder();
+        for (String url : urlArr) {
+            HttpResponse cookieResp = WebUtils.get(url);
+            List<String> ckList = cookieResp.headerList("Set-Cookie");
+            for (String s : ckList) {
+                cookie.append(s.replaceAll("expires[\\S\\s]+", ""));
+            }
+        }
+        HashMap<String, String> header = new HashMap<>();
+        header.put("Referer", "http://ai.baidu.com/tech/ocr/general");
+        header.put("Cookie", cookie.toString());
+        String data = "type="+URLUtil.encodeQuery(type)+"&detect_direction=false&image_url&image=" + URLUtil.encodeQuery("data:image/jpeg;base64," + Base64.encode(imgData)) + "&language_type=CHN_ENG";
+        HttpResponse response = WebUtils.postRaw("http://ai.baidu.com/aidemo", data, 0, header);
+        return extractBdResult(WebUtils.getSafeHtml(response));
+    }
+
+    public static String sogouMobileOcr(byte[] imgData) {
+        String boundary = "------WebKitFormBoundary8orYTmcj8BHvQpVU";
+        String url = "http://ocr.shouji.sogou.com/v2/ocr/json";
+        String header = boundary + "\r\nContent-Disposition: form-data; name=\"pic\"; filename=\"pic.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n";
+        String footer = "\r\n" + boundary + "--\r\n";
+        byte[] postData = CommUtils.mergeByte(header.getBytes(CharsetUtil.CHARSET_ISO_8859_1), imgData, footer.getBytes(CharsetUtil.CHARSET_ISO_8859_1));
+        return extractSogouResult(CommUtils.postMultiData(url, postData, boundary.substring(2)));
+    }
+
+    public static String sogouWebOcr(byte[] imgData) {
+        String url = "https://deepi.sogou.com/api/sogouService";
+        String referer = "https://deepi.sogou.com/?from=picsearch&tdsourcetag=s_pctim_aiomsg";
+        String imageData = Base64.encode(imgData);
+        long t = new Date().getTime();
+        String sign = SecureUtil.md5("sogou_ocr_just_for_deepibasicOpenOcr" + t + imageData.substring(0, Math.min(1024, imageData.length())) + "7f42cedccd1b3917c87aeb59e08b40ad");
+        Map<String, Object> data = new HashMap<>();
+        data.put("image", imageData);
+        data.put("lang", "zh-Chs");
+        data.put("pid", "sogou_ocr_just_for_deepi");
+        data.put("salt", t);
+        data.put("service", "basicOpenOcr");
+        data.put("sign", sign);
+        HttpRequest request = HttpUtil.createPost(url).timeout(15000);
+        request.form(data);
+        request.header("Referer", referer);
+        HttpResponse response = request.execute();
+        return extractSogouResult(WebUtils.getSafeHtml(response));
+    }
+
+    private static String extractSogouResult(String html) {
+        if (StrUtil.isBlank(html)) {
+            return "";
+        }
+        JSONObject jsonObject = JSONUtil.parseObj(html);
+        if (jsonObject.getInt("success", 0) != 1) {
+            return "";
+        }
+        JSONArray jsonArray = jsonObject.getJSONArray("result");
+        List<TextBlock> textBlocks = new ArrayList<>();
+        boolean isEng;
+        for (int i = 0; i < jsonArray.size(); i++) {
+            JSONObject jObj = jsonArray.getJSONObject(i);
+            TextBlock textBlock = new TextBlock();
+            textBlock.setText(jObj.getStr("content").trim());
+            //noinspection SuspiciousToArrayCall
+            String[] frames = jObj.getJSONArray("frame").toArray(new String[0]);
+            textBlock.setTopLeft(CommUtils.frameToPoint(frames[0]));
+            textBlock.setTopRight(CommUtils.frameToPoint(frames[1]));
+            textBlock.setBottomRight(CommUtils.frameToPoint(frames[2]));
+            textBlock.setBottomLeft(CommUtils.frameToPoint(frames[3]));
+            textBlocks.add(textBlock);
+        }
+        isEng = jsonObject.getStr("lang", "zh-Chs").equals("zh-Chs");
+        return CommUtils.combineTextBlocks(textBlocks, isEng);
+    }
+
+    private static String extractBdResult(String html) {
+        if (StrUtil.isBlank(html)) {
+            return "";
+        }
+        JSONObject jsonObject = JSONUtil.parseObj(html);
+        if (jsonObject.getInt("errno", 0) != 0) {
+            return "";
+        }
+        JSONArray jsonArray = jsonObject.getJSONObject("data").getJSONArray("words_result");
+        List<TextBlock> textBlocks = new ArrayList<>();
+        boolean isEng = false;
+        for (int i = 0; i < jsonArray.size(); i++) {
+            JSONObject jObj = jsonArray.getJSONObject(i);
+            TextBlock textBlock = new TextBlock();
+            textBlock.setText(jObj.getStr("words").trim());
+            //noinspection SuspiciousToArrayCall
+            JSONObject location = jObj.getJSONObject("location");
+            int top = location.getInt("top");
+            int left = location.getInt("left");
+            int width = location.getInt("width");
+            int height = location.getInt("height");
+            textBlock.setTopLeft(new Point(top, left));
+            textBlock.setTopRight(new Point(top, left + width));
+            textBlock.setBottomLeft(new Point(top + height, left));
+            textBlock.setBottomRight(new Point(top + height, left + width));
+            textBlocks.add(textBlock);
+        }
+        return CommUtils.combineTextBlocks(textBlocks, isEng);
+    }
+
+}

+ 195 - 0
tools-ocr/src/main/java/com/luooqi/ocr/utils/WebUtils.java

@@ -0,0 +1,195 @@
+package com.luooqi.ocr.utils;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import cn.hutool.http.Header;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.log.StaticLog;
+
+/**
+ * fish-web
+ * Created by 何志龙 on 2018-03-25.
+ */
+@SuppressWarnings("SpellCheckingInspection")
+public class WebUtils {
+
+    static {
+        HttpRequest.closeCookie();
+    }
+
+    public static String getSafeHtml(HttpResponse response) {
+        if (response == null) {
+            return "";
+        }
+        return response.body();
+    }
+
+    public static String getHtml(String url) {
+        HttpResponse response = get(url);
+        String html = getSafeHtml(response);
+        if (response != null) {
+            response.close();
+        }
+        return html;
+    }
+
+    public static HttpResponse get(String url) {
+        return get(url, 0, null, true);
+    }
+
+    public static String getLocation(String url, String cookie) {
+        try {
+            HttpResponse response = get(url, 0, new Hashtable<String, String>() {{
+                put("Cookie", cookie);
+            }}, false);
+            if (response == null) {
+                return url;
+            }
+            String location = response.header(Header.LOCATION);
+            response.close();
+            return location;
+        } catch (Exception ex) {
+            return "";
+        }
+    }
+
+    public static HttpResponse get(String url, String cookie) {
+        return get(url, 0, new Hashtable<String, String>() {{
+            put("Cookie", cookie);
+        }}, true);
+    }
+
+    public static HttpResponse get(String url, int userAgent, String cookie) {
+        return get(url, userAgent, new Hashtable<String, String>() {{
+            put("Cookie", cookie);
+        }}, true);
+    }
+
+    public static HttpResponse get(String url, int userAgent, Map<String, String> headers) {
+        return get(url, userAgent, headers, true);
+    }
+
+    public static HttpResponse get(String url, int userAgent, Map<String, String> headers, boolean allowRedirct) {
+        try {
+            HttpRequest request = HttpUtil.createGet(url).timeout(10000).setFollowRedirects(allowRedirct);
+            if (headers == null) {
+                headers = new Hashtable<>();
+            }
+            switch (userAgent) {
+                case 1:
+                    headers.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 9_3_2 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13F69 MicroMessenger/6.3.16 NetType/WIFI Language/zh_CN");
+                    break;
+                case 2:
+                    headers.put("User-Agent", "Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1");
+                    break;
+                case 3:
+                    headers.put("User-Agent", "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; NOKIA; Lumia 930) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/13.10586");
+                    break;
+                case 4:
+                    headers.put("User-Agent", "NativeHost");
+                    break;
+                case 5:
+                    headers.put("User-Agent", "Dalvik/1.6.0 (Linux; U; Android 4.4.2; NoxW Build/KOT49H) ITV_5.7.1.46583");
+                    break;
+                case 6:
+                    headers.put("User-Agent", "qqlive");
+                    break;
+                case 7:
+                    headers.put("User-Agent", "Dalvik/1.6.0 (Linux; U; Android 4.2.2; 6S Build/JDQ39E)");
+                    break;
+                case 8:
+                    headers.put("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) XIAMI-MUSIC/3.0.9 Chrome/56.0.2924.87 Electron/1.6.11 Safari/537.36");
+                    break;
+                case 9:
+                    headers.put("User-Agent", "okhttp/2.7.5");
+                    break;
+                case 10:
+                    headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; oppo r11 plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 SogouSearch Android1.0 version3.0");
+                    break;
+                default:
+                    headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36");
+                    break;
+            }
+            request.addHeaders(headers);
+            return request.execute();
+        } catch (Exception ex) {
+            StaticLog.error(ex);
+            return null;
+        }
+    }
+
+    public static HttpResponse postRaw(String url, String data) {
+        return postRaw(url, data, 0, null);
+    }
+
+    public static HttpResponse postRaw(String url, String data, int userAgent, Map<String, String> headers) {
+        return postData(url, new Hashtable<String, Object>() {{
+            put("FORM", data);
+        }}, 2, userAgent, headers);
+    }
+
+    public static HttpResponse postJson(String url, String data, int userAgent, Map<String, String> headers) {
+        return postData(url, new Hashtable<String, Object>() {{
+            put("JSON", data);
+        }}, 1, userAgent, headers);
+    }
+
+    public static HttpResponse postForm(String url, Map<String, Object> data, int userAgent, Map<String, String> headers) {
+        return postData(url, data, 0, userAgent, headers);
+    }
+
+    private static HttpResponse postData(String url, Map<String, Object> data, int contentType, int userAgent, Map<String, String> headers) {
+        try {
+            HttpRequest request = HttpUtil.createPost(url).timeout(10000);
+            if (contentType == 0) {
+                request.contentType("application/x-www-form-urlencoded");
+                request.form(data);
+            } else if (contentType == 1) {
+                request.body(data.values().iterator().next().toString(), "application/json;charset=UTF-8");
+            } else {
+                request.contentType("application/x-www-form-urlencoded");
+                request.body(data.values().iterator().next().toString());
+            }
+            if (headers == null) {
+                headers = new Hashtable<>();
+            }
+            switch (userAgent) {
+                case 1:
+                    headers.put("User-Agent", "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3");
+                    break;
+                case 2:
+                    headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19");
+                    break;
+                case 3:
+                    headers.put("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)");
+                    break;
+                case 4:
+                    headers.put("User-Agent", "NativeHost");
+                    break;
+                case 5:
+                    headers.put("User-Agent", "Apache-HttpClient/UNAVAILABLE (java 1.4)");
+                    break;
+                case 6:
+                    headers.put("User-Agent", "Mozilla/5.0 (iPad; CPU OS 8_1_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B466 Safari/600.1.4");
+                    break;
+                case 7:
+                    headers.put("User-Agent", "okhttp/2.7.5");
+                    break;
+                case 10:
+                    headers.put("User-Agent", "Mozilla/5.0 (Linux; Android 5.1.1; oppo r11 plus Build/LMY48Z) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 SogouSearch Android1.0 version3.0");
+                    break;
+                default:
+                    headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36");
+                    break;
+            }
+            request.addHeaders(headers);
+            return request.execute();
+        } catch (Exception ex) {
+            StaticLog.error(ex);
+            return null;
+        }
+    }
+}

+ 49 - 0
tools-ocr/src/main/resources/css/main.css

@@ -0,0 +1,49 @@
+.button.pressed { -fx-background-color: ghostwhite; }
+
+.button, .toggle-button {
+    -fx-background-position: center;
+    -fx-background-repeat: no-repeat;
+    -fx-background-size: 18px 18px;
+    -fx-cursor: pointer;
+}
+
+#snapBtn{
+    -fx-background-image: url(/img/screenshot.png);
+}
+
+#openImageBtn{
+    -fx-background-image: url(/img/add-image.png);
+}
+
+#clearBtn{
+     -fx-background-image: url(/img/clear.png);
+ }
+
+#copyBtn{
+    -fx-background-image: url(/img/copy.png);
+}
+
+#pasteBtn{
+    -fx-background-image: url(/img/paste.png);
+}
+
+#wrapBtn{
+    -fx-background-image: url(/img/wrap.png);
+}
+
+/*#resetBtn{*/
+    /*-fx-background-image: url(/img/reset.png);*/
+/*}*/
+
+/*#segmentBtn{*/
+    /*-fx-background-image: url(/img/segment.png);*/
+/*}*/
+
+/*#topBar .separator{*/
+    /*-fx-padding: 2px -5px 2px -2px;*/
+/*}*/
+
+#ocrTextArea .text {
+    -fx-line-spacing: 0px;
+    -fx-background-color: #ffffff;
+}

BIN
tools-ocr/src/main/resources/img/add-image.png


BIN
tools-ocr/src/main/resources/img/clear.png


BIN
tools-ocr/src/main/resources/img/copy.png


BIN
tools-ocr/src/main/resources/img/logo.png


BIN
tools-ocr/src/main/resources/img/paste.png


BIN
tools-ocr/src/main/resources/img/screenshot.png


BIN
tools-ocr/src/main/resources/img/wrap.png


BIN
tools-ocr/src/main/resources/logo.ico


File diff suppressed because it is too large
+ 8 - 0
tools-ocr/src/test/java/com/luooqi/ocr/utils/OcrUtilsTest.java


Some files were not shown because too many files changed in this diff