• 基于QRcode创建和识别二维码的研究


    至于什么是二维码,大家都使用过,其实比较形象,对比之前的条形码,就很容易理解,就是基于水平方向排列的通过小竖条的宽度不同表示不同的信息,而二维码,表达信息的方式是基于二维的黑白相间(不一定就是黑白,多数看到的可能是黑白,其实颜色是可以随着自己的需要,灵活调整的)的小方块,按照一定的规则排列的一个矩形区域内,形成一个传递信息的编码方式。

    二维码(本博客重点介绍的是目前主流的矩形二维码,堆叠的或者其他的模式,不做介绍),有其固有的特点,由定位矩形框,信息表达区域等功能识别器,特征明显,如下图:

    一个可视的二维码,乍一看,就是上面这个样子,只是因为里面有很多信息码元(黑白相间的矩形块),看起来比这个更丰富。

    就算这种矩阵式的二维码,也有几个不同的分支,请看下面的概要图片:

    关于二维码的理论,这里先不做过多的介绍,后面会专题介绍理论,今天主要从代码实现的角度,介绍如何生成二维码,以及一些基本的参数调整,对二维码的影响。

    下面直接上代码:

    package qcode;
    
    import com.swetake.util.Qrcode;
    
    import javax.imageio.ImageIO;
    import java.awt.*;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    /**
     *
     * 基于QRcode包实现的矩阵式二维码的创建过程研究和学习
     *
     * Created by shihuc on 2020/12/13.
     */
    public class QRCodeCreate {
        public static void main(String[] args) throws IOException {
    
            int v = 3;
            for (v = 1; v <= 40; v++) {
                try {
                    qrCreator(v, "Quick Response", 15, 3, 3);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
    
    
        }
    
        public static void qrCreator(int ver, String info, int margin, int w, int h) throws IOException {
            /**
             * 计算二维码图片的高宽比
             * API文档规定计算图片宽高的方式 ,v是本次测试的版本号,v为0时,表示生成的二维码尺寸随存储内容的变化而变化
             */
            int v = ver;
            int width = 67 + 12 * (v - 1);
            int height = 67 + 12 * (v - 1);
    
    
            Qrcode x = new Qrcode();
            /**
             * 纠错等级分为
             * level L : 最大 7% 的错误能够被纠正;
             * level M : 最大 15% 的错误能够被纠正;
             * level Q : 最大 25% 的错误能够被纠正;
             * level H : 最大 30% 的错误能够被纠正;
             * 设置二维码排错率,可选L(7%)、M(15%)、Q(25%)、H(30%),排错率越高可存储的信息越少,但对二维码清晰度的要求越小
             */
            x.setQrcodeErrorCorrect('L');
            //注意版本信息 N代表数字 、A代表 a-z,A-Z、B代表 其他)
            x.setQrcodeEncodeMode('B');
            //版本号1-40, 设置设置二维码尺寸,取值范围1-40,值越大尺寸越大,可存储的信息越大
            x.setQrcodeVersion(v);
    
            //设置二维码的边框留白信息,就是在原有的二维码外边框留下白色的边距,让二维码显得有结构感
            int qr_margin = margin;
    
    
            //String qrData = "https://www.cnblogs.com/shihuc";//内容信息
            String qrData = info + " " + v;
    
            // 获得内容的字节数组,设置编码格式,汉字转格式需要抛出异常
            byte[] d = qrData.getBytes("utf-8");
    
            //缓冲区
            BufferedImage bufferedImage = new BufferedImage(width+2*qr_margin, height+2*qr_margin, BufferedImage.TYPE_INT_BGR);
    
            //绘图, 用来画出二维码信息
            Graphics2D gs = bufferedImage.createGraphics();
    
            //设置二维码背景颜色和前景颜色,这里,可以设计出自己想要的颜色的二维码了,很方便
            gs.setBackground(Color.WHITE);
            gs.setColor(Color.RED);
            gs.clearRect(0, 0, width+2*qr_margin, height+2*qr_margin);
    
            /**
             * 偏移量 3, 可以将二维码较好的控制在最后输出的二维码图片的中央位置,太大,会导致向右下角偏移,
             * 太小,会导致向左上角偏移, 偏移太多,二维码识别时,无法找到三个定位的正方形,从而无法识别二维码
             */
            int pixOff = 3 + qr_margin;
    
    
            /**
             * 容易踩坑的地方
             * 1.注意for循环里面的i,j的顺序,
             *   s[j][i]二维数组的j,i的顺序要与这个方法中的 gs.fillRect(j*3+pixOff,i*3+pixOff, 3, 3);
             *   顺序匹配,否则会出现解析图片是一串数字
             *
             *  另外,还有需要注意的地方,fillRect里面后面的两个参数 width,height,指的是二维码里面填充的小方块,通常是指那个黑色
             *  的方块,太大了,导致整个二维码图片里面黑色太多,太小,里面的白色区域偏多,通常是选择长宽都为3居多, 不管是偏大还是偏小,
             *  都会导致最后的二维码识别有困难,甚至识别不出来
             */
            /**
             * 仔细观察,下面s数组的维度,就是码元的维度,这里将体现出版本v和码元大小的关系, 21 + 4*(v - 1)
             * v=1: 21*21
             * v=2: 25*25
             * v=3: 29*29
             * v=4: 33*33
             * .......
             */
            boolean[][] s = x.calQrcode(d); //计算需要打印前景色的位置,二维码核心算法函数
            for (int i = 0; i < s.length; i++) {
                for (int j = 0; j < s.length; j++) {
                    if (s[j][i]) {
                        gs.fillRect(j * 3 + pixOff, i * 3 + pixOff, w, h);
                    }
                }
            }
            gs.dispose();
    
            bufferedImage.flush();
            //设置图片格式,与输出的路径
            ImageIO.write(bufferedImage, "png", new File("D:\ProTempHome\margin\qrcode" + v + ".png"));
            System.out.println( "v: " + v + ",二维码生成完毕");
        }
    }

    这里要说明的是,生成二维码所需的包,只有一个QRcode.jar,可以到我的网盘下载(https://pan.baidu.com/s/1X8dNd47-5cYPQ7hgMUZB3g,提取密码:8v5q),就是一个jar文件,将其copy到项目的lib路径下,并添加到classpath路径下。

    若想在maven项目里面使用这个包,其实可以自己基于maven的安装指令,将这个jar变成maven格式的dependency配置信息。

    mvn install:install-file -Dfile=D:ProgramQRCodeQRCode.jar -DgroupId=QRCode -DartifactId=QRCode -Dversion=3.0 -Dpackaging=jar 

    安装完成后,在maven的pom文件中,添加下面的内容:

    <dependency>
           <groupId>QRCode</groupId>
           <artifactId>QRCode</artifactId>
           <version>3.0</version>
    </dependency>

    这里,代码里面的注释信息已经很丰富,下面,需要对几个重点内容强调一下:

    1. 上述案例的代码,支持设置边框留白的大小,其实,二维码生成逻辑还是蛮容易理解的,基于一套算法(纠错级别L、M、Q、H,及Model,再就是版本v都能影响这个算法的输出),得出一个布尔值类型的二维数组,然后,控制绘图程序,在矩形区域将码元信息绘制出来,最后得到我们熟悉的二维码。

    1.1 这里纠错级别,和版本号,模式等参数,影响生成的二维码的大小,能够容纳的信息,抗干扰的能力。下面,给出了版本v只有前3个的变化表,可以粗略对比,就能有所了解。

    1.2 在model,纠错参数配置不变的情况下,版本v的取值(1-40)越大,得到的二维码的尺寸也越大,最终识别这个二维码的难度也越大(识别的时间会加长),但是能够存储的信息也越多,下面给几个基于上面的代码生成的二维码的图片,大家可以对比观察下。

    v=1

    v=6

    v=11

    v=16

    v=21

    v=26

    v=40

    大家若有兴趣,可以用微信的扫一扫功能,可以体会一下,这个识别的过程是不是会越来越有困难,时间会变得比较长。

    2. 基于上诉代码,不仅可以实现各种尺寸,各种纠错码模式,各种编码模式的二维码的生成(纠错模式越高,【L最低,Q最高】,存储信息越少,抗干扰,或者说越容易识别,反之亦然)。另外,还有一个很有意思的点,需要分享,那就是码元块的大小是可以调整的,就是上述代码中的这个函数的后面两个参数w,h。

    gs.fillRect(j * 3 + pixOff, i * 3 + pixOff, w, h);

    当这个参数变大时,生成的二维码显得更厚重,这两个参数的值越小时,生成的二维码给人的感觉很稀疏,如下图:

                                                         

    2.1 上面的图片是基于上述的代码(w,h有变化)生成的,v取值5。 从左向右,w=h=5, 3,2,1. 大家可以用微信扫一扫试试,最右边的这个w=1的二维码,我的手机微信识别不出来。基于这里的调整试验,得出一个信息,二维码的定位矩形框的大小(v不变的情况下),受到代码中pixOff的影响【准确的说是受到代码中pixOff = 3 + qr_margin中的3的影响,这个3变大,二维码中的定位矩形框越大,反之亦然;然而,仅仅调整这个数字是不科学的,会导致二维码不能完整的打印在公式中BufferImage定义的图片中】,基于上述代码,3已经是一个调整的算是不错的大小因子。

    2.2 接下来说明w,h,他们的取值,在二维码定位矩形框大小不变时,w,h值越大,二维码看起来很厚重,w,h值越小,二维码越稀薄,上述的图片,当定位矩形框不是线条时(此时的码元大小也是缺失的),会导致二维码识别不出来。

    3. 程序识别二维码

    package qcode;
    
    import jp.sourceforge.qrcode.data.QRCodeImage;
    
    import java.awt.image.BufferedImage;
    
    /**
     * 实现QRCodeImage接口,
     * 设置解码的图片信息
     * 核心就是告诉codeDecoder.decode()将要解析的图片类型
     *
     * Created by shihuc on 2020/12/13.
     */
    public class MyQRCodeImage implements QRCodeImage{
    
    
        BufferedImage bufferedImage;
    
        public MyQRCodeImage(BufferedImage bufferedImage){
            this.bufferedImage=bufferedImage;
        }
    
        //
        @Override
        public int getWidth() {
            return bufferedImage.getWidth();
        }
    
        //
        @Override
        public int getHeight() {
            return bufferedImage.getHeight();
        }
    
        //获取给定坐标位置的RGB点位的像素值
        @Override
        public int getPixel(int i, int j) {
            return bufferedImage.getRGB(i,j);
        }
    }
    package qcode;
    
    import jp.sourceforge.qrcode.QRCodeDecoder;
    
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.File;
    import java.io.IOException;
    
    /**
     * 程序进行识别二维码的信息
     * 有点类似模拟手机微信扫一扫识别二维码的逻辑,只是这里图片已经存在,不像APP里面扫描二维码是拍摄照片,然后再识别。
     *
     * Created by shihuc on 2020/12/13.
     */
    public class QRCodeRead {
        public static void main(String[] args) throws IOException {
    
            int v = 1;
            for ( v = 1; v <= 40; v++) {
                qrReader(v);
            }
        }
    
        public static void qrReader(int v) throws IOException {
    
            //图片路径
            File file = new File("D:\ProTempHome\margin\qrcode" + v + ".png");
    
            //读取图片到缓冲区
            BufferedImage bufferedImage = ImageIO.read(file);
    
            //QRCode解码器
            QRCodeDecoder codeDecoder = new QRCodeDecoder();
    
            //通过解析二维码获得信息
            String result = new String(codeDecoder.decode(new MyQRCodeImage(bufferedImage)), "utf-8");
            System.out.println("识别二维码 v=" + v + "的内容: " + result);
        }
    }

    直接上了代码,这里需要说明的是,基于前面的二维码生成算法,得到的二维码图片,经过上述的二维码解码程序,都能很好的识别出来。 但是,针对上面的调整w,h(其他参数都不变的情况下),会导致有些二维码识别不出来,直接报错了(v变高的时候,w,h值对识别的影响越发明显,具体的变化根源,有待后续进一步研究,有人若知道,可以给我留言,告知根源是什么)。 

    微信的扫一扫对二维码识别的能力还是比较强的,如前面,我将二维码变得厚重后,微信基本还是能识别出二维码的内容(不能厚重的太过分。。。)

    好了,今天,博文就分享到这里,还是有很多内容需要进一步去研究,希望看官,可以分享一下你的二维码研究心得。

    下一篇博客,打算研究一下给二维码中间插入一个漂亮的logo的解决方案,并且研究一下矩阵式二维码的原理和理论,欢迎探讨!

  • 相关阅读:
    DICOMDIR结构
    给文件夹添加Everyone用户
    关于Predicate<T>委托
    开发者必备的6款源码搜索引擎
    Create XML Files Out Of SQL Server With SSIS And FOR XML Syntax
    create xml file from sql script
    DICOM中的入门概念
    小米note开启调试模式
    [转] Java基础知识——Java语言基础
    Java语言基本语法
  • 原文地址:https://www.cnblogs.com/shihuc/p/14127856.html
Copyright © 2020-2023  润新知