import cv2
|
import numpy as np
|
from skimage import morphology, img_as_ubyte
|
|
import pkg_resources
|
|
templatefile = "/template-8.png" # always use slash
|
template8 = pkg_resources.resource_filename(__name__, templatefile)
|
|
|
def kernel(x, y):
|
"""
|
Function greates square kernel of size x and y
|
"""
|
return np.ones((x, y), np.uint8)
|
|
|
def find_biggest_blob(image, original_image, sid_mask):
|
if sid_mask[0] == "1":
|
move_left = 45
|
elif sid_mask[0] == "x":
|
move_left = 50
|
else:
|
move_left = 0
|
# Remove noise
|
image2 = cv2.morphologyEx(
|
original_image, cv2.MORPH_OPEN, kernel(2, 2), iterations=3
|
)
|
# find biggest block of pixels
|
image1 = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(5, 25), iterations=3)
|
image1 = img_as_ubyte(image1 > 50)
|
cv2.imwrite("/tmp/sidblock1.png", image1)
|
im2, ctrs, hier = cv2.findContours(
|
image1.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
|
)
|
sorted_ctrs = sorted(
|
ctrs, key=lambda ctr: cv2.contourArea(ctr)
|
) # get bigges contour
|
x, y, w, h = cv2.boundingRect(sorted_ctrs[-1])
|
image = image[y : y + h, x + 25 - move_left : x + w - 30] # +25,-25
|
return image
|
|
|
def sid_compare(sid_no, sid_mask):
|
"""
|
Function compares student id number with student id mask if the recognised number is valid according to the mask
|
:param sid_no:
|
:param sid_mask:
|
:return: True if they match, else False
|
"""
|
for s, es in zip(sid_mask, sid_no):
|
if s != "x" and s != es:
|
return False
|
return True
|
|
|
def segment_by_contours(image, original_image, classifier, sid_mask):
|
"""
|
First algorithm. it segments numerals with contours. It works with numbers where individual numerals does not touch.
|
:param image:
|
:param original_image:
|
:param classifier:
|
:return: student id as a string
|
"""
|
|
sid_no = ""
|
image = find_biggest_blob(image, original_image, sid_mask)
|
cv2.imwrite("/tmp/sid_contour1.png", image)
|
im2, ctrs, hier = cv2.findContours(
|
image.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
|
)
|
sorted_ctrs = sorted(ctrs, key=lambda ctr: cv2.boundingRect(ctr)[0])
|
|
for i, ctr in enumerate(sorted_ctrs):
|
# Get bounding box
|
x, y, w, h = cv2.boundingRect(ctr)
|
# Getting ROI
|
if w < h / 3:
|
sid_no = sid_no + "1"
|
continue
|
roi = image[y : y + h, x : x + w]
|
roi = img_as_ubyte(roi < 128)
|
roi = cv2.resize(roi, (32, 32))
|
|
# cv2.rectangle(image,(x,y),( x + w, y + h ),(0,255,0),2)
|
cv2.imwrite("/tmp/sid_no_{}.png".format(i), roi)
|
sid_currno= str(classifier.predict(roi.reshape(1, -1) / 255.0)[0])
|
sid_no = sid_no + sid_currno
|
fname="/tmp/SID/"+str(sid_currno)+"/sid"+str(np.random.randint(0,1000000))+".png"
|
print("Writing all the sid images ", fname)
|
cv2.imwrite(fname, roi)
|
return sid_no
|
|
|
def segment_by_sid_len(image, original_image, sid_mask, classifier):
|
"""
|
Third algorithm. It trys to get biggest "blob" in the image and then it cuts it into individual numbers by force.
|
It has some problems with finding individual numbers, so some tweaking must be done!
|
|
:param image:
|
:param original_image:
|
:param sid_mask:
|
:param classifier:
|
:return: student id as a string
|
"""
|
sid_no = ""
|
sid_len = len(sid_mask)
|
image = find_biggest_blob(image, original_image, sid_mask)
|
cv2.imwrite("/tmp/sidblock2.png", image)
|
imgHeight, imgWidth = image.shape[0:2]
|
numWidth = int(imgWidth / (sid_len))
|
for i in range(0, sid_len):
|
num = image[:, i * numWidth : (i + 1) * numWidth]
|
num = img_as_ubyte(num < 128)
|
num = cv2.resize(num, (32, 32))
|
cv2.imwrite("/tmp/sid_no_{}.png".format(i), num)
|
sid_no = sid_no + str(classifier.predict(num.reshape(1, -1) / 255.0)[0])
|
return sid_no
|
|
|
def segment_by_7segments(image, original_image, sid_mask, classifier):
|
"""
|
Second attempt. It dilates the image to get all 7 segments wisible as 8888888 then it does pattern matching of 8 with
|
pattern image. It works if the scaned gray level is high enough.
|
|
:param image:
|
:param original_image:
|
:param sid_mask:
|
:param classifier:
|
:return: student id number as a string
|
"""
|
block_image = cv2.morphologyEx(
|
original_image, cv2.MORPH_CLOSE, kernel(2, 2), iterations=10
|
)
|
block_image = img_as_ubyte(block_image < 50)
|
cv2.imwrite("/tmp/sid_3rd1.png", block_image)
|
template = cv2.imread(template8, 0)
|
w, h = template.shape[::-1]
|
res = cv2.matchTemplate(block_image, template, cv2.TM_CCOEFF_NORMED)
|
loc = np.where(res >= 0.75)
|
cimg = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
|
loc_filtered_x = []
|
loc_filtered_y = []
|
for pt in zip(*loc[::-1]):
|
pt = (pt[0] - 10, pt[1] - 10)
|
loc_filtered_y.append(pt[1])
|
loc_filtered_x.append(pt[0])
|
# points.append(pt)
|
# filter points
|
if len(loc_filtered_x) == 0:
|
return ""
|
loc_filtered_x, loc_filtered_y = zip(*sorted(zip(loc_filtered_x, loc_filtered_y)))
|
a = np.diff(loc_filtered_x) > int(w / 2)
|
a = np.append(a, True)
|
loc_filtered_x = np.array(loc_filtered_x)
|
loc_filtered_y = np.array(loc_filtered_y)
|
points = [loc_filtered_y[a], loc_filtered_x[a]]
|
for pt in zip(*points[::-1]):
|
cv2.rectangle(cimg, pt, (pt[0] + w, pt[1] + h), (0, 255, 255), 2)
|
cv2.imwrite("/tmp/sid_3rd2.png", cimg)
|
|
sid_no = ""
|
for i, pt in enumerate(zip(*points[::-1])):
|
num = image[pt[1] : pt[1] + h, pt[0] : pt[0] + w]
|
# cv2.imwrite("sid_3no_{}.png".format(i), num)
|
num = img_as_ubyte(num < 128)
|
try:
|
num = cv2.resize(num, (32, 32))
|
except:
|
return ""
|
cv2.imwrite("/tmp/sid_3no_{}.png".format(i), num)
|
sid_no = sid_no + str(classifier.predict(num.reshape(1, -1) / 255.0)[0])
|
|
return sid_no
|
|
|
def getSID(image, classifier, sid_mask):
|
"""
|
Tries different approaches on image to get student id number. Firstly clears image of noise and then skeletonizes
|
numbers and thickens it until it gets normalized image. It sends it to the segmentation and recognition functions.
|
|
Tweak both MORPH_OPEN lines....
|
|
:param image:
|
:param classifier:
|
:param sid_mask:
|
:return: (student_id, error, warning) student id as a string, list of errors and list of warnings during the recognition
|
|
"""
|
sid_warn = []
|
sid_err = []
|
image = 255 - image
|
image_original = image.copy()
|
image = img_as_ubyte(image > 70)
|
cv2.imwrite("/tmp/enSID0.png", image)
|
|
# Remove noise
|
image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(3, 3), iterations=2)
|
|
# Closing. Connect non connected parts
|
image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(5, 1), iterations=4)
|
|
# Again noise removal after closing
|
#image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(8, 8), iterations=1)
|
# don't do too much noise removal.
|
#image = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel(3, 3), iterations=2)
|
|
# Skeletonization
|
#image = img_as_ubyte(morphology.skeletonize(image > 128))
|
image = img_as_ubyte(morphology.thin(image > 128))
|
cv2.imwrite("/tmp/enSID1.png", image)
|
|
# Stub removal (might not be necessary if thinning instead of skeletonize is used above
|
# Making lines stronger
|
image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(5, 2), iterations=1)
|
image = cv2.morphologyEx(image, cv2.MORPH_CLOSE, kernel(10, 10))
|
|
# Thining again
|
image = img_as_ubyte(morphology.skeletonize(image > 0.5))
|
image = cv2.morphologyEx(image, cv2.MORPH_DILATE, kernel(10, 10))
|
cv2.imwrite("/tmp/enhancedSID.png", image)
|
|
sid_no = segment_by_contours(image, image_original, classifier, sid_mask)
|
|
if len(sid_no) != len(sid_mask) or not sid_compare(sid_no, sid_mask):
|
sid_warn.append("Trying second SID algorithm.")
|
sid_no = segment_by_7segments(image, image_original, sid_mask, classifier)
|
|
if (len(sid_no)) != len(sid_mask):
|
sid_no = segment_by_sid_len(image, image_original, sid_mask, classifier)
|
sid_warn.append("Trying third SID algorithm.")
|
|
if not sid_compare(sid_no, sid_mask):
|
sid_err = ["Wrong SID!"]
|
cv2.imwrite("/tmp/SID_"+sid_no+".png", image)
|
return sid_no, sid_err, sid_warn
|