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.rotateAngle(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.85, 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 = [] if len(loc[0]) == 0: min_y = -1 else: 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.85, 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 = [] if len(loc[1]) == 0: min_x = -1 else: 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)