Findcontours opencv python là gì
Ngày đăng:
30/10/2021
Trả lời:
0
Lượt xem:
173
Show 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à OpenCVDướ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
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à:
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à:
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 <= 1.1: questionCnts.append(c) Dòng 64-67là để tìm ontours trên ảnh nhị phânthresh , và chúng ta khởi tạoquestionCnts,nó chính là danh sách contours tương ứng với các câu hỏi,trả lời/khoanh tròn trên bài trắc nghiệm. để xác định vùng nào của ảnh là phần trả lời/câu hỏi/khoanh tròn chúng ta cần phép lặp trên mỗi contours (Dòng 70). Với mỗi contours, chúng ta tính bounding box (Dòng72), ở đây nó cũng cho phép chúng ta tính được tỉ lệ "aspect ratio" - mình không biết tiếng việt nên dịch là gì-, hoặc đơn giản hơn là tỉnh lệ của chiều rộng với chiều cao (Dòng 73). Và chúng ta cần set điều kiện để kiểm tra xem contours đó có phải là vung tròn / câu trả lời của bài trắc nghiệm hay không:
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: |