基于Python+OpenCV的OMR答题卡检测:分割图像包围矩形生成问题
Hey there! I’ve built a few OMR grading tools with OpenCV before, so let’s walk through how to get clean, accurate bounding boxes for your split answer sheet images. The key is nailing the preprocessing and contour filtering—here’s a step-by-step solution tailored to your 4-option per question setup:
1. Preprocess the Image for Better Contour Detection
First, we need to clean up the image to make the option boxes stand out clearly. Gaussian blur reduces noise, and adaptive thresholding handles uneven lighting (super common with scanned/photographed answer sheets):
import cv2 import numpy as np # Load your split answer sheet image img = cv2.imread("split_answer_sheet.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Denoise + Adaptive Binarization blur = cv2.GaussianBlur(gray, (5, 5), 0) thresh = cv2.adaptiveThreshold( blur, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2 )
2. Filter Contours to Target Only Option Boxes
OpenCV will find all sorts of contours—we need to filter out anything that isn’t an option box. Adjust the area and aspect ratio thresholds to match your actual answer sheet’s dimensions:
# Find external contours (only the outer edges of boxes) contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) filtered_contours = [] for cnt in contours: area = cv2.contourArea(cnt) x, y, w, h = cv2.boundingRect(cnt) aspect_ratio = w / float(h) # Tweak these values to fit your option boxes (e.g., 20x20 boxes might have area ~300-600) if 300 < area < 600 and 0.8 < aspect_ratio < 1.2: filtered_contours.append(cnt)
3. Sort Contours to Match Reading Order
By default, contours are detected in random order. We’ll sort them top-to-bottom, left-to-right so the bounding boxes align with the question sequence:
def sort_contours(cnts): # Get bounding boxes for each contour bounding_boxes = [cv2.boundingRect(c) for c in cnts] # Sort by y-coordinate (row), then x-coordinate (column) sorted_pairs = sorted(zip(cnts, bounding_boxes), key=lambda b: (b[1][1], b[1][0])) return [c for c, _ in sorted_pairs] sorted_contours = sort_contours(filtered_contours)
4. Draw Bounding Boxes on the Image
Finally, draw the green rectangles around each option box and save/show the result:
# Draw bounding boxes on the original image for cnt in sorted_contours: x, y, w, h = cv2.boundingRect(cnt) cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) # Save or display the output cv2.imwrite("omr_bounding_boxes.jpg", img) cv2.imshow("OMR Bounding Boxes", img) cv2.waitKey(0) cv2.destroyAllWindows()
Quick Tips to Refine Results
- Adjust preprocessing parameters: If boxes are still fuzzy, try changing the Gaussian blur kernel size (e.g., (3,3) or (7,7)) or adaptive thresholding values.
- Handle image skew: If your answer sheet is tilted, add a perspective correction step first using
cv2.getPerspectiveTransform. - Batch process: For all 3 split images, wrap this code in a loop to process them automatically.
内容的提问来源于stack exchange,提问作者Akhilesh Chobey

