[PYTHON] A story that supports electronic scoring of exams with image recognition

Welcome to the 23rd day of Yuruyuru New Graduate Advent Calendar 2019. I'm @NamedPython, a freshman freshman with a loose margin: joy:

Today is about my hidden feat ** Image recognition with OpenCV **.

I had decided to write about image recognition in advance, but it was hard to find something new, so I tried my best to write it in the school class, but I didn't give a memorial service anywhere ~~ ** Answer sheet I will leave a record of area extraction ** here: pencil2:

What were you trying to make

I said it was a class, but the class name is ** Programming Application **, and let's work on problem solving by software development as a team, involving what we usually learn in class and the skills of students. is.

Then, what is the theme of problem solving?

** Ease the electronic scoring of the exams your teacher is doing **: pray:

It was that. In the first place, at the time of electronic scoring, it is quite now and young, but if you summarize the requirements

--Scanning student exam answers and importing them to ** iPad ** (** Now ) ――The answer sheet does not have an answer column, so you can write it freely. ――Because of the above, the position of the answer is ** different depending on the student ** ――So, the answer area of the captured image is surrounded by ** color-coded ** with Apple Pencil ( Now **) ――I want to score the same question (same color coding) at once --You guys, ** please do something **: pray:

It was like that.

team

Actually, one year before this class,

--Backend API, web front @NamedPython --iOS @Taillook --Android @ youta1119 --Python and RasPi control, natural language processing @Kyoskk

We formed the strongest team and received great criticism. As a result, the number of team members was limited: frowning2:

Even so, I pulled @Taillook and another close classmate to form a team of three people.

solution

With the team team mentioned above, I have been pulling ** the guy who can write iOS **.

Since it was a big deal, I proposed a system with the following configuration that allows you to ride iOS from anything to anything (extracted from the presentation of the final announcement). Thanks for the implementation around iOS, @Taillook.

image.png

I did my best with Keynote for this figure. The fonts are ** M + ** and ** Tsukushi B Maru Gothic **.

Recognition overview

--Since there is already a color-coded operation, it seems good to extract only the saturated part. --Convert from BGR space to HSV space and extract only S (saturation channel)

Target image

At that time, I received an actual answer sheet for operation verification, but I had a feeling that the rights were gray to bring it here, so I used the power of ** Pages craftsman ** to generate a sample.

image.png

Implementation

Well, it's almost boring, so Dawn and the source code. I prepared Dockerfile which is FROM jjanzic / docker-python3-opencv so that I can develop it anywhere. In the end, the image recognition part was overkill because I developed it fully.

The contents of Docker are as follows.

source.py


import os
import cv2
import numpy as np

DIR = os.path.dirname(os.path.abspath(__file__))

image = cv2.imread(f'{DIR}/image/sample_answer_sheet.png')
if image is None:
    print("File not found.")
    exit()


def write_out(img, name):
    cv2.imwrite(f'{DIR}/image/result/{name}.png', img)


def extract_inside(contours, hierarchy, debug=False):
    extracted = []
    contours_drawed = image.copy()
    if debug:
        print(f'len: {len(contours)}')
        print(hierarchy)

    for index in range(len(contours)):
        if hierarchy[0, index, 2] != -1 or cv2.contourArea(contours[index]) < 8000:
            continue
        extracted.append(contours[index])

        if debug:
            cv2.drawContours(contours_drawed, contours, index, (255, 0, 255), 10)

        print(f'{index} : {cv2.contourArea(contours[index])}')

    if debug:
        write_out(contours_drawed, 'out_contours')

    return extracted


width, height = image.shape[:2]

extracted = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)[:, :, 1]

write_out(extracted, 'after_cvt')

ret, thrshld_ex = cv2.threshold(extracted, 0, 255, cv2.THRESH_OTSU)

write_out(thrshld_ex, 'thrshld')

kernel = np.ones((3, 1), np.uint8)
thrshld_ex = cv2.morphologyEx(thrshld_ex, cv2.MORPH_OPEN, kernel)

write_out(thrshld_ex, 'thrshld_ex')

_, contours, hierarchy = cv2.findContours(thrshld_ex, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

inside_contours = extract_inside(contours, hierarchy, True)

print(f'extracted {len(inside_contours)} countours')

clr_img_cp = image.copy()
for circle in inside_contours:
    colors = []
    av_color = 0
    M = cv2.moments(circle)
    center_of_circle = (int(M['m10']/M['m00']), int(M['m01']/M['m00']))
    cnt = 0
    for approx in circle:
        if cnt == 20:
            break
        cv2.circle(clr_img_cp, (approx[0, 0] - 1, approx[0, 1] - 1), 1, (255, 0, 255), 1)
        colors.append(image[approx[0, 1] - 1, approx[0, 0] - 1])
        cnt += 1
    cav = np.mean(colors, axis=0)
    cv2.circle(clr_img_cp, center_of_circle, 100, cav, 20)

write_out(clr_img_cp, 'out')

It looks like this when I drop what I wrote in the recognition summary earlier into the source code. After prototyping with Python like this, I ported it to ʻObjective-C` and had @Taillook bridge it with Swift.

result

--Convert to HSV color space and extract only S --Binarization by discriminant analysis --Edge detection --Extract the color of the side

The results of each of the four processes are posted.

Convert to HSV color space and extract only S

extract_s_of_hsv.py


cv2.cvtColor(image, cv2.COLOR_BGR2HSV)[:, :, 1] # 0: H, 1: S, 2: V

image.png

At this point, it's almost binarized. It's too much.

Binarization by discriminant analysis

Just specify cv2.THRESH_OTSU. Easy.

thresold_with_otsu.py


cv2.threshold(extracted, 0, 255, cv2.THRESH_OTSU)

image.png

If there is a little noise, then apply morphologyEX to remove the noise.

Edge detection

OpenCV is already amazing. findContours is amazing. I read the paper for the time being, but it's too metamorphic to recognize even the structural information of the sides.

find_contours_with_structure.py


_, contours, hierarchy = cv2.findContours(thrshld_ex, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)

In hierarchy, which index side in contours is ** next to the index side ** or ** is included ** is stored. It's too much.

image.png

The sides are represented by a set of points and are drawn in purple.

Extract edge color + draw

This is a push. Thanks to numpy for making it so easy to average colors.

sampling_color_and_averaging_and_plotting.py


clr_img_cp = image.copy()
for circle in inside_contours:
    colors = []
    av_color = 0
    M = cv2.moments(circle)
    center_of_circle = (int(M['m10']/M['m00']), int(M['m01']/M['m00']))
    cnt = 0
    for approx in circle:
        if cnt == 20:
            break
        cv2.circle(clr_img_cp, (approx[0, 0] - 1, approx[0, 1] - 1), 1, (255, 0, 255), 1)
        colors.append(image[approx[0, 1] - 1, approx[0, 0] - 1])
        cnt += 1
    cav = np.mean(colors, axis=0)
    cv2.circle(clr_img_cp, center_of_circle, 100, cav, 20)

image.png

The center of the figure is determined, and a circle is drawn there with the average color. (Actually, if you look closely at this image, the target pixels of 20 samples are purple.) It feels good with almost the same color as the sides.

Summary

So, I made a memorial service here for the image processing program I made when I was a student: coffin:

From my point of view, it was a reflection, but I hope you read this and feel the possibility of Python + ʻOpenCV`.

The only caveat is that ʻOpenCV` can be used by learning from many cases without knowing the theory, but I feel that the theory is the first to guide the shortest path of application of means to problem solving. Therefore, ** information engineering is important **. I have only the last-minute theory because I was light and light.

However, the following can be said for sure.

―― ʻOpenCV is amazing --Python + ʻOpenCV is easy to do --Good compatibility with numpy

about it. Actually, before writing this article, I tried ʻOpenCV by Rust, but it was not good compared to the usability of Python + ʻOpenCV. Well, I'm not used to implementing Rust, so it may not be helpful, but: yum:

It's Python, isn't it slow? There seems to be a story, but excluding drawing, it is about ** 0.3 [sec] **. It's just a C-binding. Great. Even at the bicycle shopping site cyma-cyma-, which I am involved in development, I feel more motivated to recognize images, so I would like to show my strength.

@shimura_atsushi wrote 2 articles in Ateam cyma Advent Calendar 2019, so if you are interested, please.

-Challenge the challenges of Cyma using the OCR service of Google Could Platform -Continued to challenge Cyma's challenges using Google Could Platform's OCR service

in conclusion

Yuruyuru New Graduate 1st Grade Advent Calendar 2019, how was your 23rd day?

The 24th day is the turn of the front-end engineer @ cheez921, who also understands the back end. Duel, standby!

Good ** Christmas **, Good ** Year-end **, Good ** New Year **, Good ** Image recognition **.

Recommended Posts

A story that supports electronic scoring of exams with image recognition
The story of making a module that skips mail with python
A story that visualizes the present of Qiita with Qiita API + Elasticsearch + Kibana
The result of making the first thing that works with Python (image recognition)
I tried image recognition of CIFAR-10 with Keras-Learning-
I tried image recognition of CIFAR-10 with Keras-Image recognition-
The story of making a web application that records extensive reading with Django
A story that reduces the effort of operation / maintenance
A story about making 3D space recognition with Python
Image recognition with keras
A story that struggled with the common set HTTP_PROXY = ~
Create a chatbot that supports free input with Word2Vec
A story that analyzed the delivery of Nico Nama.
A story that stumbled when I made a chatbot with Transformer
Technology that supports jupyter: traitlets (story of trying to decipher)
The story of making a question box bot with discord.py
Application of CNN2 image recognition
Image recognition with Keras + OpenCV
An overview of DELG, a new method for extracting image features that attracts attention with Kaggle
A story that I had a hard time displaying graphs on top of each other with matplotlib
The story of creating a bot that displays active members in a specific channel of slack with python
A story stuck with the installation of the machine learning library JAX
[Statistics] Grasp the image of the central limit theorem with a graph
A story that struggled to handle the Python package of PocketSphinx
The story of making a standard driver for db with python.
The story of creating a site that lists the release dates of books
The story of making a tool to load an image with Python ⇒ save it as another name
Image recognition of fruits using VGG16
The story of writing a program
How to call a POST request that supports Japanese (Shift-JIS) with requests
Technology that supports a money-throwing service that can monetize a blog with one tag
The story of a Parking Sensor in 10 minutes with GrovePi + Starter Kit
The story of making a university 100 yen breakfast LINE bot with Python
Build a classifier with a handwriting recognition rate of 99.2% with a TensorFlow convolutional neural network
The story of having a hard time introducing OpenCV with M1 MAC
The story of developing a web application that automatically generates catchphrases [MeCab]
The story of making a sound camera with Touch Designer and ReSpeaker
The story of making a package that speeds up the operation of Juman (Juman ++) & KNP