ShareX-OCRRedirector,自用的截图OCR本地化程序代码
本帖最后由 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
毕竟不是商业软件,这逻辑一眼就能看懂。我们跑去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查看接口定义,对应一下不难得到:
于是我们先写一个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 < 0) {// 调整异常数据
bytes += 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;
}
```
来一张成功的截图:
最后有几个问题想和各位探讨一下:
1. 在使用BaiduSdk的时候,它的JSON和我们的JSON库不一样,我是把它转换成了json文本,然后再用自己用的JSON库转换一次,才能让它直接赋值给对象。有没有比较好的方案去解决这个问题?
2. 可以看到BaiduSDK的key值等是写死的(具体看我项目文件),有没有比较合适的手段让他从文件中读写?之前尝试过使用Springboot的application.properties但是赋值就变成空了(@Value注解)
还望各位大佬不吝赐教,小弟在此感谢各位。
xouou 发表于 2022-11-11 20:37
百度的开源离线OCR库paddle-ocr, 不比其它在线的差
配合天若OCR的魔改版, 完美使用, 不需要申请api就能使 ...
受教了,有空试一试看 百度的开源离线OCR库paddle-ocr, 不比其它在线的差
配合天若OCR的魔改版, 完美使用, 不需要申请api就能使用天若OCR翻译 自己动手还不错,用脚本或Acrobat 也行的 shareX 不是开源的吗?为什么还要 反编译? william_ni 发表于 2022-11-14 12:07
shareX 不是开源的吗?为什么还要 反编译?
因为懒得下载工具去编译,电脑不是我的,下个dnspy还行,下个全套编译工具感觉没必要。
所以才扔到编程语言区啦 给个成品啊 感觉是个好东西,不过下下来不知道咋用。。。没有可执行程序 不会用{:1_909:}——谢谢 {:1_904:} 2问题能不能从包外文件里去读取呢?在application.properties里定义配置文件路径变量,然后启动项目的时候再赋值
页:
[1]
2