顺着这个系列的传统,这次桌面应用增加一个二维码相关的功能,一方面可以输入文字,产生对应的二维码,一方面可以输入图像,识别图像中的二维码。
【自由生长的python】二维码的生成和识别
这次在GUI编程上没有什么新的内容,都是老的组件,老的用法。学到的两个新的东西:
1、调用qrcode模块生成二维码;
2、调用opencv-python识别二维码。
(以上两个都是使用现成的API,对于二维码的的具体知识,图像中二维码识别的具体难度没有了解。我感觉真要识别一张图片中的二维码,可能是难度很大的事情,没有具体思路,只能使用别人的成果)
最终你可以通过可以使用下面四行命令,体验一下程序运行的效果
git clone https://github.com/kamigo2018/myTkinter.git
cd myTkinter
git checkout V1.2.5
python HelloTkinter.py
如图所示,左右分别是两个框体(frame):左边的框体用来展示二维码图片;右边的框体用来输入文字。同时,左边的框体可以打开带有二维码的图片,右边的框体用来展示识别出二维码的内容(这个功能主要在菜单栏中实现)。
生成qrcode非常简单,引入qrcode模块后,调用qrcode.make(),返回的就是一个二维码图像对象。下面的代码中data是从Text组件中获取的字符串,是用户输入的,要编码在二维码里的内容。
... ...
data = strInfo[:-1] #Text中的字符串会默认添加一个换行符,这里干掉换行符。
if len(data) == 0:
tkMessageBox.showinfo(title="说明",message="无输入字符,不生成二维码")
return
else:
tempFile = qrcode.make(data) # 直接调用make,生成二维码图像
tempFile.save('tmp/qrcode.jpg') # 将图像保存成临时文件,
image = Image.open('tmp/qrcode.jpg') # 用Image对象打开这个文件,传给图像展示窗体,其实不用二次打开,直接把tempFile传进来就可以。
self.myQRCodeImageViewFrame.showImage(image)
... ...
仔细想一下,其实二维码在生成的时候肯定遵守了一些规范。这里直接用qrcode,而不去深究编码规范,确实属于拿来主义。看起来很简单的,直接生成一张二维码,背后可能还隐藏着很多细节。但是如果我知道编码规则,能否做一个二维码出来?应该可以,到时候应该就是操作图片,将一部分点涂黑,一部分点留白。
虽然会骑自行车,不需要知道自行车轮子是怎么造的。但是如果有一天,出现一个像共享单车一样的机会,真需要我去了解轮子是怎么造的,怎么改进,我有这个能力去挖细节么?
我认为识别二维码是很难,至少有两个难点:
1、根据编码规范,将二维码解码;
2、一张图片中定位二维码的位置;这个对我来说,想想就觉得无从下手。
所以这里只能寻找现成的模块,利用现成的方法。
在寻找过程中,我看到大多数中文博客提到了zxing。由于这么多人推荐,而且出自google,相信zxing会比较好用。但是我从github在下载zxing(不是zxing-python)时,124M的东西总是无法下载成功。所以只能试试寻找别的模块。
在这篇帖子中,提到了opencv-python,并且提供了完整的例子。我觉得挺好的,很实用。使用cv2模块,只用三步:
1、通过cv2读入图片数据
img = cv2.imread(fileName)
2、初始化一个解码器
detector = cv2.QRCodeDetector()
3、解码
data, bbox, straight_qrcode = detector.detectAndDecode(img)
返回的data是二维码中识别出来的字符串;bbox是一个列表,记录了二维码的顶点坐标;straight_qrcode是二进制序列码(不知道这个东西杂用)。
下面的例子中,将二维码识别出来后,将二维码区域用红线框了起来,显示在图片展示区域。
fileName = "tmp/decode.jpg"
image = self.myQRCodeImageViewFrame.image
image.save(fileName) # 先把他存成临时文件,给cv2用
# cv2读入图片数据
img = cv2.imread(fileName)
# 初始化一个二维码解码器
detector = cv2.QRCodeDetector()
# 识别二维码
data, bbox, straight_qrcode = detector.detectAndDecode(img)
# 如果有二维码,将二维码区域识别画出来
if bbox is not None:
self.myQRCodeInputViewFrame.txtArea.insert(0.0,data)
n_lines = len(bbox)
for i in range(n_lines):
# draw all lines
point1 = tuple(bbox[i][0])
point2 = tuple(bbox[(i+1) % n_lines][0])
# bbox 是这个二维码的四个顶点,用红线将二维码框起来。
cv2.line(img, point1, point2, color=(0, 0, 255), thickness=5)
cv2.imwrite(fileName,img) # 这时候打开fileName对应的文件,就可以看圈出二维码的新图片。
image = Image.open(fileName)
self.myQRCodeImageViewFrame.showImage(image)
这次在开发的过程中,我觉得有些组件的组合以后还可能会用到。这样就把几个组件封装到一个frame里面,为了以后使用方便,可以看一下CommonFrame.py文件。
第一个封装,是将Cavnas和Scrollbar封装在一起,用来展示图片。我觉得这个封装的不太好,一是因为有些必要的动作没有封装进去,比如适应窗口大小,放大,缩小。二是因为封装了以后,frame肯定是放在某个父容器,和别的组件发生交互。这个交互方式没有设计。
第二个封装,是将Text和一个Button封装在一起。同样有我上面的两个问题。没想好,以后应该会改进吧。
大家是否注意到,右边窗体中有个竖向的标签:“二维码内容”。这个竖向的标签实现方式是:使用了一个wraplength属性,让他在每个字后面换行,从而实现竖向排列。
self.inputStringLabel = tk.Label(self,text="二维码内容",wraplength=1,font = self.showFont) # 父容器就是self,这个frame
# 这里还有个知识点:wraplength,让他换行。参考:
# https://stackoverflow.com/questions/17650232/python-tkinter-label-orientation
我搜索了中文的网页,发现好像大家没有讨论过这个问题,所以转向英文网页,在stackoverflow上,果然有人回答过这类似的的问题。特别想给那个提问和回答的人一个赞!另外中文创作者们也应该加油,遇到的问题应该记录下来,没有遇到问题的时候,可以多想想,还有什么没做。
最后,我觉得我也应该把tkinter相关的知识总结一下了,以后自由生长的部分可能就是python相关的内容了。