فهرست منبع

Implemented single-histogram features and cleaned up some code.

Taddeus Kroes 14 سال پیش
والد
کامیت
8555e7c7d6
12فایلهای تغییر یافته به همراه220 افزوده شده و 101 حذف شده
  1. 0 6
      .gitignore
  2. 8 0
      src/.gitignore
  3. 10 3
      src/Character.py
  4. 16 9
      src/Classifier.py
  5. 27 28
      src/ClassifierTest.py
  6. 17 13
      src/GrayscaleImage.py
  7. 6 0
      src/Histogram.py
  8. 45 19
      src/LocalBinaryPatternizer.py
  9. 8 8
      src/NormalizedCharacterImage.py
  10. 41 5
      src/find_svm_params.py
  11. 24 0
      src/load_characters.py
  12. 18 10
      src/test_compare.py

+ 0 - 6
.gitignore

@@ -15,9 +15,3 @@ images/BBB
 images/Images
 images/Infos
 images/licenseplates
-chars
-learning_set
-test_set
-classifier
-classifier-model
-classifier-characters

+ 8 - 0
src/.gitignore

@@ -0,0 +1,8 @@
+chars
+learning_set
+test_set
+classifier
+classifier-model
+classifier-characters
+characters
+best_classifier

+ 10 - 3
src/Character.py

@@ -1,4 +1,4 @@
-from LocalBinaryPatternizer import LocalBinaryPatternizer
+from LocalBinaryPatternizer import LocalBinaryPatternizer as LBP
 
 class Character:
     def __init__(self, value, corners, image, filename=None):
@@ -7,7 +7,14 @@ class Character:
         self.image   = image
         self.filename = filename
 
-    def get_feature_vector(self):
-        pattern = LocalBinaryPatternizer(self.image)
+    def get_single_cell_feature_vector(self):
+        if hasattr(self, 'feature'):
+            return
+
+        self.feature = LBP(self.image).single_cell_features_vector()
+
+    def get_feature_vector(self, cell_size=None):
+        pattern = LBP(self.image) if cell_size == None \
+                  else LBP(self.image, cell_size)
 
         return pattern.create_features_vector()

+ 16 - 9
src/Classifier.py

@@ -1,9 +1,11 @@
 from svmutil import svm_train, svm_problem, svm_parameter, svm_predict, \
-        svm_save_model, svm_load_model
+        svm_save_model, svm_load_model, RBF
 
 
 class Classifier:
-    def __init__(self, c=None, gamma=None, filename=None):
+    def __init__(self, c=None, gamma=None, filename=None, cell_size=12):
+        self.cell_size = cell_size
+
         if filename:
             # If a filename is given, load a model from the given filename
             self.model = svm_load_model(filename)
@@ -11,8 +13,8 @@ class Classifier:
             raise Exception('Please specify both C and gamma.')
         else:
             self.param = svm_parameter()
-            self.param.kernel_type = 2  # Radial kernel type
             self.param.C = c  # Soft margin
+            self.param.kernel_type = RBF  # Radial kernel type
             self.param.gamma = gamma  # Parameter for radial kernel
             self.model = None
 
@@ -28,10 +30,12 @@ class Classifier:
         l = len(learning_set)
 
         for i, char in enumerate(learning_set):
-            print 'Training "%s"  --  %d of %d (%d%% done)' \
+            print 'Found "%s"  --  %d of %d (%d%% done)' \
                   % (char.value, i + 1, l, int(100 * (i + 1) / l))
             classes.append(float(ord(char.value)))
-            features.append(char.get_feature_vector())
+            #features.append(char.get_feature_vector())
+            char.get_single_cell_feature_vector()
+            features.append(char.feature)
 
         problem = svm_problem(classes, features)
         self.model = svm_train(problem, self.param)
@@ -48,9 +52,12 @@ class Classifier:
 
         return float(matches) / len(test_set)
 
-    def classify(self, character):
+    def classify(self, character, true_value=None):
         """Classify a character object, return its value."""
-        predict = lambda x: svm_predict([0], [x], self.model)[0][0]
-        prediction_class = predict(character.get_feature_vector())
+        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()
+        p = svm_predict([true_value], [character.feature], self.model)
+        prediction_class = int(p[0][0])
 
-        return chr(int(prediction_class))
+        return chr(prediction_class)

+ 27 - 28
src/ClassifierTest.py

@@ -3,57 +3,56 @@ from xml_helper_functions import xml_to_LicensePlate
 from Classifier import Classifier
 from cPickle import dump, load
 
-chars = []
-
-for i in range(9):
-    for j in range(100):
-        try:
-            filename = '%04d/00991_%04d%02d' % (i, i, j)
-            print 'loading file "%s"' % filename
-
-            # is nog steeds een licensePlate object, maar die is nu heel anders :P
-            plate = xml_to_LicensePlate(filename) 
-
-            if hasattr(plate, 'characters'):
-                chars.extend(plate.characters)
-        except:
-            print 'epic fail'
+chars = load(file('characters2', 'r'))
+learning_set = []
+test_set = []
 
-print 'loaded %d chars' % len(chars)
+#s = {}
+#
+#for char in chars:
+#    if char.value not in s:
+#        s[char.value] = [char]
+#    else:
+#        s[char.value].append(char)
+#
+#for value, chars in s.iteritems():
+#    learning_set += chars[::2]
+#    test_set += chars[1::2]
 
-dump(chars, file('chars', 'w+'))
-#----------------------------------------------------------------
-chars = load(file('chars', 'r'))[:500]
 learned = []
-learning_set = []
-test_set = []
 
 for char in chars:
-    if learned.count(char.value) > 12:
+    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 'Test set:', [c.value for c in test_set]
+print 'Learning set:', [c.value for c in learning_set]
+print 'Test set:', [c.value for c in test_set]
+print 'Saving learning set...'
 dump(learning_set, file('learning_set', 'w+'))
+print 'Saving test set...'
 dump(test_set, file('test_set', 'w+'))
 #----------------------------------------------------------------
+print 'Loading learning set'
 learning_set = load(file('learning_set', 'r'))
 
 # Train the classifier with the learning set
-classifier = Classifier(c=30, gamma=1)
+classifier = Classifier(c=512, gamma=.125, cell_size=12)
 classifier.train(learning_set)
-classifier.save('classifier')
+#classifier.save('classifier')
+#print 'Saved classifier'
 #----------------------------------------------------------------
-classifier = Classifier(filename='classifier')
+#print 'Loading classifier'
+#classifier = Classifier(filename='classifier')
+print 'Loading test set'
 test_set = load(file('test_set', 'r'))
 l = len(test_set)
 matches = 0
 
 for i, char in enumerate(test_set):
-    prediction = classifier.classify(char)
+    prediction = classifier.classify(char, char.value)
 
     if char.value == prediction:
         print ':-----> Successfully recognized "%s"' % char.value,

+ 17 - 13
src/GrayscaleImage.py

@@ -18,19 +18,23 @@ class GrayscaleImage:
             self.data = data
 
     def __iter__(self):
-        self.__i_x = -1
-        self.__i_y = 0
-        return self
-
-    def next(self):
-        self.__i_x += 1
-        if self.__i_x  == self.width:
-            self.__i_x = 0
-            self.__i_y += 1
-        if self.__i_y == self.height:
-            raise StopIteration
-
-        return  self.__i_y, self.__i_x, self[self.__i_y, self.__i_x]
+        for y in xrange(self.data.shape[0]):
+            for x in xrange(self.data.shape[1]):
+                yield y, x, self.data[y, x]
+
+        #self.__i_x = -1
+        #self.__i_y = 0
+        #return self
+
+    #def next(self):
+    #    self.__i_x += 1
+    #    if self.__i_x  == self.width:
+    #        self.__i_x = 0
+    #        self.__i_y += 1
+    #    if self.__i_y == self.height:
+    #        raise StopIteration
+
+    #    return  self.__i_y, self.__i_x, self[self.__i_y, self.__i_x]
 
     def __getitem__(self, position):
         return self.data[position]

+ 6 - 0
src/Histogram.py

@@ -16,6 +16,12 @@ class Histogram:
     def get_bin_index(self, number):
         return (number - self.min) / ((self.max - self.min) / len(self.bins))
 
+    def normalize(self):
+        minimum = min(self.bins)
+        self.bins = map(lambda b: b - minimum, self.bins)
+        maximum = float(max(self.bins))
+        self.bins = map(lambda b: b / maximum, self.bins)
+
     def intersect(self, other):
         h1 = self.bins
         h2 = other.bins

+ 45 - 19
src/LocalBinaryPatternizer.py

@@ -6,34 +6,37 @@ class LocalBinaryPatternizer:
     def __init__(self, image, cell_size=16):
         self.cell_size = cell_size
         self.image = image
-        self.setup_histograms()
 
     def setup_histograms(self):
         cells_in_width = int(ceil(self.image.width / float(self.cell_size)))
         cells_in_height = int(ceil(self.image.height / float(self.cell_size)))
-        self.features = []
+        self.histograms = []
+
         for i in xrange(cells_in_height):
-            self.features.append([])
+            self.histograms.append([])
+
             for j in xrange(cells_in_width):
-                self.features[i].append(Histogram(256,0,256))
+                self.histograms[i].append(Histogram(256, 0, 256))
 
-    def create_features_vector(self):
-        ''' Walk around the pixels in clokwise order, shifting 1 bit less
-            at each neighbour starting at 7 in the top-left corner. This gives a
-            8-bit feature number of a pixel'''
-        for y, x, value in self.image:
+    def local_binary_pattern(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) \
+             | (self.is_pixel_darker(y    , x + 1, value) << 4) \
+             | (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)
 
-            pattern = (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) \
-                    | (self.is_pixel_darker(y    , x + 1, value) << 4) \
-                    | (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)
+    def create_features_vector(self):
+        '''Walk around the pixels in clokwise order, shifting 1 bit less at
+        each neighbour starting at 7 in the top-left corner. This gives a 8-bit
+        feature number of a pixel'''
+        self.setup_histograms()
 
+        for y, x, value in self.image:
             cy, cx = self.get_cell_index(y, x)
-            self.features[cy][cx].add(pattern)
+            self.histograms[cy][cx].add(self.local_binary_pattern(y, x, value))
 
         return self.get_features_as_array()
 
@@ -44,4 +47,27 @@ class LocalBinaryPatternizer:
         return (y / self.cell_size, x / self.cell_size)
 
     def get_features_as_array(self):
-        return [h.bins for h in [h for sub in self.features for h in sub]][0]
+        f = []
+
+        # Concatenate all histogram bins
+        for row in self.histograms:
+            for hist in row:
+                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)
+
+        for y, x, value in self.image:
+            h.add(self.local_binary_pattern(y, x, value))
+
+        h.normalize()
+
+        return h
+
+    def single_cell_features_vector(self):
+        return self.get_single_histogram().bins

+ 8 - 8
src/NormalizedCharacterImage.py

@@ -5,7 +5,8 @@ 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, size=(60, 40), blur=1.1, \
+            crop_threshold=0.9):
         if image != None:
             GrayscaleImage.__init__(self, data=deepcopy(image.data))
         elif data != None:
@@ -13,18 +14,17 @@ class NormalizedCharacterImage(GrayscaleImage):
         self.blur = blur
         self.crop_threshold = crop_threshold
         self.size = size
-        self.gausse_filter()
+        self.gaussian_filter()
         self.increase_contrast()
-        self.crop_to_letter()
-        self.resize()
+        #self.crop_to_letter()
+        #self.resize()
 
     def increase_contrast(self):
         self.data -= self.data.min()
-        self.data /= self.data.max()
+        self.data = self.data.astype(float) / self.data.max()
 
-    def gausse_filter(self):
-        filter = GaussianFilter(1.1)
-        filter.filter(self)
+    def gaussian_filter(self):
+        GaussianFilter(self.blur).filter(self)
 
     def crop_to_letter(self):
         cropper = LetterCropper(0.9)

+ 41 - 5
src/find_svm_params.py

@@ -1,21 +1,57 @@
-C = [2 ** p for p in xrange(-5, 16, 2)]:
-Y = [2 ** p for p in xrange(-15, 4, 2)]
-best_result = 0
+#!/usr/bin/python
+from cPickle import load
+from Classifier import Classifier
+
+#C = [float(2 ** p) for p in xrange(-5, 16, 2)]
+#Y = [float(2 ** p) for p in xrange(-15, 4, 2)]
+C = [float(2 ** p) for p in xrange(1, 16, 2)]
+Y = [float(2 ** p) for p in xrange(-13, 4, 2)]
 best_classifier = None
 
+print 'Loading learning set...'
 learning_set = load(file('learning_set', 'r'))
+print 'Learning set:', [c.value for c in learning_set]
+print 'Loading test set...'
 test_set = load(file('test_set', 'r'))
+print 'Test set:', [c.value for c in test_set]
 
 # Perform a grid-search on different combinations of soft margin and gamma
+results = []
+maximum = (0, 0, 0)
+i = 0
+
 for c in C:
     for y in Y:
         classifier = Classifier(c=c, gamma=y)
         classifier.train(learning_set)
         result = classifier.test(test_set)
 
-        if result > best_result:
+        if result > maximum[2]:
+            maximum = (c, y, result)
             best_classifier = classifier
 
-        print 'c = %f, gamma = %f, result = %d%%' % (c, y, int(result * 100))
+        results.append(result)
+        i += 1
+        print '%d of %d, c = %f, gamma = %f, result = %d%%' \
+              % (i, len(C) * len(Y), c, y, int(result * 100))
+
+i = 0
+
+print '\n     c\y',
+for y in Y:
+    print '| %f' % y,
+
+print
+
+for c in C:
+    print ' %7s' % c,
+
+    for y in Y:
+        print '| %8d' % int(results[i] * 100),
+        i += 1
+
+    print
+
+print '\nmax:', maximum
 
 best_classifier.save('best_classifier')

+ 24 - 0
src/load_characters.py

@@ -0,0 +1,24 @@
+#!/usr/bin/python
+from os import listdir
+from cPickle import dump
+from pylab import imshow, show
+
+from GrayscaleImage import GrayscaleImage
+from NormalizedCharacterImage import NormalizedCharacterImage
+from Character import Character
+
+c = []
+
+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=1, size=(48, 36))
+        #imshow(norm.data, cmap='gray')
+        #show()
+        character = Character(char, [], norm)
+        character.get_single_cell_feature_vector()
+        c.append(character)
+        print char
+
+dump(c, open('characters2', 'w+'))

+ 18 - 10
src/test_compare.py

@@ -5,32 +5,39 @@ from GrayscaleImage import GrayscaleImage
 from cPickle import load
 from numpy import zeros, resize
 
-chars = load(file('chars', 'r'))[::2]
+chars = load(file('characters', 'r'))[::2]
 left = None
 right = None
 
-for c in chars:
-    if c.value == '8':
-        if left == None:
-            left = c.image
-        elif right == None:
-            right = c.image
-        else:
-            break
+s = {}
 
-size = 16
+for char in chars:
+    if char.value not in s:
+        s[char.value] = [char]
+    else:
+        s[char.value].append(char)
+
+left = s['F'][2].image
+right = s['A'][0].image
+
+size = 12
 
 d = (left.size[0] * 4, left.size[1] * 4)
 #GrayscaleImage.resize(left, d)
 #GrayscaleImage.resize(right, d)
 
 p1 = LocalBinaryPatternizer(left, size)
+h1 = p1.get_single_histogram()
 p1.create_features_vector()
 p1 = p1.features
+
 p2 = LocalBinaryPatternizer(right, size)
+h2 = p2.get_single_histogram()
 p2.create_features_vector()
 p2 = p2.features
 
+total_intersect = h1.intersect(h2)
+
 s = (len(p1), len(p1[0]))
 match = zeros(left.shape)
 m = 0
@@ -52,6 +59,7 @@ for y in range(s[0]):
         m += intersect
 
 print 'Match: %d%%' % int(m / (s[0] * s[1]) * 100)
+print 'Single histogram instersection: %d%%' % int(total_intersect * 100)
 
 subplot(311)
 imshow(left.data, cmap='gray')