880 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			880 lines
		
	
	
		
			22 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| #!/usr/bin/env python
 | |
| # -*- coding: utf-8 -*-
 | |
| """
 | |
| This module can be used to scan QR-Codes
 | |
| 
 | |
| (C) 2022 Louis Heredero  louis.heredero@edu.vs.ch
 | |
| """
 | |
| 
 | |
| import cv2
 | |
| import numpy as np
 | |
| from math import sqrt, degrees, atan2, radians, cos, sin
 | |
| import imutils
 | |
| 
 | |
| DB_WIN = False
 | |
| 
 | |
| TOL_CNT_DIST = 10  # Tolerance distance between finders' centers
 | |
| MASKS = [
 | |
|     lambda x,y: (x+y)%2 == 0,
 | |
|     lambda x,y: y%2 == 0,
 | |
|     lambda x,y: (x)%3 == 0,
 | |
|     lambda x,y: (x+y)%3 == 0,
 | |
|     lambda x,y: (y//2+x//3)%2 == 0,
 | |
|     lambda x,y: ((x*y)%2 + (x*y)%3) == 0,
 | |
|     lambda x,y: ((x*y)%2 + (x*y)%3)%2 == 0,
 | |
|     lambda x,y: ((x+y)%2 + (x*y)%3)%2 == 0
 | |
| ]
 | |
| 
 | |
| ALIGNMENT_PATTERN_LOCATIONS = [
 | |
|     [],
 | |
|     [6, 18],
 | |
|     [6, 22],
 | |
|     [6, 26],
 | |
|     [6, 30],
 | |
|     [6, 34],
 | |
|     [6, 22, 38],
 | |
|     [6, 24, 42],
 | |
|     [6, 26, 46],
 | |
|     [6, 28, 50],
 | |
|     [6, 30, 54],
 | |
|     [6, 32, 58],
 | |
|     [6, 34, 62],
 | |
|     [6, 26, 46, 66],
 | |
|     [6, 26, 48, 70],
 | |
|     [6, 26, 50, 74],
 | |
|     [6, 30, 54, 78],
 | |
|     [6, 30, 56, 82],
 | |
|     [6, 30, 58, 86],
 | |
|     [6, 34, 62, 90],
 | |
|     [6, 28, 50, 72, 94],
 | |
|     [6, 26, 50, 74, 98],
 | |
|     [6, 30, 54, 78, 102],
 | |
|     [6, 28, 54, 80, 106],
 | |
|     [6, 32, 58, 84, 110],
 | |
|     [6, 30, 58, 86, 114],
 | |
|     [6, 34, 62, 90, 118],
 | |
|     [6, 26, 50, 74, 98, 122],
 | |
|     [6, 30, 54, 78, 102, 126],
 | |
|     [6, 26, 52, 78, 104, 130],
 | |
|     [6, 30, 56, 82, 108, 134],
 | |
|     [6, 34, 60, 86, 112, 138],
 | |
|     [6, 30, 58, 86, 114, 142],
 | |
|     [6, 34, 62, 90, 118, 146],
 | |
|     [6, 30, 54, 78, 102, 126, 150],
 | |
|     [6, 24, 50, 76, 102, 128, 154],
 | |
|     [6, 28, 54, 80, 106, 132, 158],
 | |
|     [6, 32, 58, 84, 110, 136, 162],
 | |
|     [6, 26, 54, 82, 110, 138, 166],
 | |
|     [6, 30, 58, 86, 114, 142, 170]
 | |
| ]
 | |
| 
 | |
| VERSIONS = []
 | |
| with open("qr_versions.txt", "r") as f:
 | |
|     versions = f.read().split("\n\n")
 | |
|     for v in versions:
 | |
|         lvls = [list(map(int, lvl.split("\t"))) for lvl in v.split("\n")]
 | |
|         VERSIONS.append(lvls)
 | |
| VERSIONS = np.array(VERSIONS)
 | |
| 
 | |
| ERROR_CORRECTION =  []
 | |
| with open("error_correction.txt", "r") as f:
 | |
|     ecs = f.read().split("\n\n")
 | |
|     for ec in ecs:
 | |
|         lvls = [list(map(int, lvl.split("\t"))) for lvl in ec.split("\n")]
 | |
|         lvls = [lvl + [0]*(6-len(lvl)) for lvl in lvls]
 | |
|         
 | |
|         ERROR_CORRECTION.append(lvls)
 | |
| ERROR_CORRECTION = np.array(ERROR_CORRECTION)
 | |
| 
 | |
| EC_PARAMS =  []
 | |
| with open("ec_params.txt", "r") as f:
 | |
|     ecs = f.read().split("\n\n")
 | |
|     for ec in ecs:
 | |
|         lvls = [list(map(int, lvl.split("\t"))) for lvl in ec.split("\n") if lvl]
 | |
|         EC_PARAMS.append(lvls)
 | |
| EC_PARAMS = np.array(EC_PARAMS)
 | |
| 
 | |
| ALPHANUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
 | |
| 
 | |
| class GF:
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
| 
 | |
|     def copy(self):
 | |
|         return GF(self.val)
 | |
| 
 | |
|     def __add__(self, n):
 | |
|         return GF(self.val ^ n.val)
 | |
| 
 | |
|     def __sub__(self, n):
 | |
|         return GF(self.val ^ n.val)
 | |
| 
 | |
|     def __mul__(self, n):
 | |
|         if self.val == 0 or n.val == 0:
 | |
|             return GF(0)
 | |
| 
 | |
|         return GF.EXP[GF.LOG[self.val].val + GF.LOG[n.val].val].copy()
 | |
| 
 | |
|     def __truediv__(self, n):
 | |
|         if n.val == 0:
 | |
|             raise ZeroDivisionError
 | |
|         if self.val == 0:
 | |
|             return GF(0)
 | |
| 
 | |
|         return GF.EXP[(GF.LOG[self.val].val + 255 - GF.LOG[n.val].val)%255].copy()
 | |
| 
 | |
|     def __pow__(self, n):
 | |
|         return GF.EXP[(GF.LOG[self.val].val * n.val)%255].copy()
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return self.val.__repr__()
 | |
|     
 | |
|     def log(self):
 | |
|         return GF.LOG[self.val]
 | |
| 
 | |
| GF.EXP = [GF(0)]*512
 | |
| GF.LOG = [GF(0)]*256
 | |
| value = 1
 | |
| for exponent in range(255):
 | |
|     GF.LOG[value] = GF(exponent)
 | |
|     GF.EXP[exponent] = GF(value)
 | |
|     value = ((value << 1) ^ 285) if value > 127 else value << 1
 | |
| 
 | |
| for i in range(255, 512):
 | |
|     GF.EXP[i] = GF.EXP[i-255].copy()
 | |
| 
 | |
| 
 | |
| class Poly:
 | |
|     def __init__(self, coefs):
 | |
|         self.coefs = coefs.copy()
 | |
| 
 | |
|     @property
 | |
|     def deg(self):
 | |
|         return len(self.coefs)
 | |
| 
 | |
|     def copy(self):
 | |
|         return Poly(self.coefs)
 | |
| 
 | |
|     def __add__(self, p):
 | |
|         d1, d2 = self.deg, p.deg
 | |
|         deg = max(d1,d2)
 | |
|         result = [GF(0) for i in range(deg)]
 | |
|         
 | |
|         for i in range(d1):
 | |
|             result[i + deg - d1] = self.coefs[i]
 | |
| 
 | |
|         for i in range(d2):
 | |
|             result[i + deg - d2] += p.coefs[i]
 | |
|         
 | |
|         return Poly(result)
 | |
| 
 | |
|     def __mul__(self, p):
 | |
|         result = [GF(0) for i in range(self.deg+p.deg-1)]
 | |
| 
 | |
|         for i in range(p.deg):
 | |
|             for j in range(self.deg):
 | |
|                 result[i+j] += self.coefs[j] * p.coefs[i]
 | |
| 
 | |
|         return Poly(result)
 | |
| 
 | |
|     def __truediv__(self, p):
 | |
|         dividend = self.coefs.copy()
 | |
|         dividend += [GF(0) for i in range(p.deg-1)]
 | |
|         quotient = []
 | |
| 
 | |
|         for i in range(self.deg):
 | |
|             coef = dividend[i] / p.coefs[0]
 | |
|             quotient.append(coef)
 | |
| 
 | |
|             for j in range(p.deg):
 | |
|                 dividend[i+j] -= p.coefs[j] * coef
 | |
| 
 | |
|         while dividend[0].val == 0:
 | |
|             dividend.pop(0)
 | |
| 
 | |
|         return [Poly(quotient), Poly(dividend)]
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return f"<Poly {self.coefs}>"
 | |
|     
 | |
|     def eval(self, x):
 | |
|         y = GF(0)
 | |
|         
 | |
|         for i in range(self.deg):
 | |
|             y += self.coefs[i] * x**GF(self.deg-i-1)
 | |
|         
 | |
|         return y
 | |
|     
 | |
|     def del_lead_zeros(self):
 | |
|         while len(self.coefs) > 1 and self.coefs[0].val == 0:
 | |
|             self.coefs.pop(0)
 | |
|         
 | |
|         if len(self.coefs) == 0:
 | |
|             self.coefs = [GF(0)]
 | |
|         
 | |
|         return self
 | |
| 
 | |
| def center(c):
 | |
|     M = cv2.moments(c)
 | |
|     
 | |
|     if M["m00"] != 0:
 | |
|         cX = int(M["m10"] / M["m00"])
 | |
|         cY = int(M["m01"] / M["m00"])
 | |
|         return (cX, cY)
 | |
|     
 | |
|     return (None, None)
 | |
| 
 | |
| def dist(p1, p2):
 | |
|     return sqrt((p2[0]-p1[0])**2 + (p2[1]-p1[1])**2)
 | |
| 
 | |
| def rotate(o, p, a):
 | |
|     ox, oy = o
 | |
|     px, py = p
 | |
|     a = radians(a)
 | |
|     c, s = cos(a), sin(a)
 | |
|     
 | |
|     return [
 | |
|         int(ox + c*(px-ox) - s * (py-oy)),
 | |
|         int(oy + s*(px-ox) + c * (py-oy))
 | |
|     ]
 | |
| 
 | |
| def rotate_cnt(cnt, o, a):
 | |
|     c = cnt
 | |
|     pts = c[:, 0, :]
 | |
|     pts = np.array([rotate(o, p, a) for p in pts])
 | |
|     c[:, 0, :] = pts
 | |
|     
 | |
|     return c.astype(np.int32)
 | |
| 
 | |
| def is_finder(i, cnts, hrcy):
 | |
|     c1 = cnts[i]
 | |
|     h1 = hrcy[0][i]
 | |
|     cX1, cY1 = center(c1)
 | |
|     if len(c1) != 4:
 | |
|         return False
 | |
|     
 | |
|     if cX1 is None:
 | |
|         return False
 | |
|     
 | |
|     if h1[2] == -1:
 | |
|         return False
 | |
|     
 | |
|     i2 = h1[2]
 | |
|     c2 = cnts[i2]
 | |
|     h2 = hrcy[0][i2]
 | |
|     cX2, cY2 = center(c2)
 | |
|     if cX2 is None:
 | |
|         return False
 | |
|     
 | |
|     if len(c2) != 4:
 | |
|         return False
 | |
|     
 | |
|     if abs(dist((cX1, cY1), (cX2, cY2))) > TOL_CNT_DIST:
 | |
|         return False
 | |
|     
 | |
|     if h2[2] == -1:
 | |
|         return False
 | |
|     
 | |
|     i3 = h2[2]
 | |
|     c3 = cnts[i3]
 | |
|     h3 = hrcy[0][i3]
 | |
|     cX3, cY3 = center(c3)
 | |
|     
 | |
|     if len(c3) != 4:
 | |
|         return False
 | |
|     
 | |
|     if cX3 is None:
 | |
|         return False
 | |
|     
 | |
|     if abs(dist((cX1, cY1), (cX3, cY3))) > TOL_CNT_DIST:
 | |
|         return False
 | |
|     
 | |
|     return True
 | |
| 
 | |
| def decode(img):
 | |
|     grey = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 | |
|     grey = cv2.GaussianBlur(grey, (5,5), 0)
 | |
|     #if DB_WIN: cv2.imshow("grey", grey)
 | |
| 
 | |
|     bw = cv2.threshold(grey, np.mean(grey), 255, cv2.THRESH_BINARY)[1]
 | |
|     if DB_WIN: cv2.imshow("bw", bw)
 | |
|     
 | |
|     #laplacian = cv2.Laplacian(bw, cv2.CV_8U, 15)
 | |
|     #cv2.imshow("laplacian", laplacian)
 | |
|     
 | |
|     contours, hierarchy = cv2.findContours(bw, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
 | |
|     if DB_WIN:
 | |
|         img2 = img.copy()
 | |
|         cv2.drawContours(img2, contours, -1, (0,255,0), 1)
 | |
|     
 | |
|     candidates = []
 | |
|     
 | |
|     contours = list(contours)
 | |
|     for i, cnt in enumerate(contours):
 | |
|         peri = cv2.arcLength(cnt, True)
 | |
|         contours[i] = cv2.approxPolyDP(cnt, 0.04 * peri, True)
 | |
|     
 | |
|     for i in range(len(contours)):
 | |
|         if is_finder(i, contours, hierarchy):
 | |
|             candidates.append(i)
 | |
|     
 | |
|     if DB_WIN: 
 | |
|         for i in candidates:
 | |
|             cv2.drawContours(img2, contours, i, (0,0,255), 1)
 | |
|     
 | |
|         cv2.imshow("contours", img2)
 | |
|     
 | |
|     if DB_WIN: img3 = img.copy()
 | |
|     corners = []
 | |
|     corners_cnts = []
 | |
|     for i1 in candidates:
 | |
|         i2 = hierarchy[0][i1][2]
 | |
|         i3 = hierarchy[0][i2][2]
 | |
|         c1 = contours[i1]
 | |
|         c2 = contours[i2]
 | |
|         c3 = contours[i3]
 | |
|         
 | |
|         x1, y1 = center(c1)
 | |
|         x2, y2 = center(c2)
 | |
|         x3, y3 = center(c3)
 | |
|         x, y = (x1+x2+x3)/3, (y1+y2+y3)/3
 | |
|         x, y = int(x), int(y)
 | |
|         corners.append((x,y))
 | |
|         corners_cnts.append([c1,c2,c3])
 | |
|         
 | |
|         if DB_WIN: 
 | |
|             cv2.line(img3, [x, y-10], [x, y+10], (0,255,0), 1)
 | |
|             cv2.line(img3, [x-10, y], [x+10, y], (0,255,0), 1)
 | |
|             #cv2.drawContours(img3, [c1], 0, (255,0,0), 1)
 | |
|             #cv2.drawContours(img3, [c2], 0, (0,255,0), 1)
 | |
|             #cv2.drawContours(img3, [c3], 0, (0,0,255), 1)
 | |
|     
 | |
|     if DB_WIN: cv2.imshow("lines", img3)
 | |
|     
 | |
|     if len(corners) != 3:
 | |
|         return
 | |
|     
 | |
|     d01 = dist(corners[0], corners[1])
 | |
|     d02 = dist(corners[0], corners[2])
 | |
|     d12 = dist(corners[1], corners[2])
 | |
|     diffs = [abs(d01-d02), abs(d01-d12), abs(d02-d12)]
 | |
|     mdiff = min(diffs)
 | |
|     i = diffs.index(mdiff)
 | |
|     
 | |
|     a = corners.pop(i)
 | |
|     b, c = corners
 | |
|     
 | |
|     V = [img.shape[1], img.shape[0]]
 | |
|     d = [(b[0]+c[0])/2, (b[1]+c[1])/2]
 | |
|     v = [d[0]-a[0], d[1]-a[1]]
 | |
|     
 | |
|     C = [img.shape[1]/2, img.shape[0]/2]
 | |
|     
 | |
|     angle = degrees(atan2(v[1], v[0]))
 | |
|     angle_diff = angle-45
 | |
|     
 | |
|     if DB_WIN: 
 | |
|         img4 = img.copy()
 | |
|         cv2.line(img4, a, [int(d[0]), int(d[1])], (0,255,0), 1)
 | |
|         cv2.imshow("vecs", img4)
 | |
|     
 | |
|     cA = corners_cnts[i][0]
 | |
|     cA = rotate_cnt(cA, C, -angle_diff)
 | |
|     rA = cv2.boundingRect(cA)
 | |
|     
 | |
|     cA2 = corners_cnts[i][1]
 | |
|     cA2 = rotate_cnt(cA2, C, -angle_diff)
 | |
|     rA2 = cv2.boundingRect(cA2)
 | |
|     
 | |
|     cB = corners_cnts[i-1][0]
 | |
|     cB = rotate_cnt(cB, C, -angle_diff)
 | |
|     rB = cv2.boundingRect(cB)
 | |
|     
 | |
|     cC = corners_cnts[i-2][0]
 | |
|     cC = rotate_cnt(cC, C, -angle_diff)
 | |
|     rC = cv2.boundingRect(cC)
 | |
|     
 | |
|     a, b, c = rotate(C, a, -angle_diff), rotate(C, b, -angle_diff), rotate(C, c, -angle_diff)
 | |
|     
 | |
|     if DB_WIN: 
 | |
|         img5 = img.copy()
 | |
|         img5 = imutils.rotate(img5, angle_diff)
 | |
|         cv2.rectangle(img5, rA, (255,0,0), 1)
 | |
|         cv2.rectangle(img5, rB, (0,255,0), 1)
 | |
|         cv2.rectangle(img5, rC, (0,0,255), 1)
 | |
|         cv2.line(img5, a, b, (255,255,255), 1)
 | |
|         cv2.line(img5, a, c, (255,255,255), 1)
 | |
|         cv2.imshow("rot", img5)
 | |
|     
 | |
|     wul = rA[2]
 | |
|     if rB[1] < rC[1]:
 | |
|         wur = rB[2]
 | |
|     else:
 | |
|         wur = rC[2]
 | |
|     
 | |
|     if rB[1] < rC[1]:
 | |
|         D = dist(a, b)
 | |
|     else:
 | |
|         D = dist(a, c)
 | |
|     
 | |
|     X = (wul + wur)/14
 | |
|     V = (D/X - 10)/4
 | |
|     V = round(V)
 | |
|     
 | |
|     size = V*4+17
 | |
|     grid = np.zeros([size, size])
 | |
|     bw_rot = imutils.rotate(bw, angle_diff)
 | |
|     if DB_WIN: 
 | |
|         img6 = img.copy()
 | |
|         img6 = imutils.rotate(img6, angle_diff)
 | |
|     
 | |
|     OX, OY = (rA[0]+rA2[0])/2, (rA[1]+rA2[1])/2
 | |
|     
 | |
|     #Not fully visible
 | |
|     if (OX + size*X+1 >= bw_rot.shape[1]) or (OY + size*X+1 >= bw_rot.shape[0]):
 | |
|         return None
 | |
|     
 | |
|     for y in range(size):
 | |
|         for x in range(size):
 | |
|             zone = bw_rot[
 | |
|                 int(OY + y*X-1): int(OY + y*X+2),
 | |
|                 int(OX + x*X-1): int(OX + x*X+2)
 | |
|             ]
 | |
|             grid[y, x] = 1-round(np.mean(zone)/255)
 | |
|             #cv2.circle(img6, [int(OX+x*X), int(OY+y*X)], 3, (0,0,255), 1)
 | |
|     
 | |
|     #print(size)
 | |
|     #cv2.rectangle(img6, [int(OX-X/2), int(OY-X/2), int(X), int(X)], (0,255,0), 1)
 | |
|     #cv2.imshow("grid", img6)
 | |
|     if DB_WIN: 
 | |
|         cv2.namedWindow("bw_rot", cv2.WINDOW_NORMAL)
 | |
|         cv2.imshow("bw_rot", bw_rot)
 | |
|     #cv2.imshow("code", cv2.resize(grid, [size*10,size*10]))
 | |
|     
 | |
|     value = _decode(grid, V)
 | |
|     
 | |
|     if value is None:
 | |
|         value = _decode(1-grid, V)
 | |
|     
 | |
|     return value
 | |
| 
 | |
| def _decode(grid, V, flipped=None, f2=False):
 | |
|     if not flipped is None:
 | |
|         grid = flipped
 | |
|     
 | |
|     lvl, mask_i = get_fmt(grid, f2)
 | |
|     
 | |
|     mask = MASKS[mask_i]
 | |
|     unmasked = grid.copy()
 | |
|     mask_area = np.ones(grid.shape)
 | |
| 
 | |
|     mask_area[:9, :9] = 0
 | |
|     mask_area[:9, -8:] = 0
 | |
|     mask_area[-8:, :9] = 0
 | |
| 
 | |
|     #Add alignment patterns
 | |
|     locations = ALIGNMENT_PATTERN_LOCATIONS[V-1]
 | |
| 
 | |
|     if V > 1:
 | |
|         for y in locations:
 | |
|             for x in locations:
 | |
|                 #Check if not overlapping with finders
 | |
|                 if np.all(mask_area[y-2:y+3, x-2:x+3] == 1):
 | |
|                     mask_area[y-2:y+3, x-2:x+3] = 0
 | |
|                     mask_area[y-1:y+2, x-1:x+2] = 0
 | |
|                     mask_area[y, x] = 0
 | |
| 
 | |
|     #Add timing patterns
 | |
|     timing_length = grid.shape[0]-2*8
 | |
|     mask_area[6, 8:-8] = np.zeros([timing_length])
 | |
|     mask_area[8:-8, 6] = np.zeros([timing_length])
 | |
| 
 | |
|     if V >= 7:
 | |
|         mask_area[-11:-8, :6] = 0
 | |
|         mask_area[:6, -11:-8] = 0
 | |
|     
 | |
|     for y in range(grid.shape[0]):
 | |
|         for x in range(grid.shape[1]):
 | |
|             if mask_area[y,x] == 1 and mask(x,y):
 | |
|                 unmasked[y,x] = 1-unmasked[y,x]
 | |
|     
 | |
|     if DB_WIN: 
 | |
|         cv2.namedWindow("grid", cv2.WINDOW_NORMAL)
 | |
|         cv2.namedWindow("unmasked", cv2.WINDOW_NORMAL)
 | |
|         #cv2.namedWindow("mask_area", cv2.WINDOW_NORMAL)
 | |
|         cv2.imshow("grid", 1-grid)
 | |
|         cv2.imshow("unmasked", 1-unmasked)
 | |
|         #cv2.imshow("mask_area", mask_area)
 | |
|     
 | |
|     #Un-place data
 | |
|     dir_ = -1 #-1 = up | 1 = down
 | |
|     x, y = grid.shape[1]-1, grid.shape[0]-1
 | |
|     i = 0
 | |
|     zigzag = 0
 | |
|     
 | |
|     final_data_bits = ""
 | |
| 
 | |
|     while x >= 0:
 | |
|         if mask_area[y,x] == 1:
 | |
|             final_data_bits += str(int(unmasked[y, x]))
 | |
| 
 | |
|         if ((dir_+1)/2 + zigzag)%2 == 0:
 | |
|             x -= 1
 | |
| 
 | |
|         else:
 | |
|             y += dir_
 | |
|             x += 1
 | |
| 
 | |
|         if y == -1 or y == grid.shape[0]:
 | |
|             dir_ = -dir_
 | |
|             y += dir_
 | |
|             x -= 2
 | |
| 
 | |
|         else:
 | |
|             zigzag = 1-zigzag
 | |
| 
 | |
|         #Vertical timing pattern
 | |
|         if x == 6:
 | |
|             x -= 1
 | |
|     
 | |
|     #Remove remainder bits
 | |
|     if 2 <= V < 7:
 | |
|         final_data_bits = final_data_bits[:-7]
 | |
| 
 | |
|     elif 14 <= V < 21 or 28 <= V < 35:
 | |
|         final_data_bits = final_data_bits[:-3]
 | |
| 
 | |
|     elif 21 <= V < 28:
 | |
|         final_data_bits = final_data_bits[:-4]
 | |
|     
 | |
|     ec = ERROR_CORRECTION[V-1, lvl]
 | |
|     #print(ec)
 | |
|     
 | |
|     
 | |
|     codewords = [final_data_bits[i:i+8] for i in range(0,len(final_data_bits),8)]
 | |
|     
 | |
|     #Only one block
 | |
|     if ec[2]+ec[4] == 1:
 | |
|         data_codewords = codewords[:ec[3]]
 | |
|         ec_codewords = codewords[ec[3]:]
 | |
| 
 | |
|     else:
 | |
|         group1, group2 = [], []
 | |
|         group_ec = []
 | |
|         
 | |
|         for b in range(ec[2]):
 | |
|             block = []
 | |
|             for i in range(ec[3]):
 | |
|                 block.append(codewords[b+i*(ec[2]+ec[4])])
 | |
|             
 | |
|             group1.append(block)
 | |
|         
 | |
|         if ec[4] > 0:
 | |
|             for b in range(ec[2], ec[2]+ec[4]):
 | |
|                 block = []
 | |
|                 for i in range(ec[5]):
 | |
|                     off = 0
 | |
|                     if i >= ec[3]:
 | |
|                         off = (i-ec[3]+1)*ec[2]
 | |
|                     block.append(codewords[b+i*(ec[2]+ec[4])-off])
 | |
|                 
 | |
|                 group2.append(block)
 | |
|         
 | |
|         codewords = codewords[ec[2]*ec[3]+ec[4]*ec[5]:]
 | |
|         
 | |
|         for b in range(ec[2]+ec[4]):
 | |
|             block = []
 | |
|             for i in range(ec[1]):
 | |
|                 block.append(codewords[b+i*(ec[2]+ec[4])])
 | |
|             
 | |
|             group_ec.append(block)
 | |
|         
 | |
|         data_codewords = sum(group1, []) + sum(group2, [])
 | |
|         ec_codewords = sum(group_ec, [])
 | |
|     
 | |
|     #print(data_codewords)
 | |
|     #print(ec_codewords)
 | |
|     
 | |
|     try:
 | |
|         decoded = correct(data_codewords, ec_codewords)
 | |
|     
 | |
|     except ReedSolomonException as e:
 | |
|         #print(e)
 | |
|         
 | |
|         if not f2:
 | |
|             return _decode(grid, V, flipped, True)
 | |
|         
 | |
|         elif flipped is None:
 | |
|             f = grid.copy()
 | |
|             f = np.rot90(np.fliplr(f))
 | |
|             return _decode(grid, V, f, False)
 | |
|         
 | |
|         else:
 | |
|             #raise ReedSolomonException("Cannot decode")
 | |
|             return None
 | |
|     
 | |
|     decoded = "".join(list(map(lambda c: f"{c.val:08b}", decoded.coefs)))
 | |
|     
 | |
|     mode, decoded = decoded[:4], decoded[4:]
 | |
|     MODES = ["0001", "0010", "0100", "1000"]
 | |
|     mode = MODES.index(mode)
 | |
|     
 | |
|     if 1 <= V < 10:
 | |
|         char_count_len = [10,9,8,8][mode]
 | |
|     elif 10 <= V < 27:
 | |
|         char_count_len = [12,11,16,10][mode]
 | |
|     elif 27 <= V < 41:
 | |
|         char_count_len = [14,13,16,12][mode]
 | |
|     
 | |
|     length, decoded = decoded[:char_count_len], decoded[char_count_len:]
 | |
|     length = int(length, 2)
 | |
|     
 | |
|     value = None
 | |
|     
 | |
|     if mode == 0:
 | |
|         value = ""
 | |
|         
 | |
|         _ = length//3
 | |
|         l = _ * 10
 | |
|         if 10 - _ == 2:
 | |
|             l += 7
 | |
|         else:
 | |
|             l += 4
 | |
|         
 | |
|         data = decoded[:l]
 | |
|         groups = [data[i:i+10] for i in range(0, l, 10)]
 | |
|         
 | |
|         for group in groups:
 | |
|             value += str(int(group,2))
 | |
|         
 | |
|         value = int(value)
 | |
|     
 | |
|     elif mode == 1:
 | |
|         value = ""
 | |
|         
 | |
|         data = decoded[:length//2 * 11 + (length%2)*6]
 | |
|         
 | |
|         for i in range(0, len(data), 11):
 | |
|             s = data[i:i+11]
 | |
|             val = int(s, 2)
 | |
|             
 | |
|             if len(s) == 6:
 | |
|                 value += ALPHANUM[val]
 | |
|             
 | |
|             else:
 | |
|                 value += ALPHANUM[val//45]
 | |
|                 value += ALPHANUM[val%45]
 | |
|     
 | |
|     elif mode == 2:
 | |
|         data = decoded[:length*8]
 | |
|         data = [data[i:i+8] for i in range(0, length*8, 8)]
 | |
|         data = list(map(lambda b: int(b, 2), data))
 | |
|         value = bytes(data).decode("ISO-8859-1")
 | |
|     
 | |
|     elif mode == 3:
 | |
|         value = []
 | |
|         data = decoded[:length*13]
 | |
|         
 | |
|         for i in range(0, len(data), 13):
 | |
|             val = int(data[i:i+13], 2)
 | |
|             msb = val // 0xc0
 | |
|             lsb = val % 0xc0
 | |
|             
 | |
|             dbyte = (msb << 8) + lsb
 | |
|             
 | |
|             if 0 <= dbyte <= 0x9ffc - 0x8140:
 | |
|                 dbyte += 0x8140
 | |
|             
 | |
|             elif 0xe040 - 0xc140 <= dbyte <= 0xebbf - 0xc140:
 | |
|                 dbyte += 0xc140
 | |
|             
 | |
|             value.append(dbyte >> 8)
 | |
|             value.append(dbyte & 0xff)
 | |
|         
 | |
|         value = bytes(value).decode("shift_jis")
 | |
|     
 | |
|     #print("value:", value)
 | |
|     
 | |
|     return value
 | |
|     
 | |
|     # If unreadable
 | |
|     # -> _decode(f2=True)
 | |
|     # -> mirror image
 | |
| 
 | |
| class ReedSolomonException(Exception):
 | |
|     pass
 | |
| 
 | |
| def correct(data, ec):
 | |
|     n = len(ec)
 | |
|     
 | |
|     data = Poly([GF(int(cw, 2)) for cw in data+ec])
 | |
|     ##print("data", list(map(lambda c:c.val, data.coefs)))
 | |
|     
 | |
|     syndrome = [0]*n
 | |
|     corrupted = False
 | |
|     for i in range(n):
 | |
|         syndrome[i] = data.eval(GF.EXP[i])
 | |
|         if syndrome[i].val != 0:
 | |
|             corrupted = True
 | |
|     
 | |
|     if not corrupted:
 | |
|         print("No errors")
 | |
|         return data
 | |
|     
 | |
|     syndrome = Poly(syndrome[::-1])
 | |
|     #print("syndrome", syndrome)
 | |
|     
 | |
|     #Find locator poly
 | |
|     sigma, omega = euclidean_algorithm(Poly([GF(1)]+[GF(0) for i in range(n)]), syndrome, n)
 | |
|     #print("sigma", sigma)
 | |
|     #print("omega", omega)
 | |
|     error_loc = find_error_loc(sigma)
 | |
|     
 | |
|     error_mag = find_error_mag(omega, error_loc)
 | |
|     
 | |
|     for i in range(len(error_loc)):
 | |
|         pos = GF(error_loc[i]).log()
 | |
|         pos = data.deg - pos.val - 1
 | |
|         if pos < 0:
 | |
|             raise ReedSolomonException("Bad error location")
 | |
|         
 | |
|         data.coefs[pos] += GF(error_mag[i])
 | |
|     
 | |
|     return data
 | |
| 
 | |
| def euclidean_algorithm(a, b, R):
 | |
|     if a.deg < b.deg:
 | |
|         a, b = b, a
 | |
|     
 | |
|     r_last = a
 | |
|     r = b
 | |
|     t_last = Poly([GF(0)])
 | |
|     t = Poly([GF(1)])
 | |
|     
 | |
|     while r.deg-1 >= int(R/2):
 | |
|         r_last_last = r_last
 | |
|         t_last_last = t_last
 | |
|         r_last = r
 | |
|         t_last = t
 | |
|         if r_last.coefs[0] == 0:
 | |
|             raise ReedSolomonException("r_{i-1} was zero")
 | |
|         
 | |
|         r = r_last_last
 | |
|         q = Poly([GF(0)])
 | |
|         denom_lead_term = r_last.coefs[0]
 | |
|         dlt_inv = denom_lead_term ** GF(-1)
 | |
|         I = 0
 | |
|         while r.deg >= r_last.deg and r.coefs[0] != 0:
 | |
|             I += 1
 | |
|             deg_diff = r.deg - r_last.deg
 | |
|             scale = r.coefs[0] * dlt_inv
 | |
|             q += Poly([scale]+[GF(0) for i in range(deg_diff)])
 | |
|             r += r_last * Poly([scale]+[GF(0) for i in range(deg_diff)])
 | |
|             q.del_lead_zeros()
 | |
|             r.del_lead_zeros()
 | |
|             
 | |
|             if I > 100:
 | |
|                 raise ReedSolomonException("Too long")
 | |
|         
 | |
|         t = (q * t_last).del_lead_zeros() + t_last_last
 | |
|         t.del_lead_zeros()
 | |
|         if r.deg >= r_last.deg:
 | |
|             raise ReedSolomonException("Division algorithm failed to reduce polynomial")
 | |
|     
 | |
|     sigma_tilde_at_zero = t.coefs[-1]
 | |
|     if sigma_tilde_at_zero.val == 0:
 | |
|         raise ReedSolomonException("sigma_tilde(0) was zero")
 | |
|     
 | |
|     inv = Poly([sigma_tilde_at_zero ** GF(-1)])
 | |
|     sigma = t * inv
 | |
|     omega = r * inv
 | |
|     
 | |
|     return [sigma, omega]
 | |
| 
 | |
| def find_error_loc(error_loc):
 | |
|     num_errors = error_loc.deg-1
 | |
|     if num_errors == 1:
 | |
|         return [error_loc.coefs[-2].val]
 | |
|     
 | |
|     result = [0]*num_errors
 | |
|     e = 0
 | |
|     i = 1
 | |
|     while i < 256 and e < num_errors:
 | |
|         if error_loc.eval(GF(i)).val == 0:
 | |
|             result[e] = (GF(i) ** GF(-1)).val
 | |
|             e += 1
 | |
|         
 | |
|         i += 1
 | |
|     
 | |
|     if e != num_errors:
 | |
|         raise ReedSolomonException("Error locator degree does not match number of roots")
 | |
|     
 | |
|     return result
 | |
| 
 | |
| def find_error_mag(error_eval, error_loc):
 | |
|     s = len(error_loc)
 | |
|     result = [0]*s
 | |
|     for i in range(s):
 | |
|         xi_inv = GF(error_loc[i]) ** GF(-1)
 | |
|         denom = GF(1)
 | |
|         for j in range(s):
 | |
|             if i != j:
 | |
|                 denom *= GF(1) + GF(error_loc[j]) * xi_inv
 | |
|         
 | |
|         result[i] = ( error_eval.eval(xi_inv) * (denom ** GF(-1)) ).val
 | |
|     
 | |
|     return result
 | |
| 
 | |
| def get_fmt(grid ,f2=False):
 | |
|     fmt1 = list(grid[0:6, 8]) + [grid[7,8], grid[8,8], grid[8,7]] + list(grid[8, 0:6][::-1])
 | |
|     fmt2 = list(grid[8, -8:][::-1]) + list(grid[-7:, 8])
 | |
|     
 | |
|     fmt1 = "".join([str(int(b)) for b in fmt1])[::-1]
 | |
|     fmt2 = "".join([str(int(b)) for b in fmt2])[::-1]
 | |
|     
 | |
|     if f2:
 | |
|         return decode_fmt(fmt2)
 | |
|     
 | |
|     return decode_fmt(fmt1)
 | |
| 
 | |
| def decode_fmt(fmt):
 | |
|     format_data = int(fmt, 2)
 | |
|     format_data ^= 0b101010000010010
 | |
|     format_data = f"{format_data:015b}"
 | |
|     
 | |
|     closest = None
 | |
|     
 | |
|     with open("./valid_format_str.txt", "r") as f:
 | |
|         for i, format_str in enumerate(f):
 | |
|             diff = sum(1 for a, b in zip(format_data, format_str) if a != b)
 | |
|             if closest is None or diff < closest[1]:
 | |
|                 closest = (i, diff)
 | |
|     
 | |
|     lvl = closest[0] >> 3
 | |
|     lvl = (5-lvl)%4
 | |
|     mask = closest[0]&0b111
 | |
|     return [lvl, mask]
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|     cam = cv2.VideoCapture(0)
 | |
|     
 | |
|     while True:
 | |
|         ret_val, img = cam.read()
 | |
|         if not ret_val:
 | |
|             continue
 | |
|         
 | |
|         cv2.imshow("src", img)
 | |
|         
 | |
|         try:
 | |
|             value = decode(img)
 | |
|             if not value is None:
 | |
|                 print(value)
 | |
|             
 | |
|         except Exception as e:
 | |
|             #pass
 | |
|             #raise
 | |
|             print(e)
 | |
|         
 | |
|         cv2.waitKey(1)
 | |
|     
 | |
|     cv2.destroyAllWindows() |