{"id":294385,"date":"2019-09-07T15:00:05","date_gmt":"2019-09-07T15:00:05","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=294385"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=294385","title":{"rendered":"Python + OpenCV + Keras: \u0434\u0435\u043b\u0430\u0435\u043c \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043b\u043a\u0443 \u0442\u0435\u043a\u0441\u0442\u0430 \u0437\u0430 \u043f\u043e\u043b\u0447\u0430\u0441\u0430"},"content":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\">\u041f\u0440\u0438\u0432\u0435\u0442 \u0425\u0430\u0431\u0440.<\/p>\n<p>  \u041f\u043e\u0441\u043b\u0435 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u043e\u0432 \u0441 \u043c\u043d\u043e\u0433\u0438\u043c \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0439 \u0431\u0430\u0437\u043e\u0439 \u0438\u0437 60000 \u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u044b\u0445 \u0446\u0438\u0444\u0440 MNIST \u0432\u043e\u0437\u043d\u0438\u043a \u043b\u043e\u0433\u0438\u0447\u043d\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441, \u0435\u0441\u0442\u044c \u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0445\u043e\u0436\u0435\u0435, \u043d\u043e \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0446\u0438\u0444\u0440, \u043d\u043e \u0438 \u0431\u0443\u043a\u0432. \u041a\u0430\u043a \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0435\u0441\u0442\u044c, \u0438 \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u0430\u044f \u0431\u0430\u0437\u0430, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0433\u0430\u0434\u0430\u0442\u044c\u0441\u044f, Extended MNIST (EMNIST).<\/p>\n<p>  \u0415\u0441\u043b\u0438 \u043a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043a\u0430\u043a \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u0442\u043e\u0439 \u0431\u0430\u0437\u044b \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u0441\u043b\u043e\u0436\u043d\u0443\u044e \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043b\u043a\u0443 \u0442\u0435\u043a\u0441\u0442\u0430, \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/kq\/bl\/4r\/kqbl4rtgmtvz1xl50tzbulmdmlw.png\"><br \/>  <a name=\"habracut\"><\/a><br \/>  <i>\u041f\u0440\u0438\u043c\u0435\u0447\u0430\u043d\u0438\u0435<\/i>: \u0434\u0430\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439 \u0438 \u0443\u0447\u0435\u0431\u043d\u044b\u0439, \u043c\u043d\u0435 \u0431\u044b\u043b\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u0447\u0442\u043e \u0438\u0437 \u044d\u0442\u043e\u0433\u043e \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u0441\u044f. \u0414\u0435\u043b\u0430\u0442\u044c \u0432\u0442\u043e\u0440\u043e\u0439 FineReader \u044f \u043d\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043b \u0438 \u043d\u0435 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u044e, \u0442\u0430\u043a \u0447\u0442\u043e \u043c\u043d\u043e\u0433\u0438\u0435 \u0432\u0435\u0449\u0438 \u0442\u0443\u0442, \u0440\u0430\u0437\u0443\u043c\u0435\u0435\u0442\u0441\u044f, \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043f\u0440\u0435\u0442\u0435\u043d\u0437\u0438\u0438 \u0432 \u0441\u0442\u0438\u043b\u0435 \u00ab\u0437\u0430\u0447\u0435\u043c\u00bb, \u00ab\u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u043b\u0443\u0447\u0448\u0435\u00bb \u0438 \u043f\u0440, \u043d\u0435 \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u044e\u0442\u0441\u044f. \u041d\u0430\u0432\u0435\u0440\u043d\u043e \u0433\u043e\u0442\u043e\u0432\u044b\u0435 OCR-\u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u0434\u043b\u044f Python \u0443\u0436\u0435 \u0435\u0441\u0442\u044c, \u043d\u043e \u0431\u044b\u043b\u043e \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u043c\u0443. \u041a\u0441\u0442\u0430\u0442\u0438, \u0434\u043b\u044f \u0442\u0435\u0445 \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c, \u043a\u0430\u043a \u0434\u0435\u043b\u0430\u043b\u0441\u044f \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0439 FineReader, \u0435\u0441\u0442\u044c \u0434\u0432\u0435 \u0441\u0442\u0430\u0442\u044c\u0438 \u0432 \u0438\u0445 \u0431\u043b\u043e\u0433\u0435 \u043d\u0430 \u0425\u0430\u0431\u0440\u0435 \u0437\u0430 2014 \u0433\u043e\u0434: <a href=\"https:\/\/habr.com\/ru\/company\/abbyy\/blog\/225215\/\">1<\/a> \u0438 <a href=\"https:\/\/habr.com\/ru\/company\/abbyy\/blog\/228251\/\">2<\/a>. \u041d\u0443 \u0430 \u043c\u044b \u043f\u0440\u0438\u0441\u0442\u0443\u043f\u0438\u043c.<\/p>\n<p>  \u0414\u043b\u044f \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u043c\u044b \u0432\u043e\u0437\u044c\u043c\u0435\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0442\u0435\u043a\u0441\u0442. \u0412\u043e\u0442 \u0442\u0430\u043a\u043e\u0439:<\/p>\n<h3>HELLO WORLD<\/h3>\n<p>  \u0418 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c \u0447\u0442\u043e \u0441 \u043d\u0438\u043c \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c.<\/p>\n<h2>\u0420\u0430\u0437\u0431\u0438\u0435\u043d\u0438\u0435 \u0442\u0435\u043a\u0441\u0442\u0430 \u043d\u0430 \u0431\u0443\u043a\u0432\u044b<\/h2>\n<p>  \u041f\u0435\u0440\u0432\u044b\u043c \u0448\u0430\u0433\u043e\u043c \u0440\u0430\u0437\u043e\u0431\u044c\u0435\u043c \u0442\u0435\u043a\u0441\u0442 \u043d\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0435 \u0431\u0443\u043a\u0432\u044b. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u0433\u043e\u0434\u0438\u0442\u0441\u044f OpenCV, \u0442\u043e\u0447\u043d\u0435\u0435 \u0435\u0433\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f findContours.<\/p>\n<p>  \u041e\u0442\u043a\u0440\u043e\u0435\u043c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435 (cv2.imread), \u043f\u0435\u0440\u0435\u0432\u0435\u0434\u0435\u043c \u0435\u0433\u043e \u0432 \u0447\/\u0431 (cv2.cvtColor + cv2.threshold), \u0441\u043b\u0435\u0433\u043a\u0430 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u043c (cv2.erode) \u0438 \u043d\u0430\u0439\u0434\u0435\u043c \u043a\u043e\u043d\u0442\u0443\u0440\u044b.<\/p>\n<pre><code class=\"python\">image_file = \"text.png\" img = cv2.imread(image_file) gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY) img_erode = cv2.erode(thresh, np.ones((3, 3), np.uint8), iterations=1)  # Get contours contours, hierarchy = cv2.findContours(img_erode, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)  output = img.copy()  for idx, contour in enumerate(contours):     (x, y, w, h) = cv2.boundingRect(contour)     # print(\"R\", idx, x, y, w, h, cv2.contourArea(contour), hierarchy[0][idx])     # hierarchy[i][0]: the index of the next contour of the same level     # hierarchy[i][1]: the index of the previous contour of the same level     # hierarchy[i][2]: the index of the first child     # hierarchy[i][3]: the index of the parent     if hierarchy[0][idx][3] == 0:         cv2.rectangle(output, (x, y), (x + w, y + h), (70, 0, 0), 1)   cv2.imshow(\"Input\", img) cv2.imshow(\"Enlarged\", img_erode) cv2.imshow(\"Output\", output) cv2.waitKey(0) <\/code><\/pre>\n<p>  \u041c\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0434\u0435\u0440\u0435\u0432\u043e \u043a\u043e\u043d\u0442\u0443\u0440\u043e\u0432 (\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 cv2.RETR_TREE). \u041f\u0435\u0440\u0432\u044b\u043c \u0438\u0434\u0435\u0442 \u043e\u0431\u0449\u0438\u0439 \u043a\u043e\u043d\u0442\u0443\u0440 \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0438, \u0437\u0430\u0442\u0435\u043c \u043a\u043e\u043d\u0442\u0443\u0440\u044b \u0431\u0443\u043a\u0432, \u0437\u0430\u0442\u0435\u043c \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0443\u0440\u044b. \u041d\u0430\u043c \u043d\u0443\u0436\u043d\u044b \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043d\u0442\u0443\u0440\u044b \u0431\u0443\u043a\u0432, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u044f \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e \u0447\u0442\u043e \u00ab\u0440\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u043c\u00bb \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043e\u0431\u0449\u0438\u0439 \u043a\u043e\u043d\u0442\u0443\u0440. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u043d\u044b\u0439 \u043f\u043e\u0434\u0445\u043e\u0434, \u0438 \u0434\u043b\u044f \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0445 \u0441\u043a\u0430\u043d\u043e\u0432 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u043d\u0435 \u0441\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c, \u0445\u043e\u0442\u044f \u0434\u043b\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f \u0441\u043a\u0440\u0438\u043d\u0448\u043e\u0442\u043e\u0432 \u044d\u0442\u043e \u043d\u0435\u043a\u0440\u0438\u0442\u0438\u0447\u043d\u043e.<\/p>\n<p>  \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/7j\/zi\/pg\/7jzipgqvc9ebvxgu2j7c_ubr-rw.png\"><\/p>\n<p>  \u0421\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0448\u0430\u0433\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u043c \u043a\u0430\u0436\u0434\u0443\u044e \u0431\u0443\u043a\u0432\u0443, \u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043e\u0442\u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u0432 \u0435\u0451 \u0434\u043e \u043a\u0432\u0430\u0434\u0440\u0430\u0442\u0430 28\u044528 (\u0438\u043c\u0435\u043d\u043d\u043e \u0432 \u0442\u0430\u043a\u043e\u043c \u0444\u043e\u0440\u043c\u0430\u0442\u0435 \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u0431\u0430\u0437\u0430 MNIST). OpenCV \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d \u043d\u0430 \u0431\u0430\u0437\u0435 numpy, \u0442\u0430\u043a \u0447\u0442\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043c\u0430\u0441\u0441\u0438\u0432\u0430\u043c\u0438 \u0434\u043b\u044f \u043a\u0440\u043e\u043f\u0430 \u0438 \u043c\u0430\u0441\u0448\u0442\u0430\u0431\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.<\/p>\n<pre><code class=\"python\">def letters_extract(image_file: str, out_size=28) -&gt; List[Any]:     img = cv2.imread(image_file)     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)     ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)     img_erode = cv2.erode(thresh, np.ones((3, 3), np.uint8), iterations=1)      # Get contours     contours, hierarchy = cv2.findContours(img_erode, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)      output = img.copy()      letters = []     for idx, contour in enumerate(contours):         (x, y, w, h) = cv2.boundingRect(contour)         # print(\"R\", idx, x, y, w, h, cv2.contourArea(contour), hierarchy[0][idx])         # hierarchy[i][0]: the index of the next contour of the same level         # hierarchy[i][1]: the index of the previous contour of the same level         # hierarchy[i][2]: the index of the first child         # hierarchy[i][3]: the index of the parent         if hierarchy[0][idx][3] == 0:             cv2.rectangle(output, (x, y), (x + w, y + h), (70, 0, 0), 1)             letter_crop = gray[y:y + h, x:x + w]             # print(letter_crop.shape)              # Resize letter canvas to square             size_max = max(w, h)             letter_square = 255 * np.ones(shape=[size_max, size_max], dtype=np.uint8)             if w &gt; h:                 # Enlarge image top-bottom                 # ------                 # ======                 # ------                 y_pos = size_max\/\/2 - h\/\/2                 letter_square[y_pos:y_pos + h, 0:w] = letter_crop             elif w &lt; h:                 # Enlarge image left-right                 # --||--                 x_pos = size_max\/\/2 - w\/\/2                 letter_square[0:h, x_pos:x_pos + w] = letter_crop             else:                 letter_square = letter_crop              # Resize letter to 28x28 and add letter and its X-coordinate             letters.append((x, w, cv2.resize(letter_square, (out_size, out_size), interpolation=cv2.INTER_AREA)))      # Sort array in place by X-coordinate     letters.sort(key=lambda x: x[0], reverse=False)      return letters <\/code><\/pre>\n<p>  \u0412 \u043a\u043e\u043d\u0446\u0435 \u043c\u044b \u0441\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0431\u0443\u043a\u0432\u044b \u043f\u043e \u0425-\u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0435, \u0442\u0430\u043a\u0436\u0435 \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c, \u043c\u044b \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0432 \u0432\u0438\u0434\u0435 tuple (x, w, letter), \u0447\u0442\u043e\u0431\u044b \u0438\u0437 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u043e\u0432 \u043c\u0435\u0436\u0434\u0443 \u0431\u0443\u043a\u0432\u0430\u043c\u0438 \u043f\u043e\u0442\u043e\u043c \u0432\u044b\u0434\u0435\u043b\u0438\u0442\u044c \u043f\u0440\u043e\u0431\u0435\u043b\u044b.<\/p>\n<p>  \u0423\u0431\u0435\u0436\u0434\u0430\u0435\u043c\u0441\u044f \u0447\u0442\u043e \u0432\u0441\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442:  <\/p>\n<pre><code class=\"python\">cv2.imshow(\"0\", letters[0][2]) cv2.imshow(\"1\", letters[1][2]) cv2.imshow(\"2\", letters[2][2]) cv2.imshow(\"3\", letters[3][2]) cv2.imshow(\"4\", letters[4][2]) cv2.waitKey(0)<\/code><\/pre>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/j-\/uw\/yh\/j-uwyhhh8l0yrapth5u6fy9na0u.png\"><\/p>\n<p>  \u0411\u0443\u043a\u0432\u044b \u0433\u043e\u0442\u043e\u0432\u044b \u0434\u043b\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f, \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u0442\u044c \u0438\u0445 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u0432\u0435\u0440\u0442\u043e\u0447\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u2014 \u044d\u0442\u043e\u0442 \u0442\u0438\u043f \u0441\u0435\u0442\u0435\u0439 \u043d\u0435\u043f\u043b\u043e\u0445\u043e \u043f\u043e\u0434\u0445\u043e\u0434\u0438\u0442 \u0434\u043b\u044f \u0442\u0430\u043a\u0438\u0445 \u0437\u0430\u0434\u0430\u0447. <\/p>\n<h2>\u041d\u0435\u0439\u0440\u043e\u043d\u043d\u0430\u044f \u0441\u0435\u0442\u044c (CNN) \u0434\u043b\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f<\/h2>\n<p>  \u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u0434\u0430\u0442\u0430\u0441\u0435\u0442 EMNIST \u0438\u043c\u0435\u0435\u0442 62 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0438\u043c\u0432\u043e\u043b\u0430 (A..Z, 0..9 \u0438 \u043f\u0440):<\/p>\n<pre><code class=\"python\">emnist_labels = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122]<\/code><\/pre>\n<p>  \u041d\u0435\u0439\u0440\u043e\u043d\u043d\u0430\u044f \u0441\u0435\u0442\u044c \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u0438\u043c\u0435\u0435\u0442 62 \u0432\u044b\u0445\u043e\u0434\u0430, \u043d\u0430 \u0432\u0445\u043e\u0434\u0435 \u043e\u043d\u0430 \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f 28\u044528, \u043f\u043e\u0441\u043b\u0435 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f \u00ab1\u00bb \u0431\u0443\u0434\u0435\u0442 \u043d\u0430 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u043c \u0432\u044b\u0445\u043e\u0434\u0435 \u0441\u0435\u0442\u0438.<\/p>\n<p>  \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0441\u0435\u0442\u0438.  <\/p>\n<pre><code class=\"python\">from tensorflow import keras from keras.models import Sequential from keras import optimizers from keras.layers import Convolution2D, MaxPooling2D, Dropout, Flatten, Dense, Reshape, LSTM, BatchNormalization from keras.optimizers import SGD, RMSprop, Adam from keras import backend as K from keras.constraints import maxnorm import tensorflow as tf  def emnist_model():     model = Sequential()     model.add(Convolution2D(filters=32, kernel_size=(3, 3), padding='valid', input_shape=(28, 28, 1), activation='relu'))     model.add(Convolution2D(filters=64, kernel_size=(3, 3), activation='relu'))     model.add(MaxPooling2D(pool_size=(2, 2)))     model.add(Dropout(0.25))     model.add(Flatten())     model.add(Dense(512, activation='relu'))     model.add(Dropout(0.5))     model.add(Dense(len(emnist_labels), activation='softmax'))     model.compile(loss='categorical_crossentropy', optimizer='adadelta', metrics=['accuracy'])     return model<\/code><\/pre>\n<p>  \u041a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c, \u044d\u0442\u043e \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u0432\u0435\u0440\u0442\u043e\u0447\u043d\u0430\u044f \u0441\u0435\u0442\u044c, \u0432\u044b\u0434\u0435\u043b\u044f\u044e\u0449\u0430\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u0438\u0437\u043d\u0430\u043a\u0438 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f (\u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 32 \u0438 64), \u043a \u00ab\u0432\u044b\u0445\u043e\u0434\u0443\u00bb \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043f\u043e\u0434\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0430 \u00ab\u043b\u0438\u043d\u0435\u0439\u043d\u0430\u044f\u00bb \u0441\u0435\u0442\u044c MLP, \u0444\u043e\u0440\u043c\u0438\u0440\u0443\u044e\u0449\u0430\u044f \u043e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442.<\/p>\n<h2>\u041e\u0431\u0443\u0447\u0435\u043d\u0438\u0435 \u043d\u0435\u0439\u0440\u043e\u043d\u043d\u043e\u0439 \u0441\u0435\u0442\u0438<\/h2>\n<p>  \u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u0441\u0430\u043c\u043e\u043c\u0443 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u044d\u0442\u0430\u043f\u0443 \u2014 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044e \u0441\u0435\u0442\u0438. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0432\u043e\u0437\u044c\u043c\u0435\u043c \u0431\u0430\u0437\u0443 EMNIST, \u0441\u043a\u0430\u0447\u0430\u0442\u044c \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e <a href=\"https:\/\/www.itl.nist.gov\/iaui\/vip\/cs_links\/EMNIST\/gzip.zip\">\u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435<\/a> (\u0440\u0430\u0437\u043c\u0435\u0440 \u0430\u0440\u0445\u0438\u0432\u0430 536\u041c\u0431).<\/p>\n<p>  \u0414\u043b\u044f \u0447\u0442\u0435\u043d\u0438\u044f \u0431\u0430\u0437\u044b \u0432\u043e\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u0441\u044f \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u043e\u0439 idx2numpy. \u041f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438.<\/p>\n<pre><code class=\"python\">import idx2numpy  emnist_path = '\/home\/Documents\/TestApps\/keras\/emnist\/' X_train = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-train-images-idx3-ubyte') y_train = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-train-labels-idx1-ubyte')  X_test = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-test-images-idx3-ubyte') y_test = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-test-labels-idx1-ubyte')  X_train = np.reshape(X_train, (X_train.shape[0], 28, 28, 1)) X_test = np.reshape(X_test, (X_test.shape[0], 28, 28, 1))  print(X_train.shape, y_train.shape, X_test.shape, y_test.shape, len(emnist_labels))  k = 10 X_train = X_train[:X_train.shape[0] \/\/ k] y_train = y_train[:y_train.shape[0] \/\/ k] X_test = X_test[:X_test.shape[0] \/\/ k] y_test = y_test[:y_test.shape[0] \/\/ k]  # Normalize X_train = X_train.astype(np.float32) X_train \/= 255.0 X_test = X_test.astype(np.float32) X_test \/= 255.0  x_train_cat = keras.utils.to_categorical(y_train, len(emnist_labels)) y_test_cat = keras.utils.to_categorical(y_test, len(emnist_labels)) <\/code><\/pre>\n<p>  \u041c\u044b \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438 \u0434\u0432\u0430 \u043d\u0430\u0431\u043e\u0440\u0430, \u0434\u043b\u044f \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0438 \u0432\u0430\u043b\u0438\u0434\u0430\u0446\u0438\u0438. \u0421\u0430\u043c\u0438 \u0441\u0438\u043c\u0432\u043e\u043b\u044b \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0441\u043e\u0431\u043e\u0439 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u043c\u0430\u0441\u0441\u0438\u0432\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435\u0441\u043b\u043e\u0436\u043d\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u043d\u0430 \u044d\u043a\u0440\u0430\u043d: <\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/lb\/uj\/yt\/lbujytoizk2gxviahqxz5emvgay.png\"><\/p>\n<p>  \u0422\u0430\u043a\u0436\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043b\u0438\u0448\u044c 1\/10 \u0434\u0430\u0442\u0430\u0441\u0435\u0442\u0430 \u0434\u043b\u044f \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f (\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 k), \u0432 \u043f\u0440\u043e\u0442\u0438\u0432\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u0437\u0430\u0439\u043c\u0435\u0442 \u043d\u0435 \u043c\u0435\u043d\u0435\u0435 10 \u0447\u0430\u0441\u043e\u0432.<\/p>\n<p>  \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u0435 \u0441\u0435\u0442\u0438, \u0432 \u043a\u043e\u043d\u0446\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u043c \u043e\u0431\u0443\u0447\u0435\u043d\u043d\u0443\u044e \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430 \u0434\u0438\u0441\u043a.  <\/p>\n<pre><code class=\"python\"># Set a learning rate reduction learning_rate_reduction = keras.callbacks.ReduceLROnPlateau(monitor='val_acc', patience=3, verbose=1, factor=0.5, min_lr=0.00001)  # Required for learning_rate_reduction: keras.backend.get_session().run(tf.global_variables_initializer())  model.fit(X_train, x_train_cat, validation_data=(X_test, y_test_cat), callbacks=[learning_rate_reduction], batch_size=64, epochs=30)  model.save('emnist_letters.h5')<\/code><\/pre>\n<p>  \u0421\u0430\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u044f \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u043e\u043a\u043e\u043b\u043e \u043f\u043e\u043b\u0443\u0447\u0430\u0441\u0430:<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/vu\/xv\/_s\/vuxv_s6hsxg1q0gxcqapd0o3bv8.png\"><\/p>\n<p>  \u042d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u0440\u0430\u0437, \u0434\u0430\u043b\u044c\u0448\u0435 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0443\u0436\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u044b\u043c \u0444\u0430\u0439\u043b\u043e\u043c \u043c\u043e\u0434\u0435\u043b\u0438. \u041a\u043e\u0433\u0434\u0430 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u0435 \u0437\u0430\u043a\u043e\u043d\u0447\u0435\u043d\u043e, \u0432\u0441\u0435 \u0433\u043e\u0442\u043e\u0432\u043e, \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u0442\u044c \u0442\u0435\u043a\u0441\u0442.<\/p>\n<h2>\u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435<\/h2>\n<p>  \u0414\u043b\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f \u043c\u044b \u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u0438 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e predict_classes.<\/p>\n<pre><code class=\"python\">model = keras.models.load_model('emnist_letters.h5')  def emnist_predict_img(model, img):     img_arr = np.expand_dims(img, axis=0)     img_arr = 1 - img_arr\/255.0     img_arr[0] = np.rot90(img_arr[0], 3)     img_arr[0] = np.fliplr(img_arr[0])     img_arr = img_arr.reshape((1, 28, 28, 1))      result = model.predict_classes([img_arr])     return chr(emnist_labels[result[0]])<\/code><\/pre>\n<p>  \u041a\u0430\u043a \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u044f \u0432 \u0434\u0430\u0442\u0430\u0441\u0435\u0442\u0435 \u0438\u0437\u043d\u0430\u0447\u0430\u043b\u044c\u043d\u043e \u0431\u044b\u043b\u0438 \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044b, \u0442\u0430\u043a \u0447\u0442\u043e \u043d\u0430\u043c \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043f\u043e\u0432\u0435\u0440\u043d\u0443\u0442\u044c \u043a\u0430\u0440\u0442\u0438\u043d\u043a\u0443 \u043f\u0435\u0440\u0435\u0434 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u0435\u043c.<\/p>\n<p>  \u041e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0430 \u0432\u0445\u043e\u0434\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0444\u0430\u0439\u043b \u0441 \u0438\u0437\u043e\u0431\u0440\u0430\u0436\u0435\u043d\u0438\u0435\u043c, \u0430 \u043d\u0430 \u0432\u044b\u0445\u043e\u0434\u0435 \u0434\u0430\u0435\u0442 \u0441\u0442\u0440\u043e\u043a\u0443, \u0437\u0430\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0441\u0435\u0433\u043e 10 \u0441\u0442\u0440\u043e\u043a \u043a\u043e\u0434\u0430:<\/p>\n<pre><code class=\"python\">def img_to_str(model: Any, image_file: str):     letters = letters_extract(image_file)     s_out = \"\"     for i in range(len(letters)):         dn = letters[i+1][0] - letters[i][0] - letters[i][1] if i &lt; len(letters) - 1 else 0         s_out += emnist_predict_img(model, letters[i][2])         if (dn &gt; letters[i][1]\/4):             s_out += ' '     return s_out<\/code><\/pre>\n<p>  \u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u0443\u044e \u0440\u0430\u043d\u0435\u0435 \u0448\u0438\u0440\u0438\u043d\u0443 \u0441\u0438\u043c\u0432\u043e\u043b\u0430, \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043f\u0440\u043e\u0431\u0435\u043b\u044b, \u0435\u0441\u043b\u0438 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u043a \u043c\u0435\u0436\u0434\u0443 \u0431\u0443\u043a\u0432\u0430\u043c\u0438 \u0431\u043e\u043b\u0435\u0435 1\/4 \u0441\u0438\u043c\u0432\u043e\u043b\u0430.<\/p>\n<p>  \u041f\u0440\u0438\u043c\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f:  <\/p>\n<pre><code class=\"python\">model = keras.models.load_model('emnist_letters.h5') s_out = img_to_str(model, \"hello_world.png\") print(s_out) <\/code><\/pre>\n<p>  \u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442:<br \/>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/ck\/r5\/iq\/ckr5iqlfoiokza60cleu3w1x-hg.png\"><\/p>\n<p>  \u0417\u0430\u0431\u0430\u0432\u043d\u0430\u044f \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u044c \u2014 \u043d\u0435\u0439\u0440\u043e\u043d\u043d\u0430\u044f \u0441\u0435\u0442\u044c \u00ab\u043f\u0435\u0440\u0435\u043f\u0443\u0442\u0430\u043b\u0430\u00bb \u0431\u0443\u043a\u0432\u0443 \u00ab\u041e\u00bb \u0438 \u0446\u0438\u0444\u0440\u0443 \u00ab0\u00bb, \u0447\u0442\u043e \u0432\u043f\u0440\u043e\u0447\u0435\u043c, \u043d\u0435\u0443\u0434\u0438\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0442.\u043a. \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 EMNIST \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 <i>\u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u044b\u0435<\/i> \u0431\u0443\u043a\u0432\u044b \u0438 \u0446\u0438\u0444\u0440\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0441\u043e\u0432\u0441\u0435\u043c \u043f\u043e\u0445\u043e\u0436\u0438 \u043d\u0430 \u043f\u0435\u0447\u0430\u0442\u043d\u044b\u0435. \u0412 \u0438\u0434\u0435\u0430\u043b\u0435, \u0434\u043b\u044f \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043d\u0438\u044f \u044d\u043a\u0440\u0430\u043d\u043d\u044b\u0445 \u0442\u0435\u043a\u0441\u0442\u043e\u0432 \u043d\u0443\u0436\u043d\u043e \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 \u043d\u0430 \u0431\u0430\u0437\u0435 \u044d\u043a\u0440\u0430\u043d\u043d\u044b\u0445 \u0448\u0440\u0438\u0444\u0442\u043e\u0432, \u0438 \u0443\u0436\u0435 \u043d\u0430 \u043d\u0435\u043c \u043e\u0431\u0443\u0447\u0430\u0442\u044c \u043d\u0435\u0439\u0440\u043e\u0441\u0435\u0442\u044c.<\/p>\n<h2>\u0417\u0430\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435<\/h2>\n<p>  \u041a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0432\u0438\u0434\u0435\u0442\u044c, \u043d\u0435 \u0431\u043e\u0433\u0438 \u0433\u043e\u0440\u0448\u043a\u0438 \u043e\u0431\u0436\u0438\u0433\u0430\u044e\u0442, \u0438 \u0442\u043e \u0447\u0442\u043e \u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c \u043a\u043e\u0433\u0434\u0430-\u0442\u043e \u00ab\u043c\u0430\u0433\u0438\u0435\u0439\u00bb, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a \u0434\u0435\u043b\u0430\u0435\u0442\u0441\u044f \u0432\u043f\u043e\u043b\u043d\u0435 \u043d\u0435\u0441\u043b\u043e\u0436\u043d\u043e.<\/p>\n<p>  \u0414\u043b\u044f \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0445 \u043f\u043e\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u0430\u043c\u043e\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e, \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u043f\u043e\u0434 \u0441\u043f\u043e\u0439\u043b\u0435\u0440\u043e\u043c.  <\/p>\n<div class=\"spoiler\"><b class=\"spoiler_title\">keras_emnist.py<\/b><\/p>\n<div class=\"spoiler_text\">\n<pre><code class=\"python\"># Code source: dmitryelj@gmail.com  import os # Force CPU # os.environ[\"CUDA_VISIBLE_DEVICES\"] = \"-1\" # Debug messages # 0 = all messages are logged (default behavior) # 1 = INFO messages are not printed # 2 = INFO and WARNING messages are not printed # 3 = INFO, WARNING, and ERROR messages are not printed os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'   import cv2 import imghdr import numpy as np import pathlib from tensorflow import keras from keras.models import Sequential from keras import optimizers from keras.layers import Convolution2D, MaxPooling2D, Dropout, Flatten, Dense, Reshape, LSTM, BatchNormalization from keras.optimizers import SGD, RMSprop, Adam from keras import backend as K from keras.constraints import maxnorm import tensorflow as tf from scipy import io as spio import idx2numpy  # sudo pip3 install idx2numpy from matplotlib import pyplot as plt from typing import * import time   # Dataset: # https:\/\/www.nist.gov\/node\/1298471\/emnist-dataset # https:\/\/www.itl.nist.gov\/iaui\/vip\/cs_links\/EMNIST\/gzip.zip   def cnn_print_digit(d):     print(d.shape)     for x in range(28):         s = \"\"         for y in range(28):             s += \"{0:.1f} \".format(d[28*y + x])         print(s)   def cnn_print_digit_2d(d):     print(d.shape)     for y in range(d.shape[0]):         s = \"\"         for x in range(d.shape[1]):             s += \"{0:.1f} \".format(d[x][y])         print(s)   emnist_labels = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122]   def emnist_model():     model = Sequential()     model.add(Convolution2D(filters=32, kernel_size=(3, 3), padding='valid', input_shape=(28, 28, 1), activation='relu'))     model.add(Convolution2D(filters=64, kernel_size=(3, 3), activation='relu'))     model.add(MaxPooling2D(pool_size=(2, 2)))     model.add(Dropout(0.25))     model.add(Flatten())     model.add(Dense(512, activation='relu'))     model.add(Dropout(0.5))     model.add(Dense(len(emnist_labels), activation='softmax'))     model.compile(loss='categorical_crossentropy', optimizer='adadelta', metrics=['accuracy'])     return model  def emnist_model2():     model = Sequential()     # In Keras there are two options for padding: same or valid. Same means we pad with the number on the edge and valid means no padding.     model.add(Convolution2D(filters=32, kernel_size=(3, 3), activation='relu', padding='same', input_shape=(28, 28, 1)))     model.add(MaxPooling2D((2, 2)))     model.add(Convolution2D(64, (3, 3), activation='relu', padding='same'))     model.add(MaxPooling2D((2, 2)))     model.add(Convolution2D(128, (3, 3), activation='relu', padding='same'))     model.add(MaxPooling2D((2, 2)))     # model.add(Conv2D(128, (3, 3), activation='relu', padding='same'))     # model.add(MaxPooling2D((2, 2)))     ## model.add(Dropout(0.25))     model.add(Flatten())     model.add(Dense(512, activation='relu'))     model.add(Dropout(0.5))     model.add(Dense(len(emnist_labels), activation='softmax'))     model.compile(loss='categorical_crossentropy', optimizer='adadelta', metrics=['accuracy'])     return model  def emnist_model3():     model = Sequential()     model.add(Convolution2D(filters=32, kernel_size=(3, 3), padding='same', input_shape=(28, 28, 1), activation='relu'))     model.add(Convolution2D(filters=32, kernel_size=(3, 3), padding='same', activation='relu'))     model.add(MaxPooling2D(pool_size=(2, 2)))     model.add(Dropout(0.25))      model.add(Convolution2D(filters=64, kernel_size=(3, 3), padding='same', activation='relu'))     model.add(Convolution2D(filters=64, kernel_size=(3, 3), padding='same', activation='relu'))     model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))     model.add(Dropout(0.25))      model.add(Flatten())     model.add(Dense(512, activation=\"relu\"))     model.add(Dropout(0.5))     model.add(Dense(len(emnist_labels), activation=\"softmax\"))     model.compile(loss='categorical_crossentropy', optimizer=RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0), metrics=['accuracy'])     return model   def emnist_train(model):     t_start = time.time()      emnist_path = 'D:\\\\Temp\\\\1\\\\'     X_train = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-train-images-idx3-ubyte')     y_train = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-train-labels-idx1-ubyte')      X_test = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-test-images-idx3-ubyte')     y_test = idx2numpy.convert_from_file(emnist_path + 'emnist-byclass-test-labels-idx1-ubyte')      X_train = np.reshape(X_train, (X_train.shape[0], 28, 28, 1))     X_test = np.reshape(X_test, (X_test.shape[0], 28, 28, 1))      print(X_train.shape, y_train.shape, X_test.shape, y_test.shape, len(emnist_labels))      # Test:     k = 10     X_train = X_train[:X_train.shape[0] \/\/ k]     y_train = y_train[:y_train.shape[0] \/\/ k]     X_test = X_test[:X_test.shape[0] \/\/ k]     y_test = y_test[:y_test.shape[0] \/\/ k]      # Normalize     X_train = X_train.astype(np.float32)     X_train \/= 255.0     X_test = X_test.astype(np.float32)     X_test \/= 255.0      x_train_cat = keras.utils.to_categorical(y_train, len(emnist_labels))     y_test_cat = keras.utils.to_categorical(y_test, len(emnist_labels))      # Set a learning rate reduction     learning_rate_reduction = keras.callbacks.ReduceLROnPlateau(monitor='val_acc', patience=3, verbose=1, factor=0.5, min_lr=0.00001)      # Required for learning_rate_reduction:     keras.backend.get_session().run(tf.global_variables_initializer())      model.fit(X_train, x_train_cat, validation_data=(X_test, y_test_cat), callbacks=[learning_rate_reduction], batch_size=64, epochs=30)     print(\"Training done, dT:\", time.time() - t_start)   def emnist_predict(model, image_file):     img = keras.preprocessing.image.load_img(image_file, target_size=(28, 28), color_mode='grayscale')     emnist_predict_img(model, img)   def emnist_predict_img(model, img):     img_arr = np.expand_dims(img, axis=0)     img_arr = 1 - img_arr\/255.0     img_arr[0] = np.rot90(img_arr[0], 3)     img_arr[0] = np.fliplr(img_arr[0])     img_arr = img_arr.reshape((1, 28, 28, 1))      result = model.predict_classes([img_arr])     return chr(emnist_labels[result[0]])   def letters_extract(image_file: str, out_size=28):     img = cv2.imread(image_file)     gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)     ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY)     img_erode = cv2.erode(thresh, np.ones((3, 3), np.uint8), iterations=1)      # Get contours     contours, hierarchy = cv2.findContours(img_erode, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)      # hierarchy[i][0]: the index of the next contour of the same level     # hierarchy[i][1]: the index of the previous contour of the same level     # hierarchy[i][2]: the index of the first child     # hierarchy[i][3]: the index of the parent      output = img.copy()      letters = []     for idx, contour in enumerate(contours):         # contour, hier = component[0], component[1]         (x, y, w, h) = cv2.boundingRect(contour)         # print(\"R\", idx, x, y, w, h, cv2.contourArea(contour), hierarchy[0][idx])         if hierarchy[0][idx][3] == 0:             cv2.rectangle(output, (x, y), (x + w, y + h), (70, 0, 0), 1)             letter_crop = gray[y:y + h, x:x + w]             # print(letter_crop.shape)              # Resize letter canvas to square             size_max = max(w, h)             letter_square = 255 * np.ones(shape=[size_max, size_max], dtype=np.uint8)             if w &gt; h:                 # Enlarge image top-bottom                 # ------                 # ======                 # ------                 y_pos = size_max\/\/2 - h\/\/2                 letter_square[y_pos:y_pos + h, 0:w] = letter_crop             elif w &lt; h:                 # Enlarge image left-right                 # --||--                 x_pos = size_max\/\/2 - w\/\/2                 letter_square[0:h, x_pos:x_pos + w] = letter_crop             else:                 letter_square = letter_crop              # Resize letter to 28x28 and add letter and its X-coordinate             letters.append((x, w, cv2.resize(letter_square, (out_size, out_size), interpolation=cv2.INTER_AREA)))      # Sort array in place by X-coordinate     letters.sort(key=lambda x: x[0], reverse=False)      # cv2.imshow(\"Input\", img)     # # cv2.imshow(\"Gray\", thresh)     # cv2.imshow(\"Enlarged\", img_erode)     # cv2.imshow(\"Output\", output)     # cv2.imshow(\"0\", letters[0][2])     # cv2.imshow(\"1\", letters[1][2])     # cv2.imshow(\"2\", letters[2][2])     # cv2.imshow(\"3\", letters[3][2])     # cv2.imshow(\"4\", letters[4][2])     # cv2.waitKey(0)     return letters   def img_to_str(model: Any, image_file: str):     letters = letters_extract(image_file)     s_out = \"\"     for i in range(len(letters)):         dn = letters[i+1][0] - letters[i][0] - letters[i][1] if i &lt; len(letters) - 1 else 0         s_out += emnist_predict_img(model, letters[i][2])         if (dn &gt; letters[i][1]\/4):             s_out += ' '     return s_out   if __name__ == \"__main__\":      # model = emnist_model()     # emnist_train(model)     # model.save('emnist_letters.h5')      model = keras.models.load_model('emnist_letters.h5')     s_out = img_to_str(model, \"hello_world.png\")     print(s_out) <\/code><\/pre>\n<p>  <\/div>\n<\/div>\n<p>  \u041a\u0430\u043a \u043e\u0431\u044b\u0447\u043d\u043e, \u0432\u0441\u0435\u043c \u0443\u0434\u0430\u0447\u043d\u044b\u0445 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u043e\u0432.<\/div>\n<p>               <script class=\"js-mediator-script\">!function(e){function t(t,n){if(!(n in e)){for(var r,a=e.document,i=a.scripts,o=i.length;o--;)if(-1!==i[o].src.indexOf(t)){r=i[o];break}if(!r){r=a.createElement(\"script\"),r.type=\"text\/javascript\",r.async=!0,r.defer=!0,r.src=t,r.charset=\"UTF-8\";var d=function(){var e=a.getElementsByTagName(\"script\")[0];e.parentNode.insertBefore(r,e)};\"[object Opera]\"==e.opera?a.addEventListener?a.addEventListener(\"DOMContentLoaded\",d,!1):e.attachEvent(\"onload\",d):d()}}}t(\"\/\/mediator.mail.ru\/script\/2820404\/\",\"_mediator\")}(window);<\/script>     <br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/466565\/\"> https:\/\/habr.com\/ru\/post\/466565\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html js-mediator-article\">\u041f\u0440\u0438\u0432\u0435\u0442 \u0425\u0430\u0431\u0440.<\/p>\n<p>  \u041f\u043e\u0441\u043b\u0435 \u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u043e\u0432 \u0441 \u043c\u043d\u043e\u0433\u0438\u043c \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0439 \u0431\u0430\u0437\u043e\u0439 \u0438\u0437 60000 \u0440\u0443\u043a\u043e\u043f\u0438\u0441\u043d\u044b\u0445 \u0446\u0438\u0444\u0440 MNIST \u0432\u043e\u0437\u043d\u0438\u043a \u043b\u043e\u0433\u0438\u0447\u043d\u044b\u0439 \u0432\u043e\u043f\u0440\u043e\u0441, \u0435\u0441\u0442\u044c \u043b\u0438 \u0447\u0442\u043e-\u0442\u043e \u043f\u043e\u0445\u043e\u0436\u0435\u0435, \u043d\u043e \u0441 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u043e\u0439 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u0446\u0438\u0444\u0440, \u043d\u043e \u0438 \u0431\u0443\u043a\u0432. \u041a\u0430\u043a \u043e\u043a\u0430\u0437\u0430\u043b\u043e\u0441\u044c, \u0435\u0441\u0442\u044c, \u0438 \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0442\u0430\u043a\u0430\u044f \u0431\u0430\u0437\u0430, \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0433\u0430\u0434\u0430\u0442\u044c\u0441\u044f, Extended MNIST (EMNIST).<\/p>\n<p>  \u0415\u0441\u043b\u0438 \u043a\u043e\u043c\u0443 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u043d\u043e, \u043a\u0430\u043a \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u0442\u043e\u0439 \u0431\u0430\u0437\u044b \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435\u0441\u043b\u043e\u0436\u043d\u0443\u044e \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0432\u0430\u043b\u043a\u0443 \u0442\u0435\u043a\u0441\u0442\u0430, \u0434\u043e\u0431\u0440\u043e \u043f\u043e\u0436\u0430\u043b\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u0434 \u043a\u0430\u0442.<\/p>\n<p>  <img decoding=\"async\" src=\"https:\/\/habrastorage.org\/webt\/kq\/bl\/4r\/kqbl4rtgmtvz1xl50tzbulmdmlw.png\">  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-294385","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/294385","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=294385"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/294385\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=294385"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=294385"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=294385"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}