最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

opencv - how to extract omr data in python? - Stack Overflow

programmeradmin2浏览0评论

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:

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.

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:

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.

Share Improve this question edited Mar 14 at 14:38 Christoph Rackwitz 15.9k5 gold badges39 silver badges51 bronze badges asked Mar 13 at 22:54 Dipan NamaDipan Nama 11 silver badge2 bronze badges 6
  • We cannot debug your code without some images? Put imshow commands in your code the make sure your image at each step is as you would expect it. Stop when and image is not looking correct and figure out what went wrong. You may be making assumptions that incorrect. – fmw42 Commented Mar 13 at 23:12
  • Welcome to Stack Overflow. Please take the tour (stackoverflow/tour) and read the information guides in the help center (stackoverflow/help), – fmw42 Commented Mar 13 at 23:13
  • 1 See stackoverflow/questions/72418797/… and stackoverflow/questions/72465878/…, stackoverflow/questions/77619667/… and stackoverflow/questions/77032956/… – fmw42 Commented Mar 13 at 23:21
  • Learn to search this forum and Google to look for similar examples. – fmw42 Commented Mar 13 at 23:22
  • the answer below "does something", but it does nothing useful in service of reading the answers on that form. – Christoph Rackwitz Commented Mar 14 at 14:37
 |  Show 1 more comment

1 Answer 1

Reset to default 0

To get the mark locaitons:

  • First preprocess the image for contour detection
  • Get the contours and their center locaitons as (x,y)
  • Using the coordinates decide the selection as A,B,C or D

I can explain how tho filter the image properly and how to get the contours. You first need to apply opening operation which enables you to get rid of small noises while preserving the image.

This is your original image:

This is the opened image:

Finally this is the detected contours(green) and their centers(blue):

This is the complete code, using theese coordinates i believe you can decide the selected option for each marker.

import cv2
import numpy

#Read the image and convert it to black-white the binary image 
test_image = cv2.imread('test_result.png')
h,w,c = test_image.shape
gray_image = cv2.cvtColor(test_image,cv2.COLOR_BGR2GRAY)
lvl, thresholded_image = cv2.threshold(gray_image,150,255,cv2.THRESH_BINARY)

#Make opening on the image to remove the samaller white blobs
opened = cv2.morphologyEx(thresholded_image,cv2.MORPH_OPEN,cv2.getStructuringElement(cv2.MORPH_RECT,(11,11)))

#Make the contour detection on the image and get their coordinates
contours,hierarchy = cv2.findContours(opened,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

for cnt in contours:
    cv2.drawContours(test_image,[cnt],-1,(0,255,0),2)
    # compute the center of the contour
    M = cv2.moments(cnt)
    cx = int(M["m10"] / M["m00"])
    cy = int(M["m01"] / M["m00"])
    
    cv2.circle(test_image,(cx,cy),7,(255,0,0),-1)



#Show the results
cv2.imshow('Thresh',cv2.resize(thresholded_image,(int(w*720/h),720)))
cv2.imshow('Original',cv2.resize(test_image,(int(w*720/h),720)))
cv2.imshow('Opened',cv2.resize(opened,(int(w*720/h),720)))
cv2.waitKey()`
发布评论

评论列表(0)

  1. 暂无评论