[Python] how to extract omr data in python?

Discussão em 'Python' iniciado por Stack, Março 13, 2025.

  1. Stack

    Stack Membro Participativo

    I am currently working on a OMR data extraction project where i have to check students OMR sheets. here is my code snippet:

    import cv2
    import numpy as np

    # read image
    path = './data/images/5.jpg'
    img = cv2.imread(path)
    h, w = img.shape[:2]
    # resize image
    img = cv2.resize(img, (w//2, h//2))

    img = img[0:h-15, 0:w-5]
    # threshold on white color
    lower=(225,225,225)
    upper=(255,255,255)
    thresh = cv2.inRange(img, lower, upper)
    thresh = 255 - thresh



    imgCanny = cv2.Canny(thresh, 10, 50)

    # # get contours
    contoursImage = img.copy()
    firstOMRBoxImage = img.copy()
    contours = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    cv2.drawContours(contoursImage, contours[0], -1, (0,255,0), 2)


    def rectContour(contours):
    rectContours = []
    for i in contours:
    area = cv2.contourArea(i)
    if area > 50:
    peri = cv2.arcLength(i, True)
    approx = cv2.approxPolyDP(i, 0.02*peri, True)
    if (len(approx) == 4):
    rectContours.append(i)
    rectContours = sorted(rectContours, key=cv2.contourArea, reverse=True)
    firstOMRBox = getCornerPoints(rectContours[0])
    secondOMRBox = getCornerPoints(rectContours[1])
    thirdOMRBox = getCornerPoints(rectContours[3])
    fourthOMRBox = getCornerPoints(rectContours[2])
    rollNoPoints = getCornerPoints(rectContours[4])
    districtPoints = getCornerPoints(rectContours[5])
    nameAndDatePoints = getCornerPoints(rectContours[7])
    candidateSign = getCornerPoints(rectContours[8])
    invigilatorSign = getCornerPoints(rectContours[9])
    groupPoints = getCornerPoints(rectContours[10])
    classPoints = getCornerPoints(rectContours[12])

    if firstOMRBox.size != 0 and secondOMRBox.size != 0:
    cv2.drawContours(firstOMRBoxImage, firstOMRBox, -1, (0,255,0), 30)
    cv2.drawContours(firstOMRBoxImage, secondOMRBox, -1, (255,0,0), 30)
    cv2.drawContours(firstOMRBoxImage, thirdOMRBox, -1, (0,0,255), 30)
    cv2.drawContours(firstOMRBoxImage, fourthOMRBox, -1, (255,255,0), 30)
    cv2.drawContours(firstOMRBoxImage, rollNoPoints, -1, (0,255,255), 30)
    cv2.drawContours(firstOMRBoxImage, districtPoints, -1, (255,0,255), 30)
    cv2.drawContours(firstOMRBoxImage, nameAndDatePoints, -1, (255,255,255), 30)
    cv2.drawContours(firstOMRBoxImage, candidateSign, -1, (0,0,0), 30)
    cv2.drawContours(firstOMRBoxImage, invigilatorSign, -1, (255,255,255), 30)
    cv2.drawContours(firstOMRBoxImage, groupPoints, -1, (0,0,255), 30)
    cv2.drawContours(firstOMRBoxImage, classPoints, -1, (255,0,0), 30)
    firstOMRBox = reorder(firstOMRBox)
    secondOMRBox = reorder(secondOMRBox)


    # Get the width and height of the first OMR box
    # Calculate the width and height of the first OMR box
    width_omr = np.linalg.norm(firstOMRBox[0][0] - firstOMRBox[1][0])
    height_omr = np.linalg.norm(firstOMRBox[0][0] - firstOMRBox[2][0])

    # Use the original aspect ratio for the destination points
    pt1 = np.float32(firstOMRBox)
    pt2 = np.float32([[0,0],[width_omr,0],[0,height_omr],[width_omr,height_omr]])
    matrix = cv2.getPerspectiveTransform(pt1, pt2)
    imgWarpColoured = cv2.warpPerspective(img, matrix, (int(width_omr), int(height_omr)))


    # max_side = max(w, h)
    # pt1 = np.float32(firstOMRBox)
    # pt2 = np.float32([[0,0],[max_side,0],[0,max_side],[max_side,max_side]])
    # matrix = cv2.getPerspectiveTransform(pt1, pt2)
    # imgWarpColoured = cv2.warpPerspective(img, matrix, (max_side,max_side))

    cv2.imwrite('5Wrap_contour.png', imgWarpColoured)

    # Apply Threshhold
    # imgWarpGray = cv2.cvtColor(imgWarpColoured, cv2.COLOR_BGR2GRAY)
    # imgThresh = cv2.threshold(imgWarpGray, 200, 255, cv2.THRESH_BINARY_INV)[1]
    # cv2.imwrite('6biggest_thresh.png', imgThresh)
    # print(imgThresh.shape)
    # x1 = int(w * 0.2) # Start cropping from 70% width
    # y1 = 0 # Start from the top
    # x2 = w # End at full width (rightmost)
    # y2 = h # Full height
    # imgThresh = imgThresh[y1:y2, x1:x2]
    # cv2.imwrite('7after_crop.png', imgThresh)


    # Apply Threshhold
    imgWarpGray = cv2.cvtColor(imgWarpColoured, cv2.COLOR_BGR2GRAY)
    imgThresh = cv2.threshold(imgWarpGray, 200, 255, cv2.THRESH_BINARY_INV)[1]
    cv2.imwrite('6biggest_thresh.png', imgThresh)
    print(imgThresh.shape)

    afterContourIMage = imgThresh.copy()
    grey = cv2.cvtColor(imgWarpColoured, cv2.COLOR_BGR2GRAY)
    # Find contours
    contours, hierarchy = cv2.findContours(grey, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    print(len(contours))
    cv2.drawContours(afterContourIMage, contours, -1, (0,255,0), 10)
    cv2.imwrite('7after_contour.png', afterContourIMage)

    # grey_inverted = cv2.bitwise_not(grey)
    # cv2.imwrite('7grey_inverted.png', grey_inverted)

    cv2.threshold(grey, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU, imgThresh)
    cv2.imwrite('7after_thresh.png', imgThresh)


    aginAfterContourIMage = imgWarpColoured.copy()
    # Find contours
    contours, hierarchy = cv2.findContours(imgThresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    print(len(contours))
    cv2.drawContours(aginAfterContourIMage, contours, -1, (0,255,0), 2)
    cv2.imwrite('7after_contour2.png', aginAfterContourIMage)

    # Get the current dimensions of imgThresh
    thresh_h, thresh_w = imgThresh.shape

    # Now use the dimensions of imgThresh for cropping
    x1 = int(thresh_w * 0.2) # Start cropping from 20% width
    y1 = 0 # Start from the top
    x2 = thresh_w # End at full width
    y2 = thresh_h # Full height

    # Make sure our cropping coordinates are valid
    if x1 < thresh_w and y2 <= thresh_h:
    imgThresh = imgThresh[y1:y2, x1:x2]
    cv2.imwrite('7after_crop.png', imgThresh)
    else:
    print("Cropping coordinates are out of bounds!")
    # Use the uncropped version instead
    cv2.imwrite('7after_crop.png', imgThresh)






    # biggestThreshContoursImage = imgThresh.copy()
    # threshContours = cv2.findContours(biggestThreshContoursImage, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
    # cv2.drawContours(biggestThreshContoursImage, threshContours[0], -1, (0,255,0), 1)
    # cv2.imwrite('biggest_thresh_contours.png', biggestThreshContoursImage)

    splitBoxes(imgThresh)

    return firstOMRBox

    def getCornerPoints(cont):
    peri = cv2.arcLength(cont, True)
    approx = cv2.approxPolyDP(cont, 0.02*peri, True)
    return approx

    def reorder(points):
    points = points.reshape((4,2))
    pointsNew = np.zeros((4,1,2), np.int32)
    add = points.sum(1)
    # print(points)
    # print(add)
    pointsNew[0] = points[np.argmin(add)]
    pointsNew[3] = points[np.argmax(add)]
    diff = np.diff(points, axis=1)
    pointsNew[1] = points[np.argmin(diff)]
    pointsNew[2] = points[np.argmax(diff)]
    # print(pointsNew)

    return pointsNew


    def splitBoxes(img):
    h, w = img.shape[:2]

    # Make sure height is divisible by 25
    new_h = (h // 25) * 25
    img = img[:new_h, :] # Crop height to nearest multiple of 25

    # Make sure width is divisible by 5
    new_w = (w // 5) * 5
    img = img[:, :new_w] # Crop width to nearest multiple of 5

    rows = np.vsplit(img, 25) # Split into 25 vertical parts

    cv2.imwrite('8Split_image.png', rows[0]) # Save first row for debugging

    boxes = []
    for i, r in enumerate(rows):
    cols = np.hsplit(r, 5) # Now width is divisible by 5
    for j, box in enumerate(cols):
    boxes.append(box)
    # cv2.imwrite(f'Split_image_{i}_{j}.png', box) # Save each box for debugging

    return boxes

    # Load image
    # img = cv2.imread("image.jpg") # Replace with your image path
    # splitBoxes(img)


    # Example usage
    rectContour(contours[0])

    # save results
    cv2.imwrite('1omr_sheet_thresh2.png',thresh)
    cv2.imwrite('2omr_sheet_canny2.png',imgCanny)
    cv2.imwrite('3contours2.png',contoursImage)
    cv2.imwrite('4biggest_contour2.png',firstOMRBoxImage)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

    This is where I'm currently stuck on


    I have an idea of converting them into grid and somehow defining each of the block into binary. 0 -> white, 1-> black like this. but I don't know how to do that as I'm currently learning about all these stuffs.

    [After contour image](https://i.sstatic.net/v82NAdjo.png)

    I've tried to solve it by identifying contours but I didn't succeed as after identifying the contours i don't know how to extract the one's which are rounded and also have fully black color inside the bubble. if i somehow manage to do this I think I can do the rest of the part.

    Continue reading...

Compartilhe esta Página