【问题标题】:Python - Detect a QR code from an image and crop using OpenCVPython - 从图像中检测二维码并使用 OpenCV 进行裁剪
【发布时间】:2020-06-07 02:35:38
【问题描述】:

我正在使用 Python(3.7) 和 OpenCV 开展一个项目,其中我有一个带有 QR 码的文档的图像(使用相机捕获)。

这个二维码有6个变量分别为:

  1. 二维码图片尺寸

  2. 顶部

  3. 正确

  4. 底部

  5. 单位


最新更新:

以下是我需要按相同顺序执行的步骤:

  1. 检测二维码并对其进行解码以读取大小值
  2. 因此,如果 QR 码(图像)的大小不等于其中提到的大小,则将图像缩放为等于两个大小值。
  3. 然后根据二维码里面提到的数值,从二维码图像向四周裁剪图像。

我试过这段代码:

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()

上面的代码给出了一个错误: final_img = cv2.imwrite("finalized_image.jpg", roi) cv2.error:OpenCV(4.2.0)/Users/travis/build/skvark/opencv-python/opencv/modules/imgcodecs/src/loadsave.cpp:715:错误:(-215:断言失败)!_img.empty () 在函数'imwrite'中


最新更新结束:


二维码的解码信息示例为:100,20,40,60,20,px

现在,我需要从该文档图像中检测 QR 码,并且在第一步中,我需要将捕获的文档图像中 QR 码的大小与解码信息中提到的大小进行比较,例如,如果在捕获的图像 QR 图像的大小为 90X90px,解码信息的大小为 100X100px,我们需要进行比较。

然后,在第二步中,我必须相应地使用 Top、Right、Bottom 和 Left 变量来裁剪整个图像。根据上面的例子,我们需要将图像从检测到的二维码位置裁剪为上20px、右40px、下60px和右20px。我在下面添加了一个示例图片。

我已经完成了二维码信息的解码,但是如何将检测到的二维码区域作为单独的图像并将其大小与提到的大小进行比较,然后相应地裁剪图像?

这是我迄今为止尝试过的:

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")

这是一个示例图片:

这是输入图像的示例:

【问题讨论】:

  • 在您的示例中,似乎您正在获取图像,我无法完全理解问题所在
  • @YunusTemurlenk 实际上我需要实现两件事:1):裁剪 QR 码并将其与其中提到的尺寸进行比较 2):根据 Top、Right、Bottom 的值裁剪图像&左
  • 我上面提到的第一张图片就是想要的东西。
  • 您已经有检测到的矩形点。因此,通过减去点 x 和 y 的值,可以得到检测到的 QR 的大小。然后你也可以根据这些点来操作裁剪图像点。
  • @YunusTemurlenk 你能放一个代码示例吗!

标签: python image opencv computer-vision qr-code


【解决方案1】:
For QR detection and parsing

import cv2
import sys

filename = sys.argv[1]

# read the QRCODE image
#in case if QR code is not black/white it is better to convert it into grayscale
img = cv2.imread(filename, 0)# Zero means grayscale
img_origin = cv2.imread(filename)

# initialize the cv2 QRCode detector
detector = cv2.QRCodeDetector()

# detect and decode
data, bbox, straight_qrcode = detector.detectAndDecode(img)

# if there is a QR code
if bbox is not None:
    print(f"QRCode data:\n{data}")
    # display the image with lines
    # length of bounding box
    n_lines = len(bbox[0])#Cause bbox = [[[float, float]]], we need to convert fload into int and loop over the first element of array
    bbox1 = bbox.astype(int) #Float to Int conversion
    for i in range(n_lines):
        # draw all lines
        point1 = tuple(bbox1[0, [i][0]])
        point2 = tuple(bbox1[0, [(i+1) % n_lines][0]])
        cv2.line(img_origin, point1, point2, color=(255, 0, 0), thickness=2)


    # display the result
    cv2.imshow("img", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
else:
    print("QR code not detected")

【讨论】:

  • 您好,如果您能解释一下您的代码的作用,那就太好了。它确实使我们其他人的答案更好,更容易理解!
【解决方案2】:

这是一个使用阈值、形态学运算和轮廓过滤的简单方法。

  1. 获取二值图像。 Load image, grayscale, Gaussian blur, Otsu's threshold

  2. 连接各个 QR 轮廓。 使用cv2.getStructuringElement() 创建一个矩形结构内核,然后使用cv2.MORPH_CLOSE 执行morphological operations

  3. 过滤二维码。 Find contours 并使用contour approximationcontour areaaspect ratio 进行过滤。


检测到二维码

提取的二维码

从这里您可以将二维码与您的参考信息进行比较

代码

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()     

【讨论】:

    【解决方案3】:

    所以,你这里主要有 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()
    

    请注意,我忽略了轮换问题。如果有旋转,您可以通过计算我计算距离的切线/反正切来找到角度。

    【讨论】:

      【解决方案4】:

      我使用点获得了widthheight 数据,并将其与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")
      

      结果:

      【讨论】:

      • 是否裁剪了整个图像?还是只有二维码?
      • 通过将原始 qr 矩形的顶部 20 像素、左侧 20 像素、右侧 40 像素和底部 60 像素来裁剪 qr
      • 实际上,我们必须裁剪 qr 矩形的图像(平均四舍五入)。
      • 这也是二维码。你应该更清楚你需要什么。
      • 我想我应该先用不同的图像试试,让我用合适的图像试试。
      猜你喜欢
      • 1970-01-01
      • 2019-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-05
      • 1970-01-01
      相关资源
      最近更新 更多