Время прочтения: 5 мин.

В моей работе встретилась такая задача, как проверка кассовых чеков на корректность и легальность. Чеки хранились в отсканированном виде и требовалось найти решение получения платежных данных для последующей проверки в налоговых органах. На помощь пришла технология Computer Vision (компьютерное зрение). С помощью этой технологии получилось считать с изображений QR-коды чеков, расшифровать и проверить их.

Классическая задача компьютерного зрения — это обработка изображений и видеоданных, и библиотека pyzbar эту задачу успешно выполняет. Поиск QR-кода реализует на достаточно высоком уровне, поэтому мой выбор пал на нее.

Но для начала краткая истории появления QR-кода. QR-код (Quick response code – код быстрого реагирования) был разработан Масахиро Хара в японской компании Denso-Wave в 1994г. Несмотря на то что код был разработан для упрощения работы сотрудников на производстве за счет простоты и удобства он получил распространение во многих сферах деятельности человека.

В первой части статьи я расскажу, как сгенерировать свой QR-код. Во второй части будет рассказано как считать и декодировать QR-код.

Первым делом установим все необходимые библиотеки.

pip install pyzbar qrcode opencv-python glob
pyzbar - декодирование QR-кодов;
qrcode - создание QR-кодов;
opencv-python - работа с изображениями;
glob - считывание всех файлов в указанной папке.

Как сгенерировать QR-код

Создать собственный QR-код легче, чем может показаться.

import qrcode

qr = qrcode.QRCode()
qr.add_data('https://yandex.ru')
img = qr.make_image()
img.save('ya.png')

Результат работы кода.

Если требуется создать код под свои нужды, можно вписать параметры создания QR-кода.

В таком простом исполнении параметры создания QR-кода будут выставлены автоматически, но можно создать и по своим параметрам.

qr = qrcode.QRCode(
    version = 3,
    box_size = 10,
    border = 2,
    error_correction = qrcode.constants.ERROR_CORRECT_Q
)
qr.add_data('https://yandex.ru')
img = qr.make_image(back_color='yellow', fill_color='black')
img.save('ya2.png')

Тот же самый адрес сайта, но QR-код уже отличается.

Параметр version может быть от 1 до 40, от 21х21 до 177х177 пикселей, не учитывая поля.

Параметр box_size отвечает за количество пикселей в каждом квадрате QR-кода.

Параметр border создает границу вокруг QR-кода.

Параметр Error_correction служит для восстановления кода, если код повредился и плохо читаем.

Каждый уровень указывает на процент данных для восстановления.

ERROR_CORRECT_L7%
ERROR_CORRECT_M15%
ERROR_CORRECT_Q25%
ERROR_CORRECT_H30%

В методе make_image() добавил параметр back_color, отвечающий за цвет фона и  fill_color- за цвет QR-кода.

Добавим логотип в наш QR-код. Раз у нас ссылка ведет на сайт yandex.ru, то и логотип будем использовать от Яндекса.

from PIL import Image

logo = Image.open('yandex_logo.png')
qr = qrcode.QRCode(
    version = 3,
    box_size = 10,
    border = 2,
    error_correction = qrcode.constants.ERROR_CORRECT_H
)
pos = (
    (img.size[0] - logo.size[0]) // 2,
    (img.size[1] - logo.size[1]) // 2,
)
qr.add_data('https://yandex.ru')
img = qr.make_image().convert('RGB')  # конвертируем в RGB чтобы логотип был цветным
img.paste(logo, pos)
img.save('yandex_qr_logo.png')

Вставляя логотип, мы повреждаем часть QR-кода, но из-за того, что мы добавили 30% на восстановление при ошибках, код считывается без проблем.

Перейдем ко второй части и попробуем декодировать зашифрованные данные кода.

Как декодировать QR-код

Импортируем установленные библиотеки.

Также я буду использовать библиотеку glob. Glob создает список файлов по указанному пути и мы поочередно их считываем.

from pyzbar import pyzbar
import cv2
from glob import glob

Чтобы показать, как работает библиотека, считаем 1 чек и посмотрим, какой результат она выдаст.

filename = 'receipts/1.jpg'
img = cv2.imread(filename)    # Считываем файл с изображением
qrcodes = pyzbar.decode(img)  # Создается список найденных кодов
print(qrcodes)

pyzbar.decode() принимает изображение с QR-кодом и декодирует его, выдавая параметры самого QR-кода и обнаруженные данные.

[Decoded(data=b't=20170221T095900&s=30.00&fn=99990789659&i=501&fp=4189225230&n=1', type='QRCODE', rect=Rect(left=111, top=462, width=159, height=159), polygon=[Point(x=111, y=462), Point(x=112, y=619), Point(x=270, y=621), Point(x=268, y=462)])]

Для тестирования я взял несколько чеков, для тестового решения можно взять любые QR-коды, которые вы сможете найти.

Для упрощения работы все операции с изображением я вывел в отдельную функцию.

def getQr(filename):
    all_data = []
    img = cv2.imread(filename)    # Считываем файл с изображением
    qrcodes = pyzbar.decode(img)  # Создается список найденных кодов
    for qrcode in qrcodes:
        qrcodeData = qrcode.data.decode('utf-8')
        if qrcode.type == 'QRCODE' and 'fn=' in qrcodeData:  # проверяем тип кода и проверяем вхождение строки 'fn='
            all_data.append(qrcodeData.split('&'))
    return all_data

В основном коде дополнительно импортировал библиотеку pandas для создания датафрейма.

# данные чеков которые я буду собирать в отдельную таблицу
# t — timestamp, время, когда осуществлена покупка
# s — сумма чека
# fn —номер фискального накопителя
# fd — номер фискального документа
# fp — информация для проверки ФД
# n — номер чека

import pandas as pd

df_good = pd.DataFrame() # Удачно считанные данные
df_bad = pd.DataFrame()  # список не распознанных кодов

for filename in glob('receipts/*.jpg'):  # Считываем тип файлов только jpg
    data = getQr(filename)
    if data != []:
        for cell in data:  # если на изображении больше 1 кода
            df_good = df_good.append(pd.DataFrame([cell]))
    else:
        df_bad = df_bad.append(pd.DataFrame([filename]))
        
if len(df_good) != 0:
    df_good.columns = ['t', 's', 'fn', 'fd', 'fp', 'number']
    df_good.reset_index(drop=True, inplace=True)
if len(df_bad) != 0:
    df_bad.columns = ['filename']
    df_bad.reset_index(drop=True, inplace=True)
data - сами данные которые мне нужны;
type - тип кода, меня интересовал тип 'QRCODE';
rect - координаты QR-кода на изображении;
polygon – координаты точек(углов) QR-кода.

Посмотрим удачно считанные коды.

tsfnfdfpnumber
t=20170221T095900s=30.00fn=99990789659i=501fp=4189225230n=1
t=20170512T080900s=1300.32fn=8710000100308965i=8218fp=3172198901n=1
t=20180717T1608s=350.00fn=8710000101441965i=3760fp=3967995548n=1
t=20190411T1620s=199.00fn=9289000100355637i=4522fp=4253291993n=1
t=20190320T2303s=5803.00fn=9251440300007971i=141637fp=4087570038n=1

А вот название файлов не найденных, либо не считанных кодов.

filename
0receipts\4.jpg

Т.е. файлы, в которых pyzbar не нашел QR-кодов, можно дополнительно обработать, сделать изображение черно-белым добавляя четкости, убирать шумы и т.д.

Как вы уже увидели в создании и декодировании QR-кодов нет ничего сложного. Я показал лишь основу, которую вы можете модернизировать, добавляя дополнительные библиотеки или встраивая в другие программы. Это еще один пример того, как Computer Vision облегчает жизнь и работу людей.