Find a 8 in a forest of 9
The goal here is to find one non-repeting pattern in an image. First, find the pattern, then second, compute correlation.
Let's first initialize the notebook:
%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import matplotlib.pyplot as plt
import numpy as np
phi = (np.sqrt(5)+1)/2
fig_width = 15
figsize = (fig_width, fig_width/phi)
im = plt.imread('../files/2021-12-01-99999999999998.jpg')
im = im.sum(axis=-1)
N_X, N_Y = im.shape
N_X, N_Y
Original image
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(im, cmap=plt.gray());
Zoom
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(im[:40, :40]);
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(im[-40:, -40:]);
Crop
im = im[:, 24:1194]
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(im, cmap=plt.gray());
Let's smooth a bit the image to reduce noise
from scipy import ndimage
# Define a simple edge detection kernel
kernel = np.array([[0, 1, 0],
[1, 4, 1],
[0, 1, 0]])/8.
# Perform the convolution
im = ndimage.convolve(im*1., kernel, mode='reflect')
im = im[:, 24:1194]
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(im, cmap=plt.gray());
Average on one axis
im_x = im.mean(axis=1)
im_x = np.roll(im_x, N_X//2) # avoid border effects
#im_x = np.correlate(im_x, [-1, 2, 1], 'same')*1. # contrast detection
im_x -= im_x.mean()
fig, ax = plt.subplots(figsize=(fig_width, fig_width/phi**2))
ax.plot(im_x[:100]);
Cross-correlation
xcorr = np.correlate(im_x, im_x, 'same')*1.
xcorr /= xcorr.max()
fig, ax = plt.subplots(figsize=(fig_width, fig_width/phi**2))
ax.plot(xcorr);
N_X//2, im_x.shape, xcorr.shape, im.shape
fig, ax = plt.subplots(figsize=(fig_width, fig_width/phi**2))
ax.plot(xcorr[(N_X//2+1):(50+N_X//2)]);
period_X = np.argmax(xcorr[(1+N_X//2):]) + 1
print(f'{period_X=}')
The other dimension:
im_y = im.mean(axis=0)
im_y -= im_y.mean()
#im_y[-1] = im_y[0]
#im_y[-2] = im_y[1]
im_y = np.roll(im_y, N_Y//2) # avoid border effects
#im_y = np.correlate(im_y, [-1, 2, -1], 'same')*1. # contrast detection
fig, ax = plt.subplots(figsize=(fig_width, fig_width/phi**2))
ax.plot(im_y);
fig, ax = plt.subplots(figsize=(fig_width, fig_width/phi**2))
ax.plot(im_y[:40]);
xcorr = np.correlate(im_y, im_y, 'same')*1.
xcorr /= xcorr.max()
fig, ax = plt.subplots(figsize=(fig_width, fig_width/phi**2))
ax.plot(xcorr[(N_Y//2+3):]);
xcorr /= xcorr.max()
fig, ax = plt.subplots(figsize=(fig_width, fig_width/phi**2))
ax.plot(xcorr[(N_Y//2+3):(40+N_Y//2)]);
period_Y = np.argmax(xcorr[(3+N_Y//2):]) + 3
print(f'{period_Y=}')
One sample (other random choices would most probably fit - do not go in the borders!)
idx, idy = 0, 21
kernel = im[idx:idx+period_X, idy:idy+period_Y]
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(kernel);
compute cross correlation using one of these variants : https://laurentperrinet.github.io//sciblog/posts/2017-09-20-the-fastest-2d-convolution-in-the-world.html
from scipy import ndimage
# Perform the convolution
xcorr = ndimage.convolve(im*1., kernel, mode='wrap')
xcorr /= xcorr.max()
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.hist(xcorr.ravel(), bins=100);
Now you can spot where there is a difference (and then in the rest due to kerning...)
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(1-xcorr, cmap=plt.magma());
We can easily find the minimum value using https://laurentperrinet.github.io/sciblog/posts/2016-11-17-finding-extremal-values-in-a-nd-array.html
row_min, col_min = np.unravel_index(np.argmin(xcorr.ravel()), xcorr.shape)
row_min, col_min
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(1-xcorr[row_min-10:row_min+10, col_min-10:col_min+10], cmap=plt.magma());
fig, ax = plt.subplots(figsize=(fig_width, fig_width))
ax.matshow(im[row_min-10:row_min+10, col_min-10:col_min+10], cmap=plt.gray());
Here it is ! A solitary 8...
some book keeping for the notebook¶
%load_ext watermark
%watermark -i -h -m -v -p numpy,matplotlib,imageio -r -g -b