吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3766|回复: 10
收起左侧

[Java 原创] ShareX-OCRRedirector,自用的截图OCR本地化程序代码

[复制链接]
Pinenut666 发表于 2022-11-11 17:12
本帖最后由 Pinenut666 于 2022-11-11 17:58 编辑

起因

前几天有个朋友问我有没有什么不需要登录QQ的OCR软件,我一琢磨:Steam上那个ShareX不就挺好的。
结果下载下来一看,旧版(13.X)用的是ocr.space的免费服务,新版是直接调用的本地OCR,总结下来就是一句话:识别不精准,需求达不到。

于是怒(不是)而写了一个对接百度OCR的Java程序,和大家一起分享。

(另:因为这个东西既涉及到对ShareX的反编译(修改OCR地址),又涉及到一个小Java项目的编写,但是考虑到ShareX是开源软件,而且修改的部分只占很小一部分,更多的是Java项目的实现思考,所以最后还是决定放在编程语言区……如果放错了区,本人万分抱歉)

(另2:在写的时候有一些问题,也希望和大家一起探讨~)

确定思路和对象

因为我实在懒得编译原版代码,于是我从ocr.space的入手,因为它是靠网络API来进行OCR识别,偷梁换柱比较方便。

(不是,我点了个保存草稿,怎么给我发出去了,算了那我接着写好了)

先用Dnspy进行一番查看,考虑到是OCR,我们直接搜Ocr,搜索到一个OcrSpace
@MI]NOHP[D$O2YLA[0)TZ{6.png

毕竟不是商业软件,这逻辑一眼就能看懂。我们跑去ocr.space 的网站 https://ocr.space/OCRAPI , 不费吹灰之力我们就能得到它的API信息,请求和返回都有。

既然我们要用自己的OCR做OCR识别,(而我还不擅长C#,算了我怎么好像什么都不擅长)那么我们遵循这样一个思想:

让软件认为返回值是原本的接口返回,而实际上的OCR工作,由百度等其他接口实现。

粗俗点说就是:软件不动,我们改我们的服务器的项目,让服务器的返回值和原本的接口返回值保持一致(而OCR的内容由百度等其他接口实现)。

编写代码

先用Idea创建一个SpringBoot项目,引入fastjson,引入Baidu SDK。

        <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.17</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.baidu.aip/java-sdk -->
        <dependency>
            <groupId>com.baidu.aip</groupId>
            <artifactId>java-sdk</artifactId>
            <version>4.16.12</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-simple</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

然后我们观察一下dnspy里的接口:

            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            dictionary.Add("apikey", this.APIKey);
            dictionary.Add("language", this.Language.ToString());
            dictionary.Add("isOverlayRequired", this.Overlay.ToString());
            UploadResult uploadResult = base.SendRequestFile("https://apipro1.ocr.space/parse/image", stream, fileName, "file", dictionary, null, null, HttpMethod.POST, "multipart/form-data", null);

我们不难看出传值方式为POST,然后是multipart/form-data,显然传输的是二进制文件流,有了这些回到ocr.space查看接口定义,对应一下不难得到:

L8MJ}LD1$PQGYJDM[L8VF%R.png

于是我们先写一个controller放着:

    @RequestMapping("/select_ocr")
    public ocrSpaceResponse selectOcr(HttpServletRequest request,
                                      @RequestParam(value = "apikey") String apikey,
                                      @RequestParam(value = "language") String language,
                                      @RequestParam(value = "isOverlayRequired") String required,
                                      @RequestParam(value = "file") MultipartFile file
    )  {}

之后我们创建一个service,用spring管理service对象:

public class BaiduAPI {
    public static final String APP_ID = "";
    public static final String API_KEY = "";
    public static final String SECRET_KEY = "";
    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    AipOcr client;
    public BaiduAPI()
    {
        client = new AipOcr(APP_ID, API_KEY, SECRET_KEY);
        // 可选:设置网络连接参数
        client.setConnectionTimeoutInMillis(2000);
        client.setSocketTimeoutInMillis(60000);
    }

multipart file先转换成base64,这样方便我们处理。

(结果根据百度SDK的说法,给百度SDK传值要么传图片地址,要么传图片数组,那么我们还需要一个base64转byte[]数组的代码。网上百度一下,就能找到对应的需求:

package com.example.demo.tools;

import com.example.demo.Demo1Application;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class ImageToBase64 {
    private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Demo1Application.class);
    /**
     * 将MultipartFile 图片文件编码为base64
     * @Param file
     * @return
     * @throws Exception
     */
    public static String generateBase64(MultipartFile file){
        if (file == null || file.isEmpty()) {
            throw new RuntimeException("图片不能为空!");
        }
        String fileName = file.getOriginalFilename();
        String fileType = fileName.substring(fileName.lastIndexOf("."));
        String contentType = file.getContentType();
        byte[] imageBytes = null;
        String base64EncoderImg="";
        try {
            imageBytes = file.getBytes();
            BASE64Encoder base64Encoder =new BASE64Encoder();
            /**
             * 1.Java使用BASE64Encoder 需要添加图片头("data:" + contentType + ";base64,"),
             *   其中contentType是文件的内容格式。
             * 2.Java中在使用BASE64Enconder().encode()会出现字符串换行问题,这是因为RFC 822中规定,
             *   每72个字符中加一个换行符号,这样会造成在使用base64字符串时出现问题,
             *   所以我们在使用时要先用replaceAll("[\\s*\t\n\r]", "")解决换行的问题。
             */
            base64EncoderImg = "data:" + contentType + ";base64," + base64Encoder.encode(imageBytes);
            base64EncoderImg = base64EncoderImg.replaceAll("[\\s*\t\n\r]", "");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //logger.info("当前图片为:" + base64EncoderImg);
        return base64EncoderImg;
    }
    public static byte[] base64ToImgByteArray(String base64){
        try{
        sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();
        //因为参数base64来源不一样,需要将附件数据替换清空掉。如果此入参来自canvas.toDataURL("image/png");
        base64 = base64.replaceAll("data:image/png;base64,", "");
        //base64解码并转为二进制数组
        byte[] bytes = decoder.decodeBuffer(base64);
        for (int i = 0; i < bytes.length; ++i) {
            if (bytes[i] < 0) {// 调整异常数据
                bytes[i] += 256;
            }
        }
        return bytes;
        }
        catch (Exception e)
        {
            return null;
        }
    }
//这里对参数属性,参数来自html img.src或者canvas.toDataURL("image/png");
//如果是其他类型的图片请做base64 = base64.replaceAll("data:image/png;base64,", "");里面的png修改
}

接下来把ocr.space的接口返回和百度接口的返回JSON拖到GSONFormatplus里,生成对应的对象:(对象名分别是ocrSpaceResponse和BaiduResponse)

之后编写获取百度OCR的代码:

    public ocrSpaceResponse getBaiduResult(String base64Pic,String language)
    {
        HashMap<String,String> options = new HashMap<>();
        //options.put("detect_language", "true");
                //这里的JSONObject并不是fastjson,而是org.json,所以还要转换一下,才能用到GsonFormatplus的对象里
        JSONObject json = client.basicGeneral(base64ToImgByteArray(base64Pic),options);
        com.alibaba.fastjson.JSONObject c = com.alibaba.fastjson.JSONObject.parseObject(json.toString());
                //这里
        BaiduResponse response1 = com.alibaba.fastjson.JSONObject.toJavaObject(c, BaiduResponse.class);
        if(response1.getErrorMsg()==null)
        {
            StringBuilder wordback = new StringBuilder();
            for(BaiduResponse.WordsResultDTO word:response1.getWordsResult())
            {
                //拼凑数据,因为百度返回和OCRSPACE对换行的处理不一样,这样就换成了OcrSpace的换行方式
                wordback.append(word.getWords()).append("\r\n");
            }
            return CreateResponse.createResponse(wordback);
        }
        else {
            return null;
        }
    }

修改一下client.basicGeneral,改成client.basicAccurateGeneral就是百度高精度了。
再看一眼这里的代码:

            if (!uploadResult.IsSuccess)
            {
                uploadResult = base.SendRequestFile("https://apipro2.ocr.space/parse/image", stream, fileName, "file", dictionary, null, null, HttpMethod.POST, "multipart/form-data", null);
            }
            if (uploadResult.IsSuccess)
            {
                return JsonConvert.DeserializeObject<OCRSpaceResponse>(uploadResult.Response);
            }

这个!IsSuccess就代表第一个接口失败。也是,毕竟百度免费SDK额度有限,用完了高精度的怎么办呢,再用低精度的就好。(毕竟加一起2000条,一个月用完属于是有点难度)

于是首先创建一个报错的异常

package com.example.demo.exceptions;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(code= HttpStatus.INTERNAL_SERVER_ERROR,reason="Failed so use another!")
public class ServerException extends Exception {

}

再从这里,检测到如果为空就抛出异常

    @RequestMapping("/baidu_high_ocr")
    public ocrSpaceResponse baidu_high_ocr(HttpServletRequest request,
                                           @RequestParam(value = "apikey") String apikey,
                                           @RequestParam(value = "language") String language,
                                           @RequestParam(value = "isOverlayRequired") String required,
                                           @RequestParam(value = "file") MultipartFile file
    ) throws ServerException {
        logger.info("使用百度高精度");
        String base = ImageToBase64.generateBase64(file);
        ocrSpaceResponse response = baidu.getBaiduHighResult(base, "schinese");
        if (response == null) {
            throw new ServerException();
        }
        return response;
    }

之后只需要修改一下dnspy这里的地址,再启动程序即可。(我的Springboot开的端口是8082)

        public OCRSpaceResponse DoOCR(Stream stream, string fileName)
        {
            Dictionary<string, string> dictionary = new Dictionary<string, string>();
            dictionary.Add("apikey", this.APIKey);
            dictionary.Add("language", this.Language.ToString());
            dictionary.Add("isOverlayRequired", this.Overlay.ToString());
            UploadResult uploadResult = base.SendRequestFile("http://127.0.0.1:8082/baidu_low_ocr", stream, fileName, "file", dictionary, null, null, HttpMethod.POST, "multipart/form-data", null);
            if (!uploadResult.IsSuccess)
            {
                uploadResult = base.SendRequestFile("http://127.0.0.1:8082/baidu_high_ocr", stream, fileName, "file", dictionary, null, null, HttpMethod.POST, "multipart/form-data", null);
            }
            if (uploadResult.IsSuccess)
            {
                return JsonConvert.DeserializeObject<OCRSpaceResponse>(uploadResult.Response);
            }
            return null;
        }

来一张成功的截图:

0(Z]0CD~_5]H3%7Q`U0[B[X.png

最后有几个问题想和各位探讨一下:

  1. 在使用BaiduSdk的时候,它的JSON和我们的JSON库不一样,我是把它转换成了json文本,然后再用自己用的JSON库转换一次,才能让它直接赋值给对象。有没有比较好的方案去解决这个问题?
  2. 可以看到BaiduSDK的key值等是写死的(具体看我项目文件),有没有比较合适的手段让他从文件中读写?之前尝试过使用Springboot的application.properties但是赋值就变成空了(@Value注解)

还望各位大佬不吝赐教,小弟在此感谢各位。

demo1.zip (128.49 KB, 下载次数: 75)

免费评分

参与人数 3吾爱币 +9 热心值 +2 收起 理由
烟不离手 + 1 + 1 热心回复!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
supersup + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| Pinenut666 发表于 2022-11-12 01:35
xouou 发表于 2022-11-11 20:37
百度的开源离线OCR库paddle-ocr, 不比其它在线的差
配合天若OCR的魔改版, 完美使用, 不需要申请api就能使 ...

受教了,有空试一试看

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
xouou + 1 + 1 楼主源码级解说分享更让人敬佩

查看全部评分

xouou 发表于 2022-11-11 20:37
百度的开源离线OCR库paddle-ocr, 不比其它在线的差
配合天若OCR的魔改版, 完美使用, 不需要申请api就能使用天若OCR翻译
eaglexiong 发表于 2022-11-13 17:28
自己动手还不错,用脚本或Acrobat 也行的

免费评分

参与人数 1热心值 +1 收起 理由
Pinenut666 + 1 用心讨论,共获提升!

查看全部评分

william_ni 发表于 2022-11-14 12:07
shareX 不是开源的吗?为什么还要 反编译?
 楼主| Pinenut666 发表于 2022-11-14 15:46
william_ni 发表于 2022-11-14 12:07
shareX 不是开源的吗?为什么还要 反编译?

因为懒得下载工具去编译,电脑不是我的,下个dnspy还行,下个全套编译工具感觉没必要。
所以才扔到编程语言区啦
senlly 发表于 2023-3-12 07:36
给个成品啊
hesi2010bit 发表于 2023-4-18 15:45
感觉是个好东西,不过下下来不知道咋用。。。没有可执行程序
LOVEPOJIEcd 发表于 2023-4-21 09:36
不会用——谢谢
xors 发表于 2023-4-21 16:59
2问题能不能从包外文件里去读取呢?在application.properties里定义配置文件路径变量,然后启动项目的时候再赋值
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 23:05

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表