No ACL setup yet! Denying access to everyone.
en:edu:zmitac:ai:projects:idrecognition
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| en:edu:zmitac:ai:projects:idrecognition [2018/04/07 11:47] – marikol897 | en:edu:zmitac:ai:projects:idrecognition [2018/04/13 23:20] (current) – [2.3 Video MRZ Recognition] maribaj746 | ||
|---|---|---|---|
| Line 1: | Line 1: | ||
| ===== ID MRZ RECOGNITION ===== | ===== ID MRZ RECOGNITION ===== | ||
| - | ====1. | + | ====1. |
| - | **MRZ** //(ang. machine readable zone – strefa odczytu maszynowego)// – obszar na różnego rodzaju dokumentach, zawierający zapisane czcionką do odczytu maszynowego (w przypadku dowodów osobistych jest to OCR-B) dane zawarte w danym dokumencie w sposób skrócony. Celem takiego zapisu jest ułatwienie komputerowego odczytu dokumentu na lotniskach. Dane zawarte z MRZ dowodów osobistych ułożone są w 3 liniach po 30 znaków. Używane są, podobnie jak w paszportach, | + | **MRZ** //(ang. machine readable zone)// – is a place in a different kind of documents, that contains data written in shorter way and using a special, machine readable letters. This zone helps computers |
| - | **Wiersz 1** | + | ^**Example of ID card**^^ |
| - | ^**Pole** ^**Znak** ^**Znaczenie**^ | + | |
| - | |1|A-Z|I, A lub C na oznaczenie dokumentu podróży innego niż paszport| | + | |
| - | |2|A-Z|typ| | + | |
| - | |3-5|A-Z|kod kraju wydającego dokument| | + | |
| - | |6-14|A-Z, 0-9|Numer dokumentu| | + | |
| - | |15|0-9|cyfra kontrolna dla pól 6-14| | + | |
| - | |16-30|A-Z, 0-9|inne dane, zgodnie z uznaniem państwa wydającego| | + | |
| - | + | ||
| - | **Wiersz 2** | + | |
| - | ^**Pole** ^**Znak** ^**Znaczenie**^ | + | |
| - | |1-6|0-9|Data urodzenia (RRMMDD)| | + | |
| - | |7|0-9|cyfra kontrolna dla pól 1-6| | + | |
| - | |8|A-Z|płeć (M dla mężczyzn, F dla kobiet, < dla płci nieokreślonej)| | + | |
| - | |9-14|0-9|data ważności dokumentu (RRMMDD)| | + | |
| - | |15|0-9|cyfra kontrolna dla pól 9-14| | + | |
| - | |16-18|A-Z|obywatelstwo| | + | |
| - | |19-29|A-Z, 0-9|inne (dla posiadaczy dokumentów amerykańskich może to być umieszczony numer ich paszportu)| | + | |
| - | |30|0-9|cyfra kontrolna dla pól 6-30 z 1. wiersza oraz 1-7, 9-15, 19-29 z 2. wiersza| | + | |
| - | + | ||
| - | + | ||
| - | **Wiersz 3** | + | |
| - | Tu umieszczone są nazwisko (nazwiska) i imiona posiadacza w formacie NAZWISKO<< | + | |
| - | + | ||
| - | ^**Przykładowy dowód osobisty**^^ | + | |
| |{{: | |{{: | ||
| - | ====2. | + | ====2. |
| - | Program do odczytu strefy MRZ został napisyany w języku **PYTHON | + | Application are written in Python language |
| - | Po uruchomieniu program wyświetli nam listę zadań które można wykonać: | + | After application launch we can see menu: |
| {{ : | {{ : | ||
| ====2.1 Start Training ==== | ====2.1 Start Training ==== | ||
| - | Uczenie Nadzorowane | + | Supervised learning- it is a function, that generate neccessary data for KNN classifier. |
| - | **Algorytm działania:** | + | **Working algorithm:** |
| ^Step ^Description ^ | ^Step ^Description ^ | ||
| - | | 1 | Wyświetl przygotowany przez użytkownika plik z danymi wzorcowymi | + | | 1 | Shows file prepared by user which contains pattern data (characters are written using OCR-B font). In this file every character is prepared several time in a different size.{{: |
| - | |2|Funkcja nakłada niezbędne filtry na plik z danymi:<code python> | + | |2|Function put some neccessary filters on file with data:<code python> |
| gray = cv2.cvtColor(im, | gray = cv2.cvtColor(im, | ||
| blur = cv2.GaussianBlur(gray, | blur = cv2.GaussianBlur(gray, | ||
| thresh = cv2.adaptiveThreshold(blur, | thresh = cv2.adaptiveThreshold(blur, | ||
| - | </ | + | </ |
| - | |3 |Następnie funkcja wyszukuje znaki, wycina oraz skaluje do rozmiaru | + | |3 |In the next step function find characters, crop and scale it to size 20x20 pixels:<code python> |
| image, contours, _ = cv2.findContours(thresh, | image, contours, _ = cv2.findContours(thresh, | ||
| for cnt in contours: | for cnt in contours: | ||
| Line 57: | Line 33: | ||
| roi = thresh[y:y + h, x:x + w] | roi = thresh[y:y + h, x:x + w] | ||
| roismall = cv2.resize(roi, | roismall = cv2.resize(roi, | ||
| - | </ | + | </ |
| - | |4 |Następnie Użytkownik jest proszony o wpisanie na klawiaturze jaki to znak. Po tej operacji obraz oraz numer znaku jest zapisywany w tablicy pamięci programu. | | + | |4 |In the last step user is obligate |
| - | |5 |Po klasyfikacji wszystkich znaków tablice danych są zapisane na dysku <code python> | + | |5 |When all characters are classified the arrays were save in drive <code python> |
| np.savetxt(' | np.savetxt(' | ||
| np.savetxt(' | np.savetxt(' | ||
| </ | </ | ||
| ====2.2 Verify Engine ==== | ====2.2 Verify Engine ==== | ||
| + | Testing and verification " | ||
| + | ^Step ^Description ^ | ||
| + | | 1 |We initialize engine and load data to learning: <code python> | ||
| + | self.samples = np.loadtxt(" | ||
| + | self.responses = np.loadtxt(" | ||
| + | self.model = cv2.ml.KNearest_create() | ||
| + | self.model.train(self.samples, | ||
| + | </ | ||
| + | | 2 |Function load picture with test data. {{ : | ||
| + | | 3 |We put necessary filters | | ||
| + | | 4 |Based on contours we extract single lines. We have to check that our application recognize 3 lines and if not we return " | ||
| + | ef extract_lines(self, | ||
| + | line_list = {} | ||
| + | # dilation | ||
| + | cv2.imshow(' | ||
| + | kernel = cv2.getStructuringElement(cv2.MORPH_RECT, | ||
| + | img_dilation = cv2.dilate(image, | ||
| + | # find contours | ||
| + | image, contours, _ = cv2.findContours(img_dilation.copy(), | ||
| + | for cnt in contours: | ||
| + | [x, y, w, h] = cv2.boundingRect(cnt) | ||
| + | # ic contour is big enough | ||
| + | #print w * h | ||
| + | if w * h > 5000: # wszystko co jest poniżej 5000 ignorujemy | ||
| + | line_list[y] = [x, y, w, h] | ||
| + | # sort contours | ||
| + | print " | ||
| + | if len(line_list) == 3: | ||
| + | return sorted(line_list.items()) | ||
| + | else: | ||
| + | print "Read Error, Detected Lines Count Error ! -> %s" % len(line_list) | ||
| + | return None | ||
| + | </ | ||
| + | | 5 |We cut and extract pictures of each lines based on parameters from extract_lines function and we pass it to OCR function.< | ||
| + | def decode(self, | ||
| + | if len(image) > 0 and image is not None: | ||
| + | contours = self.extract_lines(image) | ||
| + | MZR_lines = [] | ||
| + | if contours is not None: | ||
| + | for key, value in contours: | ||
| + | # Get bounding box | ||
| + | x, y, w, h = value | ||
| + | line_image = image[y:y + h, x:x + w] | ||
| + | MZR_lines.append(self.ocr(line_image)) | ||
| + | cv2.imshow(" | ||
| + | return MZR_lines | ||
| + | return None | ||
| + | </ | ||
| + | | 6 |OCR function extract character from lines using extract_chars_from_line function: <code python> | ||
| + | def extract_chars_from_line(self, | ||
| + | image, contours, _ = cv2.findContours(image, | ||
| + | char_list = [] | ||
| + | i = 0 | ||
| + | lista_rect = {} | ||
| + | for cnt in contours: | ||
| + | [x, y, w, h] = cv2.boundingRect(cnt) | ||
| + | if cv2.contourArea(cnt) > 10: | ||
| + | char_list.append([x, | ||
| + | lista_rect[x] = [x, y, w, h] | ||
| + | return sorted(lista_rect.items()) | ||
| + | </ | ||
| + | def validate(self, | ||
| + | print "Read Data: %s" % read_data | ||
| + | print "Test Data: %s" % self.test_lines | ||
| + | read_line_count = len(read_data) | ||
| + | test_line_count = len(self.test_lines) | ||
| + | PASS_COUNT = 0; | ||
| + | FAIL_COUNT = 0; | ||
| + | if (test_line_count == read_line_count): | ||
| + | for i in xrange(read_line_count): | ||
| + | read_line_data = read_data[i] | ||
| + | test_line_data = self.test_lines[i] | ||
| + | if (len(read_line_data) == len(test_line_data)): | ||
| + | for x in xrange(len(read_line_data)): | ||
| + | if read_line_data[x] == test_line_data[x]: | ||
| + | PASS_COUNT += 1 | ||
| + | else: | ||
| + | FAIL_COUNT += 1 | ||
| + | else: | ||
| + | return None | ||
| + | else: | ||
| + | return None | ||
| + | print " | ||
| + | Accuracy = float(float(PASS_COUNT) / float(PASS_COUNT + FAIL_COUNT)) * 100 | ||
| + | return Accuracy | ||
| + | </ | ||
| + | | 7 |Returned string in form of 3 lines array we compare with our prepared pattern. Counting all positive and negaive recognition we calculate accuracy of our engine.< | ||
| + | def validate(self, | ||
| + | print "Read Data: %s" % read_data | ||
| + | print "Test Data: %s" % self.test_lines | ||
| + | read_line_count = len(read_data) | ||
| + | test_line_count = len(self.test_lines) | ||
| + | PASS_COUNT = 0; | ||
| + | FAIL_COUNT = 0; | ||
| + | if (test_line_count == read_line_count): | ||
| + | for i in xrange(read_line_count): | ||
| + | read_line_data = read_data[i] | ||
| + | test_line_data = self.test_lines[i] | ||
| + | if (len(read_line_data) == len(test_line_data)): | ||
| + | for x in xrange(len(read_line_data)): | ||
| + | if read_line_data[x] == test_line_data[x]: | ||
| + | PASS_COUNT += 1 | ||
| + | else: | ||
| + | FAIL_COUNT += 1 | ||
| + | else: | ||
| + | return None | ||
| + | else: | ||
| + | return None | ||
| + | print " | ||
| + | Accuracy = float(float(PASS_COUNT) / float(PASS_COUNT + FAIL_COUNT)) * 100 | ||
| + | return Accuracy | ||
| + | </ | ||
| ====2.3 Photo MRZ Recognition ==== | ====2.3 Photo MRZ Recognition ==== | ||
| - | ====2.3 Video MZR Recognition | + | This module is responsible for decoding MRZ zone from static picture. Algorithm works like this: |
| + | ^Step ^Description ^ | ||
| + | | 1 | Application turn on camera and shows image, user are taking picture using ' | ||
| + | def capture_process(self, | ||
| + | cap = cv2.VideoCapture(cam) | ||
| + | while (True): | ||
| + | ret, frame = cap.read() | ||
| + | gray = cv2.cvtColor(frame, | ||
| + | cv2.imshow(' | ||
| + | key = cv2.waitKey(1) | ||
| + | if chr(key & 255) == ' ': | ||
| + | thresh = self.filtr(gray) | ||
| + | text = self.locate_MZR(thresh, | ||
| + | print text | ||
| + | |||
| + | if chr(key & 255) == ' | ||
| + | break | ||
| + | </ | ||
| + | | 2 |When picture are taken necessary filters are put and picture is passing to function. Original picture and picture with filters is passed to locate_MRZ function which localize and extract MRZ from picture.Filtr() function which localize MRZ zone on ID:<code python> | ||
| + | def filtr(self, image): | ||
| + | rectKernel = cv2.getStructuringElement(cv2.MORPH_RECT, | ||
| + | sqKernel = cv2.getStructuringElement(cv2.MORPH_RECT, | ||
| + | # load the image, resize it, and convert it to grayscal | ||
| + | # smooth the image using a 3x3 Gaussian, then apply the blackhat | ||
| + | # morphological operator to find dark regions on a light background | ||
| + | gray = cv2.GaussianBlur(image, | ||
| + | blackhat = cv2.morphologyEx(gray, | ||
| + | # compute the Scharr gradient of the blackhat image and scale the | ||
| + | # result into the range [0, 255] | ||
| + | gradX = cv2.Sobel(blackhat, | ||
| + | gradX = np.absolute(gradX) | ||
| + | (minVal, maxVal) = (np.min(gradX), | ||
| + | gradX = (255 * ((gradX - minVal) / (maxVal - minVal))).astype(" | ||
| + | # apply a closing operation using the rectangular kernel to close | ||
| + | # gaps in between letters -- then apply Otsu's thresholding method | ||
| + | gradX = cv2.morphologyEx(gradX, | ||
| + | thresh = cv2.threshold(gradX, | ||
| + | # perform another closing operation, this time using the square | ||
| + | # kernel to close gaps between lines of the MRZ, then perform a | ||
| + | # series of erosions to break apart connected components | ||
| + | thresh = cv2.morphologyEx(thresh, | ||
| + | thresh = cv2.erode(thresh, | ||
| + | # during thresholding, | ||
| + | # included in the thresholding, | ||
| + | # right borders to zero | ||
| + | p = int(image.shape[1] * 0.05) | ||
| + | thresh[:, 0:p] = 0 | ||
| + | thresh[:, image.shape[1] - p:] = 0 | ||
| + | return thresh | ||
| + | </ | ||
| + | def locate_MRZ(self, | ||
| + | _oryginal = cv2.adaptiveThreshold(_oryginal, | ||
| + | |||
| + | cnts = cv2.findContours(_thresh.copy(), | ||
| + | cnts = sorted(cnts, | ||
| + | if len(cnts) > 0: | ||
| + | (x, y, w, h) = cv2.boundingRect(cnts[0]) | ||
| + | # Shape[1] jest najwiekszy zatem to zawsze bedzie | ||
| + | pX = int((x + w) * 0.03) | ||
| + | pY = int((y + h) * 0.03) | ||
| + | (x, y) = (x - pX, y - pY) | ||
| + | (w, h) = (w + (pX * 2), h + (pY * 2)) | ||
| + | result = _oryginal[y: | ||
| + | return self.decode(result) | ||
| + | </ | ||
| + | | 3 | This picture are passing to Decode function which cuts picture of every line based on returned parameters of extract_lines function and pass it to OCR function.< | ||
| + | def decode(self, | ||
| + | if len(image) > 0 and image is not None: | ||
| + | contours = self.extract_lines(image) | ||
| + | MZR_lines = [] | ||
| + | if contours is not None: | ||
| + | for key, value in contours: | ||
| + | # Get bounding box | ||
| + | x, y, w, h = value | ||
| + | line_image = image[y:y + h, x:x + w] | ||
| + | MZR_lines.append(self.ocr(line_image)) | ||
| + | cv2.imshow(" | ||
| + | return MZR_lines | ||
| + | return None | ||
| + | </ | ||
| + | | 4 |OCR function extract characters of every line using extract_chars_from_line function: <code python> | ||
| + | def extract_chars_from_line(self, | ||
| + | image, contours, _ = cv2.findContours(image, | ||
| + | char_list = [] | ||
| + | i = 0 | ||
| + | lista_rect = {} | ||
| + | for cnt in contours: | ||
| + | [x, y, w, h] = cv2.boundingRect(cnt) | ||
| + | if cv2.contourArea(cnt) > 10: | ||
| + | char_list.append([x, | ||
| + | lista_rect[x] = [x, y, w, h] | ||
| + | return sorted(lista_rect.items()) | ||
| + | </ | ||
| + | self.clear_folder(' | ||
| + | result = "" | ||
| + | chars = self.extract_chars_from_line(image) | ||
| + | i = 0 | ||
| + | for key, value in chars: | ||
| + | x, y, w, h = value | ||
| + | if h > 10: | ||
| + | # cv2.rectangle(oryginal, | ||
| + | # cv2.imshow(' | ||
| + | roi = image[y:y + h, x:x + w] | ||
| + | roismall = cv2.resize(roi, | ||
| + | file_name = " | ||
| + | self.save_image_to_png(roismall, | ||
| + | roismall = roismall.reshape((1, | ||
| + | roismall = np.float32(roismall) | ||
| + | retval, results, neigh_resp, dists = self.model.findNearest(roismall, | ||
| + | string = str(chr((results[0][0]))) | ||
| + | result += (string) | ||
| + | return result | ||
| + | </ | ||
| + | | 5 | Result are printed to console.| | ||
| + | ====2.3 Video MRZ Recognition ==== | ||
| + | The task of this module is decoding | ||
| + | {{ : | ||
en/edu/zmitac/ai/projects/idrecognition.1523094465.txt.gz · Last modified: by marikol897
