Python - обнаружение QR-кода из изображения и обрезка с использованием OpenCV - PullRequest
2 голосов
/ 23 февраля 2020

Я работаю над проектом с использованием Python (3.7) и OpenCV, в котором у меня есть изображение (снятое с помощью камеры) документа с размещенным на нем QR-кодом.

Этот QR-код Код имеет 6 переменных соответственно:

  1. Размер изображения QR-кода

  2. Верх

  3. справа

  4. Низ

  5. Слева

  6. Единица


Последнее обновление:

Вот шаги, которые мне нужно выполнить в том же порядке:

  1. Определение кода qr и декодирование чтобы прочитать значения размера
  2. Итак, если размер QR-кода (изображения) не равен указанному в нем размеру, то масштабируйте изображение, чтобы оно равнялось обоим значениям размера.
  3. Затем обрежьте изображение со всех сторон от изображения QR-кода в соответствии со значениями, указанными в qr-коде.

Я пробовал этот код:

def decodeAndCrop(inputImage):
    print(str(inputImage))
    image = cv2.imread(str(inputImage))
    qrCodeDetector = cv2.QRCodeDetector()
    decodedText, points, _ = qrCodeDetector.detectAndDecode(image)
    qr_data = decodedText.split(",")
    print("qr data from fucntion: {}".format(qr_data))
    if points is not None:
        pts = len(points)
    # print(pts)
    for i in range(pts):
        nextPointIndex = (i + 1) % pts
        if str(inputImage) == "scaled_img.jpg":
            cv2.line(
                image,
                tuple(points[i][0]),
                tuple(points[nextPointIndex][0]),
                (255, 0, 0),
                5,
            )
        print(points[i][0])
        width = int(
            math.sqrt(
                (points[0][0][0] - points[1][0][0]) ** 2
                + (points[0][0][1] - points[1][0][1]) ** 2
            )
        )
        height = int(
            math.sqrt(
                (points[1][0][0] - points[2][0][0]) ** 2
                + (points[1][0][1] - points[2][0][1]) ** 2
            )
        )
        print("height and width after scaling: {} {}".format(height, width))
        if not str(inputImage) == "scaled_img.jpg":
            scaled_img = None
            if width == qr_data[0] and height == qr_data[0]:
                print("Sizes are equal")
                # Add the extension values to points and crop
                y = int(points[0][0][1]) - int(qr_data[1])
                x = int(points[0][0][0]) - int(qr_data[4])
                roi = image[
                    y : y + height + int(qr_data[3]), x : x + width + int(qr_data[2])
                ]
                scaled_img = cv2.imwrite("scaled_img.jpg", roi)
                return scaled_img
            else:
                print(
                    "Width and height  "
                    + str(width)
                    + "x"
                    + str(height)
                    + "  not equal to "
                    + str(qr_data[0])
                    + "x"
                    + str(qr_data[0])
                )
                if height > int(qr_data[0]):
                    scale_width = int(width) - int(qr_data[0])
                    scale_height = int(height) - int(qr_data[0])
                    print(f"scaled width: {scale_width} scaled height: {scale_height}")
                    dimension = (scale_width, scale_height)
                    scaled_img = cv2.resize(
                        image, dimension, interpolation=cv2.INTER_AREA
                    )
                    print("new img dims: {}".format(scaled_img.shape))
                    cv2.imshow("scaled image:", scaled_img)
                    cv2.imwrite("scaled_img.jpg", scaled_img)
                elif height < int(qr_data[0]):
                    scale_width = int(qr_data[0]) - width
                    scale_height = int(qr_data[0] - height)
                    print(f"scaled width: {scale_width} scaled height: {scale_height}")
                    dimension = (scale_width, scale_height)
                    scaled_img = cv2.resize(
                        image, dimension, interpolation=cv2.INTER_AREA
                    )
                    print("new img dims: {}".format(scaled_img.shape))
                    cv2.imshow("scaled image:", scaled_img)
                    cv2.imwrite("scaled_img.jpg", scaled_img)
                    cv2.imshow("final output:", roi)
                return scaled_img

        else:
            y = int(points[0][0][1]) - int(qr_data[1])
            x = int(points[0][0][0]) - int(qr_data[4])
            print(" x and y")
            print(x)
            print(y)
            roi = image[
                y : y + height + int(qr_data[3]), x : x + width + int(qr_data[2])
            ]
            final_img = cv2.imwrite("finalized_image.jpg", roi)
            cv2.imshow("finalized image:", final_img)
            return final_img


if __name__ == "__main__":
    image_to_crop = decodeAndCrop("example_input_1.jpg")
    final_image = decodeAndCrop("scaled_img.jpg")
    cv2.imshow("Cropped:", image_to_crop)
    # cv2.imshow("Final: ", final_image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

Приведенный выше код дает rror as: final_img = cv2.imwrite ("finalized_image.jpg", roi) cv2.error: OpenCV (4.2.0) / Пользователи / travis / build / skvark / opencv-python / opencv / modules / imgcodecs / src /loadave. cpp: 715: ошибка: (-215: подтверждение не выполнено)! _Img.empty () в функции 'imwrite'


Конец последнего обновления:


Пример декодированной информации о QR-коде: 100, 20, 40, 60, 20, px

Теперь мне нужно определить QR-код по изображению этого документа и на первом этапе мне нужно сравнить размер QR-кода в захваченном изображении документа с размером, который указан в декодированной информации, например, если в захваченном изображении размер QR-изображения составляет 90X90 пикселей, а размер из декодированной информации равен 100X100px нам нужно сравнить это.

Затем, на втором шаге, я должен обрезать полное изображение, используя переменные Top, Right, Bottom & Left соответственно. В соответствии с приведенным выше примером нам нужно обрезать изображение с позиции обнаруженного QR-кода до 20px Top, 40px Right, 60px Bottom и 20px Right. Я добавил пример изображения ниже.

Я сделал для декодирования информации QR-кода, но как я могу взять обнаруженную область QR-кода в качестве отдельного изображения и сравнить ее размер с указанным размером, а затем обрезать изображение соответственно?

Вот что я пробовал до сих пор:

import cv2

image = cv2.imread('/Users/abdul/PycharmProjects/QScanner/images/second.jpg')

qrCodeDetector = cv2.QRCodeDetector()
decodedText, points, _ = qrCodeDetector.detectAndDecode(image)
qr_data = decodedText.split(',')
qr_size = qr_data[0]
top = qr_data[1]
right = qr_data[2]
bottom = qr_data[3]
left = qr_data[4]

print(f'Size: {qr_size}' + str(qr_data[5]))
print(f'Top: {top}')
print(f'Right: {right}')
print(f'Bottom: {bottom}')
print(f'Left: {left}')
if points is not None:
    pts = len(points)
    print(pts)
    for i in range(pts):
        nextPointIndex = (i+1) % pts
        cv2.line(image, tuple(points[i][0]), tuple(points[nextPointIndex][0]), (255,0,0), 5)
        print(points[i][0])
    print(decodedText)    
    cv2.imshow("Image", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
else:
    print("QR code not detected")

Вот пример изображения:

enter image description here

и вот пример входного изображения:

enter image description here

Ответы [ 3 ]

0 голосов
/ 24 февраля 2020

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

  1. Если изображение поворачивается на угол \ theta,
  2. Если лист представляет собой плоскость. (то есть на изображениях верхняя линия не кажется линейной. Но это не должно иметь большого значения.)
  3. Черные границы. Будете ли вы всегда иметь их или это будет другой фон? Это важно, потому что без их обрезания вы не сможете получить разумный результат.

Я немного улучшил ваш код и удалил пиксели границы:

import cv2
import matplotlib.pyplot as plt    
import math
import numpy as np

image = cv2.imread('/Users/samettaspinar/Public/im.jpg')

qrCodeDetector = cv2.QRCodeDetector()
decodedText, points, _ = qrCodeDetector.detectAndDecode(image)
qr_data = decodedText.split(',')
qr_size = int(qr_data[0])
top = int(qr_data[1])
right = int(qr_data[2])
bottom = int(qr_data[3])
left = int(qr_data[4])

print(f'Size: {qr_size}' + str(qr_data[5]))
print(f'Top: {top}')
print(f'Right: {right}')
print(f'Bottom: {bottom}')
print(f'Left: {left}')

plt.imshow(image)
plt.show()

dists = [] #This is for estimating distances between corner points.
           #I will average them to find ratio of pixels in image vs qr_size  
           #in the optimal case, all dists should be equal

if points is not None:
    pts = len(points)
    for i in range(pts):
        p1 = points[i][0]
        p2 = points[(i+1) % pts][0]

        dists.append(math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2))

        print('line', tuple(p1), tuple(p2))
        image = cv2.line(image, tuple(p1), tuple(p2), (255,0,0), 5)
else:
    print("QR code not detected")

print('distances: ', dists)


# Remove the black border pixels. I had a simple idea for this
# Get the average intensity of the gray image
# If count the row average of the first half that are less than intensity/2. 
# It approx gives number of black borders on the left. etc.  
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
inten = np.mean(gray)

x = np.mean(gray, axis=0) # finds the vertical average
y = np.mean(gray, axis=1) # finds horizontal average

bl_left = np.sum([x[:int(col/2)] < inten/2])
bl_right = np.sum([x[int(col/2)+1:] < inten/2])

bl_top = np.sum([y[:int(row/2)] < inten/2])
bl_bottom = np.sum([y[int(row/2)+1:] < inten/2])

print('black margins: ', bl_left, bl_right, bl_top, bl_bottom)

# Estimate how many pixel you will crop out
ratio = np.mean(dists)/ int(qr_size)
print('actual px / qr_size in px: ', ratio)

row,col,dim = image.shape

top, left, right, bottom = int(top*ratio), int(left*ratio), int(right*ratio), int(bottom*ratio)
top += bl_top
left += bl_left
right += bl_right
bottom += bl_bottom

print('num pixels to be cropped: ', top, left, right, bottom)

gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
image2 = image[top:row-bottom, left:col-right, :]

plt.imshow(image2)
plt.show()

Обратите внимание, что я проигнорировал проблему поворота. Если есть вращение, вы можете найти угол, вычислив касательные / арктаны, где я рассчитал расстояния.

0 голосов
/ 25 февраля 2020

Вот простой подход с использованием определения порогов, морфологических операций и фильтрации контуров.

  1. Получение двоичного изображения. Загрузка изображения, оттенки серого, Размытие по Гауссу , Порог Оцу

  2. Соединить отдельные контуры QR. Создать прямоугольное angular структурирующее ядро ​​с cv2.getStructuringElement затем выполните морфологические операции с cv2.MORPH_CLOSE.

  3. Фильтр для QR-кода. Поиск контуров и фильтр используя контурное приближение , площадь контура и соотношение сторон .


Обнаруженный QR-код

enter image description here

Извлеченный QR-код

enter image description here

Здесь вы можете сравнить QR-код с вашей справочной информацией

код

import cv2
import numpy as np

# Load imgae, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread('1.jpg')
original = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (9,9), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

# Morph close
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5,5))
close = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)

# Find contours and filter for QR code
cnts = cv2.findContours(close, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
for c in cnts:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.04 * peri, True)
    x,y,w,h = cv2.boundingRect(approx)
    area = cv2.contourArea(c)
    ar = w / float(h)
    if len(approx) == 4 and area > 1000 and (ar > .85 and ar < 1.3):
        cv2.rectangle(image, (x, y), (x + w, y + h), (36,255,12), 3)
        ROI = original[y:y+h, x:x+w]
        cv2.imwrite('ROI.png', ROI)

cv2.imshow('thresh', thresh)
cv2.imshow('close', close)
cv2.imshow('image', image)
cv2.imshow('ROI', ROI)
cv2.waitKey()     
0 голосов
/ 23 февраля 2020

Я получил данные width и height, используя точки, и сравнил их с размером qr_data. Затем обрезать QR в соответствии с необходимостью.

import cv2
import math  

image = cv2.imread('/ur/image/directory/qr.jpg')

qrCodeDetector = cv2.QRCodeDetector()
decodedText, points, _ = qrCodeDetector.detectAndDecode(image)
qr_data = decodedText.split(',')
qr_size = qr_data[0]
top = qr_data[1]
right = qr_data[2]
bottom = qr_data[3]
left = qr_data[4]

if points is not None:
    pts = len(points)
    print(pts)
    for i in range(pts):
        nextPointIndex = (i+1) % pts
        cv2.line(image, tuple(points[i][0]), tuple(points[nextPointIndex][0]), (255,0,0), 5)
        print(points[i][0])

    width = int(math.sqrt((points[0][0][0]-points[1][0][0])**2 + (points[0][0][1]-points[1][0][1])**2))
    height = int(math.sqrt((points[1][0][0]-points[2][0][0])**2 + (points[1][0][1]-points[2][0][1])**2))

    # Compare the size
    if(width==qr_data[0] and height==qr_data[0]):
        print("Sizes are equal")
    else:
        print("Width and height  " + str(width) + "x" +  str(height) + "  not equal to " 
        + str(qr_data[0]) + "x" + str(qr_data[0]))

    # Add the extension values to points and crop
    y = int(points[0][0][1]) - int(qr_data[1])
    x = int(points[0][0][0]) - int(qr_data[4])
    roi = image[y:y+height + int(qr_data[3]), x:x+width + int(qr_data[2])]
    print(decodedText)    
    cv2.imshow("Image", image)
    cv2.imshow("Crop", roi)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
else:
    print("QR code not detected")

Результат:

enter image description here

...