泛微E9 -明细表附件上传同步识别二维码功能,zxing+opencv实现

发布于:2024-03-25 ⋅ 阅读:(81) ⋅ 点赞:(0)

概要

提示:需求描述
在这里插入图片描述前端上传附件,携带发票信息,后端收到附件信息,进行识别。

前端代码

前端代码位置
在这里插入图片描述
插入代码
在这里插入图片描述

<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;
}

小结

提示:这里可以添加总结
关于这个需求,难点有很多,主要还是靠自己琢磨如何实现功能,不要放弃。共勉!

本文含有隐藏内容,请 开通VIP 后查看