layout: post title: Performance shootout - python libraries for computer vision (Part 2/2)
Update 2017-02-03
In the last post, we benchmarked of three different libraries for performing simple morphological operations on images. Current post covers benchmarks for more computation intensive operations like computing texture features of an image. Such features, for example, haralick features, zernike moments, hu moments describe various aspects of image like texture, shape, patterns and are still often used for classification and tagging purposes
In [18]:
import sys, os, random
import numpy as np
import timeit
import cv2, mahotas, skimage, skimage.feature, skimage.measure
from skimage.feature import local_binary_pattern
from mahotas.features.lbp import lbp_transform
import matplotlib.pyplot as plt
%matplotlib inline
ImagePath = 'brodatz/'
def pyplots(images, titles=None):
num = len(images)
plt.figure(figsize=(15,6))
for index, im in enumerate(images):
plt.subplot(2, num/2, index+1)
plt.imshow(im, cmap='gray')
if titles is None:
plt.title('%d'%(index))
else:
plt.title(str(titles[index]))
plt.axis('off')
In [4]:
images = os.listdir(ImagePath)
testIdx = 40
testImg = mahotas.imresize(mahotas.imread(os.path.join(ImagePath,images[testIdx])), (128,128)).astype(np.int32)
allImg = [mahotas.imresize(mahotas.imread(os.path.join(ImagePath,im)), (128, 128)).astype(np.int32) for im in images]
sample = random.sample(allImg,8)
pyplots(sample)
In order to see how good or bad a particular feature set is performing, we will compute k-nearest neighbors for given image from whole of the dataset. I am using cv2.HISTCMP_CORREL for computing distances between representation (feature vectors) of two images. To be honest, this method doesn't perform well in most of the cases. Different distances might be relevant depending on the dataset and algorithm you use for computing features. Here we will just stick to one of the meterics for sanity.
I might write a separate blog post comparing distance meterics. Stay tuned.
In [ ]:
def kNN(srcVec, destVecs, k=7, method=cv2.HISTCMP_CORREL, absolute=True, reverse=False):
distances = []
srcVec = srcVec.flatten().astype(np.float32)
for destVec in destVecs:
distance = cv2.compareHist(srcVec, destVec.flatten().astype(np.float32), method=method)
distances.append(distance)
if absolute:
distances = np.absolute(distances)
if reverse:
sortedIdx = np.argsort(distances)[-k:][::-1]
else:
sortedIdx = np.argsort(distances)[:k]
return sortedIdx, [distances[idx] for idx in sortedIdx]
Haralick features date back to as far as 1970s and were one of the first used to classify aerial imagery collected from satellites.
The idea behind haralick feature extraction is to
In [5]:
inVector = mahotas.features.haralick(testImg)
allVectors = [mahotas.features.haralick(img) for img in allImg]
nn, distances = kNN(inVector, allVectors, reverse=True)
pyplots([testImg] + [allImg[idx] for idx in nn],
titles=['Original']+['%d:%.2g'%(idx, distance) for idx,distance in zip(nn,distances)])
In [6]:
def haralick_sk(img):
g = skimage.feature.greycomatrix(img, range(4), np.pi/4*np.arange(4), levels=256, symmetric=True, normed=True)
return skimage.feature.greycoprops(g)
inVector = haralick_sk(testImg)
allVectors = [haralick_sk(img) for img in allImg]
nn, distances = kNN(inVector, allVectors, reverse=True)
pyplots([testImg] + [allImg[idx] for idx in nn],
titles=['Original']+['%d:%.2f'%(idx, distance) for idx,distance in zip(nn,distances)])
Zernike polynomials are a sequence of polynomials that are orthogonal on the unit disk. There is a theorem that says any sufficiently smooth real-valued phase field over the unit disk can be represented in terms of its Zernike coefficients (odd and even), just as periodic functions find an orthogonal representation with the Fourier series.
Refer - In order to convert a rectangular region of each image to a unit circle for calculation of Zernike moments -
In [7]:
inVector = mahotas.features.zernike_moments(testImg, radius=5)
allVectors = [mahotas.features.zernike_moments(img, radius=5) for img in allImg]
nn, distances = kNN(inVector, allVectors, reverse=True)
pyplots([testImg] + [allImg[idx] for idx in nn],
titles=['Original']+['%d:%.2g'%(idx, distance) for idx,distance in zip(nn,distances)])
An image moment is a certain particular weighted average (moment) of the image pixels' intensities.
Hu Moments are typically used to describe properties of objects (and shaped) in the image after segmentation. They are not particulary relevant for describing texture. Just keeping them here for speed comparison.
In [8]:
def hu_moments_sk(img):
img = img.astype(np.uint8)
raw_m = skimage.measure.moments(img)
cr = raw_m[0, 1] / raw_m[0, 0]; cc = raw_m[1, 0] / raw_m[0, 0]
central_m = skimage.measure.moments_central(img, cr, cc)
norm_m = skimage.measure.moments_normalized(central_m)
return skimage.measure.moments_hu(norm_m)
inVector = hu_moments_sk(testImg)
allVectors = [hu_moments_sk(img) for img in allImg]
nn, distances = kNN(inVector, allVectors, reverse=True)
pyplots([testImg] + [allImg[idx] for idx in nn],
titles=['Original']+['%d:%.2g'%(idx, distance) for idx,distance in zip(nn,distances)])
In [9]:
def hu_moments_cv(img):
img = img.astype(np.uint8)
m = cv2.moments(img)
return cv2.HuMoments(m)
inVector = hu_moments_cv(testImg)
allVectors = [hu_moments_cv(img) for img in allImg]
nn, distances = kNN(inVector, allVectors, reverse=True)
pyplots([testImg] + [allImg[idx] for idx in nn],
titles=['Original']+['%d:%.2g'%(idx, distance) for idx,distance in zip(nn,distances)])
Developed in early 1990s, Local binary patterns (LBP) were commonly used for image classification before deep learning storm.
Ref - In LBP, each image is divided into cells (say 16x16 pixels) and a histogram is computed for each cell as following
Feature vector of an image is a concatenated version of these histograms
In [10]:
inVector = lbp_transform(testImg, radius=2, points=16)
allVectors = [lbp_transform(img, radius=2, points=16) for img in allImg]
nn, distances = kNN(inVector, allVectors, reverse=True)
pyplots([testImg] + [allImg[idx] for idx in nn],
titles=['Original']+['%d:%.2f'%(idx, distance) for idx,distance in zip(nn,distances)])
In [11]:
inVector = local_binary_pattern(testImg, 16, 2, 'uniform')
allVectors = [local_binary_pattern(img, 16, 2, 'uniform') for img in allImg]
nn, distances = kNN(inVector, allVectors, reverse=True)
pyplots([testImg] + [allImg[idx] for idx in nn],
titles=['Original']+['%d:%.2g'%(idx, distance) for idx,distance in zip(nn,distances)])
In [15]:
pre ='''
import os
import numpy as np
import cv2, mahotas, skimage, skimage.feature, skimage.measure
from skimage.feature import local_binary_pattern
from mahotas.features.lbp import lbp_transform
ImagePath = 'brodatz/'
images = os.listdir(ImagePath)
testImg = mahotas.imresize(mahotas.imread(os.path.join(ImagePath,images[40])), (128,128)).astype(np.int32)
testImg8 = testImg.astype(np.uint8)
def haralick_sk(img):
g = skimage.feature.greycomatrix(img, range(4), np.pi/4*np.arange(4), levels=256, symmetric=True, normed=True)
return skimage.feature.greycoprops(g)
def hu_moments_sk(img):
raw_m = skimage.measure.moments(img)
cr = raw_m[0, 1] / raw_m[0, 0]; cc = raw_m[1, 0] / raw_m[0, 0]
central_m = skimage.measure.moments_central(img, cr, cc)
norm_m = skimage.measure.moments_normalized(central_m)
return skimage.measure.moments_hu(norm_m)
def hu_moments_cv(img):
m = cv2.moments(img)
return cv2.HuMoments(m)
'''
In [21]:
def t(s):
return timeit.timeit(s, setup=pre, number=10)
tests = [
('haralick', [
'mahotas.features.haralick(testImg)',
'haralick_sk(testImg)',
None,
]),
('zernike', [
'mahotas.features.zernike_moments(testImg, radius=5)',
None,
None,
]),
('hu', [
None,
'hu_moments_sk(testImg8)',
'hu_moments_cv(testImg8)',
]),
('lbp', [
'lbp_transform(testImg, radius=3, points=24)',
'local_binary_pattern(testImg, 24, 3, "uniform")',
None,
]),
]
print(r'%-12s|%9s |%9s |%9s |' % ('Algorithm', 'mahotas','skimage','opencv'))
for name,statements in tests:
sys.stdout.write(r'%-12s|' % name)
for st in statements:
if st is None:
sys.stdout.write(' NA |')
else:
time = '%.4f' % (t(st))
sys.stdout.write('%8s |' % time)
sys.stdout.write('\n')
Here it is! While OpenCV is blazingly faster than other libraries, a lot of implementations seems missing (at least in the python API of OpenCV). skimage would be a good alternate with lot of algorithms ready to be played with