Working With High-Frequency Striped Patterns in Image Analysis
When you are trying to segment textures that contain very fine repeating stripes, most standard edge detectors will just produce noise. I spent three weeks on a project dealing with fabric inspection at a mill in North Carolina, trying to catch defects in zebra-print materials used for certain specialty apparel runs. The problem was not detecting the stripes themselves. The problem was detecting the occasional color shift or woven irregularity inside a field of Lots And Lots Of Zebra Stripes without the pattern completely drowning out the subtle signal. The core issue is aliasing and frequency overlap. Standard Canny edge detection tuned for broad objects will either pick up every individual stripe boundary or nothing at all. What you actually need is a multi-scale approach. Start by converting your image to grayscale, then run a Gabor filter bank across several orientations and frequencies before you ever attempt segmentation. Gabor filters naturally align with stripe geometry, and tuning the spatial frequency parameter to match the actual stripe spacing in your image is where most people go wrong. They use default values from OpenCV tutorials instead of measuring the dominant frequency in their own imagery. Here is what worked for me after about a dozen failed attempts. I used the FFT to measure the dominant frequency of the stripe pattern first, then set the Gabor kernel parameters based on that measurement rather than guessing. The specific process was running a 2D Fourier transform on a crop of the clean stripe area, finding the peak frequency values along the axis perpendicular to the stripes, and using those numbers to set the wavelength parameter in the Gabor filter. I measured peak frequency at about 0.12 cycles per pixel in my fabric images, which meant a wavelength of roughly 8.3 pixels between stripe centers. Using that number cut my false positive rate on defect detection from around forty percent down to about six percent.
The workaround I ended up using for the worst case involved a hybrid method. Pure Gabor filtering still struggled when lighting changed across the fabric roll during scanning. The fix was running adaptive thresholding using a mean filter with a kernel sized slightly larger than one full stripe period, which effectively subtracted out the background illumination gradient while preserving the high-frequency stripe pattern. This meant the stripe contrast stayed consistent across the entire image even when the overhead lights flickered or the fabric was slightly wrinkled. I combined the output of the Gabor-filtered result with the adaptive-thresholded result using a simple weighted average rather than an OR operation, because the OR approach introduced far too many artifacts at the edges where stripe curvature changed. One thing nobody warns you about: zebra-like stripe patterns are terrible for traditional Harris corner detection. The algorithm looks for significant eigenvalue changes in both directions at a point, but along a straight stripe there is high variance in only one direction. If your pipeline depends on feature matching across frames or panels, corner detectors will return almost nothing useful. I switched to Shi-Tomasi good features to track with a smaller maximum corners parameter, and those actually tracked between frames reliably because they favor corners with high minimum eigenvalues rather than high values in both directions. It is a small change but it matters when you are doing stitching or alignment work. If you are using Python, the practical stack I settled on was OpenCV with scikit-image for the FFT analysis and Gabor filtering, since OpenCV's built-in Gabor function works fine for basic cases but scikit-image gives you better control over the filter bank generation with custom frequency and orientation grids. For the FFT measurement step, numpy's fft2 combined with np.abs and fftshift is all you need. No special libraries required.
The main limitation with this whole approach is computational cost. Running a full Gabor filter bank at multiple scales and orientations on a 4K image took me roughly forty seconds per frame on a Ryzen 7 5800X, which is unacceptable for anything approaching real-time inspection. The workaround was downsampling to about 1080p for the initial filter pass, finding regions of interest where defects appeared likely based on residual energy after filtering, and then running a second, higher-resolution pass only on those cropped regions. This brought effective throughput from about fourteen frames per minute up to around eighty-five frames per minute, which was plenty for our line speed. There is also a hard limit to how well this works when stripe width varies significantly across the image. If your zebra pattern has bands that transition from two-pixel-wide stripes to six-pixel-wide stripes within the same field of view, no single Gabor kernel will catch everything. You end up needing overlapping filter banks at different wavelength ranges and merging the results, which brings the processing time back up. In those cases, a wavelet-based decomposition might be more appropriate than Gabor filters because continuous wavelets handle multi-scale frequency content natively without requiring you to manually specify each kernel size. I also ran into an issue where the defect I was looking for was actually darker than the surrounding stripes rather than lighter. The standard approach assumes defects deviate from the mean brightness upward, but a shadow or ink bleed can go either direction. I had to run two separate detection passes, one for bright anomalies and one for dark anomalies, and merge them. Skipping the dark pass meant I missed about a third of the actual defects in my test set.
Get the Full Details

If your pattern is simpler and you do not need full texture segmentation, just trying Otsu's thresholding after converting to LAB color space and thresholding on the B channel often gets you surprisingly clean results with a fraction of the processing overhead. It will not catch every edge case, and it breaks down on low-contrast samples, but for quick-and-dirty defect screening on well-lit fabric it is faster than writing a Gabor pipeline from scratch. You can always layer the Gabor approach on top for the edge cases later. The code I ended up using as my baseline for the multi-scale Gabor approach is straightforward enough that pasting it here makes sense. I defined a function that takes an input image, estimates the dominant stripe frequency via FFT, generates a Gabor filter bank centered on that frequency with orientations spanning zero to one hundred eighty degrees in ten-degree increments, applies each kernel, takes the envelope maximum across all orientations at each pixel, and returns the result normalized to eight-bit grayscale. From there I applied the adaptive threshold subtraction and combined the two outputs with a weight of zero seven on the Gabor result and zero three on the adaptive threshold result. That weight distribution held up across different fabric types and lighting conditions without needing recalibration. I did not test this on video feeds or live camera inputs, only on scanned static images at fixed resolution, so I cannot speak to how stable the frequency estimation is under motion blur or compression artifacts. If you are working with compressed JPEG streams, the high-frequency content gets eaten by quantization before it ever reaches your filter, and no amount of Gabor tuning will recover it. In that scenario you are better off preprocessing with a wavelet denoising step first, though that introduces its own artifacts that can smear small defects into nothing.
One final note about testing. Do not evaluate your pipeline using only clean reference images and obviously damaged ones. The hard cases are the mildly deformed stripes, the partially washed-out sections, and the lighting gradients that shift by only a few percent across the frame. My first pass at validation used textbook examples and looked great. The real production data exposed every gap in the approach. I recommend building a test set with at least fifty images that cover the full range of normal variation you expect to encounter, not just the extremes.