How do I find the repetition size in a scanned texture?

Viewed 123

Given the scan of a fabric (snippet included here, it could easily be A4 size at 600 dpi) what would be the best method for finding the repetition pattern in the scan?

I have tried:

  • splitting the image in 4 quarters and trying to find points via SIFT and OpenCV
  • FFT as suggested here

I am aware of other answers on stackoverflow and other sites (this and this and this but they tend to be a bit too terse for an OpenCV beginner.

I am thinking of eyeballing an area and optimizing via row-by-row and column-by-column comparison of pixels, but I am wondering if there is a another better path.

enter image description here

1 Answers

The image's 2D autocorrelation is a good generic way to find repeating structures, as others have commented. There are some details to doing this effectively:

  • For this analysis, it is often fine to just convert the image to grayscale; that's what the code snippet below does. To extend this to a color-aware analysis, you could compute the autocorrelation for each color channel and aggregate the results.
  • It helps to apply a window function first, to avoid boundary artifacts.
  • For efficient computation, it helps to compute the autocorrelation through FFTs.
  • Rather than use the whole spectrum in computing the autocorrelation, it helps to zero out very low frequencies, since these are irrelevant for texture.
  • Rather than the plain autocorrelation, it helps to partially "equalize" or "whiten" the spectrum, as suggested by the Generalized Cross Correlation with Phase Transform (GCC-PHAT) technique.

Python code:

# Copyright 2022 Google LLC.
# SPDX-License-Identifier: Apache-2.0

from PIL import Image
import matplotlib.pyplot as plt
import numpy as np
import scipy.signal

image = np.array(Image.open('texture.jpg').convert('L'), float)

# Window the image.
window_x = np.hanning(image.shape[1])
window_y = np.hanning(image.shape[0])
image *= np.outer(window_y, window_x)
# Transform to frequency domain.
spectrum = np.fft.rfft2(image)
# Partially whiten the spectrum. This tends to make the autocorrelation sharper,
# but it also amplifies noise. The -0.6 exponent is the strength of the
# whitening normalization, where -1.0 would be full normalization and 0.0 would
# be the usual unnormalized autocorrelation.
spectrum *= (1e-12 + np.abs(spectrum))**-0.6
# Exclude some very low frequencies, since these are irrelevant to the texture.
fx = np.arange(spectrum.shape[1])
fy = np.fft.fftshift(np.arange(spectrum.shape[0]) - spectrum.shape[0] // 2)
fx, fy = np.meshgrid(fx, fy)
spectrum[np.sqrt(fx**2 + fy**2) < 10] = 0
# Compute the autocorrelation and inverse transform.
acorr = np.real(np.fft.irfft2(np.abs(spectrum)**2))

plt.figure(figsize=(10, 10))
plt.imshow(acorr, cmap='Blues', vmin=0, vmax=np.percentile(acorr, 99.5))
plt.xlim(0, image.shape[1] / 2)
plt.ylim(0, image.shape[0] / 2)
plt.title('2D autocorrelation', fontsize=18)
plt.xlabel('Horizontal lag (px)', fontsize=15)
plt.ylabel('Vertical lag (px)', fontsize=15)
plt.show()

Output:

enter image description here

The period of the flannel texture is visible at the circled point at 282 px horizontally and 290 px vertically.

Related