[Python] Python helpppppppppppppppppppppppppppppppppppppp [closed]

Discussão em 'Python' iniciado por Stack, Abril 7, 2025.

  1. Stack

    Stack Membro Participativo

    I am making an automatic exam marking program using Python and OpenCV. The input image is a scanned image of the multiple choice answer sheet (OMR). The goal is to align the image based on the 4 black squares in the 4 corners and then identify the registration number, code, and answers.

    ⚠️ Problem encountered:

    When running the find_corners_and_warp function, the program reports the error "❌ Could not find 4 squares in the 4 corners." and alignment cannot be performed. I tried checking the image (link below), and it seems that the detection of contours is not enough 4 squares.

    Photo link: https://i.sstatic.net/OlYPwjc1.jpg

    Question:

    How to be sure to find the correct 4 squares in the 4 corners of the scanned image? Is there a better way?

    import cv2
    import numpy as np

    # PHẦN I: Đáp án đúng mẫu (A=0, B=1, C=2, D=3)
    answer_key_part1 = [
    1, 0, 2, 3, 1, 0, 2, 2, 3, 1,
    1, 2, 3, 1, 0, 2, 1, 3, 2, 0,
    0, 1, 3, 2, 1, 0, 3, 2, 2, 0,
    1, 1, 3, 1, 2, 0, 3, 1, 2, 0
    ]

    answer_key_part2 = ["Đúng", "Sai", "Đúng", "Đúng", "Sai", "Sai", "Đúng", "Sai"]
    answer_key_part3 = ['1,23', '3,14', '0,50', '2,35', '5,00', '0,75']

    def find_corners_and_warp(image_path, output_size=(800, 1120)):
    image = cv2.imread(image_path)
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    blurred = cv2.GaussianBlur(gray, (5, 5), 1)
    edged = cv2.Canny(blurred, 75, 200)

    # Tìm tất cả contour
    contours, _ = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    # Lọc contour là hình vuông nhỏ (ô vuông góc)
    square_centers = []
    for c in contours:
    peri = cv2.arcLength(c, True)
    approx = cv2.approxPolyDP(c, 0.02 * peri, True)
    area = cv2.contourArea(c)
    if len(approx) == 4 and 300 < area < 3000: # vừa đủ với ô vuông đen
    x, y, w, h = cv2.boundingRect(c)
    cx, cy = x + w // 2, y + h // 2
    square_centers.append((cx, cy))

    if len(square_centers) < 4:
    return None, "❌ Không tìm đủ 4 ô vuông ở 4 góc."

    # Tìm 4 góc theo vị trí
    h_img, w_img = gray.shape
    def closest(ref, candidates):
    return min(candidates, key=lambda pt: (pt[0]-ref[0])**2 + (pt[1]-ref[1])**2)

    tl = closest((0, 0), square_centers)
    tr = closest((w_img, 0), square_centers)
    br = closest((w_img, h_img), square_centers)
    bl = closest((0, h_img), square_centers)

    corners = np.array([tl, tr, br, bl], dtype="float32")
    dst = np.array([
    [0, 0],
    [output_size[0] - 1, 0],
    [output_size[0] - 1, output_size[1] - 1],
    [0, output_size[1] - 1]
    ], dtype="float32")

    M = cv2.getPerspectiveTransform(corners, dst)
    warped = cv2.warpPerspective(image, M, output_size)
    return warped, "✅ Đã căn chỉnh chính xác theo ô vuông góc."


    def extract_digits(thresh, roi, num_digits=6, num_options=10):
    x, y, w, h = roi
    region = thresh[y:y+h, x:x+w]
    digit_width = w // num_digits
    extracted_number = ""
    for i in range(num_digits):
    digit_img = region[:, i * digit_width:(i + 1) * digit_width]
    cell_height = h // num_options
    detected_digit = None
    for j in range(num_options):
    cell = digit_img[j * cell_height:(j + 1) * cell_height, :]
    if np.count_nonzero(cell == 0) > 200:
    detected_digit = str(j)
    break
    extracted_number += detected_digit if detected_digit else "X"
    return extracted_number

    def extract_sbd(thresh):
    # Cột bên trái (7) trong ảnh: tọa độ x ~ 100, width ~ 160
    # Dòng ~ 90–170
    roi_sbd = (100, 90, 160, 80)
    return extract_digits(thresh, roi_sbd, num_digits=6)

    def extract_ma_de(thresh):
    # Cột bên phải (8): tọa độ x ~ 600, width ~ 80
    roi_ma_de = (600, 90, 80, 80)
    return extract_digits(thresh, roi_ma_de, num_digits=3)

    def grade_part1_debug(thresh):
    roi = thresh[250:980, 100:810]
    rows, cols = 40, 4
    h, w = roi.shape
    box_h, box_w = h // rows, w // cols
    selected = []
    print(" Bắt đầu chấm PHẦN I (40 câu trắc nghiệm):\n")
    for r in range(rows):
    marks = []
    for c in range(cols):
    x1, y1 = c * box_w, r * box_h
    x2, y2 = (c + 1) * box_w, (r + 1) * box_h
    box = roi[y1:y2, x1:x2]
    count = cv2.countNonZero(box)
    marks.append(count)
    max_val = max(marks)
    max_idx = marks.index(max_val)
    avg_others = (sum(marks) - max_val) / (len(marks) - 1)
    print(f"Câu {r + 1:02}: A={marks[0]:3} | B={marks[1]:3} | C={marks[2]:3} | D={marks[3]:3} -> ", end="")
    if max_val < 80 or max_val < avg_others * 1.25:
    selected.append(-1)
    print("Không rõ hoặc tô không đủ đậm")
    else:
    selected.append(max_idx)
    print(f"Chọn: {'ABCD'[max_idx]}")
    score = sum([1 for a, b in zip(selected, answer_key_part1) if a != -1 and a == b])
    percent = int(score * 100 / len(answer_key_part1))
    print(f"\n✅ PHẦN I Score: {percent}%")
    print(f" Đáp án đã chọn: {selected}")
    print(f"ℹ️ Số câu không tô rõ: {selected.count(-1)}")
    return percent, selected

    def grade_part2(thresh):
    roi = thresh[1070:1300, 110:610]
    rows, cols = 8, 2
    h, w = roi.shape
    box_h, box_w = h // rows, w // cols
    result = []
    for r in range(rows):
    d_box = roi[r*box_h:(r+1)*box_h, 0:box_w]
    s_box = roi[r*box_h:(r+1)*box_h, box_w:2*box_w]
    d_val = cv2.countNonZero(d_box)
    s_val = cv2.countNonZero(s_box)
    result.append("Đúng" if d_val > s_val else "Sai")
    score = sum([1 for a, b in zip(result, answer_key_part2) if a == b])
    percent = int(score * 100 / len(answer_key_part2))
    print(f"✅ PHẦN II Score: {percent}%")
    print(f" Kết quả: {result}")
    return percent, result

    def grade_part3(thresh):
    roi3 = thresh[1330:1750, 100:730]
    rows = 11
    digits = 4
    cols = 6
    h, w = roi3.shape
    box_h = h // rows
    box_w = w // (cols * digits)
    answers = []
    for c in range(cols):
    number = ""
    for d in range(digits):
    max_val = 0
    selected_digit = ''
    for r in range(rows):
    x = (c * digits + d) * box_w
    y = r * box_h
    box = roi3[y:y+box_h, x:x+box_w]
    val = cv2.countNonZero(box)
    if val > max_val:
    max_val = val
    selected_digit = ',' if r == 0 else str(r - 1)
    number += selected_digit
    answers.append(number)
    score = sum([1 for a, b in zip(answers, answer_key_part3) if a == b])
    percent = int(score * 100 / len(answer_key_part3))
    print(f"✅ PHẦN III Score: {percent}%")
    print(" Số đã tô:", answers)
    return percent, answers

    def grade_omr(image_path):
    warped, msg = find_corners_and_warp(image_path)
    if warped is None:
    print("❌", msg)
    return
    gray = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
    thresh = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY_INV)[1]

    sbd = extract_sbd(thresh)
    made = extract_ma_de(thresh)
    print(f" Số báo danh: {sbd}")
    print(f" Mã đề: {made}")

    print(" Đang chấm bài sau khi đã căn chỉnh...\n")
    p1_score, _ = grade_part1_debug(thresh)
    p2_score, _ = grade_part2(thresh)
    p3_score, _ = grade_part3(thresh)

    print("\n TỔNG KẾT ĐIỂM:")
    print(f" PHẦN I: {p1_score}%")
    print(f"✅ PHẦN II: {p2_score}%")
    print(f" PHẦN III: {p3_score}%")
    print(f"\n **Tổng điểm trung bình**: {(p1_score + p2_score + p3_score) // 3}%")

    if __name__ == "__main__":
    image_path = "StudentInfo/scanbanchuan.jpg"
    grade_omr(image_path)

    Continue reading...

Compartilhe esta Página