Skip to content

Commit 6354643

Browse files
author
Michael Pusterhofer
authored
Add template matching demo (#218)
1 parent d4df16a commit 6354643

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

docs/Project.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
[deps]
22
DemoCards = "311a05b2-6137-4a5a-b473-18580a3d38b5"
33
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
4+
ImageContrastAdjustment = "f332f351-ec65-5f6a-b3d1-319c6670881a"
45
ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
6+
ImageDistances = "51556ac3-7006-55f5-8cb3-34580c88182d"
57
ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
68
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
9+
ImageMorphology = "787d08f9-d448-5407-9aad-5290dd7ab264"
710
ImageShow = "4e3cecfd-b093-5904-9786-8bbb286a6a31"
11+
Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80"
812
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
913
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1014
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# ---
2+
# title: Template Matching
3+
# cover: assets/template_matching.png
4+
# author: Michael Pusterhofer
5+
# date: 2021-06-22
6+
# ---
7+
8+
# This demo shows how to find objects in an image using template matching.
9+
10+
# The main idea is to check the similarity between a search target(template) and a subsection of the image. The subsection is usually the same size as the template. Therefore by using [`mapwindow`](@ref) we can assign a value for every subsection of the image.
11+
12+
# At first we import the following packages.
13+
using ImageCore: Gray
14+
using ImageMorphology: label_components, component_centroids
15+
using ImageFiltering: mapwindow, Fill, imfilter, KernelFactors
16+
using ImageDistances: sqeuclidean
17+
using ImageContrastAdjustment: adjust_histogram, LinearStretching
18+
using TestImages
19+
using Plots: scatter!, plot
20+
21+
# `ImageCore` enables the generation of images, `ImageFiltering` provides the [`mapwindow`](@ref) function and `ImageFeatures` provides functions to label segments of an image. To calculate the similarity `squeclidean` is used. This defines a distance between two images as:
22+
#
23+
# ```math
24+
# \textrm{distance} = \sum_i^{\textrm{number of pixels}} (\textrm{template}_i - \textrm{subsection}_i)^2
25+
# ```
26+
#
27+
# `ImageContrastAdjustment` provides functions to adjust the histogram, which is useful when an image contains values bigger than 1 or smaller than 0. The `Testimages` package will provide our image and plot is used to overlay a scatter plot onto the image.
28+
29+
# To start we first load our image.
30+
img = testimage("moonsurface")
31+
32+
# Let's say we want to find the medium sized craters in the image based one of the impact areas. For this we generate a template from a subsection of the image and apply a small gaussian blur using [`imfilter`](@ref). The gaussian blur often helps when the search targets are not exactly the same.
33+
template = img[12:22,20:30]
34+
template = imfilter(template,KernelFactors.gaussian((0.5,0.5)))
35+
36+
# Now that we have an image and a template, the next step is to define how we measure the similarity between a section of the image and the template. This can be done in multiple way, but a sum of square distances should work quite well. The `ImageDistance` package provides an already optimized version called `sqeuclidean`, which can be used to define a function for [`mapwindow`](@ref). Let's call it `SDIFF`.
37+
38+
function SDIFF(template)
39+
(subsection)->sqeuclidean(subsection, template)
40+
end
41+
42+
# To actually generate our similarity map we use [`mapwindow`](@ref) in the following way.
43+
44+
res = mapwindow(SDIFF(template), img, size(template), border=Fill(1)) .|> Gray
45+
rescaled_map = adjust_histogram(res, LinearStretching())
46+
47+
# If the subsection is located at the border of the image the image has to be extended and in our case we will fill all values outside the image with 1. One thing to keep in mind is that because all of the square differences will be summed up per subsection the resulting sum can be bigger than 1. This will be a problem if we just convert it to an image to check the values. To rescale the values to be between 0 and 1 we can use `adjust_histogram`.
48+
49+
# To find the best locations we have to look for small values on the similarity map. This can be done by comparing if the pixel is below a certain value. Let's chose a value of `0.05`.
50+
51+
threshold = rescaled_map .< 0.05
52+
Gray.(threshold)
53+
54+
# Now we see small blobs at the locations which match our template and we can label the connected regions by `label_components`. This will enumerate are connected regions and `component_centroids` can be used to get the centroid of each region. `component_centroids` also return the centroid for the background region, which is at the first position and we will omit it.
55+
56+
centroids = component_centroids(label_components(threshold))[2:end]
57+
58+
# To check if it worked correctly we can overlay the centroids with the original image using the `Plots` package. As the images are stored using the first index for rows we have to reverse the order of the coordinates to match the order of the plotting library.
59+
plot(Gray.(img), size=(512,512))
60+
scatter!(reverse.(centroids), label="centroids", ms=10, alpha=0.5, c=:red, msw=3)
61+
62+
using Plots: savefig #src
63+
savefig("assets/template_matching.png") #src

0 commit comments

Comments
 (0)