TrWebOCR-开源的离线OCR
介绍
TrWebOCR,基于开源项目 Tr 构建。
在其基础上提供了http调用的接口,便于你在其他的项目中调用。
并且提供了易于使用的web页面,便于调试或日常使用。
特性
- 中文识别
快速高识别率 - 文字检测
支持一定角度的旋转 - 并发请求
由于模型本身不支持并发,但通过tornado多进程的方式,能支持一定数量的并发请求。具体并发数取决于机器的配置。
安装说明
使用docker-compose.yml部署
version: '3.5'
services:
trwebocrsrv:
image: mmmz/trwebocr:latest
container_name: trwebocrsrv
ports:
- 8089:8089
volumes:
- ./data/logs:/opt/logs
networks:
trwebocr:
aliases:
- trwebocrsrv
networks:
trwebocr:
name: trwebocr
driver: bridge
访问:http://127.0.0.1:8089 即可
Java访问【基于spring-boot】
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.TypeReference;
import com.xxxxx.common.msg.ResponseData;
import com.xxxxx.common.utils.JsonUtils;
import com.xxxxx.web.module.form.OcrBase64Form;
import com.xxxxx.web.module.vo.OrcBaseVo;
import com.xxxxx.web.module.vo.OrcDataVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringEscapeUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.io.File;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.List;
/**
* @author HanKeQi
* @Date 2021/11/25 4:10 PM
* @Version 1.0
*/
@Slf4j
@Api(tags = "OCR文字识别")
@RestController
public class OcrWordController {
private static final String OCR_WORD = "http://127.0.0.1:8089/api/tr-run/";
@Autowired
private RestTemplate restTemplate;
@ApiOperation(value="OCR文字识别文件型", notes="\nOCR文字识别文件型")
@PostMapping(value = "/orc/multipart-file")
public ResponseData<OrcDataVo> getBaseFile(HttpServletRequest request){
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest)request;
MultipartFile file = multipartHttpServletRequest.getFile("file");
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("multipart/form-data");
headers.setContentType(type);
Resource resource = file.getResource();
MultiValueMap<String, Object> mapFile = new LinkedMultiValueMap<>();
// FileSystemResource resource = new FileSystemResource(resource1);
mapFile.add("file", resource);
HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(mapFile,headers);
ResponseEntity<String> postForEntity = restTemplate.postForEntity(OCR_WORD, httpEntity, String.class);
String body = postForEntity.getBody();
String s = StringEscapeUtils.unescapeJava(body);
OrcBaseVo<OrcDataVo> orcBaseVo = JsonUtils.parseObject(s, new TypeReference<OrcBaseVo<OrcDataVo>>() {
});
if (orcBaseVo != null){
OrcDataVo orcDataVo = orcBaseVo.getData();
List<Object> raw_out = orcDataVo.getRaw_out();
BigDecimal nextLineHeight;
StringBuffer ocrText = new StringBuffer();
int size = raw_out.size();
for (int i = 0; i < size; i++) {
String ocrRaw = JsonUtils.toJSONString(raw_out.get(i));
JSONArray array = JSONArray.parseArray(ocrRaw);
JSONArray arrayChildren = JSONArray.parseArray(JsonUtils.toJSONString(array.get(0)));
ocrText.append(array.get(1));
// // 合并同一行的数据
if (i < size - 1){
JSONArray arrayNext = JSONArray.parseArray(JsonUtils.toJSONString(raw_out.get(i + 1)));
JSONArray arrayNextChildren = JSONArray.parseArray(JsonUtils.toJSONString(arrayNext.get(0)));
nextLineHeight = (BigDecimal) arrayNextChildren.get(1);
// 判断判断同一行的依据是 两段的行高差 小于 行高的一半
BigDecimal arrayChildrenBig0 = (BigDecimal)arrayChildren.get(1);
BigDecimal arrayChildrenBig2 = (BigDecimal) arrayChildren.get(3);
if (Math.abs(arrayChildrenBig0.subtract(nextLineHeight).doubleValue()) < arrayChildrenBig2.divide(new BigDecimal("2")).doubleValue()){
ocrText.append(" ");
}else {
ocrText.append("\r");
}
}
}
log.info("ocrText = {}", ocrText);
orcDataVo.setData(ocrText.toString());
return ResponseData.newInstanceOfSuccess(orcDataVo);
}
return ResponseData.newInstanceOfDefaultError();
}
@ApiOperation(value="OCR文字识别base64Form", notes="\nOCR文字识别base64Form")
@PostMapping(value = "/orc/multipart-base64")
public ResponseData<OrcDataVo> getBaseBase64(@Valid @RequestBody OcrBase64Form form, BindingResult result){
if (result.hasErrors()){
return ResponseData.newInstanceOfInvalid(result);
}
HttpHeaders headers = new HttpHeaders();
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("img", form.getImg());
HttpEntity<MultiValueMap<String, String>> httpEntity = new HttpEntity<>(params, headers);
ResponseEntity<String> postForEntity = restTemplate.postForEntity(OCR_WORD, httpEntity, String.class);
String body = postForEntity.getBody();
String s = StringEscapeUtils.unescapeJava(body);
OrcBaseVo<OrcDataVo> orcBaseVo = JsonUtils.parseObject(s, new TypeReference<OrcBaseVo<OrcDataVo>>() {
});
if (orcBaseVo != null){
OrcDataVo orcDataVo = orcBaseVo.getData();
List<Object> raw_out = orcDataVo.getRaw_out();
BigDecimal nextLineHeight;
String ocrRaw = "";
StringBuffer ocrText = new StringBuffer();
int size = raw_out.size();
for (int i = 0; i < size; i++) {
ocrRaw = JsonUtils.toJSONString(raw_out.get(i));
JSONArray array = JSONArray.parseArray(ocrRaw);
JSONArray arrayChildren = JSONArray.parseArray(JsonUtils.toJSONString(array.get(0)));
ocrText.append(array.get(1));
// // 合并同一行的数据
if (i < size - 1){
JSONArray arrayNext = JSONArray.parseArray(JsonUtils.toJSONString(raw_out.get(i + 1)));
JSONArray arrayNextChildren = JSONArray.parseArray(JsonUtils.toJSONString(arrayNext.get(0)));
nextLineHeight = (BigDecimal) arrayNextChildren.get(1);
// 判断判断同一行的依据是 两段的行高差 小于 行高的一半
BigDecimal arrayChildrenBig0 = (BigDecimal)arrayChildren.get(1);
BigDecimal arrayChildrenBig2 = (BigDecimal) arrayChildren.get(3);
if (Math.abs(arrayChildrenBig0.subtract(nextLineHeight).doubleValue()) < arrayChildrenBig2.divide(new BigDecimal("2")).doubleValue()){
ocrText.append(" ");
}else {
ocrText.append("\r");
}
}
}
log.info("ocrText = {}", ocrText);
orcDataVo.setData(ocrText.toString());
return ResponseData.newInstanceOfSuccess(orcDataVo);
}
return ResponseData.newInstanceOfDefaultError();
}
}
请求参数代码
package com.xxx.web.module.form;
import lombok.Data;
/**
* @author HanKeQi
* @Date 2021/11/26 2:04 PM
* @Version 1.0
*/
@Data
public class OcrBase64Form {
private String img;
}
返回参数代码
package com.xxxx.web.module.vo;
import lombok.Data;
import lombok.ToString;
/**
* @author HanKeQi
* @Date 2021/11/25 4:10 PM
* @Version 1.0
*/
@Data
@ToString
public class OrcBaseVo<T> {
private Integer code;
private String msg;
private T data;
}
package com.xxxx.web.module.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
import java.util.List;
/**
* @author HanKeQi
* @Date 2021/11/25 4:11 PM
* @Version 1.0
*/
@Data
@ToString
public class OrcDataVo {
//base64图片
@ApiModelProperty("识别文字后base64图片")
private String img_detected;
//输出
@ApiModelProperty("输出坐标、识别文字位置")
private List<Object> raw_out;
//速度
@ApiModelProperty("识别速度")
private BigDecimal speed_time;
//识别输出
@ApiModelProperty("识别整理后输出")
private String data;
}