SHIHUC

好记性不如烂笔头,还可以分享给别人看看! 专注基础算法,互联网架构,人工智能领域的技术实现和应用。
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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

Posted on 2020-12-13 23:41  shihuc  阅读(799)  评论(0编辑  收藏  举报

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

 

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

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

 

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

 

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

下面直接上代码:

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:\Program\QRCode\QRCode.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的解决方案,并且研究一下矩阵式二维码的原理和理论,欢迎探讨!