Information

Section: Image classification
Goal: Understand a way to perform image classification.
Time needed: 20 min
Prerequisites: Introduction about machine learning experiments

Image classification

Build the dataset

In the previous section, we saw how to decompose an image into its HOG descriptors. This allows us to reduce the number of features that will be used to perform the classification.

Those HOG descriptors can now be used as any normal set of attributes, just the same way we did in the previous chapter with AIS data. We only need to create a dataset, with one entry for each image, one attribute for each HOG descriptor, and a class attribute. As an example, a dataset for animal classification could look like that:

text

For this to work fine, we need each image to have the same number of HOG descriptor. We saw in the previous section that the number of HOG descriptors depends on two parameters:

  • the parameters chosen in the HOG function: we decide on the number of pixels to group together for the HOG descriptors. The more pixels are in the same group, the less HOG descriptors we get.

  • the size of the image: similarly as for the parameters of the function, the bigger the image, the more HOG descriptors we get.

In our case, it is therefore important for each image to have the same size (same number of pixels) and the same parameters for the HOG descriptors.

With that in mind, we can now build our dataset by importing the images with their class and calculating the HOG descriptors for each of them. We are in possession of a .csv file 2-images.csv containing the path and the class of each image. Then, we only need to loop over the images to calculate their HOG descriptor and put everything in a Pandas DataFrame.

First, we read the .csv file.

import pandas as pd

images = pd.read_csv('./2-images.csv')

images.head()
image_path label
0 ./data/1.jpg cat
1 ./data/2.jpg cat
2 ./data/3.jpg cat
3 ./data/4.jpg cat
4 ./data/5.jpg cat

Now, we can loop on each image path to read the image itself, calculate its HOG descriptors and put everything in a Dataframe for classification. We keep the read image in the DataFrame to be able to plot it later if needed.

from skimage import io
from skimage.feature import hog

hog_features = []
for file in images['image_path']:
    hog_features.append(hog(io.imread(file), # the image read
                            orientations = 8,
                            pixels_per_cell = (40, 40),
                            visualize = False)
                       )

images = images.assign(hog_features = hog_features)

images.head()
image_path label hog_features
0 ./data/1.jpg cat [0.10489434136776081, 0.03000765876042029, 0.1...
1 ./data/2.jpg cat [0.06511460912671653, 0.036009570336997296, 0....
2 ./data/3.jpg cat [0.0645168443208118, 0.012273334354864062, 0.0...
3 ./data/4.jpg cat [0.15030760450655362, 0.04317479024740703, 0.0...
4 ./data/5.jpg cat [0.04515955710031582, 0.023032858411843354, 0....

In this example, all the HOG descriptors are stored as a list in a single attribute. This is not a problem for classification, we will unpack them when doing the classification to treat each of them as a single attribute.

Perform the classification

Now that we have the dataset, we can do the classification exactly how we are doing it usually.

We choose to use a Random Forest classifier.

import numpy as np
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

train, test, y_train, y_test = train_test_split(images[['image_path', 'hog_features']], # we keep the attribute 'image_path' to
                                                #be able to access the image to check the classification if needed
                                                images['label'],
                                                test_size = 0.2,
                                                random_state = 0)

x_train = np.stack(train['hog_features'].values)
x_test = np.stack(test['hog_features'].values)
    
random_forest = RandomForestClassifier(n_estimators = 10, max_depth = 7, random_state = 0)
random_forest.fit(x_train, y_train.values)
predictions = random_forest.predict(x_test)
    
accuracy = accuracy_score(predictions, y_test)

print('Accuracy: ' + str(accuracy))
Accuracy: 0.25

After performing the classification, we can have a better look at the results to see which images were correctly or wrongly classified by our model:

import matplotlib.pyplot as plt

i = 0
l = len(test)
for index, row in test.iterrows():
    plt.subplot(1, l, i+1)
    plt.imshow(io.imread(row['image_path']))
    plt.title(str(predictions[i]))
    i = i + 1
plt.show()
_images/2-1-3-image-classification_14_0.png

In that case, we can see that the classification is very bad. This is very likely due to the very small size of the dataset.

Indeed, the dataset contains 19 images, but each image contains 7200 HOG descriptors. The amount of images in the dataset is way too small.

Create functions

To automatize the creation of the dataset, the classification of the images and the representation of the results, we can write a few functions that we can reuse with any other image dataset.

def create_hog(images):
    # Takes a list of images, returns a list of the hog descriptors
    
    from skimage.feature import hog

    hog_features = []
    for image in images:
        hog_features.append(hog(image,
                                orientations = 8,
                                pixels_per_cell = (40, 40),
                                visualize = False)
                           )
    
    return hog_features
def classify_images(df, x, y, random_state = 0):
    # From a df, performs image classification and returns the results
    # Takes random_state as an input to allow different reproducible results
    
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.model_selection import train_test_split

    train, test, y_train, y_test = train_test_split(df[x], # we keep the attribute 'image_path' to
                                                    #be able to access the image to check the classification if needed
                                                    df[y],
                                                    test_size = 0.2,
                                                    random_state = random_state)

    x_train = np.stack(train['hog_features'].values)
    x_test = np.stack(test['hog_features'].values)

    random_forest = RandomForestClassifier(n_estimators = 10, max_depth = 7, random_state = 0)
    random_forest.fit(x_train, y_train.values)
    predictions = random_forest.predict(x_test)
    
    return predictions, y_test, test
def print_results(predictions, test):
    
    import matplotlib.pyplot as plt

    i = 0
    l = len(test)
    for index, row in test.iterrows():
        plt.subplot(1, l, i+1)
        plt.imshow(io.imread(row['image_path']))
        plt.title(str(predictions[i]))
        i = i + 1
    plt.show()

Now, to perform the classification, we only have to run those functions as following:

import pandas as pd
from skimage import io
from sklearn.metrics import accuracy_score

images = pd.read_csv('./2-images.csv')

list_images = []
for image in images['image_path']:
    list_images.append(io.imread(image))

hog_features = create_hog(list_images)
images = images.assign(hog_features = hog_features)

x = ['image_path', 'hog_features']
y = ['label']
pred, y_test, test = classify_images(images, x, y, 1)

print('Accuracy: ' + str(accuracy_score(pred, y_test)))
print_results(pred, test)
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:18: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
Accuracy: 0.5
_images/2-1-3-image-classification_22_2.png

We can try different values for random_state to see different results. Replace the value for random_state to visualize different results.

x = ['image_path', 'hog_features']
y = ['label']
pred, y_test, test = classify_images(images, x, y, 2)
print('Accuracy: ' + str(accuracy_score(pred, y_test)))
print_results(pred, test)
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:18: DataConversionWarning: A column-vector y was passed when a 1d array was expected. Please change the shape of y to (n_samples,), for example using ravel().
Accuracy: 0.75
_images/2-1-3-image-classification_24_2.png
# Beginner version

import matplotlib.pyplot as plt
import ipywidgets as widgets
from ipywidgets import interact

def create_hog(images):
    # Takes a list of images, returns a list of the hog descriptors
    
    from skimage.feature import hog

    hog_features = []
    for image in images:
        hog_features.append(hog(image,
                                orientations = 8,
                                pixels_per_cell = (40, 40),
                                visualize = False)
                           )
    
    return hog_features

def classify_images(df, x, y, random_state = 0):
    # From a df, performs image classification and returns the results
    # Takes random_state as an input to allow different reproducible results
    
    from sklearn.ensemble import RandomForestClassifier
    from sklearn.model_selection import train_test_split

    train, test, y_train, y_test = train_test_split(df[x], # we keep the attribute 'image_path' to
                                                    #be able to access the image to check the classification if needed
                                                    df[y],
                                                    test_size = 0.2,
                                                    random_state = random_state)

    x_train = np.stack(train['hog_features'].values)
    x_test = np.stack(test['hog_features'].values)

    random_forest = RandomForestClassifier(n_estimators = 10, max_depth = 7, random_state = 0)
    random_forest.fit(x_train, y_train.values)
    predictions = random_forest.predict(x_test)
    
    return predictions, y_test, test

def print_results(predictions, test):
    
    import matplotlib.pyplot as plt
    from skimage import io

    i = 0
    l = len(test)
    for index, row in test.iterrows():
        plt.subplot(1, l, i+1)
        plt.imshow(io.imread(row['image_path']))
        plt.title(str(predictions[i]))
        i = i + 1
    plt.show()

def classify(random_state):
    import pandas as pd
    from skimage import io
    from sklearn.metrics import accuracy_score

    images = pd.read_csv('./2-images.csv')

    list_images = []
    for image in images['image_path']:
        list_images.append(io.imread(image))

    hog_features = create_hog(list_images)
    images = images.assign(hog_features = hog_features)

    x = ['image_path', 'hog_features']
    y = ['label']
    pred, y_test, test = classify_images(images, x, y, random_state)

    print('Accuracy: ' + str(accuracy_score(pred, y_test)))
    print_results(pred, test)

    
interact(classify,
         random_state = widgets.IntText(value = 0,
                                    description = 'random_state = ',
                                    disabled = False))
<function __main__.classify(random_state)>

Quiz

from IPython.display import IFrame
IFrame("https://blog.hoou.de/wp-admin/admin-ajax.php?action=h5p_embed&id=62", "760", "399")