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

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



Solution 1:[1]

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.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Pascal Getreuer