from pyzbar.pyzbar import decode
|
import cv2
|
import numpy as np
|
import math
|
|
|
|
|
class Paper():
|
|
def __init__(self, filename=None):
|
self.filename=filename
|
self.invalid=None
|
self.QRData=None
|
self.errors=[]
|
self.warnings=[]
|
if filename is not None:
|
self.loadImage(filename)
|
self.runOcr()
|
|
|
def loadImage(self, filename, rgbchannel=0):
|
self.img=cv2.imread(filename,rgbchannel)
|
if self.img is None:
|
self.errors.append("File could not be loaded!")
|
self.invalid=True
|
return
|
self.imgHeight, self.imgWidth = self.img.shape[0:2]
|
|
def saveImage(self, filename='debug_image.png'):
|
cv2.imwrite(filename, self.img)
|
|
def runOcr(self):
|
if self.invalid==True:
|
return
|
self.decodeQRandRotate()
|
self.imgTreshold()
|
skewAngle=0
|
# try:
|
# skewAngle=self.getSkewAngle()
|
# except:
|
# self.errors.append("Could not determine skew angle!")
|
# self.rotateAngle(skewAngle)
|
|
self.generateAnswerMatrix()
|
|
self.saveImage()
|
|
def decodeQRandRotate(self):
|
if self.invalid == True:
|
return
|
blur = cv2.blur(self.img,(3,3))
|
d=decode(blur)
|
self.img=blur
|
if len(d) == 0:
|
self.errors.append("QR code could not be found!")
|
self.data=None
|
self.invalid=True
|
return
|
self.QRDecode=d
|
self.QRData=d[0].data
|
xpos=d[0].rect.left
|
ypos=d[0].rect.top
|
#check if image is rotated wrongly
|
if xpos>self.imgHeight/2.0 and ypost>self.imgWidth/2.0:
|
self.rotate(180)
|
|
def rotateAngle(self,angle=0):
|
rot_mat = cv2.getRotationMatrix2D((self.imgHeight/2, self.imgWidth/2), angle, 1.0)
|
result = cv2.warpAffine(self.img,
|
rot_mat,
|
(self.imgHeight, self.imgWidth),
|
flags=cv2.INTER_CUBIC,
|
borderMode=cv2.BORDER_CONSTANT,
|
borderValue=(255, 255, 255))
|
|
self.img=result
|
self.imgHeight, self.imgWidth = self.img.shape[0:2]
|
|
|
#todo, make better tresholding
|
def imgTreshold(self):
|
(self.thresh, self.bwimg) = cv2.threshold(self.img, 128, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)
|
|
|
def getSkewAngle(self):
|
neg = 255 - self.bwimg # get negative image
|
cv2.imwrite('debug_1.png', neg)
|
|
angle_counter = 0 # number of angles
|
angle = 0.0 # collects sum of angles
|
cimg = cv2.cvtColor(self.img,cv2.COLOR_GRAY2BGR)
|
|
# get all the Hough lines
|
for line in cv2.HoughLinesP(neg, 1, np.pi/180, 325):
|
x1, y1, x2, y2 = line[0]
|
cv2.line(cimg,(x1,y1), (x2,y2), (0,0,255),2)
|
# calculate the angle (in radians)
|
this_angle = np.arctan2(y2 - y1, x2 - x1)
|
if this_angle and abs(this_angle) <= 10:
|
# filtered zero degree and outliers
|
angle += this_angle
|
angle_counter += 1
|
|
# the skew is calculated of the mean of the total angles, #try block helps with division by zero.
|
try:
|
skew = np.rad2deg(angle / angle_counter) #the 1.2 factor is just experimental....
|
except:
|
skew=0
|
|
cv2.imwrite('debug_2.png',cimg)
|
return skew
|
|
|
def locateUpMarkers(self, threshold=0.8, height=200):
|
template = cv2.imread('template.png',0)
|
w, h = template.shape[::-1]
|
crop_img = self.img[0:height, :]
|
res = cv2.matchTemplate(crop_img,template,cv2.TM_CCOEFF_NORMED)
|
loc = np.where( res >= threshold)
|
cimg = cv2.cvtColor(crop_img,cv2.COLOR_GRAY2BGR)
|
#remove false matching of the squares in qr code
|
loc_filtered_x=[]
|
loc_filtered_y=[]
|
min_y=np.min(loc[0])
|
for pt in zip(*loc[::-1]):
|
if(pt[1]<min_y+20):
|
loc_filtered_y.append(pt[1])
|
loc_filtered_x.append(pt[0])
|
#order by x coordinate
|
loc_filtered_x,loc_filtered_y = zip(*sorted(zip(loc_filtered_x, loc_filtered_y)))
|
#loc=[loc_filtered_y,loc_filtered_x]
|
#remove duplicates
|
a=np.diff(loc_filtered_x)>40
|
a=np.append(a,True)
|
loc_filtered_x=np.array(loc_filtered_x)
|
loc_filtered_y=np.array(loc_filtered_y)
|
loc=[loc_filtered_y[a],loc_filtered_x[a]]
|
for pt in zip(*loc[::-1]):
|
cv2.rectangle(cimg, pt, (pt[0] + w, pt[1] + h), (0,255,255), 2)
|
|
|
cv2.imwrite('debug_3.png',cimg)
|
|
self.xMarkerLocations=loc
|
return loc
|
|
def locateRightMarkers(self, threshold=0.8, width=200):
|
template = cv2.imread('template.png',0)
|
w, h = template.shape[::-1]
|
crop_img = self.img[:, -width:]
|
res = cv2.matchTemplate(crop_img,template,cv2.TM_CCOEFF_NORMED)
|
loc = np.where( res >= threshold)
|
cimg = cv2.cvtColor(crop_img,cv2.COLOR_GRAY2BGR)
|
#remove false matching of the squares in qr code
|
loc_filtered_x=[]
|
loc_filtered_y=[]
|
max_x=np.max(loc[1])
|
for pt in zip(*loc[::-1]):
|
if(pt[1]>max_x-20):
|
loc_filtered_y.append(pt[1])
|
loc_filtered_x.append(pt[0])
|
#order by y coordinate
|
loc_filtered_y,loc_filtered_x = zip(*sorted(zip(loc_filtered_y, loc_filtered_x)))
|
#loc=[loc_filtered_y,loc_filtered_x]
|
#remove duplicates
|
a=np.diff(loc_filtered_y)>40
|
a=np.append(a,True)
|
loc_filtered_x=np.array(loc_filtered_x)
|
loc_filtered_y=np.array(loc_filtered_y)
|
loc=[loc_filtered_y[a],loc_filtered_x[a]]
|
for pt in zip(*loc[::-1]):
|
cv2.rectangle(cimg, pt, (pt[0] + w, pt[1] + h), (0,255,255), 2)
|
|
|
cv2.imwrite('debug_4.png',cimg)
|
|
self.yMarkerLocations=[loc[0], loc[1]+self.imgWidth-width]
|
return self.yMarkerLocations
|
|
|
def generateAnswerMatrix(self):
|
self.locateUpMarkers()
|
self.locateRightMarkers()
|
|
roixoff=10
|
roiyoff=5
|
roiwidth=50
|
roiheight=roiwidth
|
totpx=roiwidth*roiheight
|
|
self.answerMatrix=[]
|
for y in self.yMarkerLocations[0]:
|
oneline=[]
|
for x in self.xMarkerLocations[1]:
|
roi=self.bwimg[ y-roiyoff:y+int(roiheight-roiyoff),x-roixoff:x+int(roiwidth-roixoff)]
|
#cv2.imwrite('ans_x'+str(x)+'_y_'+str(y)+'.png',roi)
|
black=totpx-cv2.countNonZero(roi)
|
oneline.append(black/totpx)
|
self.answerMatrix.append(oneline)
|
|