Findcontours opencv python là gì

Optical Mark Recognition là gì [OMR]?

Optical Mark Recognition, hoặc OMR đơn giản là quá trình tự dộng phân tích tài liệu được con người tạo ra và làm sáng tỏ kết quả.

Tiết hành làm chương trình scan biểu test bubble và and phân loại xử dụng OMR, Python, và OpenCV


Dưới đây là một ví dụ của một biểu test mà chúng ta sẽ sử dụng để quét và phân loại trong bài viết này:

Ảnh 1: Ví dụ về biểu test trắc nghiệm

Chúng ta sẽ sử dụng form này như ví dụ quét form kiểm tra trắc nghiệm.

Các bước trong bài

  • Step #1:Phát hiện biểu kiểm tra trong một ảnh.
  • Step #2:Áp dụng perspective transform để trích ra trên-dưới, birds-eye-view của biểu kiểm tra.
  • Step #3:Extract the set of bubbles [i.e., the possible answer choices] from the perspective transformed exam.
  • Step #4:Sắp xếp các câu hỏi/ô tròn vào hàng.
  • Step #5:Xác định các ô tròn đã được đánh dấu [trả lời] vào
  • Step #6: Tìm câu trả lời đúng trong đáp án để xác định kết quả của người được kiểm tra có chọn đúng đáp án hay không.
  • Step #7: Lặp lại các câu hỏi trong bài kiểm tra trắc nghiệm.
Bài này chúng ta sẽ dùng OpenCV và Python để giải quyết vấn đề.
Dầu tiên mở một file mới và đặt tên làtest_grader.py:
Thiết lập ban đầu
Python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# import các thư viện cần thiết
from imutils.perspective import four_point_transform
from imutils import contours
import numpy as np
import argparse
import imutils
import cv2
# thiết lập tham số
ap = argparse.ArgumentParser[]
ap.add_argument["-i", "--image", required=True,
help="path to the input image"]
args = vars[ap.parse_args[]]
# thiết lập từ khóa cho câu trả lời
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
Bạn phải đảm bảo rằng máy tính của bạn đã được cài OpenCVvà Numpy cùng vớiimutils.
để càiimutils [hoặc update version mới nhất], các bạn cần chạy lệnh dưới đây.
Cài đặt thư viện imutils
Shell
1
$ pip install --upgrade imutils
Dòng 10-12các biến--image,--i, chính là tham số để đưa ảnh bài kiểm tra trắc nghiệm.
Dòng 17định nghĩa từ khóa cho câu trả lờiANSWER_KEY. Các bạn có thể tùy chỉnh key này tùy thuộc vào form trả lời chuẩn của bạn.
Trong trường hợp này Key 0là key cho câu hỏi đầu tiên,và câu trả lời đúng là B [tương đương với index =1]. Và cứ theo quy luật như vậy cho các trường hợp còn lại.
Cụ thể trong bài này, form trả lời chính xác để đối chiếu là:
  • Question #1:B
  • Question #2:E
  • Question #3:A
  • Question #4:D
  • Question #5:B
Tiếp theo chúng ta sẽ xử lý ảnh đầu vào :
Xử lý ảnh đầu vào
Python
19
20
21
22
23
24
# load ảnh, chuyển sang định dạng gray và dùng phép mờ ảnh blur
# làm mỏng và tìm cạnh
image = cv2.imread[args["image"]]
gray = cv2.cvtColor[image, cv2.COLOR_BGR2GRAY]
blurred = cv2.GaussianBlur[gray, [5, 5], 0]
edged = cv2.Canny[blurred, 75, 200]
DòngLine 21tải ảnh vào
DòngLine 22 chuyển đổi sang g
Dòng Line 23 làm mờ ảnh
DòngLine 24dùng hàm Canny để tìm cạnh của đối tượng trong ảnh.
Sau khi thực hiện các lệnh trên sẽ cho ra kết quả:

Figure 2:Xử lý ảnh ban đầu, tìm cạnh
Lưu ý rằng cách các cạnh của tài liệu cần được xác định rõ ràng, với cả bốn đỉnh của ảnh _ bài trắc nghiệm scan_ Việc này rấtquan trọng trong bước tiếp theo của chúng ta, vì chúng ta sẽ sử dụng nó như một điểm đánh dấu để kéo giãn và xóa hiệu ứng mắt chim:

Python
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# tìm contours trong edge map, sau đó khởi tạo

cnts = cv2.findContours[edged.copy[], cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE]
cnts = cnts[0] if imutils.is_cv2[] else cnts[1]
docCnt = None
# phải chắc rằng có nhiều hơn 1 contour được tìm thấy
if len[cnts] > 0:
# săp xếp các contour tìm được
# theo thứ tự lớn tới bé
cnts = sorted[cnts, key=cv2.contourArea, reverse=True]
# loop over the sorted contours
for c in cnts:
# approximate contour
peri = cv2.arcLength[c, True]
approx = cv2.approxPolyDP[c, 0.02 * peri, True]
# nếu approximated contour lớn hơn 4 điểm
# thì nó chính là 4 góc của bài trắc nghiệm
if len[approx] == 4:
docCnt = approx
break
Bây giờ chúng ta đẵ có đường viền bên ngoài của ảnh bài trắc nghiệm, chúng ta áp dụngcv2.findContours để tìm find the lines that correspond to the exam itself.
Chúng ta sẽ tiến hành xắp xếp theo độ lớn của contour từ lớn tới bé ở Dòng36là hàm để xắp xếp. Sau khi thực hiện sort chúng ta sẽ có được contour lớn sẽ nằm ở đầu list và bé nằm ở cuối cùng.
Tiếp tới ở dòngLine 40/41. ở mỗi contour chúng ta sẽ tìm các góc của contours sau khi approximated.
ảnh dưới đây là kết qua sau khi tìm ra vùng của bài trắc nghiệm và cạnh của nódocCnt được vẽ như đường màu đỏ.

Figure 3:Tìm khung bài trắc nghiệm
Bây giờ chúng ta tiến hành sử dụng perspective transform để kéo giãn khung bài trắc nghiệm.
Python and OpenCV
Python
51
52
53
54
55
# apply a four point perspective transform to both the
# original image and grayscale image to obtain a top-down
# birds eye view of the paper
paper = four_point_transform[image, docCnt.reshape[4, 2]]
warped = four_point_transform[gray, docCnt.reshape[4, 2]]
Trong trường hợp này chúng ta sẽ sử dụng hàmfour_point_transform với chức năng là:
  1. Xác định tọa độ[x, y]- contours với khả năngspecific, reproducible manner.
  2. áp dụng perspective transform cho các vùng.

Figure 4: ảnh sau khi đã được perspective transform
Vậy là chúng ta đã tìm và kéo khung bài trắc nghiệm thành công, để đảm bảo ảnh không bị méo giống như ta dùng máy scan.
Bước tiếp theo là chúng ta tiến hành nhị phân hóa ảnh:
Bubble sheet scanner and test grader using OMR, Python and OpenCV
Python
57
58
59
60
# sử dụng phương pháp Otsu's thresholding
# piece of paper
thresh = cv2.threshold[warped, 0, 255,
cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU][1]
ảnh sau khi nhị phân hóa

Figure 5:ostus nhị phân hóa
ảnh được nhị phân này sẽ giúp chúng ta sử dụng phép tìm contour để tìm các khung tròn đáp án trên bài trắc nghiệm.
Python
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# find contours in the thresholded image, then initialize
# the list of contours that correspond to questions
cnts = cv2.findContours[thresh.copy[], cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE]
cnts = cnts[0] if imutils.is_cv2[] else cnts[1]
questionCnts = []
# loop over the contours
for c in cnts:
# compute the bounding box of the contour, then use the
# bounding box to derive the aspect ratio
[x, y, w, h] = cv2.boundingRect[c]
ar = w / float[h]
# in order to label the contour as a question, region
# should be sufficiently wide, sufficiently tall, and
# have an aspect ratio approximately equal to 1
if w >= 20 and h >= 20 and ar >= 0.9 and ar 20 pixels .
  • Cần có tỉ lệ "aspect ratio"" xấp xỉ =1.
  • Và dự vào điều kiện chúng ta sẽ tìm được các vùng tròn/câu trả lờiquestionCnts
    ảnh dưới đây là kết quả tìm vùng tròn/câu trả lời trên bài trắc nghiệmquestionCnts

    Figure 6:Vùng trả lời được nhận dạng và khoanh đỏ
    Bây giờ bước tiếp theo là nhận dạng các câu trả lời của người dùng trên bài trắc nghiệm:
    Python and OpenCV
    Python
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    # sắp xếp các contours câu hỏi từ trên xuống dưới sau đó khởi tạo
    # tổng số các câu trả lời đúng
    questionCnts = contours.sort_contours[questionCnts,
    method="top-to-bottom"][0]
    correct = 0
    # mỗi câu hỏi có thể có 5 câu trả lời
    # cần loop 5 lần
    for [q, i] in enumerate[np.arange[0, len[questionCnts], 5]]:
    # xắp xếp các câu trả lời từ trái sang phải
    cnts = contours.sort_contours[questionCnts[i:i + 5]][0]
    bubbled = None

    đầu tiên xắp xếpquestionCnts từ trên xuống dưới, điều này sẽ đảm bảo rằng dòng trả lời đầu tiên nằm ngay phía trên cùng cứ như vậy cho tới dòng trả lời cuối cùng.
    Chúng ta cũng khởi tạo biến "bookkeeper" để luôn track số lượng câu trả lời đúngcorrect.
    ở Dòng89chúng tiến hành loop các câu hỏi. Như form chuẩn mỗi câu hỏi sẽ có thể có 5 đáp án. Chúng ta sẽ sử dụng NumPy array slicing và contour sorting để sắp xếpcontourstừ trái qua phải.
    Lý do phương pháp này hoạt động là vì chúng ta đã sắp xếp cáccontours dòng trả lờitừ trên xuống dưới. Chúng ta biết rằng 5 vòng tròn cho mỗi câu hỏi sẽ xuất hiện tuần tự trong danh sách của chúng ta - nhưng chúng ta không biết liệu các vòng tròn này liệu có được sẽ được sắp xếp từ trái sang phải. Việc sắp xếpcontourstrên Dòng 93 sẽ giải quyết vấn đề này và đảm bảo mỗi hàng củacontoursđược sắp xếp thành hàng, từ trái sang phải.
    Để hình dung khái niệm này, ảnh chụp màn hình dưới đây mô tả mỗi hàng câu hỏi như một màu riêng biệt:

    Figure 7:Bằng cách phân loại các contours của chúng ta từ trên xuống dưới, tiếp theo là từ trái sang phải, chúng ta có thể trích xuất từng hàng khoanh tròn.
    Với mỗi hàng câu trả lời chúng ta tiến hành tìm kiếm các câu được trả lời bên trong ảnh.
    Chúng ta có thể thực hiện điều này bằng cách sử dụng ảnhthresh và đếm số lượng điểm ảnh có giá trị = 0 trên mỗi vùng khoanh tròn.
    Python and OpenCV
    Python
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    # loop over the sorted contours
    for [j, c] in enumerate[cnts]:
    # construct a mask that reveals only the current
    # "bubble" for the question
    mask = np.zeros[thresh.shape, dtype="uint8"]
    cv2.drawContours[mask, [c], -1, 255, -1]
    # apply the mask to the thresholded image, then
    # count the number of non-zero pixels in the
    # bubble area
    mask = cv2.bitwise_and[thresh, thresh, mask=mask]
    total = cv2.countNonZero[mask]
    # if the current total has a larger number of total
    # non-zero pixels, then we are examining the currently
    # bubbled-in answer
    if bubbled is None or total > bubbled[0]:
    bubbled = [total, j]
    Dòng 98thực hiện phép lặp trên mỗi khoanh tròn trên mỗi dòng.
    Chúng khởi tạo mask trên vùng khoanh tròn hiện tại sau đó là tính số lương pixels = 0 trong vùng masked [Dòng 107 và 108]. Với vùng khoanh tròn có nhiều pixcel giá trị =0 thì chính là câu trả lời trắc nghiệm của người dùng.

    Figure 8:Ví dụ sử dụng mark cho mỗi vùng khoanh tròn
    Rất rõ ràng, vùng khoanh tròn với ký hiệu "B" có nhiều pixcel giá trị =0, chính vì vậy nó là câu trả lời của người dùng điền vào.

    Code tiếp theo là chúng ta kiểm tra xem câu trả lời có đúng với đáp án khôngANSWER_KEY:
    Python and OpenCV
    Python
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    # initialize the contour color and the index of the
    # *correct* answer
    color = [0, 0, 255]
    k = ANSWER_KEY[q]
    # check to see if the bubbled answer is correct
    if k == bubbled[1]:
    color = [0, 255, 0]
    correct += 1
    # draw the outline of the correct answer on the test
    cv2.drawContours[paper, [cnts[k]], -1, color, 3]
    Tiếp đến là chúng ta sẽ thực hiện việc highlight tô màu xanh đối với các câu trả lời đúng và tô màu đỏ đối với đáp án đúng mà người dùng đã trả lời sai.

    Figure 9: "xanh lục" để đánh dấu "chính xác" hoặc "đỏ" để đánh dấu "không chính xác".
    Cuối cùng ở phần cuối của code chúng ta tiến hành tính điểm cho cả bài trắc nghiệm và hiển thị trên màn hình.
    Python and OpenCV
    Python
    129
    130
    131
    132
    133
    134
    135
    136
    # grab the test taker
    score = [correct / 5.0] * 100
    print["[INFO] score: {:.2f}%".format[score]]
    cv2.putText[paper, "{:.2f}%".format[score], [10, 30],
    cv2.FONT_HERSHEY_SIMPLEX, 0.9, [0, 0, 255], 2]
    cv2.imshow["Original", image]
    cv2.imshow["Exam", paper]
    cv2.waitKey[0]
    Kết quả như ảnh dưới:

    Figure 10:Finishing our OMR system for grading human-taken exams.
    để chạy chương trình chúng ta sẽ đưa ảnh vào như lệnh dưới đây:
    Python and OpenCV
    Shell
    1
    $ python test_grader.py --image images/test_02.png
    Kết thúc chương trình, ta gặp một số vấn đề như sau
    1. Điều gì xảy ra nếu người dùng không trả lời câu nào cả
    2. Điều gì xảy ra nếu người dùng điền nhiều hơn một câu trả lời.
    Để xử lý hai vấn đề này chúng ta cần thêm một số điều kiện trong code của chúng ta như sau.
    #Vấn đề 1:
    - Đầu tiên ta cần đặt giá trị nhỏ nhất có thể khi tiến hành threshold.
    - Khi kiểm tra điều kiện nếu không có vung tròn nào có tổng số pixcel có giá trị 0 đặt mức như yêu cầu chúng ta có thể đánh dấu câu hỏi được bỏ qua từ người dùng.

    Figure 11:
    #Vấn đề 2:
    - Kiểm tra nếu trên mỗi dòng trả lời có tới hai hoặc nhiều hơn hai vùng tròn được đánh dấu thì chúng ta đánh dấu câu hỏi trả lời sai từ người dùng.


    Figure 12:.
    Mã nguồn:Tải về ở đây
    Video:

    Nguồn: từ pyimagesearch.

    Video liên quan

    Chủ Đề