Spaces:
Sleeping
Sleeping
AKA Math commited on
Commit ·
c5d647f
1
Parent(s): a36a809
initial version
Browse files- .gitignore +2 -0
- README.md +18 -1
- packages.txt +3 -0
- requirements.txt +7 -0
- template-matching-demo.py +131 -0
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/*
|
| 2 |
+
.vscode*
|
README.md
CHANGED
|
@@ -1,2 +1,19 @@
|
|
| 1 |
# template-matching
|
| 2 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# template-matching
|
| 2 |
+
|
| 3 |
+
This is a demonstration of how template matching works by computing correlation between the search space and the template.
|
| 4 |
+
|
| 5 |
+
[Click here to run this on Streamlit](https://tinyurl.com/template-matching).
|
| 6 |
+
|
| 7 |
+
## What is Template Matching?
|
| 8 |
+
|
| 9 |
+
*
|
| 10 |
+
|
| 11 |
+
## What situations could this method be applied to?
|
| 12 |
+
|
| 13 |
+
*
|
| 14 |
+
|
| 15 |
+
## When would it not work?
|
| 16 |
+
|
| 17 |
+
*
|
| 18 |
+
|
| 19 |
+
## Are there better similarity measures than correlation?
|
packages.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
freeglut3-dev
|
| 2 |
+
libgtk2.0-dev
|
| 3 |
+
libgl1-mesa-glx
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pip==21.3.1
|
| 2 |
+
setuptools==60.2.0
|
| 3 |
+
wheel==0.37.1
|
| 4 |
+
opencv-python-headless
|
| 5 |
+
streamlit
|
| 6 |
+
Pillow
|
| 7 |
+
numpy
|
template-matching-demo.py
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Inspired by https://www.loginradius.com/blog/engineering/guest-post/opencv-web-app-with-streamlit/
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import cv2 as cv
|
| 7 |
+
import streamlit as st
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def compute_correlation(scene: np.array, template: np.array):
|
| 11 |
+
"""
|
| 12 |
+
COMPUTE_CORRELATION computes the correlation between the pixels in scene and template
|
| 13 |
+
when the center of template is placed at location (x, y) on the scene. (x, y) is assumed
|
| 14 |
+
to be within bounds of the scene - this function doesn't check for out of bounds.
|
| 15 |
+
"""
|
| 16 |
+
gray_scene = cv.cvtColor(scene, cv.COLOR_BGR2GRAY)
|
| 17 |
+
cv.normalize(gray_scene, gray_scene, 0, 255, cv.NORM_MINMAX)
|
| 18 |
+
|
| 19 |
+
gray_template = cv.cvtColor(template, cv.COLOR_BGR2GRAY)
|
| 20 |
+
cv.normalize(gray_template, gray_template, 0, 255, cv.NORM_MINMAX)
|
| 21 |
+
|
| 22 |
+
res = cv.matchTemplate(gray_scene, gray_template, cv.TM_CCOEFF_NORMED)
|
| 23 |
+
return res
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def overlay_correlation(scene: np.array, corr: np.array, template: np.array, x: int, y: int):
|
| 27 |
+
pass
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def main_loop():
|
| 31 |
+
"""
|
| 32 |
+
MAIN_LOOP is the main loop (duh) for this streamlit App.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
st.set_page_config(layout="wide")
|
| 36 |
+
|
| 37 |
+
st.title("Template Matching Demo")
|
| 38 |
+
st.subheader(
|
| 39 |
+
"This app demonstrates how a template matching algorithm works: by sliding the template across the scene!")
|
| 40 |
+
|
| 41 |
+
template_image = cv.imread('waldo-template.jpeg')
|
| 42 |
+
template_image = cv.cvtColor(template_image, cv.COLOR_BGR2RGB)
|
| 43 |
+
|
| 44 |
+
st.markdown(
|
| 45 |
+
"To introduce this method, let's first get introduced to our protagonist - Waldo")
|
| 46 |
+
|
| 47 |
+
st.text("Introducing Waldo!")
|
| 48 |
+
st.image(template_image, width=100)
|
| 49 |
+
|
| 50 |
+
st.markdown(
|
| 51 |
+
"Now for the fun bit - can you find Waldo in the scene below? Most of us will take about 20 seconds, if not more!")
|
| 52 |
+
|
| 53 |
+
scene_image = cv.imread("waldo-scene.jpeg")
|
| 54 |
+
scene_image = cv.cvtColor(scene_image, cv.COLOR_BGR2RGB)
|
| 55 |
+
|
| 56 |
+
st.text("Can you find Waldo?")
|
| 57 |
+
st.image(scene_image, width=1000)
|
| 58 |
+
|
| 59 |
+
st.markdown(
|
| 60 |
+
"Can a computer do better? Certainly. The template matching algorithm is conceptually really simple.")
|
| 61 |
+
st.markdown(
|
| 62 |
+
"The idea is to hold the template over all possible patches in the scene, and then compute a similarity.")
|
| 63 |
+
st.markdown(
|
| 64 |
+
"Naturally, the similarity will be the highest when the template matches what's in the patch underneath.")
|
| 65 |
+
st.markdown(
|
| 66 |
+
"We could then record the location of the maximum similarity, and return it when we have scanned everywhere.")
|
| 67 |
+
|
| 68 |
+
corr = compute_correlation(scene_image, template_image)
|
| 69 |
+
norm_corr = (corr - corr.min()) / (corr.max() - corr.min())
|
| 70 |
+
st.text("Here's the correlation image:")
|
| 71 |
+
st.image(norm_corr, width=1000)
|
| 72 |
+
|
| 73 |
+
result_image = scene_image.copy()
|
| 74 |
+
threshold = 0.6
|
| 75 |
+
# finding the values where it exceeds the threshold
|
| 76 |
+
loc = np.where(corr >= threshold)
|
| 77 |
+
template_shape = template_image.shape[::-1]
|
| 78 |
+
for pt in zip(*loc[::-1]):
|
| 79 |
+
# draw rectangle on places where it exceeds threshold
|
| 80 |
+
cv.rectangle(
|
| 81 |
+
result_image, pt, (pt[0] + template_shape[1], pt[1] + template_shape[2]), (0, 255, 0), 2)
|
| 82 |
+
|
| 83 |
+
st.text("Here's the result:")
|
| 84 |
+
st.image(result_image, width=1000)
|
| 85 |
+
|
| 86 |
+
st.markdown("How does this work? The template slides across the scene, and \
|
| 87 |
+
computes the correlation at each location. Use the slider below \
|
| 88 |
+
to see how this works!")
|
| 89 |
+
|
| 90 |
+
alpha = st.slider('Move to slide the template', 0.0,
|
| 91 |
+
1.0, value=0.00, step=0.0001)
|
| 92 |
+
scene_image = scene_image * 0.25
|
| 93 |
+
scene_image = scene_image.astype(np.uint8)
|
| 94 |
+
|
| 95 |
+
norm_corr = cv.copyMakeBorder(norm_corr,
|
| 96 |
+
template_shape[2]//2, template_shape[2]//2,
|
| 97 |
+
template_shape[1]//2, template_shape[1]//2,
|
| 98 |
+
cv.BORDER_CONSTANT)
|
| 99 |
+
norm_corr = cv.multiply(255.0, norm_corr)
|
| 100 |
+
norm_corr = np.dstack((norm_corr, norm_corr, norm_corr))
|
| 101 |
+
|
| 102 |
+
out_image = scene_image.copy()
|
| 103 |
+
out_shape = out_image.shape # H, W, 3
|
| 104 |
+
print(out_shape)
|
| 105 |
+
print(template_shape) # 3, W, H
|
| 106 |
+
range_movement = (out_shape[1] - template_shape[1]) * \
|
| 107 |
+
(out_shape[0] - template_shape[2])
|
| 108 |
+
|
| 109 |
+
absolute_loc = np.int32(alpha * range_movement)
|
| 110 |
+
absolute_loc_y = absolute_loc // (out_shape[1] - template_shape[1])
|
| 111 |
+
absolute_loc_x = absolute_loc % (out_shape[0] - template_shape[2])
|
| 112 |
+
out_image[0:absolute_loc_y, :, :] = norm_corr[0:absolute_loc_y, :, :]
|
| 113 |
+
|
| 114 |
+
out_image[absolute_loc_y:(absolute_loc_y + template_shape[2]),
|
| 115 |
+
absolute_loc_x:(absolute_loc_x + template_shape[1]), :] = template_image
|
| 116 |
+
|
| 117 |
+
if absolute_loc_y > (pt[1] + template_shape[2]):
|
| 118 |
+
for pt in zip(*loc[::-1]):
|
| 119 |
+
# draw rectangle on places where it exceeds threshold
|
| 120 |
+
cv.rectangle(
|
| 121 |
+
out_image, pt, (pt[0] + template_shape[1], pt[1] + template_shape[2]), (0, 255, 0), 2)
|
| 122 |
+
|
| 123 |
+
st.text("Here's how the correlation is computed:")
|
| 124 |
+
st.image(out_image, width=1000)
|
| 125 |
+
|
| 126 |
+
st.markdown("Image copyrights for Where is Waldo - fully attributed to original owners. \
|
| 127 |
+
It is used here purely for educational purposes.")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
if __name__ == '__main__':
|
| 131 |
+
main_loop()
|