Tags: 二维码,OCR,Python,OpenCV,pytesseract,pyzbar
前阵子在做一个小工具,需要把一段文字转成二维码,再用手机扫出来;另一边又要把纸质单据上的文字抓进系统。两件事凑一起,干脆把二维码生成/识别和 OCR 整成一条链,记录一下关键代码和踩过的坑。
Python 里 qrcode 库最省事。装一下:
pip install qrcode[pil]
基本用法几行搞定:
import qrcode
img = qrcode.make("https://symoon.icu")
img.save("qr_basic.png")
想调尺寸、容错率、颜色,就换 QRCode 对象:
from qrcode import QRCode, constants
qr = QRCode(
version=1, # 1-40,控制容量
error_correction=constants.ERROR_CORRECT_H, # 最高容错,适合中间加 logo
box_size=10,
border=4,
)
qr.add_data("https://symoon.icu")
qr.make(fit=True)
img = qr.make_image(fill_color="#1a1a1a", back_color="white")
img.save("qr_custom.png")
中间塞个 logo 也常见,注意 logo 别超过二维码面积的 30%,否则容错再高也扫不出来:
from PIL import Image
logo = Image.open("logo.png").convert("RGBA")
qr_img = img.convert("RGBA")
# logo 缩放到二维码的 1/4
logo_size = qr_img.width // 4
logo.thumbnail((logo_size, logo_size), Image.Resampling.LANCZOS)
pos = ((qr_img.width - logo.width) // 2, (qr_img.height - logo.height) // 2)
qr_img.paste(logo, pos, logo)
qr_img.save("qr_with_logo.png")
识别二维码主要用 pyzbar,能读 QR Code、Data Matrix、Code128 这些。需要 OpenCV 或者 Pillow 读图。
pip install pyzbar opencv-python-headless
用 OpenCV 读图识别:
import cv2
from pyzbar import pyzbar
img = cv2.imread("qr_with_logo.png")
barcodes = pyzbar.decode(img)
for bc in barcodes:
print(bc.data.decode("utf-8"))
# 画框
x, y, w, h = bc.rect
cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
有时候摄像头画面反光、角度歪,识别率会掉。加个预处理能救回不少:
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化 + 降噪
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
barcodes = pyzbar.decode(binary)
我自己测试下来,光线正常时 pyzbar 基本一枪命中;太暗、太快、摩尔纹厉害的情况下,就得上图像预处理或者换相机对焦。
OCR 我用的是 pytesseract,底层是 Tesseract。先装系统依赖:
# Ubuntu/Debian
sudo apt-get install tesseract-ocr tesseract-ocr-chi-sim
pip install pytesseract pillow
中文识别要在调用时指定语言包:
from PIL import Image
import pytesseract
img = Image.open("invoice.png")
text = pytesseract.image_to_string(img, lang="chi_sim+eng")
print(text)
Tesseract 对清晰、白底黑字的印刷体效果最好。手写体、倾斜、低分辨率会掉链子。预处理能明显提升准确率:
import cv2
import numpy as np
img = cv2.imread("invoice.png", cv2.IMREAD_GRAYSCALE)
# 放大分辨率
img = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
# 二值化
_, img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 去噪
cv2.imwrite("processed.png", img)
一个实际场景:单据上印了二维码,里面存的是业务 ID;OCR 识别的是单据上的金额、日期。流程可以这么串:
import cv2
from pyzbar import pyzbar
import pytesseract
from PIL import Image
img = cv2.imread("doc.jpg")
# 1. 先扫二维码拿 ID
barcodes = pyzbar.decode(img)
order_id = barcodes[0].data.decode("utf-8") if barcodes else ""
# 2. 裁剪文字区域做 OCR
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
text = pytesseract.image_to_string(gray, lang="chi_sim+eng")
print("单号:", order_id)
print("识别内容:", text)
如果二维码和文字区域位置固定,直接按坐标裁剪更稳,少跑很多识别干扰。比如二维码永远在左上角 200×200:
qr_roi = img[0:200, 0:200]
barcodes = pyzbar.decode(qr_roi)
encode/decode 确认一下。chi_sim,需要手动指定 pytesseract.pytesseract.tesseract_cmd 和 TESSDATA_PREFIX。pyzbar。二维码和 OCR 都不是什么新技术,但组合起来能省很多手工录入的麻烦。如果是手机端扫描,现在微信小程序的 scanCode 和 OCR 接口也已经很成熟,后端只需要一个接收和校验的接口就行。日常小需求,上面这些代码片段基本够用了。