I use Tesseract and python to read digits (from a energy meter).
Everything works well except for the number "1".
Tesseract can not read the "1" Digit.
This is the picture I send to tesseract :
And tesseract reads "0000027 ".
How can I tell Tesseract that the vertical rod is a "1" ?
This is my tesseract initialisation :
import tesseractTESSERACT_LIBRARY_PATH = "C:\\Program Files (x86)\\Tesseract-OCR"
LANGUAGE = "eng"
CHARACTERS = "0123456789"
FALSE = "0"
TRUE = "1"def init_ocr():""" .. py:function:: init_ocr()Utilize the Tesseract-OCR library to create an tesseract_ocr that predicts the numbers to be read off of the meter. :return: tesseract_ocr Tesseracts OCR API.:rtype: Class"""# Initialize the tesseract_ocr with the english language package.tesseract_ocr = tesseract.TessBaseAPI()tesseract_ocr.Init(TESSERACT_LIBRARY_PATH, LANGUAGE, tesseract.OEM_DEFAULT)# Limit the characters being seached for to numerics.tesseract_ocr.SetVariable("tessedit_char_whitelist", CHARACTERS)# Set the tesseract_ocr to predict for only one character.tesseract_ocr.SetPageSegMode(tesseract.PSM_AUTO)# Tesseract's Directed Acyclic Graph.# Not necessary for number recognition.tesseract_ocr.SetVariable("load_system_dawg", FALSE)tesseract_ocr.SetVariable("load_freq_dawg", FALSE)tesseract_ocr.SetVariable("load_number_dawg", TRUE)tesseract_ocr.SetVariable("classify_enable_learning", FALSE)tesseract_ocr.SetVariable("classify_enable_adaptive_matcher", FALSE)return tesseract_ocr
Slightly irrelevant answer, though may serve your original goal.
I had similar problem with tesseract and I had very strict performance requirements as well. I found this simple solution on SO and crafted simple recogniser with OpenCV.
It boils down to finding bounding rectangles (from edges) on the very clear image that you have and then trying to match found objects versus templates. I believe the solution in your case will be both simple and precise though will require slightly more code than you have now.
I will follow this question, since it will be nice to have working solution with tesseract.
I have a limited time, but it seems to be a working solution:
import os
import cv2
import numpy
KNN_SQUARE_SIDE = 50 # Square 50 x 50 px.def resize(cv_image, factor):new_size = tuple(map(lambda x: x * factor, cv_image.shape[::-1]))return cv2.resize(cv_image, new_size)def crop(cv_image, box):x0, y0, x1, y1 = boxreturn cv_image[y0:y1, x0:x1]def draw_box(cv_image, box):x0, y0, x1, y1 = boxcv2.rectangle(cv_image, (x0, y0), (x1, y1), (0, 0, 255), 2)def draw_boxes_and_show(cv_image, boxes, title='N'):temp_image = cv2.cvtColor(cv_image, cv2.COLOR_GRAY2RGB)for box in boxes:draw_box(temp_image, box)cv2.imshow(title, temp_image)cv2.waitKey(0)class BaseKnnMatcher(object):distance_threshold = 0def __init__(self, source_dir):self.model, self.label_map = self.get_model_and_label_map(source_dir)@staticmethoddef get_model_and_label_map(source_dir):responses = []label_map = []samples = numpy.empty((0, KNN_SQUARE_SIDE * KNN_SQUARE_SIDE), numpy.float32)for label_idx, filename in enumerate(os.listdir(source_dir)):label = filename[:filename.index('.png')]label_map.append(label)responses.append(label_idx)image = cv2.imread(os.path.join(source_dir, filename), 0)suit_image_standard_size = cv2.resize(image, (KNN_SQUARE_SIDE, KNN_SQUARE_SIDE))sample = suit_image_standard_size.reshape((1, KNN_SQUARE_SIDE * KNN_SQUARE_SIDE))samples = numpy.append(samples, sample, 0)responses = numpy.array(responses, numpy.float32)responses = responses.reshape((responses.size, 1))model = cv2.KNearest()model.train(samples, responses)return model, label_mapdef predict(self, image):image_standard_size = cv2.resize(image, (KNN_SQUARE_SIDE, KNN_SQUARE_SIDE))image_standard_size = numpy.float32(image_standard_size.reshape((1, KNN_SQUARE_SIDE * KNN_SQUARE_SIDE)))closest_class, results, neigh_resp, distance = self.model.find_nearest(image_standard_size, k=1)if distance[0][0] > self.distance_threshold:return Nonereturn self.label_map[int(closest_class)]class DigitKnnMatcher(BaseKnnMatcher):distance_threshold = 10 ** 10class MeterValueReader(object):def __init__(self):self.digit_knn_matcher = DigitKnnMatcher(source_dir='templates')@classmethoddef get_symbol_boxes(cls, cv_image):ret, thresh = cv2.threshold(cv_image.copy(), 150, 255, cv2.THRESH_BINARY)contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)symbol_boxes = []for contour in contours:x, y, width, height = cv2.boundingRect(contour)# You can test here for box size, though not required in your example:# if cls.is_size_of_digit(width, height):# symbol_boxes.append((x, y, x+width, y+height))symbol_boxes.append((x, y, x+width, y+height))return symbol_boxesdef get_value(self, meter_cv2_image):symbol_boxes = self.get_symbol_boxes(meter_cv2_image)symbol_boxes.sort() # x is first in tuplesymbols = []for box in symbol_boxes:symbol = self.digit_knn_matcher.predict(crop(meter_cv2_image, box))symbols.append(symbol)return symbolsif __name__ == '__main__':# If you want to see how boxes detection works, uncomment these:# img_bw = cv2.imread(os.path.join('original.png'), 0)# boxes = MeterValueReader.get_symbol_boxes(img_bw)# draw_boxes_and_show(img_bw, boxes)# Uncomment to generate templates from image# import random# TEMPLATE_DIR = 'templates'# img_bw = cv2.imread(os.path.join('original.png'), 0)# boxes = MeterValueReader.get_symbol_boxes(img_bw)# for box in boxes:# # You need to label templates manually after extraction# cv2.imwrite(os.path.join(TEMPLATE_DIR, '%s.png' % random.randint(0, 1000)), crop(img_bw, box))img_bw = cv2.imread(os.path.join('original.png'), 0)vr = MeterValueReader()print vr.get_value(img_bw)