diff --git a/src/.gitignore b/src/.gitignore index be1fd40c7ab8827aa2bf4a75bbf0864e7c1a3c63..b70230f22109bdb365a8d20816278019fc489f84 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,2 +1,2 @@ *.dat -results.txt +results*.txt diff --git a/src/Character.py b/src/Character.py index ec49b65b930d550180b3050b5c46ddf802b4839d..110ef119157eaf1aee90b5c2ac7096351196bddb 100644 --- a/src/Character.py +++ b/src/Character.py @@ -7,11 +7,12 @@ class Character: self.image = image self.filename = filename - def get_single_cell_feature_vector(self): + def get_single_cell_feature_vector(self, neighbours=5): if hasattr(self, 'feature'): return - self.feature = LBP(self.image).single_cell_features_vector() + pattern = LBP(self.image, neighbours=neighbours) + self.feature = pattern.single_cell_features_vector() def get_feature_vector(self, cell_size=None): pattern = LBP(self.image) if cell_size == None \ diff --git a/src/Classifier.py b/src/Classifier.py index a9a58a700925199400142ad7e354dda9695a17f2..02c155c2ddee7fd49ba115ba465d1d0be02cba6e 100644 --- a/src/Classifier.py +++ b/src/Classifier.py @@ -3,8 +3,8 @@ from svmutil import svm_train, svm_problem, svm_parameter, svm_predict, \ class Classifier: - def __init__(self, c=None, gamma=None, filename=None, cell_size=12): - self.cell_size = cell_size + def __init__(self, c=None, gamma=None, filename=None, neighbours=3): + self.neighbours = neighbours if filename: # If a filename is given, load a model from the given filename @@ -34,7 +34,7 @@ class Classifier: % (char.value, i + 1, l, int(100 * (i + 1) / l)) classes.append(float(ord(char.value))) #features.append(char.get_feature_vector()) - char.get_single_cell_feature_vector() + char.get_single_cell_feature_vector(self.neighbours) features.append(char.feature) problem = svm_problem(classes, features) @@ -56,7 +56,7 @@ class Classifier: """Classify a character object, return its value.""" true_value = 0 if true_value == None else ord(true_value) #x = character.get_feature_vector(self.cell_size) - character.get_single_cell_feature_vector() + character.get_single_cell_feature_vector(self.neighbours) p = svm_predict([true_value], [character.feature], self.model) prediction_class = int(p[0][0]) diff --git a/src/LocalBinaryPatternizer.py b/src/LocalBinaryPatternizer.py index 468f0cb653bd11ade71949737c043ba7ced0aa44..b1f42101da95f458453e3c4cf4a1840cad3c7d54 100644 --- a/src/LocalBinaryPatternizer.py +++ b/src/LocalBinaryPatternizer.py @@ -3,9 +3,13 @@ from math import ceil class LocalBinaryPatternizer: - def __init__(self, image, cell_size=16): + def __init__(self, image, cell_size=16, neighbours=3): self.cell_size = cell_size self.image = image + self.pattern_callback, self.bins = { + 3: (self.pattern_3x3, 256), + 5: (self.pattern_5x5, 4096) + }[neighbours] def setup_histograms(self): cells_in_width = int(ceil(self.image.width / float(self.cell_size))) @@ -16,9 +20,9 @@ class LocalBinaryPatternizer: self.histograms.append([]) for j in xrange(cells_in_width): - self.histograms[i].append(Histogram(256, 0, 256)) + self.histograms[i].append(Histogram(self.bins, 0, self.bins)) - def local_binary_pattern(self, y, x, value): + def pattern_3x3(self, y, x, value): return (self.is_pixel_darker(y - 1, x - 1, value) << 7) \ | (self.is_pixel_darker(y - 1, x , value) << 6) \ | (self.is_pixel_darker(y - 1, x + 1, value) << 5) \ @@ -26,7 +30,21 @@ class LocalBinaryPatternizer: | (self.is_pixel_darker(y + 1, x + 1, value) << 3) \ | (self.is_pixel_darker(y + 1, x , value) << 2) \ | (self.is_pixel_darker(y + 1, x - 1, value) << 1) \ - | (self.is_pixel_darker(y , x - 1, value) << 0) + | (self.is_pixel_darker(y , x - 1, value)) + + def pattern_5x5(self, y, x, value): + return (self.is_pixel_darker(y - 1, x - 2, value) << 11) \ + | (self.is_pixel_darker(y , x - 2, value) << 10) \ + | (self.is_pixel_darker(y + 1, x - 2, value) << 9) \ + | (self.is_pixel_darker(y + 2, x - 1, value) << 8) \ + | (self.is_pixel_darker(y + 2, x , value) << 7) \ + | (self.is_pixel_darker(y + 2, x + 1, value) << 6) \ + | (self.is_pixel_darker(y + 1, x + 2, value) << 5) \ + | (self.is_pixel_darker(y , x + 2, value) << 4) \ + | (self.is_pixel_darker(y - 1, x + 2, value) << 3) \ + | (self.is_pixel_darker(y - 2, x + 1, value) << 2) \ + | (self.is_pixel_darker(y - 2, x , value) << 1) \ + | (self.is_pixel_darker(y - 2, x - 1, value)) def create_features_vector(self): '''Walk around the pixels in clokwise order, shifting 1 bit less at @@ -36,7 +54,7 @@ class LocalBinaryPatternizer: for y, x, value in self.image: cy, cx = self.get_cell_index(y, x) - self.histograms[cy][cx].add(self.local_binary_pattern(y, x, value)) + self.histograms[cy][cx].add(self.pattern_callback(y, x, value)) return self.get_features_as_array() @@ -55,15 +73,14 @@ class LocalBinaryPatternizer: f.extend(hist.bins) return f - #return [h.bins for h in [h for sub in self.histograms for h in sub]][0] def get_single_histogram(self): """Create a single histogram of the local binary patterns in the image.""" - h = Histogram(256, 0, 256) + h = Histogram(self.bins, 0, self.bins) for y, x, value in self.image: - h.add(self.local_binary_pattern(y, x, value)) + h.add(self.pattern_callback(y, x, value)) h.normalize() diff --git a/src/NormalizedCharacterImage.py b/src/NormalizedCharacterImage.py index e682c1ca39934614ead67f4d45923c87d55767df..52b34b99418031fd474ee94ed98a2e2310a94ed0 100644 --- a/src/NormalizedCharacterImage.py +++ b/src/NormalizedCharacterImage.py @@ -5,19 +5,22 @@ from GaussianFilter import GaussianFilter class NormalizedCharacterImage(GrayscaleImage): - def __init__(self, image=None, data=None, size=(60, 40), blur=1.1, \ - crop_threshold=0.9): + def __init__(self, image=None, data=None, height=None, blur=1.1): if image != None: GrayscaleImage.__init__(self, data=deepcopy(image.data)) elif data != None: GrayscaleImage.__init__(self, data=deepcopy(data)) + self.blur = blur - self.crop_threshold = crop_threshold - self.size = size self.gaussian_filter() + self.increase_contrast() + + #self.crop_threshold = crop_threshold #self.crop_to_letter() - #self.resize() + + self.height = height + self.resize() def increase_contrast(self): self.data -= self.data.min() @@ -31,4 +34,9 @@ class NormalizedCharacterImage(GrayscaleImage): cropper.crop_to_letter(self) def resize(self): - GrayscaleImage.resize(self, self.size) + """Resize the image to a fixed height.""" + if self.height == None: + return + + h, w = self.data.shape + GrayscaleImage.resize(self, (self.height, self.height * w / h)) diff --git a/src/find_svm_params.py b/src/find_svm_params.py index 6eb1d9e7e4380758b07458649be4fc822ce7fec0..f2defe5fc48779e674b33e7068bbed9192f61dbe 100755 --- a/src/find_svm_params.py +++ b/src/find_svm_params.py @@ -1,26 +1,92 @@ #!/usr/bin/python -from cPickle import load +from os import listdir +from os.path import exists +from cPickle import load, dump +from sys import argv, exit + +from GrayscaleImage import GrayscaleImage +from NormalizedCharacterImage import NormalizedCharacterImage +from Character import Character from Classifier import Classifier +if len(argv) < 3: + print 'Usage: python %s NEIGHBOURS BLUR_SCALE' % argv[0] + exit(1) + +neighbours = int(argv[1]) +blur_scale = float(argv[2]) +suffix = '_%s_%s' % (blur_scale, neighbours) + +chars_file = 'characters%s.dat' % suffix +learning_set_file = 'learning_set%s.dat' % suffix +test_set_file = 'test_set%s.dat' % suffix +classifier_file = 'classifier%s.dat' % suffix +results_file = 'results%s.txt' % suffix + + +# Load characters +if exists(chars_file): + print 'Loading characters...' + chars = load(open(chars_file, 'r')) +else: + print 'Going to generate character objects...' + chars = [] + + for char in sorted(listdir('../images/LearningSet')): + for image in sorted(listdir('../images/LearningSet/' + char)): + f = '../images/LearningSet/' + char + '/' + image + image = GrayscaleImage(f) + norm = NormalizedCharacterImage(image, blur=blur_scale, height=42) + #imshow(norm.data, cmap='gray'); show() + character = Character(char, [], norm) + character.get_single_cell_feature_vector(neighbours) + chars.append(character) + print char + + print 'Saving characters...' + dump(chars, open(chars_file, 'w+')) + + +# Load learning set and test set +if exists(learning_set_file): + print 'Loading learning set...' + learning_set = load(open(learning_set_file, 'r')) + print 'Learning set:', [c.value for c in learning_set] + print 'Loading test set...' + test_set = load(open(test_set_file, 'r')) + print 'Test set:', [c.value for c in test_set] +else: + print 'Going to generate learning set and test set...' + learning_set = [] + test_set = [] + learned = [] + + for char in chars: + if learned.count(char.value) == 70: + test_set.append(char) + else: + learning_set.append(char) + learned.append(char.value) + + print 'Learning set:', [c.value for c in learning_set] + print '\nTest set:', [c.value for c in test_set] + print '\nSaving learning set...' + dump(learning_set, file(learning_set_file, 'w+')) + print 'Saving test set...' + dump(test_set, file(test_set_file, 'w+')) + + +# Perform a grid-search to find the optimal values for C and gamma C = [float(2 ** p) for p in xrange(-5, 16, 2)] Y = [float(2 ** p) for p in xrange(-15, 4, 2)] -best_classifier = None - -print 'Loading learning set...' -learning_set = load(file('learning_set.dat', 'r')) -print 'Learning set:', [c.value for c in learning_set] -print 'Loading test set...' -test_set = load(file('test_set.dat', 'r')) -print 'Test set:', [c.value for c in test_set] -# Perform a grid-search on different combinations of soft margin and gamma results = [] best = (0,) i = 0 for c in C: for y in Y: - classifier = Classifier(c=c, gamma=y) + classifier = Classifier(c=c, gamma=y, neighbours=neighbours) classifier.train(learning_set) result = classifier.test(test_set) @@ -33,22 +99,30 @@ for c in C: % (i, len(C) * len(Y), c, y, int(round(result * 100))) i = 0 +s = ' c\y' -print '\n c\y', for y in Y: - print '| %f' % y, + s += '| %f' % y -print +s += '\n' for c in C: - print ' %7s' % c, + s += ' %7s' % c for y in Y: - print '| %8d' % int(round(results[i] * 100)), + s += '| %8d' % int(round(results[i] * 100)) i += 1 - print + s += '\n' + +s += '\nBest result: %.3f%% for C = %f and gamma = %f' % best[:3] + +print 'Saving results...' +f = open(results_file, 'w+') +f.write(s) +f.close() -print '\nBest result: %.3f%% for C = %f and gamma = %f' % best[:3] +print 'Saving best classifier...' +best[3].save(classifier_file) -best[3].save('classifier.dat') +print '\n' + s