Python - Detect a QR code from an image and crop using OpenCV

后端 未结 3 772
不思量自难忘°
不思量自难忘° 2020-12-18 13:37

I\'m working on a project using Python(3.7) and OpenCV in which I have an Image(captured using the camera) of a document with a QR code placed on it.

This QR code ha

相关标签:
3条回答
  • 2020-12-18 13:50

    Here's a simple approach using thresholding, morphological operations, and contour filtering.

    1. Obtain binary image. Load image, grayscale, Gaussian blur, Otsu's threshold

    2. Connect individual QR contours. Create a rectangular structuring kernel with cv2.getStructuringElement then perform morphological operations with cv2.MORPH_CLOSE.

    3. Filter for QR code. Find contours and filter using contour approximation, contour area, and aspect ratio.


    Detected QR code

    Extracted QR code

    From here you can compare the QR code with your reference information

    Code

    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 讨论(0)
  • 2020-12-18 13:53

    So, you mainly have 3 problems here.

    1. If the image is rotated with an angle \theta,
    2. If the sheet is one a plane. (i.e., in the images, the upper line doesn't seem to be linear. But it should not be a big deal.)
    3. The black borders. Will you always have those or may it be a different background? This is important because without cropping out those, you won't be able to get a reasonable result.

    I improved your code a little bit and removed the border pixels:

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

    Notice that I ignored the rotation issue. If there is rotation, you can find the angle by calculating the tangents/arctan where I calculated the distances.

    0 讨论(0)
  • 2020-12-18 14:01

    I got the width and height data using points and compare it with the qr_data size. Then cropped the QR according to needed.

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

    Result:

    0 讨论(0)
提交回复
热议问题