Browse Source

Merge branch 'master' of github.com:taddeus/licenseplates

Jayke Meijer 14 năm trước cách đây
mục cha
commit
2e1b2e8c8d

+ 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

+ 2 - 0
src/.gitignore

@@ -0,0 +1,2 @@
+*.dat
+results.txt

+ 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)

+ 0 - 69
src/ClassifierTest.py

@@ -1,69 +0,0 @@
-#!/usr/bin/python
-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'
-
-print 'loaded %d chars' % len(chars)
-
-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:
-        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]
-dump(learning_set, file('learning_set', 'w+'))
-dump(test_set, file('test_set', 'w+'))
-#----------------------------------------------------------------
-learning_set = load(file('learning_set', 'r'))
-
-# Train the classifier with the learning set
-classifier = Classifier(c=30, gamma=1)
-classifier.train(learning_set)
-classifier.save('classifier')
-#----------------------------------------------------------------
-classifier = Classifier(filename='classifier')
-test_set = load(file('test_set', 'r'))
-l = len(test_set)
-matches = 0
-
-for i, char in enumerate(test_set):
-    prediction = classifier.classify(char)
-
-    if char.value == prediction:
-        print ':-----> Successfully recognized "%s"' % char.value,
-        matches += 1
-    else:
-        print ':( Expected character "%s", got "%s"' \
-                % (char.value, prediction),
-
-    print '  --  %d of %d (%d%% done)' % (i + 1, l, int(100 * (i + 1) / l))
-
-print '\n%d matches (%d%%), %d fails' % (matches, \
-        int(100 * matches / len(test_set)), \
-        len(test_set) - matches)

+ 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)

+ 42 - 9
src/find_svm_params.py

@@ -1,21 +1,54 @@
-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)]
 best_classifier = None
 
-learning_set = load(file('learning_set', 'r'))
-test_set = load(file('test_set', 'r'))
+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.train(learning_set)
         result = classifier.test(test_set)
 
-        if result > best_result:
-            best_classifier = classifier
+        if result > best[0]:
+            best = (result, c, y, classifier)
+
+        results.append(result)
+        i += 1
+        print '%d of %d, c = %f, gamma = %f, result = %d%%' \
+              % (i, len(C) * len(Y), c, y, int(round(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(round(results[i] * 100)),
+        i += 1
+
+    print
 
-        print 'c = %f, gamma = %f, result = %d%%' % (c, y, int(result * 100))
+print '\nBest result: %.3f%% for C = %f and gamma = %f' % best[:3]
 
-best_classifier.save('best_classifier')
+best[3].save('classifier.dat')

+ 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('characters.dat', 'w+'))

+ 1 - 1
src/test_chars.py

@@ -3,7 +3,7 @@ from pylab import subplot, show, imshow, axis
 from cPickle import load
 
 x, y = 25, 25
-chars = load(file('chars', 'r'))[:(x * y)]
+chars = load(file('characters.dat', 'r'))[:(x * y)]
 
 for i in range(x):
     for j in range(y):

+ 0 - 14
src/test_chars_a.py

@@ -1,14 +0,0 @@
-#!/usr/bin/python
-from pylab import subplot, show, imshow, axis
-from cPickle import load
-
-chars = filter(lambda c: c.value == 'A', load(file('chars', 'r')))
-
-for i in range(10):
-    for j in range(3):
-        index = j * 10 + i
-        subplot(10, 3, index + 1)
-        axis('off')
-        imshow(chars[index].image.data, cmap='gray')
-
-show()

+ 68 - 0
src/test_classifier.py

@@ -0,0 +1,68 @@
+#!/usr/bin/python
+from xml_helper_functions import xml_to_LicensePlate
+from Classifier import Classifier
+from cPickle import dump, load
+
+chars = load(file('characters.dat', 'r'))
+learning_set = []
+test_set = []
+
+#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]
+
+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 'Test set:', [c.value for c in test_set]
+print 'Saving learning set...'
+dump(learning_set, file('learning_set.dat', 'w+'))
+print 'Saving test set...'
+dump(test_set, file('test_set.dat', 'w+'))
+#----------------------------------------------------------------
+print 'Loading learning set'
+learning_set = load(file('learning_set.dat', 'r'))
+
+# Train the classifier with the learning set
+classifier = Classifier(c=512, gamma=.125, cell_size=12)
+classifier.train(learning_set)
+classifier.save('classifier.dat')
+print 'Saved classifier'
+#----------------------------------------------------------------
+print 'Loading classifier'
+classifier = Classifier(filename='classifier.dat')
+print 'Loading test set'
+test_set = load(file('test_set.dat', 'r'))
+l = len(test_set)
+matches = 0
+
+for i, char in enumerate(test_set):
+    prediction = classifier.classify(char, char.value)
+
+    if char.value == prediction:
+        print ':-----> Successfully recognized "%s"' % char.value,
+        matches += 1
+    else:
+        print ':( Expected character "%s", got "%s"' \
+                % (char.value, prediction),
+
+    print '  --  %d of %d (%d%% done)' % (i + 1, l, int(100 * (i + 1) / l))
+
+print '\n%d matches (%d%%), %d fails' % (matches, \
+        int(100 * matches / len(test_set)), \
+        len(test_set) - matches)

+ 1 - 0
src/combined_test.py → src/test_combined.py

@@ -1,3 +1,4 @@
+#!/usr/bin/python
 from GrayscaleImage import GrayscaleImage
 from NormalizedCharacterImage import NormalizedCharacterImage
 

+ 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.dat', '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')

+ 1 - 0
src/GaussianFilterTest.py → src/test_gauss.py

@@ -1,3 +1,4 @@
+#!/usr/bin/python
 from GaussianFilter import GaussianFilter
 from GrayscaleImage import GrayscaleImage
 

+ 2 - 1
src/histogram_test.py → src/test_histogram.py

@@ -1,3 +1,4 @@
+#!/usr/bin/python
 from Histogram import Histogram
 
 his = Histogram(10, 10, 110)
@@ -22,4 +23,4 @@ his.add(99)
 his.add(100)
 his.add(109)
 
-print his.bins
+print his.bins

+ 1 - 0
src/LocalBinaryPatternizerTest.py → src/test_lbp.py

@@ -1,3 +1,4 @@
+#!/usr/bin/python
 from GrayscaleImage import GrayscaleImage
 from LocalBinaryPatternizer import LocalBinaryPatternizer
 

+ 2 - 1
src/LetterCropperTest.py → src/test_lettercropper.py

@@ -1,3 +1,4 @@
+#!/usr/bin/python
 from LetterCropper import LetterCropper
 from GrayscaleImage import GrayscaleImage
 
@@ -7,4 +8,4 @@ cropper = LetterCropper(image)
 
 cropped_letter = cropper.get_cropped_letter()
 
-cropped_letter.show()
+cropped_letter.show()