[Python]在Windows系统中使用ZXing模块实现二维码、条形码读码
需要实现Python读取图片中二维码、条形码信息。前段时间研究使用zbarlight模块,费了很大功夫安装调试好,但是发现有些图片读取不正确,而且如果图片中二维码倾斜,就读取不了,不能满足要求。昨天琢磨着试一试ZXing,下载ZXing模块安装后,却一直报错。打开模块源码仔细分析,原来该模块是通过调用java程序,使用ZXing的java库来实现的,通过分析命令行输出得到解码结果。忙活了一天多,各种测试、查资料,终于解决了问题。调试过程非常艰辛,现将做法整理如下(Windows 10系统):
一、 因为需要调用java程序,必须安装jdk。
- 下载java安装程序,根据提示一步步安装。
下载地址:http://www.oracle.com/technetwork/java/javase/downloads/index.html。
- 设置环境变量。
添加JAVA_HOME环境变量,其值设置为jdk安装目录。将java运行程序所在目录(%JAVA_HOME%\bin)添加到PATH。
- 在命令行输入
java -version
并执行,如果没有问题则说明安装成功。
二、下载ZXing的jar库文件,以及所需java库文件。
- 下载ZXing Core和ZXing Java SE Extensions库文件。
登录网址http://www.mvnrepository.com,在搜索栏中输入"zxing"并查找,就能找到这两个库,下载库文件并将其分别改名为core.jar和javase.jar。
- 下载 JCommander库文件。
在测试中,安装了上面两个库以后,仍然报错,错误信息中提示JCommander模块找不到,后来下载了这个库文件,经过多番调试,终于成功。登录网址http://www.mvnrepository.com,在搜索栏中输入"jcommander"并查找,就能找到这个库,下载库文件并将其改名为jcommander.jar。
- 创建一个目录,并将以上三个jar文件复制到其中。
这个目录可以创建在java安装目录下。我是在java目录下建了一个名为jar的文件夹,将上面三个文件复制到里面。
- 设置环境变量。
添加ZXING_JAR_PATH环境变量,将其值设为上面创建的那个文件夹的绝对路径,我的路径是“d:\java\jar”。
三、在ZXing源代码基础上进行修改,实现二维码、条形码解码功能;增加了两个函数,解决了不支持图片Windows格式绝对路径的问题。
#导入模块
import subprocess, re, os
class BarCodeReader():
"""
解码器类,调用java执行ZXing库命令实现解码,通过分析输出获得结果。
此模块在ZXing源码基础上修改,改动较大。
"""
#ZXing库路径
location = ""
#ZXing库
libs = ["javase.jar", "core.jar", "jcommander.jar"]
#subprocess.Popen参数
args = ["java", "-cp", "LIBS", "com.google.zxing.client.j2se.CommandLineRunner"]
def __init__(self, loc=None):
"""
初始化函数,参数loc设置ZXing库所在位置
"""
#如果loc为空或者不是字符串对象,读取环境变量ZXING_JAR_PATH的值(没找到就设为空字符串)
if not loc or not isinstance(loc,str):
loc = os.environ.get("ZXING_JAR_PATH", "")
#如果loc不为空,在末尾加上'\',否则设为默认值(即在当前目录的zxing子目录下)
if loc:
loc = loc.rstrip("\\") + "\\"
else:
loc = "zxing\\"
#设置location
self.location = loc
def decode(self, files, try_harder = False, qr_only = False):
"""
解码方法,参数files可以是一个文件名(字符串),也可以是一个文件名列表。
"""
#根据try_harder和qr_only参数设置,添加相应选项
if try_harder:
self.args.append("--try_harder")
if qr_only:
self.args.append("--possibleFormats=QR_CODE")
#为ZXing库加上路径
libraries = [self.location + lib for lib in self.libs]
#生成命令列表
cmd = [ c if c != "LIBS" else os.pathsep.join(libraries) for c in self.args ]
#单文件标志设为False
SINGLE_FILE = False
#如果files不是列表,将文件files添加到cmd列表,单文件标志设为True
if not isinstance(files, list):
cmd.append(files)
SINGLE_FILE = True
#否则,将文件列表files添加到cmd列表
else:
cmd += files
#利用subprocess.Popen函数执行命令
(stdout, stderr) = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=True).communicate()
#初始化编码列表
codes = []
#分析输出内容
file_results = stdout.split("\nfile:")
for result in file_results:
lines = result.split("\n")
if re.search("No barcode found", lines[0]):
codes.append(None)
else:
codes.append(BarCode(result))
#如果是单文件,返回第一个BarCode对象;否则,返回BarCode对象列表
if SINGLE_FILE:
return codes[0]
else:
return codes
class BarCode:
"""
BarCode信息类,包含解码所得各项信息。
此模块基本使用了ZXing源码,为方便使用,添加了几个属性。
"""
format = ""
points = []
data = ""
raw = ""
def __init__(self, zxing_output):
lines = zxing_output.split("\n")
raw_block = False
parsed_block = False
point_block = False
self.points = []
for l in lines:
m = re.search("format:\s([^,]+)", l)
if not raw_block and not parsed_block and not point_block and m:
self.format = m.group(1)
continue
if not raw_block and not parsed_block and not point_block and l == "Raw result:":
raw_block = True
continue
if raw_block and l != "Parsed result:":
self.raw += l + "\n"
continue
if raw_block and l == "Parsed result:":
raw_block = False
parsed_block = True
continue
if parsed_block and not re.match("Found\s\d\sresult\spoints", l):
self.data += l + "\n"
continue
if parsed_block and re.match("Found\s\d\sresult\spoints", l):
parsed_block = False
point_block = True
continue
if point_block:
m = re.search("Point\s(\d+):\s\(([\d\.]+),([\d\.]+)\)", l)
if (m):
self.points.append((float(m.group(2)), float(m.group(3))))
#去除多余换行符
self.raw = self.raw.rstrip("\n")
self.data = self.data.rstrip("\n")
@property
def Format(self):
"""
返回编码格式
"""
return self.format
@property
def Data(self):
"""
返回编码数据
"""
return self.data
@property
def Points(self):
"""
返回坐标点
"""
return self.points
@property
def RawData(self):
"""
返回解码所得原始数据
"""
return self.raw
def GetCodeString(filename):
"""
解码函数,只处理单个文件,参数为文件名(含路径)。
返回编码字符串,如果未能成功解码或未找到条码,返回空字符串。
"""
#将'\'替换成'/'
filename = filename.replace("\\", "/")
#如果带有盘符(绝对路径),前面加上'file:/'
if re.match('^[A-Za-z]:', filename):
filename = "file:/" + filename
#创建解码器对象
zxcode = BarCodeReader()
#解码,并将结果保存到barcode
barcode = zxcode.decode(filename)
#如果结果为None,返回空串,否则返回编码字符串
if barcode is None:
return ""
else:
return barcode.Data
def GetCodeObject(filename):
"""
解码函数,只处理单个文件,参数为文件名(含路径)。
返回编码对象,其属性如下:
Data,编码信息;
Format,编码类别;
Points,坐标点;
RawData,未处理的编码信息。
如果未能成功解码或未找到条码,返回None
"""
#将'\'替换成'/'
filename = filename.replace("\\", "/")
#如果带有盘符(绝对路径),前面加上'file:/'
if re.match('^[A-Za-z]:', filename):
filename = "file:/" + filename
#创建解码器对象
zxcode = BarCodeReader()
#解码,并将结果保存到barcode
barcode = zxcode.decode(filename)
#返回结果
return barcode
四、测试。将上述代码保存到zxing.py文件中,编辑测试代码如下:
from zxing import GetCodeString,GetCodeObject
result = GetCodeString("d:\\barcode.png")
print(result)
result = GetCodeObject("d:\\qr.png")
if result:
print("Format:", result.Format)
print("Data: ", result.Data)
print("Points:")
for point in result.Points:
print(point)
五、可以将以上面的代码安装到Python库(注册为zxing
模块)。
-
下载python-zxing模块。下载地址为https://github.com/oostendo/python-zxing。
-
解压缩到python-zxing文件夹。
-
将python-zxing\zxing文件夹内__init__.py文件的内容替换成第三步的代码。
-
在python-zxing文件夹下打开命令行,运行
python setup.py install
即可。 -
同样可以使用第四步的代码进行测试。