文章目录
概要
提示:需求描述
前端上传附件,携带发票信息,后端收到附件信息,进行识别。
前端代码
前端代码位置
插入代码
<script>
jQuery(document).ready(function () {
var fpbhID = WfForm.convertFieldNameToId("fpbh", "detail_2"); //发票编号ID
var sfzfID = WfForm.convertFieldNameToId("sfzf", "detail_2"); //是否重复ID
var scfpID = WfForm.convertFieldNameToId("scfp", "detail_2"); //上传发票ID
var sfyfpID = WfForm.convertFieldNameToId("sfyfp")//是否有发票ID
var arr = [];//本条流程发票编号数组
var rowArr = WfForm.getDetailAllRowIndexStr("detail_2").split(",");
for (var i = 0; i < rowArr.length; i++) {
var rowIndex = rowArr[i];
if (rowIndex !== "") {
arr.push(WfForm.getFieldValue(fpbhID + "_" + rowIndex)); //遍历明细行字段
}
}
console.log("xxxxxxxxxxxx:", arr);
WfForm.bindDetailFieldChangeEvent(fpbhID, function (id, rowIndex, value) {
console.log("WfForm.bindDetailFieldChangeEvent--", id, rowIndex, value);
setTimeout(() => {
var sfzf = WfForm.getFieldValue(sfzfID + "_" + rowIndex)
console.log("sfzf:", sfzf)
if (sfzf == 1) {
Dialog.alert("发票编号重复,请检查填写是否有误!!!")
WfForm.changeFieldValue(id + "_" + rowIndex, { value: "" })
WfForm.changeFieldValue(scfpID + "_" + rowIndex, { value: "" })
console.log("发票编号重复,请检查填写是否有误!!!")
} else {
if (arr.includes(value) && value != "") {
Dialog.alert("本条流程发票编号重复!!!")
WfForm.changeFieldValue(id + "_" + rowIndex, { value: "" })
WfForm.changeFieldValue(scfpID + "_" + rowIndex, { value: "" })
console.log("本条流程发票编号重复!!!")
}
else if (arr.indexOf(arr[rowIndex]) == -1) {
arr.push(value);
console.log("新的一行数据")
} else {
arr[rowIndex] = value;
console.log("重复行数据")
}
}
}, 1000)
});
WfForm.bindDetailFieldChangeEvent(scfpID, function (id, rowIndex, value) {
console.log(value);
var targetSpan = $("span[data-index='" + value + "']");
console.log(targetSpan);
// 查找该 span 元素下面的 img 元素,并获取其 src 属性
var imgSrc = " http://192.168.87.146:8086"
imgSrc = imgSrc + targetSpan.next('img').attr('src');
// 输出 img 元素的 src 属性
//console.log(imgSrc);
// 请求
getInvoiceInformation(id, rowIndex, value,imgSrc)
});
});
function getInvoiceInformation(id, rowIndex, value,imgSrc){
console.log("axios");
var fpbh= WfForm.convertFieldNameToId("fpbh", "detail_2"); //发票编号ID
var fprq= WfForm.convertFieldNameToId("fprq", "detail_2"); //发票日期
var fpwsje= WfForm.convertFieldNameToId("fpwsje", "detail_2"); //发票未税金额
var fpse= WfForm.convertFieldNameToId("fpse", "detail_2"); //发票税额
var jshj= WfForm.convertFieldNameToId("jshj", "detail_2"); //价税合计
var fplx= WfForm.convertFieldNameToId("fplx", "detail_2"); //发票类型
var fpxsfmc= WfForm.convertFieldNameToId("fpxsfmc", "detail_2"); //发票销售名称
var url = "http://192.168.87.146:8086/api/formmode/custom/findconflict";
// var url = "http://localhost:8086/api/formmode/custom/findconflict";
//(window.ecologyContentPath||'') +
$.ajax({
url:url,
type:"POST",
data:{
"docid":value
},
success:function(res){
var result = res.result;
var resultArray = result.replace(/^"|"$/g, '').split(',');
console.log(resultArray);
var dateString = resultArray[5];
var formattedDate = dateString.substring(0, 4) + '-' + dateString.substring(4, 6) + '-' + dateString.substring(6, 8);
WfForm.changeFieldValue(fprq + "_" + rowIndex, { value: formattedDate });
WfForm.changeFieldValue(fpbh + "_" + rowIndex, { value: resultArray[3] });
WfForm.changeFieldValue(jshj + "_" + rowIndex, { value: resultArray[4]});
if(resultArray[1]=="01"||resultArray[1]=="08"||resultArray[1]=="31"){
WfForm.changeFieldValue(fplx + "_" + rowIndex, { value: "0"});
}else{
WfForm.changeFieldValue(fplx + "_" + rowIndex, { value: "1"});
}
},
error:function(res){
// alert('后端异常!请联系运维相关人员.');
WfForm.changeFieldValue(fprq + "_" + rowIndex, { value: "" });
WfForm.changeFieldValue(fpbh + "_" + rowIndex, { value: ""});
WfForm.changeFieldValue(fpse + "_" + rowIndex, { value: ""});
WfForm.changeFieldValue(jshj + "_" + rowIndex, { value: ""});
WfForm.changeFieldValue(fplx + "_" + rowIndex, { value: ""});
WfForm.changeFieldValue(fpwsje + "_" + rowIndex, { value: ""});
WfForm.changeFieldValue(fpxsfmc + "_" + rowIndex, { value: ""});
},
jsonp:true
});
}
</script>
<style>
</style>
代码分析:
1、绑定上传发票动作
var scfpID = WfForm.convertFieldNameToId(“scfp”, “detail_2”); //上传发票ID
WfForm.bindDetailFieldChangeEvent(scfpID, function (id, rowIndex, value){ }
2、发送ajax请求到后端 ,携带参数为图片的值
$.ajax({
url:url,
type:"POST",
data:{
"docid":value
},
success:function(res){
var result = res.result;
//得到相应之后 前端赋值
WfForm.changeFieldValue(fpbh + "_" + rowIndex, { value: ""});
}
后端代码
提示:使用无侵入post开发
参考思路:泛微OA自定义post接口和路径(无侵入)
识别二维码用了 谷歌的zxing 进行第一次识别,如果识别不出来,会进入我发别的服务端口9999 接口中,用了OpenCV 技术
第一步,引入jar包
参考思路: 泛微E9 项目引入jar包
第二步, 定义controller路径
@Path(“/formmode/custom”)
@POST
@Path(“/findconflict”)
第三步,对于附件的解密操作
我们需要接收前端传回来的值,也就是上传的附件的ID。有了这个值,我们可以在数据库找到上传的附件的加密文件,在进行解密操作
参考思路:从OA中将加密文件解密并保存本地,或者保存到共享盘
第四步 ,将解密的文件写入指定位置,进行二维码解析
参考思路:java+ zxing识别二维码信息
如果上传的是PDF文件,需要将PDF文件转换为图片类型
参考思路:pdf转图片【java版实现】
第五步 ,OpenCV识别二维码
如果zxing 识别不了,需要提交识别率,我这里用了OpenCV来实现,效果不错
参考思路:JAVA提高ZXING对图片中的二维码的识别率(第二弹)
如果打包的jar 中没有引入你需要的jar包 ,需要修改为如下
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<scope>system</scope>
<version>3.1.0</version>
<systemPath>${pom.basedir}/src/main/resources/lib/core-3.1.0.jar</systemPath>
</dependency>
第六步、打包项目为jar包,暴露接口
用于泛微无法引入动态库dll,导致我一直失败,无法引入OpenCV的动态库dll,然后我将这段代码,写入了一个springboot项目,并且打包为jar包,暴露接口给OA调用
参考思路:maven如何打包jar包,已经如何后台运行jar包
后端完整代码
package com.api.formmode.web;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import leadyo.util.LogTool;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import weaver.conn.RecordSet;
import weaver.file.ImageFileManager;
import weaver.general.Util;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import com.google.zxing.*;
// 自定义action,无侵入开发
@Path("/formmode/custom")
public class CustomFormmodeAction {
static String currentPath = System.getProperty("user.dir");
static LogTool log = new LogTool("/log/selfdev/ysc/MES/findConflict", false);
//static QRcodeDecode QR = new QRcodeDecode();
@POST
@Path("/findconflict")
@Produces({"application/json"})
public String findConflict(@Context HttpServletRequest var1, @Context HttpServletResponse var2) {
RecordSet rs = new RecordSet();
log.writeLog("------" + "DG01-费用报销申请流程识别明细表2附件二维码信息" + "开始------");
String docid = var1.getParameter("docid");
log.writeLog("附件id:"+docid);
int imagefileid =0;
String imagefilename = "";
String sql = "select i.imagefileid ,i.imagefilename from DocImageFile i where i.docid = " + docid ;
rs.execute(sql);
if (rs.next()) {
log.writeLog(rs);
imagefileid = Integer.parseInt(Util.null2String(rs.getString("imagefileid")));
imagefilename = Util.null2String(rs.getString("imagefilename"));
log.writeLog("------imagefileid:" + imagefileid + "imagefilename:"+imagefilename+"------");
}
//判断是否为PDF格式,png格式,jpg格式
String fileExtension = "";
if (!imagefilename.isEmpty()) {
int lastDotIndex = imagefilename.lastIndexOf(".");
if (lastDotIndex != -1) {
fileExtension = imagefilename.substring(lastDotIndex + 1).toLowerCase();
}
}
ImageFileManager imageFileManager = new ImageFileManager();
imageFileManager.getImageFileInfoById(imagefileid);
InputStream inputStream = imageFileManager.getInputStream();
File file = new File(currentPath+"\\imgFile", "1.jpg");
if (!fileExtension.isEmpty()) {
if (fileExtension.equals("pdf")) {
file = new File(currentPath+"\\pdfFile", "2.pdf");
System.out.println("文件为PDF格式");
}
} else {
System.out.println("文件名不包含有效的后缀");
log.writeLog("------imagefileid:" + imagefileid + "imagefilename:"+imagefilename+"文件名不包含有效的后缀------");
}
// 获取目录路径
File directory = file.getParentFile();
// 如果目录不存在,则创建目录
if (!directory.exists()) {
if (directory.mkdirs()) {
log.writeLog("目录已创建: " + directory.getAbsolutePath());
} else {
log.writeLog("无法创建目录: " + directory.getAbsolutePath());
}
} else {
log.writeLog("目录已经存在: " + directory.getAbsolutePath());
}
// 创建文件
try {
if (file.createNewFile()) {
log.writeLog("文件已创建: " + file);
} else {
log.writeLog("文件已存在: " + file);
}
} catch (IOException e) {
log.writeLog("无法创建文件: " + file);
}
String result = copyInputStreamToFile(inputStream, file, fileExtension);
JSONObject obj = new JSONObject();
String jsonString = JSON.toJSONString(result);
obj.put("result", jsonString);
return JSON.toJSONString(obj);
}
private static String copyInputStreamToFile( InputStream in, File file, String fileExtension ) {
try {
OutputStream out = new FileOutputStream(file);
byte[] buf = new byte[1024];
int len;
while((len=in.read(buf))>0){
out.write(buf,0,len);
}
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
log.writeLog("无法解密文件: " + file + e );
}
if (fileExtension.equals("pdf")){
//将PDF转为图片 再解析
pdf2png(file.getParentFile().getAbsolutePath(), "2", "jpg");
File PDFfile = new File("D:\\WEAVER\\Resin\\pdfFile\\2.jpg");;
return deEncodeByPath(PDFfile.getAbsolutePath());//二维码图片路径
}else{
return deEncodeByPath(file.getAbsolutePath());//二维码图片路径
}
}
/**
* 解析二维码,此方法解析一个路径的二维码图片
* path:图片路径
*/
public static String deEncodeByPath(String path) {
String content = null;
BufferedImage image;
JSONObject paramsJson = new JSONObject();
String url ="http://192.168.18.70:9999/api/QRcodeDecode/Recognition";
String msg = null;
HttpClient client = null;
PostMethod post =null;
try {
image = ImageIO.read(new File(path));
LuminanceSource source = new BufferedImageLuminanceSource(image);
Binarizer binarizer = new HybridBinarizer(source);
BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解码
System.out.println("图片中内容: ");
System.out.println("content: " + result.getText());
content = result.getText();
log.writeLog("解析图片二维码成功: " + content );
} catch (IOException e) {
log.writeLog("优化解析图片二维码开始!" );
try {
/* content = QR.Recognition(path);*/
// static String url = "https://192.168.18.70:9999/api/QRcodeDecode/Recognition";
paramsJson.put("path",path);
msg =paramsJson.toJSONString();
InputStream inputStrem = new ByteArrayInputStream(msg.getBytes("utf-8"));
client = new HttpClient();
post = new PostMethod(url);
post.setRequestBody(inputStrem);
post.setRequestHeader("Content-Type", "application/json; charset=utf-8");
post.getParams().setParameter("http.protocol.content-charset", "utf-8");
int code = client.executeMethod(post);
log.writeLog("----------" + "请求URL" +url+"?"+ msg);
if (code == 200) {
content = post.getResponseBodyAsString(); // 获取响应内容字符串
} else {
log.writeLog("----------" + "请求URL" +"https://192.168.18.70:9999/api/QRcodeDecode/Recognition"+ ",失败!");
}
}catch (Exception e3){
log.writeLog(e3);
}
} catch (NotFoundException e) {
//这里判断如果识别不了带LOGO的图片,重新添加上一个属性
try {
image = ImageIO.read(new File(path));
LuminanceSource source = new BufferedImageLuminanceSource(image);
Binarizer binarizer = new HybridBinarizer(source);
BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
//设置编码格式
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
//设置优化精度
hints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE);
//设置复杂模式开启
hints.put(DecodeHintType.PURE_BARCODE,Boolean.TYPE);
Result result = new MultiFormatReader().decode(binaryBitmap, hints);//解码
System.out.println("图片中内容: ");
System.out.println("content: " + result.getText());
content = result.getText();
log.writeLog("解析图片二维码成功: " + content );
} catch (IOException e1) {
log.writeLog("优化解析图片二维码开始!" );
try {
paramsJson.put("path",path);
msg =paramsJson.toJSONString();
InputStream inputStrem = new ByteArrayInputStream(msg.getBytes("utf-8"));
client = new HttpClient();
post = new PostMethod(url);
post.setRequestBody(inputStrem);
post.setRequestHeader("Content-Type", "application/json; charset=utf-8");
post.getParams().setParameter("http.protocol.content-charset", "utf-8");
int code = client.executeMethod(post);
log.writeLog("----------" + "请求URL" +url+"?"+ msg);
if (code == 200) {
content = post.getResponseBodyAsString(); // 获取响应内容字符串
} else {
log.writeLog("----------" + "请求URL" +"https://192.168.18.70:9999/api/QRcodeDecode/Recognition"+ ",失败!");
}
}catch (Exception e3){
log.writeLog(e3);
}
} catch (NotFoundException e2) {
log.writeLog("优化解析图片二维码开始!" );
try {
paramsJson.put("path",path);
msg =paramsJson.toJSONString();
InputStream inputStrem = new ByteArrayInputStream(msg.getBytes("utf-8"));
client = new HttpClient();
post = new PostMethod(url);
post.setRequestBody(inputStrem);
post.setRequestHeader("Content-Type", "application/json; charset=utf-8");
post.getParams().setParameter("http.protocol.content-charset", "utf-8");
int code = client.executeMethod(post);
log.writeLog("----------" + "请求URL" +url+"?"+ msg);
if (code == 200) {
content = post.getResponseBodyAsString(); // 获取响应内容字符串
} else {
log.writeLog("----------" + "请求URL" +"https://192.168.18.70:9999/api/QRcodeDecode/Recognition"+ ",失败!");
}
}catch (Exception e3){
log.writeLog(e3);
}
}
}
return content;
}
/**
* 使用pdfbox将整个pdf转换成图片
*
* @param fileAddress 文件地址 如:C:\\Users\\user\\Desktop\\test
* @param filename PDF文件名不带后缀名
* @param type 图片类型 png 和jpg
*/
public static void pdf2png(String fileAddress, String filename, String type) {
long startTime = System.currentTimeMillis();
// 将文件地址和文件名拼接成路径 注意:线上环境不能使用\\拼接
File file = new File(fileAddress + "\\" + filename + ".pdf");
try {
// 写入文件
PDDocument doc = PDDocument.load(file);
PDFRenderer renderer = new PDFRenderer(doc);
int pageCount = doc.getNumberOfPages();
for (int i = 0; i < pageCount; i++) {
// dpi为144,越高越清晰,转换越慢
BufferedImage image = renderer.renderImageWithDPI(i, 144); // Windows native DPI
// 将图片写出到该路径下
ImageIO.write(image, type, new File(fileAddress + "\\" + filename + "." + type));
}
long endTime = System.currentTimeMillis();
System.out.println("共耗时:" + ((endTime - startTime) / 1000.0) + "秒"); //转化用时
log.writeLog("将PDF转换成图片成功: " + file +"共耗时:" + ((endTime - startTime) / 1000.0) + "秒" );
} catch (IOException e) {
e.printStackTrace();
log.writeLog("无法将PDF转换成图片: " + file + e );
}
}
}
jar包代码
pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<!-- 跳过测试 -->
<skipTests>true</skipTests>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>core</artifactId>
<scope>system</scope>
<version>3.1.0</version>
<systemPath>${pom.basedir}/src/main/resources/lib/core-3.1.0.jar</systemPath>
</dependency>
<dependency>
<groupId>com.google.zxing</groupId>
<artifactId>javase</artifactId>
<scope>system</scope>
<version>3.1.0</version>
<systemPath>${pom.basedir}/src/main/resources/lib/javase-3.1.0.jar</systemPath>
</dependency>
<dependency>
<groupId>org.opencv</groupId>
<artifactId>opencv</artifactId>
<scope>system</scope>
<version>4.5.3</version>
<systemPath>${pom.basedir}/src/main/resources/lib/opencv-453.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
<configuration>
<executable>true</executable>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
<compilerArguments>
<!-- 打包本地jar包 -->
<extdirs>E:\spring_boot_demo\src\main\resources\lib</extdirs>
</compilerArguments>
</configuration>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.example.demo.DemoApplication</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
端口号: server.port = 9999
QRcodeDecode.java
package com.example.demo.controller;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;
import com.example.demo.Entity.ParamsDTO;
import org.opencv.core.Core;
import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.MatOfPoint;
import org.opencv.core.MatOfPoint2f;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Size;
import org.opencv.imgcodecs.Imgcodecs;
import org.opencv.imgproc.CLAHE;
import org.opencv.imgproc.Imgproc;
import org.opencv.objdetect.QRCodeDetector;
import org.opencv.utils.Converters;
import com.google.zxing.Binarizer;
import com.google.zxing.BinaryBitmap;
import com.google.zxing.DecodeHintType;
import com.google.zxing.LuminanceSource;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.Result;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@ComponentScan
@RestController
@RequestMapping("/api/QRcodeDecode")
public class QRcodeDecode {
static {
try {
// jni Windows系统Tipray.dll
//System.loadLibrary("opencv_java453");
/* URL url = ClassLoader.getSystemResource("lib/opencv_java453.dll");
System.load(url.getPath());*/
System.load("C:\\Windows\\System32\\opencv_java453.dll");
} catch (SecurityException ex) {
System.err.print(ex.getMessage());
}
}
// 定位后截取出来的二维码图片放大倍数
private static int TIMES = 4;
// 图像处理后的图片存放地址
private static String PATH = "D:\\WEAVER\\33.png";
@RequestMapping("/test")
public static String test()
{
return "test1";
}
@RequestMapping("/Recognition")
public static String Recognition(@RequestBody ParamsDTO paramsDTO){
String path = paramsDTO.getPath();
String directoryPath = extractDirectoryPath(path);
String res = decode(directoryPath);
System.out.println(directoryPath);
return res;
};
public static String extractDirectoryPath(String filePath) {
String res = System.getProperty("java.library.path");
System.out.println(res);
int lastSeparatorIndex = Math.max(filePath.lastIndexOf('\\'), filePath.lastIndexOf('/'));
String directoryPath = filePath.substring(0, lastSeparatorIndex);
return directoryPath;
}
/* public static void main(String[] args) {
String res = decode("D:\\SqlSever\\aa");
System.out.println(res+"aaaaa");
}*/
/**
* @param directoryPath 要进行二维码识别的图片所在文件夹目录路径
*/
public static String decode(String directoryPath) {
int sum = 0; // 统计本次识别的总张数
int count = 0; // 统计识别成功的张数
int notFound = 0; // 记录未定位成功的图片张数
long startTime = System.currentTimeMillis();
String result =null;
// 需要进行识别的图片所在文件夹路径
File file = new File(directoryPath);
File[] vouchers = file.listFiles();
QRCodeDetector detector = new QRCodeDetector();
for (File voucher : vouchers) {
sum++;
/**
* 第一次识别,直接识别,若失败,则进行图像二维码定位处理
*/
String qRcode = decodeQRcode(detector, voucher.getAbsolutePath());
if (qRcode == null || "".equals(qRcode)) {
// 对图像进行处理,定位图像中的二维码,将其截取出来
findQRcodeAndCut(voucher.getAbsolutePath());
File file1 = new File(PATH);
if (file1.exists()) {
/**
*
* 第二次识别,若失败,则将定位后截取的二维码图片进行二值化处理再识别
*/
qRcode = decodeQRcode(detector, PATH);
if (qRcode == null || "".equals(qRcode)) {
Mat mat = Imgcodecs.imread(PATH, 1);
// 彩色图转灰度图
Imgproc.cvtColor(mat, mat, Imgproc.COLOR_RGB2GRAY);
// 对图像进行平滑处理
Imgproc.blur(mat, mat, new Size(3, 3));
// 中值去噪
Imgproc.medianBlur(mat, mat, 5);
// 这里定义一个新的Mat对象,主要是为了保留原图,未下次处理做准备
Mat mat2 = new Mat();
// 根据OTSU算法进行二值化
Imgproc.threshold(mat, mat2, 205, 255, Imgproc.THRESH_OTSU);
// 生成二值化后的图像
Imgcodecs.imwrite(PATH, mat2);
/**
* 第三次识别,若失败,则将图像进行限制对比度的自适应直方图均衡化处理
*/
qRcode = decodeQRcode(detector, PATH);
if (qRcode == null || "".equals(qRcode)) {
// 限制对比度的自适应直方图均衡化
CLAHE clahe = Imgproc.createCLAHE(2, new Size(8, 8));
clahe.apply(mat, mat);
Imgcodecs.imwrite(PATH, mat);
/**
* 第四次识别,失败就标红打印失败的图片名称
*/
qRcode = decodeQRcode(detector, PATH);
if (qRcode == null || "".equals(qRcode)) {
System.err.println(voucher.getName());
} else {
result = qRcode;
System.out.println(voucher.getName() + "---4---" + qRcode);
}
} else {
result = qRcode;
System.out.println(voucher.getName() + "---3---" + qRcode);
}
} else {
result = qRcode;
System.out.println(voucher.getName() + "---2---" + qRcode);
}
} else {
notFound++;
}
} else {
result = qRcode;
System.out.println(voucher.getName() + "---1---" + qRcode);
}
// 每次检查处理图片时是否有生成图片,若存在,则删除,避免干扰下一次图像识别结果
File file2 = new File(PATH);
if (file2.exists()) {
file2.delete();
}
if (qRcode != null && !"".equals(qRcode)) {
count++;
}
}
long endTime = System.currentTimeMillis();
long time = (endTime-startTime)/1000;
System.out.println("一共扫描" + vouchers.length + "张图片,耗时" + time + "秒,平均每张耗时:" + Math.round(100.0 * time/vouchers.length)*1.0/100 + "秒");
System.out.println(
"未定位到的图片数量:" + notFound + ",sun = " + sum + ",count = " + count + "识别率:" + 1.0 * count / sum + "%");
return result;
}
public static void findQRcodeAndCut(String filePath) {
Mat src_gray = new Mat();
Mat src = Imgcodecs.imread(filePath, 1);
if (src.empty()) {
System.out.println("Error: Input image is empty.");
return;
}
List<MatOfPoint> contours = new ArrayList<MatOfPoint>();
List<MatOfPoint> markContours = new ArrayList<MatOfPoint>();
System.loadLibrary(Core.NATIVE_LIBRARY_NAME);
/** 图片太小就放大 **/
if (src.width() * src.height() < 90000) {
Imgproc.resize(src, src, new Size(800, 600));
}
// 彩色图转灰度图
Imgproc.cvtColor(src, src_gray, Imgproc.COLOR_RGB2GRAY);
// 对图像进行平滑处理
Imgproc.GaussianBlur(src_gray, src_gray, new Size(3, 3), 0);
Imgproc.Canny(src_gray, src_gray, 112, 255);
Mat hierarchy = new Mat();
Imgproc.findContours(src_gray, contours, hierarchy, Imgproc.RETR_TREE, Imgproc.CHAIN_APPROX_NONE);
for (int i = 0; i < contours.size(); i++) {
MatOfPoint2f newMtx = new MatOfPoint2f(contours.get(i).toArray());
RotatedRect rotRect = Imgproc.minAreaRect(newMtx);
double w = rotRect.size.width;
double h = rotRect.size.height;
double rate = Math.max(w, h) / Math.min(w, h);
/***
* 长短轴比小于1.3,总面积大于60
*/
if (rate < 1.3 && w < src_gray.cols() / 4 && h < src_gray.rows() / 4
&& Imgproc.contourArea(contours.get(i)) > 60) {
/***
* 计算层数,二维码角框有五层轮廓(有说六层),这里不计自己这一层,有4个以上子轮廓则标记这一点
*/
double[] ds = hierarchy.get(0, i);
if (ds != null && ds.length > 3) {
int count = 0;
if (ds[3] == -1) {/** 最外层轮廓排除 */
continue;
}
/***
* 计算所有子轮廓数量
*/
while ((int) ds[2] != -1) {
++count;
ds = hierarchy.get(0, (int) ds[2]);
}
if (count >= 4) {
markContours.add(contours.get(i));
}
}
}
}
/***
* 二维码有三个角轮廓,正常需要定位三个角才能确定坐标,但由于公司使用的凭证干扰因素较少,故当识别到两个点的时候也将二维码定位出来;
* 当识别到三个点时,根据三个点定位可以确定二维码位置和形状,根据三个点组成三角形形状最大角角度判断是不是二维码的三个角
* 当识别到两个点时,取两个点中间点,往四周扩散截取 当小于两个点时,直接返回
*/
if (markContours.size() == 0) {
return;
} else if (markContours.size() == 1) {
capture(markContours.get(0), src);
} else if (markContours.size() == 2) {
List<MatOfPoint> threePointList = new ArrayList<>();
threePointList.add(markContours.get(0));
threePointList.add(markContours.get(1));
capture(threePointList, src);
} else {
for (int i = 0; i < markContours.size() - 2; i++) {
List<MatOfPoint> threePointList = new ArrayList<>();
for (int j = i + 1; j < markContours.size() - 1; j++) {
for (int k = j + 1; k < markContours.size(); k++) {
threePointList.add(markContours.get(i));
threePointList.add(markContours.get(j));
threePointList.add(markContours.get(k));
capture(threePointList, src, i + "-" + j + "-" + k);
threePointList.clear();
}
}
}
}
}
/**
* 针对对比度不高的图片,只能识别到一个角的,直接以该点为中心截取
*
* @param matOfPoint
* @param src
*/
private static void capture(MatOfPoint matOfPoint, Mat src) {
Point centerPoint = centerCal(matOfPoint);
int width = 200;
Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0,
(int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width),
(int) (2 * width));
// 截取二维码
Mat dstRoi = new Mat(src, roiArea);
// 放大图片
Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width));
Imgcodecs.imwrite(PATH, dstRoi);
}
/**
* 当只识别到二维码的两个定位点时,根据两个点的中点进行定位
*
* @param threePointList
* @param src
*/
private static void capture(List<MatOfPoint> threePointList, Mat src) {
Point p1 = centerCal(threePointList.get(0));
Point p2 = centerCal(threePointList.get(1));
Point centerPoint = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
double width = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y) + 50;
// 设置截取规则
Rect roiArea = new Rect((int) (centerPoint.x - width) > 0 ? (int) (centerPoint.x - width) : 0,
(int) (centerPoint.y - width) > 0 ? (int) (centerPoint.y - width) : 0, (int) (2 * width),
(int) (2 * width));
// 截取二维码
Mat dstRoi = new Mat(src, roiArea);
// 放大图片
Imgproc.resize(dstRoi, dstRoi, new Size(TIMES * width, TIMES * width));
Imgcodecs.imwrite(PATH, dstRoi);
}
/**
* 对图片进行矫正,裁剪
*
* @param contours
* @param src
* @param idx
*/
private static void capture(List<MatOfPoint> contours, Mat src, String idx) {
Point[] pointthree = new Point[3];
for (int i = 0; i < 3; i++) {
pointthree[i] = centerCal(contours.get(i));
}
double[] ca = new double[2];
double[] cb = new double[2];
ca[0] = pointthree[1].x - pointthree[0].x;
ca[1] = pointthree[1].y - pointthree[0].y;
cb[0] = pointthree[2].x - pointthree[0].x;
cb[1] = pointthree[2].y - pointthree[0].y;
/*
* angle1,angle2,angle3分别对应识别到的二维码定位角的三个点所组成三角形的三个角
*/
double angle1 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
double ccw1;
if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
ccw1 = 0;
} else {
ccw1 = 1;
}
ca[0] = pointthree[0].x - pointthree[1].x;
ca[1] = pointthree[0].y - pointthree[1].y;
cb[0] = pointthree[2].x - pointthree[1].x;
cb[1] = pointthree[2].y - pointthree[1].y;
double angle2 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
double ccw2;
if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
ccw2 = 0;
} else {
ccw2 = 1;
}
ca[0] = pointthree[1].x - pointthree[2].x;
ca[1] = pointthree[1].y - pointthree[2].y;
cb[0] = pointthree[0].x - pointthree[2].x;
cb[1] = pointthree[0].y - pointthree[2].y;
double angle3 = 180 / 3.1415 * Math.acos((ca[0] * cb[0] + ca[1] * cb[1])
/ (Math.sqrt(ca[0] * ca[0] + ca[1] * ca[1]) * Math.sqrt(cb[0] * cb[0] + cb[1] * cb[1])));
int ccw3;
if (ca[0] * cb[1] - ca[1] * cb[0] > 0) {
ccw3 = 0;
} else {
ccw3 = 1;
}
if (Double.isNaN(angle1) || Double.isNaN(angle2) || Double.isNaN(angle3)) {
return;
}
Point[] poly = new Point[4];
if (angle3 > angle2 && angle3 > angle1) {
if (ccw3 == 1) {
poly[1] = pointthree[1];
poly[3] = pointthree[0];
} else {
poly[1] = pointthree[0];
poly[3] = pointthree[1];
}
poly[0] = pointthree[2];
Point temp = new Point(pointthree[0].x + pointthree[1].x - pointthree[2].x,
pointthree[0].y + pointthree[1].y - pointthree[2].y);
poly[2] = temp;
} else if (angle2 > angle1 && angle2 > angle3) {
if (ccw2 == 1) {
poly[1] = pointthree[0];
poly[3] = pointthree[2];
} else {
poly[1] = pointthree[2];
poly[3] = pointthree[0];
}
poly[0] = pointthree[1];
Point temp = new Point(pointthree[0].x + pointthree[2].x - pointthree[1].x,
pointthree[0].y + pointthree[2].y - pointthree[1].y);
poly[2] = temp;
} else if (angle1 > angle2 && angle1 > angle3) {
if (ccw1 == 1) {
poly[1] = pointthree[1];
poly[3] = pointthree[2];
} else {
poly[1] = pointthree[2];
poly[3] = pointthree[1];
}
poly[0] = pointthree[0];
Point temp = new Point(pointthree[1].x + pointthree[2].x - pointthree[0].x,
pointthree[1].y + pointthree[2].y - pointthree[0].y);
poly[2] = temp;
}
Point[] trans = new Point[4];
int temp = 50;
trans[0] = new Point(0 + temp, 0 + temp);
trans[1] = new Point(0 + temp, 100 + temp);
trans[2] = new Point(100 + temp, 100 + temp);
trans[3] = new Point(100 + temp, 0 + temp);
double maxAngle = Math.max(angle3, Math.max(angle1, angle2));
// System.out.println("maxAngle:" + maxAngle);
// 二维码为直角,最大角过大或者过小都判断为不是二维码
if (maxAngle < 75 || maxAngle > 115) {
return;
}
Mat perspectiveMmat = Imgproc.getPerspectiveTransform(
Converters.vector_Point_to_Mat(Arrays.asList(poly), CvType.CV_32F),
Converters.vector_Point_to_Mat(Arrays.asList(trans), CvType.CV_32F)); // warp_mat
Mat dst = new Mat();
// 计算透视变换结果
Imgproc.warpPerspective(src, dst, perspectiveMmat, src.size(), Imgproc.INTER_LINEAR);
Rect roiArea = new Rect(0, 0, 200, 200);
Mat dstRoi = new Mat(dst, roiArea);
// 放大图片
Imgproc.resize(dstRoi, dstRoi, new Size(2 * dstRoi.width(), 2 * dstRoi.height()));
Imgcodecs.imwrite(PATH, dstRoi);
}
/**
* 将Mat转换为流,为了方便测试,代码中没有将Mat转换成流进行识别,若有需要,可以不落地文件
*
* @param m
* @return
*/
public static BufferedImage toBufferedImage(Mat m) {
int type = BufferedImage.TYPE_BYTE_GRAY;
if (m.channels() > 1) {
type = BufferedImage.TYPE_3BYTE_BGR;
}
int bufferSize = m.channels() * m.cols() * m.rows();
byte[] b = new byte[bufferSize];
m.get(0, 0, b); // get all the pixels
BufferedImage image = new BufferedImage(m.cols(), m.rows(), type);
final byte[] targetPixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
System.arraycopy(b, 0, targetPixels, 0, b.length);
return image;
}
/**
* 获取轮廓的中心坐标
*
* @param matOfPoint
* @return
*/
private static Point centerCal(MatOfPoint matOfPoint) {
double centerx = 0, centery = 0;
MatOfPoint2f mat2f = new MatOfPoint2f(matOfPoint.toArray());
RotatedRect rect = Imgproc.minAreaRect(mat2f);
Point vertices[] = new Point[4];
rect.points(vertices);
centerx = ((vertices[0].x + vertices[1].x) / 2 + (vertices[2].x + vertices[3].x) / 2) / 2;
centery = ((vertices[0].y + vertices[1].y) / 2 + (vertices[2].y + vertices[3].y) / 2) / 2;
Point point = new Point(centerx, centery);
return point;
}
/**
* 解析读取二维码
* 先使用ZXING二维码识别,若失败,使用OPENCV自带的二维码识别
* 个人测试,两者的识别率差不多,都不尽人意,但一起使用还是可以略微提高一点识别率,毕竟实现算法不一样
* 若还要其它的识别,类似Zbar,都可以集成进来
*
* @param qrCodePath 二维码图片路径
* @return 成功返回二维码识别结果,失败返回null
* @throws Exception
*/
public static String decodeQRcode(QRCodeDetector detector, String qrCodePath){
String qrCodeText = null;
try {
BufferedImage image = ImageIO.read(new File(qrCodePath));
LuminanceSource source = new BufferedImageLuminanceSource(image);
Binarizer binarizer = new HybridBinarizer(source);
BinaryBitmap binaryBitmap = new BinaryBitmap(binarizer);
Map<DecodeHintType, Object> hints = new HashMap<DecodeHintType, Object>();
hints.put(DecodeHintType.CHARACTER_SET, "UTF-8");
Result result = new MultiFormatReader().decode(binaryBitmap, hints);
if(result != null){
qrCodeText = result.getText();
}
} catch (Exception e) {
qrCodeText = null;
}
return qrCodeText;
}
}
ParamsDTO
package com.example.demo.Entity;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ParamsDTO {
private String path;
}
小结
提示:这里可以添加总结
关于这个需求,难点有很多,主要还是靠自己琢磨如何实现功能,不要放弃。共勉!