I wanted to write a template matching algorithm from scratch.
The key area's I'm interesting in are readability, maintainability, DRY, and if the program performance/efficiency could be improved in any way.
Image scanning class:
import sys
from itertools import izip
class ScanImage:
def __init__(self, image, template, process='sad'):
self.image = image
self.temp = template
self.pro = process #sad or ssd
self.x_axis_leng = len(image[0]) - len(template[0]) # how far to scan on x axis of image
self.y_axis_hgt = len(image) - len(template) # how far to scan on y axis
self.best_location = list() # will hold the coordinates of the final location
self.score = sys.maxint # similarity strength score (the less the better)
self.temp_matrix_holder = list() # holds a matrix of the same size as the template
self.calculate = ImageCalculation() # class that is used to calculate strength score using ssd or sad
def _empty_list(self):
del self.temp_matrix_holder[:]
def run(self):
return self._scan_image(self.image, self.temp, self.pro)
def _scan_image(self, image, template, process):
"""main function: will iterate over the image one pixel at a time"""
for i in xrange(len(image)):
for j in xrange(len(image[i])):
if (j <= self.x_axis_leng) and \
(i <= self.y_axis_hgt):
self.temp_matrix_holder.append(\
image[i][j:j+len(template[0])])
self.temp_matrix_holder.append(\
image[i + 1][j:j+len(template[0])])
new_score = self.calculate.run(\
self.temp_matrix_holder, self.temp, process)
if new_score <= self.score:
self.score = new_score
self.best_location = [j, i]
self._empty_list()
return self.score, self.best_location
Score calculation class:
class ImageCalculation:
def __init__(self):
self.score = 0.0
def _reset_score(self):
self.score = 0.0
def _minus(self, atuple):
return abs(atuple[0]-atuple[1])
def _sad(self, atuple):
"""sum of absolute difference"""
# this is not nessesary but keeps the code a little neater
# and also shows the explicit difference between sad and ssd
return self._minus(atuple)
def _ssd(self, atuple):
"""sum of squared difference"""
diff = self._minus(atuple)
return diff * diff
def _block_template_comparison(self, image_block, template, process):
if (len(image_block) != len(template)) or \
(len(image_block[0]) != len(template[0])):
return 'Error'
for i in xrange(len(image_block)):
for j in xrange(len(image_block[i])):
one_pixel_score = 0.0
for k in izip(image_block[i][j], template[i][j]):
if process == 'sad':
one_pixel_score += self._sad(k)
elif process == 'ssd':
one_pixel_score += self._ssd(k)
self.score += (one_pixel_score/3.0) # average out all 3 (RGB) channels for each pixle
return self.score
def run(self, image_block, template, process='sad'):
if process == 'ssd':
process = 'ssd'
elif process == 'sad':
process = 'sad'
else:
raise ValueError, 'Proccess must be either sad or ssd'
if not self.score == 0.0:
self._reset_score()
return self._block_template_comparison(image_block, template, process)
Outputs:
if __name__ == '__main__':
p_i = [
[[0], [1], [2], [10]],
[[3], [4], [5], [11]],
[[6], [7], [8], [12]],
]
t_i = [
[[4], [5]],
[[7], [12]],
]
s = ScanImage(p_i, t_i)
print s.run()
# ssd outputs : 5.333 (sength score), [1,1] (x, y coords)
# sad outputs : 1.3333, [1, 1]
Template matching is taking a small 'image' and scanning it over a larger image one pixel at a time until a match is found.
During each iteration of the scan the template and a block of the image, which is the same size as the template, are compared. They are given a score. The closer the score (in this specific scoring metric) to 0 the better. A perfect match gets a score of 0.
Special note:
This program is designed to work with pixels with 3 channels each (RGB), this is just a dummy example but it also works for fully built out 'images'.