Python基于pyzbar、opencv、pyqt5库,实现二维码识别 gui 应用程序开发

二维码组成结构基本介绍

二维码识别背景介绍

视觉的方法可以用来估计位置和姿态。最容易想到的是在目标上布置多个容易识别的特征,这样使用opencv相机标定和、相机畸变矫正、轮廓提取、solvepnp来获取目标相对于相机的位姿。在实际使用中只要相机和目标一方是估计的,那就可以得到全局坐标。如果相机和靶标都在移动,那只能获取到相对坐标。但是受限于相机视角和景深,这样多个特征的识别虽然精度可以很高,但是范围却很小。

对于如何扩大范围,使用二维码是一个很好的思路。首先,二维码本身具有多个特征,单个二维码可以用来实现上述方法的功能。其次,二维码本身带有信息,如果二维码的布置事先已知,那么位置和姿态估计的范围将只受限于二维码的数量。

什么是二维码?二维码的基本组成?

二维码另一个名称是QR Code(Quick Response Code),近年来在移动设备上经常使用,与传统条形码相比,可以存储更多的信息。二维码本质上是个密码算法,基本知识总结如下。

首先,二维码存在 40 种尺寸,在官方文档中,尺寸又被命名为 Version。尺寸与 Version 存在线性关系:Version 1 是 21 × 21 21×21 21×21 的矩阵,Version 2 是 25 × 25 25×25 25×25 的矩阵,每增加一个 Version,尺寸都会增加 4,故尺寸 Size 与 Version 的线性关系为:
S i z e = ( V e r s i o n − 1 ) × 4 Size=(Version − 1) × 4 Size=(Version1)×4

Version 的最大值是 40 40 40,故尺寸最大值是 ( 40 − 1 ) ∗ 4 + 21 = 177 (40-1)*4+21 = 177 (401)4+21=177,即 177 ∗ 177 177 * 177 177177 的矩阵。

二维码的结构图:功能图形 + 编码区格式 组成
在这里插入图片描述

二维码识别原理介绍

二维码特征识别的思路:

第一步,寻找二维码的三个角的定位角点,需要对图片进行平滑滤波,二值化,寻找轮廓,筛选轮廓中有两个子轮廓的特征,从筛选后的轮廓中找到面积最接近的3个即是二维码的定位角点。

第二步:判断3个角点处于什么位置,主要用来对图片进行透视校正(相机拍到的图片)或者仿射校正(对网站上生成的图片进行缩放拉伸旋转等操作后得到的图片)。需要判断三个角点围成的三角形的最大的角就是二维码左上角的点。然后根据这个角的两个边的角度差确定另外两个角点的左下和右上位置。

第三步,根据这些特征识别二维码的范围。

识别二维码,依次是灰度图,二值化,角点定位,透视矫正,识别结果。

二维码扫描顺序:举例 Version 3 二维码,从二维码的右下角开始,沿着红线进行填充,遇到非数据区域,则绕开或跳过。

二维码容错等级:

在二维码中的存储数据区域可以分成两个部分:纠错区域、存储信息区域。纠错部分是备份数据的区域,所以即使二维码被遮挡了一部分,仍然可以用手机识别出来。

二维码是用容错等级的,共 4 个等级分别:(1)L 7%(2)M 15%(3)Q 25%(4)H 30%
在这里插入图片描述
所以二维码的容错等级越高,即使二维码被遮挡的部分大一点依旧不影响扫描,但是提高容错率意味着纠错区域越大,那么二维码存储的数据就自然变少了。

在接下来,我们制作带有图标的二维码就是凭借二维码的容错等级原理

qrcode库生成二维码

在生活我们可能会看到一些二维码,在我们扫描之后跳到了某个地址,我们想制作一个二维码,这样的功能 qrcode 就可以实现,来看一下示例:

在 Windows 的环境下只需要使用 pip 安装即可:

pip install qrcode -i https://pypi.tuna.tsinghua.edu.cn/simple

由于生成 qrcode 图片需要依赖 Python 的图像库,所以需要先安装 Python 图像库 PIL(Python Imaging Library),不然会遇到 “ImportError: No module named Image” 的错误:

pip install PIL -i https://pypi.tuna.tsinghua.edu.cn/simple

简单二维码生成,比如把我的博客地址生成二维码:

import qrcode

if __name__ == "__main__":
    data = 'https://wrist.blog.csdn.net/'
    # 二维码内容(链接地址或文字)
    img = qrcode.make(data=data)
    # 生成二维码
    img.show()
    # 显示二维码
    img.save('code.jpg')
    # 保存二维码

运行结果:如果你是windows系统,系统打开默认的图片展示程序为你呈现图片(就是二维码的图片)

官方不允许出现二维码,否则图片违规,这里就不展示了,大家自己运行测试。

进行二维码简单的设置和美化

当然上面的二维码生成是比较原始的,我们还可以对其进行简单的设置和美化,这些美化需要一些配置信息,代码实现如下所示:

import qrcode

if __name__ == "__main__":
    qr = qrcode.QRCode(
        version=2,
        #  version:二维码的格子矩阵大小,可以是1到40,1最小为21*21,40是177*177
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        #  error_correction:二维码错误容许率,默认 ERROR_CORRECT_M,容许小于 15% 的错误率
        box_size=15,
        #  box_size:二维码每个小格子包含的像素数量
        border=3
        #  border:二维码到图片边框的小格子数,默认值为 4
    )

    # 二维码内容(链接地址或文字)
    data = '我是唤醒手腕,我是个大傻逼'
    qr.add_data(data=data)
    # 启用二维码颜色设置
    qr.make(fit=True)
    img = qr.make_image(fill_color='green', back_color='white')
    # 显示二维码
    img.show()
    # 保存二维码
    img.save('qrcode.jpg')

美化二维码的生成结果展示如下:(官方csdn会标记图片违规,这边打马赛克)
在这里插入图片描述

增加我们喜欢的图标Logo:

我们可以为我们的二维码添加Logo,当然Logo尺寸不能过大,否则会影响二维码的识别

version:值为1~40的整数,控制二维码的大小:

  • 最小值是1,是个21×21的矩阵;即每行每列有21个小方块,而不是指图片的像素;

  • version 每增加 1,生成的二维码就会添加 4 尺寸,例如 version 是 2,则生成 25 * 25 的二维码;

  • 如果想让程序自动确定,将值设置为 None 并使用 fit 参数即可。

error_correction:控制二维码的错误纠正功能。可取值下列常量:

ERROR_CORRECT_L:大约 7% 或更少的错误能被纠正。
ERROR_CORRECT_M(默认):大约 15% 或更少的错误能被纠正。
ROR_CORRECT_H:大约 30% 或更少的错误能被纠正。

这边定义增加图标的py方法,方便刚才的代码进行调用:

from PIL import Image

def addLogoFunc(img):
    icon = Image.open("icon.png")

    img_width, img_height = img.size
    factor = 4
    size_width = int(img_width / factor)
    size_height = int(img_width / factor)

    icon_width, icon_height = icon.size
    if icon_width > size_width or icon_height > size_height:
        icon_width, icon_height = size_width, size_height

    icon = icon.resize((icon_width, icon_height), Image.ANTIALIAS)

    w = int((img_width - icon_width) / 2)
    h = int((img_height - icon_height) / 2)
    img.paste(icon, (w, h), icon)

在进行保存二维图片之前,执行addLogoFunc(img),就能增加图标:

if __name__ == "__main__":
    qr = qrcode.QRCode(
        version=2,
        #  version:二维码的格子矩阵大小,可以是1到40,1最小为21*21,40是177*177
        error_correction=qrcode.constants.ERROR_CORRECT_L,
        #  error_correction:二维码错误容许率,默认 ERROR_CORRECT_M,容许小于 15% 的错误率
        box_size=15,
        #  box_size:二维码每个小格子包含的像素数量
        border=3
        #  border:二维码到图片边框的小格子数,默认值为 4
    )

    # 二维码内容(链接地址或文字)
    data = '我是唤醒手腕,我是个大傻逼'
    qr.add_data(data=data)
    # 启用二维码颜色设置
    qr.make(fit=True)
    img = qr.make_image(fill_color='green', back_color='white')
     # 增加图标 addLogoFunc
    addLogoFunc(img)
    # 显示二维码
    img.show()
    # 保存二维码
    img.save('qrcode.jpg')	

增加图标后,二维码的生成结果展示如下:(官方csdn会标记图片违规,这边打马赛克)
在这里插入图片描述

pyzbar库识别图片二维码

pyzbar 是什么?

首先需要了解下 ZBar:ZBar是一个开源软件套件,用于从各种来源(如视频流、图像文件和原始强度传感器)读取条形码。它支持许多流行的符号(条形码类型),包括EAN-13/UPC-A、UPC-E、EAN-8、代码128、代码39、交错2/5和二维码。

而 pyzbar 是通过 Python2和3接口,使用 ZBar 库读取一维条形码和QR码 。

在 Windows 的环境下只需要使用 pip 安装即可:

pip install pyzbar -i https://pypi.tuna.tsinghua.edu.cn/simple

这边采用的清华镜像库加快下载速度!

简单代码编写测试:

首先我们在py文件所在的目录下准备一张二维码图片,这边就那位的QQ二维码做个测试:

import pyzbar.pyzbar as pyzbar
import cv2

imagePath = "qqcode.png"
srcImg = cv2.imread(imagePath)
# 读过所在目录下 二维码图片
srcImg = cv2.resize(srcImg, (0, 0), fx=0.5, fy=0.5)
# 缩小图片尺寸,加快计算处理
cv2.imshow("Image", srcImg)
# 展示二维码图片
cv2.waitKey(0)
# 暂停
barcodes = pyzbar.decode(srcImg)
for barcode in barcodes:
    barcodeData = barcode.data.decode("utf-8")
    # 对二维码信息进行解码,"utf-8"编码格式
    print(barcodeData)
    # 打印二维码识别的结构

运行代码,输出的结果:图片我打马赛克了(CSDN 官方不允许)

在这里插入图片描述

识别出来信息展示如下,可见是QQ加好友的链接,点击访问,可以访问,说明识别成功:

https://qm.qq.com/cgi-bin/qm/qr?k=5aUI5A51mmSXVasQZJHHkKzTjSjlQ7Ae&noverify=0

pyzbar库动态识别二维码

刚才我们是进行读取图片进行识别图片中的二维码信息,接下来我们就要调用摄像头外设,进行动态识别二维码,当然如果你没有摄像头,也可以将手机作为摄像头:

手机摄像头方法:基于opencv第三方视觉库,通过内网IP调用手机摄像头,实现人脸识别与图形监测

这便我是采用外接摄像头进行测试,如果你没有,上面有局域网IP方案调用手机摄像头的教程:

定义调用摄像头进行扫描的函数

def detect():
    cv2.namedWindow("cam", cv2.WINDOW_NORMAL)
    cam = cv2.VideoCapture(1)
    # 获取摄像头对象
    while True:       
        ret, frame = cam.read() 
        # 读取当前识别当前帧的二维码
        decodeDisplay(frame)
        # 按ESC键退出
        if (cv2.waitKey(5) == 27):
            break
    cam.release()
    # 释放摄像头对象,销毁内存资源
    cv2.destroyAllWindows()

特别注意:cv2.VideoCapture(1)是外接摄像头,cv2.VideoCapture(0)是笔记本内置摄像头

定义读取当前识别当前帧的二维码 函数:

def decodeDisplay(video):
    # 转为灰度图像
    gray = cv2.cvtColor(video, cv2.COLOR_BGR2GRAY)
    barcodes = pyzbar.decode(gray)
    for barcode in barcodes:
        # 提取二维码的位置
        (x, y, w, h) = barcode.rect
        # 用边框标识出来在视频中 (0, 255, 0), 2 是rgb颜色;边框的宽度,这边是2
        cv2.rectangle(video, (x, y), (x + w, y + h), (0, 255, 0), 2)
        # 字符串转换
        barcodeData = barcode.data.decode("utf-8")
        barcodeType = barcode.type
        # 在图像上面显示识别出来的内容
        text = "{}".format(barcodeData)
        cv2.putText(video, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
        # 打印识别后的内容
        print("[扫描结果] 二维码类别: {0} 内容: {1}".format(barcodeType, barcodeData))
    cv2.imshow("cam", video)

那么两者结合,就是完整代码的展示:

import cv2
import pyzbar.pyzbar as pyzbar

def decodeDisplay(video):
    gray = cv2.cvtColor(video, cv2.COLOR_BGR2GRAY)
    barcodes = pyzbar.decode(gray)
    for barcode in barcodes:
        (x, y, w, h) = barcode.rect
        cv2.rectangle(video, (x, y), (x + w, y + h), (0, 255, 0), 2)
        barcodeData = barcode.data.decode("utf-8")
        barcodeType = barcode.type
        text = "{}".format(barcodeData)
        cv2.putText(video, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
        print("[扫描结果] 二维码类别: {0} 内容: {1}".format(barcodeType, barcodeData))
    cv2.imshow("cam", video)

def detect():
    cv2.namedWindow("cam", cv2.WINDOW_NORMAL)
    cam = cv2.VideoCapture(1)
    while True:
        ret, frame = cam.read()
        decodeDisplay(frame)
        if (cv2.waitKey(5) == 27):
            break
    cam.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    detect()

运行代码进行调用测试:
在这里插入图片描述
测试结果:我们可以发现,我QQ的二维码信息被成功地读取出来了,而且被绿色的标注框所捕捉,解码信息展示在上面了,测试成功。

自动捕捉保存二维码

接下来,我们借助pyqt5来完善我们地功能,做到自动捕捉二维码信息,进行保存。

首先编写ui文件,当然可以借助qt designer 进行编辑:

from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 460)
        MainWindow.setMinimumSize(QtCore.QSize(800, 460))
        MainWindow.setMaximumSize(QtCore.QSize(800, 460))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.SaveButton = QtWidgets.QPushButton(self.centralwidget)
        self.SaveButton.setGeometry(QtCore.QRect(570, 130, 200, 81))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.SaveButton.setFont(font)
        self.SaveButton.setObjectName("SaveButton")
        self.VideoLabel = QtWidgets.QLabel(self.centralwidget)
        self.VideoLabel.setGeometry(QtCore.QRect(30, 20, 500, 360))
        self.VideoLabel.setMinimumSize(QtCore.QSize(480, 360))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.VideoLabel.setFont(font)
        self.VideoLabel.setStyleSheet("background-color:#D1D1D1")
        self.VideoLabel.setAlignment(QtCore.Qt.AlignCenter)
        self.VideoLabel.setObjectName("VideoLabel")
        self.ImageLabel = QtWidgets.QLabel(self.centralwidget)
        self.ImageLabel.setGeometry(QtCore.QRect(570, 230, 200, 150))
        font = QtGui.QFont()
        font.setPointSize(15)
        self.ImageLabel.setFont(font)
        self.ImageLabel.setStyleSheet("background-color:#D1D1D1")
        self.ImageLabel.setAlignment(QtCore.Qt.AlignCenter)
        self.ImageLabel.setObjectName("ImageLabel")
        self.PauseButton = QtWidgets.QPushButton(self.centralwidget)
        self.PauseButton.setGeometry(QtCore.QRect(570, 20, 200, 81))
        font = QtGui.QFont()
        font.setPointSize(11)
        self.PauseButton.setFont(font)
        self.PauseButton.setObjectName("PauseButton")
        self.DataLabel = QtWidgets.QLabel(self.centralwidget)
        self.DataLabel.setGeometry(QtCore.QRect(30, 400, 741, 41))
        self.DataLabel.setStyleSheet("background-color:#E1E1E1")
        self.DataLabel.setAlignment(QtCore.Qt.AlignCenter)
        self.DataLabel.setObjectName("DataLabel")
        MainWindow.setCentralWidget(self.centralwidget)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.SaveButton.setText(_translate("MainWindow", "保存二维码图片"))
        self.VideoLabel.setText(_translate("MainWindow", "摄像头展示区域"))
        self.ImageLabel.setText(_translate("MainWindow", "二维码截取区"))
        self.PauseButton.setText(_translate("MainWindow", "开启摄像头扫描"))
        self.DataLabel.setText(_translate("MainWindow", "数据展示"))

ui设计结构展示如下所示:
在这里插入图片描述
那么接下来就是,mian.py 完整的代码展示如下所示:

import sys
from datetime import datetime

import qrcode
from PyQt5.QtGui import QPixmap, QImage
from PyQt5.QtWidgets import QApplication, QMainWindow, QFileDialog
from pyzbar import pyzbar

from CameraScan import *
import cv2

capture = cv2.VideoCapture(1)


class MyClass(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        super(MyClass, self).__init__(parent)
        self.setupUi(self)
        self.isStartState = True
        # 判断是否启动摄像头
        self.isImageExist = False
        # 判断当前是否存在已经解析的二维码
        self.NowCodeData = ""
        # 当前解析出来的数据,刚开始没解析,所以为空
        self.codeImage = None
        # 右下角当前解析出来的二维码,刚开始没解析,所以为空

        self.PauseButton.clicked.connect(self.startVideo)
        # 绑定事件,开启/关闭摄像头
        self.SaveButton.clicked.connect(self.saveImage)
        # 绑定事件,存储解析生成的二维码


	# 开启 / 关闭摄像头
    def startVideo(self):
        self.isStartState = ~self.isStartState
        while self.isStartState:
            self.VideoLabel.setPixmap(self.nowImage())
            cv2.waitKey(50)

	# 存储二维码图片
    def saveImage(self):
        if self.isImageExist == False:
            return
        FileName = datetime.now().strftime('%Y-%m-%d_%H_%M_%S')
        file, tmp = QFileDialog.getSaveFileName(self, 'Save Image', FileName, '*.png *.jpg *.bmp')
        self.codeImage.save(file)

    # 当前的画面
    def nowImage(self):
        ret, self.img = capture.read()
        # 摄像头读取, ret为是否成功打开摄像头, true, false:frame为视频的每一帧图像
        self.checkCode(self.img)
        # 检测二维码
        height, width, channel = self.img.shape
        # 提取图像的通道和尺寸,用于将OpenCV下的image转换成Qimage
        bytesPerline = 3 * width
        img = QImage(self.img.data, width, height, bytesPerline, QImage.Format_RGB888).rgbSwapped()
        # 将QImage显示出来
        return QPixmap.fromImage(img.scaled(480, 360))

	# 原理上面已经说明了,就是检测二维码
    def checkCode(self, video):
        gray = cv2.cvtColor(video, cv2.COLOR_BGR2GRAY)
        barcodes = pyzbar.decode(gray)
        for barcode in barcodes:
            (x, y, w, h) = barcode.rect
            cv2.rectangle(video, (x, y), (x + w, y + h), (0, 255, 0), 2)
            barcodeData = barcode.data.decode("utf-8")
            text = "{}".format(barcodeData)
            
            # 防止重复生成二维码影响效率
            if text != self.NowCodeData:
                self.createCode(text)
                
            self.NowCodeData = text
            self.DataLabel.setText("解析数据 : {}".format(self.NowCodeData))
            cv2.putText(video, text, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)

    def createCode(self, text):
        # 二维码内容(链接地址或文字)
        code = qrcode.make(data=text)
        self.codeImage = code
        # 存储临时图片 code.temp.jpg
        code.save("code.temp.jpg"
        pix = QPixmap("code.temp.jpg")
         # 展示解析生成的二维码图片
        self.ImageLabel.setPixmap(pix.scaled(200, 200)) 
        self.isImageExist = True

if __name__ == '__main__':
    app = QApplication(sys.argv)
    myWin = MyClass()
    myWin.setWindowTitle("唤醒手腕 - 猫头鹰Code-Scanner-2000")
    myWin.show()
    sys.exit(app.exec_())

运行结果测试;当我们在镜头中捕捉到二维码,会在右下角生成二维码,保证两者二维码的解码数据是一致的,当然解析的数据也会在下方进行展示出来。(这边就不具体展示了,大家自己运行测试)

当然我们的二维码文件可以进行保存,点击保存二维码图片,会自动弹出存储窗口:
在这里插入图片描述

pyinstaller库生成exe程序

PyInstaller是一个压缩python文件成为可执行程序的一个软件。它会扫描你所有的Python文档,并分析所有代码从而找出所有你的代码运行所需的模块。

PyInstaller会将所有这些模块和你的code放在一个文件夹里,或者一个可执行文件里。这样以来,你的用户就不用下载各种你的软件运行环境了,例如各种版本的python,各种不同的python包等等。他们只需要执行打包好的可执行文件就可以使用你的软件了。

安装 第三方库 pyinstaller

pip install pyinstaller

然后打包exe,若需将xxx.py文件打包,只需在终端执行:

pyinstaller xxx.py

特别注意:终端需切换至xxx.py文件所在目录下

pyinstaller 常用可选项及说明:

-F:打包后只生成单个exe格式文件;
-D:默认选项,创建一个目录,包含exe文件以及大量依赖文件;
-c:默认选项,使用控制台(就是类似cmd的黑框);
-w:不使用控制台;
-p:添加搜索路径,让其找到对应的库;
-i:改变生成程序的icon图标。

制作图标网站:https://www.bitbug.net/

这边的话,我们要生成不包含源码的,仅是单个exe格式的文件,执行如下:

 pyinstaller .\main.py -i .\bitiicon.ico -w -F

最后在main.py所在目录下,生成了 dist 和 build 文件夹,dist 目录下就是我们的 exe 程序:
在这里插入图片描述
双击点击运行发现了,报错问题:展示如下

在这里插入图片描述

报错解析:程序里使用了pyzbar,但是没有找到 libiconv-2.dll 和 libzbar-32.dll,我是直接进python环境下,site-packages第三方库文件地址,找到pyzbar安装位置,里面刚好有这两个dll,把它复制出来到exe同目录下。

在这里插入图片描述
添加完成后,dist目录下,就是如下的状态:
在这里插入图片描述
再次双击点击运行发现了,运行成功了:发现已经是成熟 exe 文件
在这里插入图片描述
感谢大佬对我的支持:如果有错误,请大佬们在评论区指正下,谢谢

博客推荐:基于opencv第三方视觉库,通过内网IP调用手机摄像头,实现人脸识别与图形监测

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:代码科技 设计师:Amelia_0503 返回首页
评论

打赏作者

唤醒手腕

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值