In [ ]:
%matplotlib inline

Template Matching

We use template matching to identify the occurrence of an image patch (in this case, a sub-image centered on a single coin). Here, we return a single match (the exact same coin), so the maximum value in the match_template result corresponds to the coin location. The other coins look similar, and thus have local maxima; if you expect multiple matches, you should use a proper peak-finding function.

The match_template function uses fast, normalized cross-correlation [1]_ to find instances of the template in the image. Note that the peaks in the output of match_template correspond to the origin (i.e. top-left corner) of the template.

.. [1] J. P. Lewis, "Fast Normalized Cross-Correlation", Industrial Light and Magic.


In [1]:
import numpy as np
import matplotlib.pyplot as plt

from skimage import data
from skimage.feature import match_template
from skimage.io import imread, imsave
from skimage.transform import rotate, rescale
from skimage.draw import polygon_perimeter

from glob import glob

import mpld3
mpld3.enable_notebook()

In [2]:
def rotate_good(img, angle):
    return 1 - rotate(1 - img, angle)

In [3]:
triangles = [imread(file_name, as_grey=True) for file_name in reversed(['trojkat-maly.png', 'trojkat.png', 'trojkat-duzy.png'])]
triangles_straight = [rotate_good(triangle, angle) for angle in [-90, 0, 90] for triangle in triangles] + [imread('hak.png', as_grey=True), imread('hak-maly.png', as_grey=True)]
triangles_skewed = [rotate_good(triangle, angle) for angle in [-45, 45] for triangle in triangles]
triangles = triangles_straight + triangles_skewed

In [4]:
def match(image):
    def match_patterns(patterns):
        THRESHOLD = 0.7
        best_score = THRESHOLD
        best_tid = None
        best_x = 0
        best_y = 0
        best_result = None
        for (tid, triangle) in enumerate(patterns):
            result = match_template(image, triangle)
            ij = np.unravel_index(np.argmax(result), result.shape)
            x, y = ij[::-1]
            score = result[y, x]
            if score > best_score:
                best_score = score
                best_tid = tid
                best_x = x
                best_y = y
                best_result = result
        return (best_result, best_tid, best_x, best_y)
        
    (best_result, best_tid, best_x, best_y) = match_patterns(triangles_straight)
    if best_tid is None:
        (best_result, best_tid, best_x, best_y) = match_patterns(triangles_skewed)
        if best_tid is not None:
            best_tid += len(triangles_straight)

    if best_tid is not None:
        new_image = image.copy()
        triangle = triangles[best_tid]
        for i in xrange(triangle.shape[0]):
            for j in xrange(triangle.shape[1]):
                new_image[best_y + i, best_x + j] = 1 - (1 - image[best_y + i, best_x + j]) * triangle[i, j]
        
        return (new_image, best_result, best_tid, best_x, best_y)
    else:
        return None

In [5]:
def do_stuff(image):
    infos = []
    while True:
        res = match(image)
        if res is None:
            break

        (image, result, tid, x, y) = res
        infos.append((tid, x, y))

    #ax2.set_axis_off()
    #plt.set_title('image')
    # highlight matched region
    image2 = image.copy()
    for (tid, x, y) in infos:
        h, w = triangles[tid].shape
        (rr, cc) = polygon_perimeter([y, y + h, y + h, y], [x, x, x + w, x + w])
        image2[rr, cc] = 0
        #rect = plt.Rectangle((x, y), wcoin, hcoin, edgecolor='r', facecolor='none')
        #ax2.add_patch(rect)
        

    return image2

In [ ]:
print glob('pics/*.png')
for file_name in glob('pics/*.png'):
    print 'dupa1'
    image = imread(file_name, as_grey=True)
    print 'dupa2'
    image2 = do_stuff(image)
    print 'dupa3'
    #print fig
    #print file_name
    #file_name = '.'.join(file_name.split('.')[:-1]) + '_plot.png'
    #print file_name
    #imsave(file_name, image2)
    #print 'dupa4'


['pics/150.2.png', 'pics/208.png', 'pics/543.1.png', 'pics/43.png', 'pics/63.2.png', 'pics/403.v2.png', 'pics/4.png', 'pics/108.png', 'pics/320.png', 'pics/176.png', 'pics/494.png', 'pics/418.png', 'pics/295.png', 'pics/35.png', 'pics/105.a.png', 'pics/337.png', 'pics/12.png', 'pics/401.png', 'pics/185.1.png', 'pics/593.1.png', 'pics/87a.png', 'pics/165.v.png', 'pics/460.png', 'pics/583.png', 'pics/377.png', 'pics/438.png', 'pics/206.png', 'pics/317.png', 'pics/579.1.png', 'pics/139.png', 'pics/40.png', 'pics/62.png', 'pics/579.6.png', 'pics/358.png', 'pics/470.png', 'pics/541.png', "pics/577'.png", 'pics/5.png', 'pics/398.2.png', 'pics/2.png', 'pics/111.png', 'pics/293.png', 'pics/101.png', 'pics/398.1.png', 'pics/334.png', 'pics/44.png', 'pics/424.v.png', 'pics/376*.png', 'pics/270.png', 'pics/598e.png', 'pics/210.png', 'pics/211a.png', 'pics/92b.png', 'pics/464.png', 'pics/589.png', 'pics/307.png', 'pics/72.png', 'pics/405.v.png', 'pics/134.png', 'pics/457.png', 'pics/8.png', 'pics/309.png', 'pics/573.png', 'pics/63.1.png', 'pics/152.v.png', 'pics/535.png', 'pics/326.png', 'pics/58.png', 'pics/9.png', 'pics/123.png', "pics/323'.png", 'pics/563.png', 'pics/393.a.png', 'pics/115.png', 'pics/390.png', 'pics/185.2.png', 'pics/417.png', 'pics/112.png', 'pics/126.png', 'pics/70.png', 'pics/30.png', 'pics/366.png', 'pics/512.png', 'pics/291.png', 'pics/355.png', 'pics/52.png', 'pics/69x.png', 'pics/537.b.png', 'pics/540.png', 'pics/102.png', 'pics/594.png', 'pics/319a.png', 'pics/421.png', 'pics/148.png', 'pics/472.png', 'pics/152.8.png', 'pics/92.png', 'pics/152.a.png', 'pics/362.png', 'pics/49x.2.png', 'pics/424.png', 'pics/128.png', 'pics/248.png', 'pics/140.png', 'pics/308.png', 'pics/445.png', 'pics/592.png', 'pics/167.png', 'pics/557.png', 'pics/11.png', 'pics/286.png', 'pics/280.a.png', 'pics/598b.png', 'pics/560.png', 'pics/346.png', 'pics/79x.v.png', 'pics/233.png', 'pics/468.png', 'pics/325.png', 'pics/80.png', 'pics/452.png', 'pics/53.png', 'pics/555.png', 'pics/29.v.png', 'pics/1.png', 'pics/252.2.png', 'pics/394c.png', 'pics/371.png', 'pics/435.png', 'pics/232.png', 'pics/209.png', 'pics/397.png', 'pics/579.5.png', 'pics/147.png', 'pics/165.png', 'pics/425.png', 'pics/295f.png', 'pics/336.png', 'pics/7.png', 'pics/295b.png', 'pics/211.png', 'pics/133.png', 'pics/554.png', 'pics/122.png', 'pics/487.png', 'pics/169.png', 'pics/212.png', 'pics/290.png', 'pics/440.png', 'pics/75.png', 'pics/107.png', 'pics/103a.png', 'pics/483.png', 'pics/23.png', 'pics/511.png', 'pics/306.png', 'pics/392.png', 'pics/405.2.png', 'pics/131.png', 'pics/536.a.png', 'pics/467.png', 'pics/172.png', 'pics/146.png', 'pics/200.png', 'pics/131a.png', 'pics/454.png', 'pics/271.png', 'pics/229.a.png', 'pics/381.2.png', 'pics/202.png', 'pics/65.png', 'pics/118.png', 'pics/545.png', 'pics/571.png', 'pics/402.png', 'pics/427.png', 'pics/27.png', 'pics/491.png', 'pics/129a.png', 'pics/252.1.png', 'pics/372.png', 'pics/50.2.png', 'pics/114.png', 'pics/422.png', 'pics/150.2_plot.png', 'pics/313.png', 'pics/528.png', 'pics/60.a.png', 'pics/244.2.png', 'pics/124.v.png', 'pics/187.v3.png', 'pics/444.a.png', 'pics/60.b.png', 'pics/381.png', 'pics/214.png', 'pics/17.png', 'pics/354.1.png', 'pics/465.png', 'pics/149.png', 'pics/510.png', 'pics/323.png', 'pics/485.png', 'pics/87b.png', 'pics/354.2.png', 'pics/577.png', 'pics/152.b.png', 'pics/567.png', 'pics/287.png', 'pics/352.png', 'pics/13.png', 'pics/436.png', 'pics/411.png', 'pics/575.png', 'pics/215.png', 'pics/586.png', 'pics/569.png', 'pics/546.2.png', 'pics/396.c.png', 'pics/295c.png', 'pics/532.png', 'pics/522.png', "pics/28'.png", 'pics/549.png', 'pics/203.png', 'pics/329.png', 'pics/207.png', 'pics/170.png', 'pics/81.png', 'pics/99.png', 'pics/493.png', 'pics/598c.png', 'pics/396.b.png', 'pics/315.png', 'pics/455.png', 'pics/69.png', 'pics/542.png', 'pics/345.png', 'pics/434a.png', 'pics/280.b.png', 'pics/49.png', 'pics/109.png', 'pics/237.png', 'pics/593.2.png', 'pics/86.png', 'pics/551.png', 'pics/321.png', 'pics/278.png', 'pics/74.1b.png', 'pics/416.png', 'pics/354b.2.png', 'pics/415.png', 'pics/556.png', 'pics/429.png', 'pics/456.png', 'pics/229.b.png', 'pics/547.png', 'pics/89.png', 'pics/451.png', 'pics/533.v.png', 'pics/38.png', 'pics/564.png', 'pics/230.png', 'pics/332.png', 'pics/71.png', 'pics/433.png', 'pics/396.a.png', 'pics/328.png', 'pics/31.png', 'pics/144.png', 'pics/598d.png', 'pics/132.png', 'pics/351.v.png', 'pics/536.b.png', 'pics/430.png', 'pics/34.png', 'pics/281.png', 'pics/449.png', 'pics/480.png', 'pics/591.png', 'pics/339.png', 'pics/15.png', 'pics/187.png', 'pics/20.png', 'pics/41.png', 'pics/367.png', 'pics/482.png', 'pics/319.png', 'pics/60x.png', 'pics/168.png', 'pics/354b.1.png', 'pics/228.png', 'pics/579.4.png', 'pics/138.png', 'pics/205.png', 'pics/419.png', 'pics/36.png', 'pics/538.png', 'pics/74.2b.png', 'pics/100.png', 'pics/375.v.png', 'pics/256.png', 'pics/78.png', 'pics/66.png', 'pics/74.3,74x.png', 'pics/434.png', 'pics/548.png', 'pics/196.png', 'pics/98.png', 'pics/84.png', 'pics/359.png', 'pics/18.png', 'pics/546.png', 'pics/344.png', 'pics/394.png', 'pics/347.png', 'pics/173.png', 'pics/61.png', 'pics/226.png', 'pics/295k.png', 'pics/350.v.png', 'pics/284.png', 'pics/166.1.png', 'pics/122b.png', 'pics/426.png', 'pics/22x.png', 'pics/96.png', 'pics/295l.png', 'pics/403.png', 'pics/428.png', 'pics/24.png', 'pics/598a.png', 'pics/206a.png', 'pics/110.png', 'pics/26.png', 'pics/484.png', 'pics/596.png', 'pics/73.png', 'pics/539.png', 'pics/515.1.png', 'pics/459.1.png', 'pics/231.png', 'pics/18x.png', 'pics/576.png', 'pics/55.png', 'pics/295m.png', 'pics/458.png', 'pics/195.png', 'pics/87.png', 'pics/394d.png', 'pics/105.b.png', 'pics/481.png', 'pics/176x.png', 'pics/326a.png', 'pics/208_plot.png', 'pics/57.png', 'pics/581.png', 'pics/190h.png', 'pics/191.png', 'pics/16.png', 'pics/550.png', 'pics/6.png', 'pics/403.v3.png', 'pics/32.png', 'pics/77.png', 'pics/559.png', 'pics/142.png', 'pics/122a.png', 'pics/393.b.png', 'pics/444.b.png', 'pics/314.b.png', 'pics/420.png', 'pics/375.png', 'pics/314.a.png', 'pics/384.png', 'pics/46.png', 'pics/335.png', 'pics/94.png', 'pics/143.b.png', 'pics/124.png', 'pics/67.png', 'pics/281a.png', 'pics/95.png', 'pics/68.png', 'pics/255.png', 'pics/565.png', 'pics/295e.png', 'pics/349.png', 'pics/28.png', 'pics/558.png', 'pics/383.png', 'pics/318.png', 'pics/289.png', 'pics/580.png', 'pics/63.3.png', 'pics/562.png', 'pics/431.png', 'pics/3.png', 'pics/88.a.png', 'pics/224.png', 'pics/187.v2.png', 'pics/475.png', 'pics/412.png', 'pics/225.png', 'pics/579.2.png', 'pics/181.png', 'pics/78x.png', 'pics/463.png', 'pics/113.png', 'pics/201.png', 'pics/29.png', 'pics/406.png', 'pics/311.png', 'pics/500.png', 'pics/331.png', 'pics/570.png', 'pics/333.png', 'pics/328.v.png', 'pics/83.png', 'pics/295a.png', 'pics/441.png', 'pics/90.png', 'pics/597.png', "pics/24'.png", 'pics/283.png', 'pics/119.png', 'pics/93.png', 'pics/104.png', 'pics/544.png', 'pics/593.3.png', 'pics/343.png', 'pics/400.png', 'pics/330.png', 'pics/529.png', 'pics/579.3.png', 'pics/356.png', 'pics/324.png', 'pics/183.png', 'pics/171.png', 'pics/437.png', 'pics/376.png', 'pics/143.a.png', 'pics/59.png', 'pics/82.png', 'pics/513.png', 'pics/350.png', 'pics/49x.png', 'pics/79x.png', 'pics/517.png', 'pics/459.2.png', 'pics/351.png', 'pics/561.png', 'pics/85.png', 'pics/471.png', 'pics/152.4.png', 'pics/536.c.png', 'pics/249.png', 'pics/166.2.png', 'pics/129.png', 'pics/363.png', 'pics/348.png', 'pics/19.png', 'pics/292.png', 'pics/446.png', 'pics/33.png', 'pics/353.png', 'pics/574.png', 'pics/399.png', 'pics/297.png', 'pics/10.png', 'pics/405.1.png', 'pics/295d.png', 'pics/298.png', 'pics/450.png', 'pics/145.png', 'pics/310.png', 'pics/378.png', 'pics/572.png', 'pics/395.png', 'pics/74.1a.png', 'pics/462.png', 'pics/338.png', 'pics/244.1.png', 'pics/374.png', 'pics/50.png', 'pics/461.png', 'pics/88.b.png', 'pics/190.png', 'pics/533.png', "pics/166'.png", 'pics/322.png', 'pics/103.png', 'pics/543.2.png', 'pics/103b.png', 'pics/473.png', 'pics/439.png', 'pics/595.png', 'pics/150.1.png', 'pics/469.png', 'pics/76.png', 'pics/79.png', 'pics/130.png', 'pics/525.png', 'pics/296.png', 'pics/151.png', 'pics/164.png', 'pics/142a.png', 'pics/54.png', 'pics/261.png', 'pics/312.png', 'pics/106.png', 'pics/373.png', 'pics/192.png', 'pics/56.png', 'pics/537.a.png', 'pics/97.png', 'pics/108x.png', 'pics/406.2.png', 'pics/342.png']
dupa1
dupa2
dupa3
dupa1
dupa2
dupa3

In [ ]:
(image, result, tid, x, y) = match(image)
triangle = triangles[tid]
print tid

fig = plt.figure(figsize=(8, 3))
ax1 = plt.subplot(1, 3, 1)
ax2 = plt.subplot(1, 3, 2, adjustable='box-forced')
ax3 = plt.subplot(1, 3, 3, sharex=ax2, sharey=ax2, adjustable='box-forced')

ax1.imshow(triangle, cmap=plt.cm.gray)
ax1.set_axis_off()
ax1.set_title('template')

ax2.imshow(image, cmap=plt.cm.gray)
ax2.set_axis_off()
ax2.set_title('image')
# highlight matched region
hcoin, wcoin = triangles[tid].shape
rect = plt.Rectangle((x, y), wcoin, hcoin, edgecolor='r', facecolor='none')
ax2.add_patch(rect)

ax3.imshow(result)
ax3.set_axis_off()
ax3.set_title('`match_template`\nresult')
# highlight matched region
ax3.plot(x, y, 'o', markeredgecolor='r', markerfacecolor='none', markersize=10)

plt.show()

In [ ]: