Texture Analysis a la Langlet

Binary Texture Analysis
Stuart Smith
[email protected]
Introduction
Many fabric and wallpaper designs are tessellations based on a square or rectangular tile. If such
a design is represented as a binary image, Langlet’s Helical and Cognitive transforms [1] can
“de-tessellate” simple cases, revealing the tile. Even if the tile is obscured by a significant
amount of noise, an approximation can still be recovered by methods that rely on “extended
exclusive-or,” which comprises ≠ , ≠ /, and ≠ \ [2]. APL functions for experimentation with
texture analysis are listed in the Appendix, and the complete texture workspace is available at
www.cs.uml.edu/~stu/textile.dws.
Basic Texture Analysis
The simplest case of binary texture analysis involves a square binary image composed of
identical square tiles. Suppose, for example, we are given the patch of texture shown in fig 1. On
casual inspection it is obvious that it is made up of repetitions of some smaller pattern. What is
that pattern?
Figure 1. Texture swatch composed of identical square tiles
If the patch is square with side a power of 2 (fig.1 is 256×256) the two-dimensional Helical
transform (HEL2) may be able to identify the tile. The result from HEL2 is shown in fig. 2. As
can be seen, there is a 16×16-pixel subimage in the upper left corner of an otherwise blank
square.1 This result confirms that the swatch is composed of identical 16×16 tiles.2 The original
1
The Cognitive transform (COG2) can also be used, in which case the subimage will appear in the lower right
corner.
2
If even one pixel in one tile had differed from the pixels in the same position in the other tiles, the transform would
have been “smeared” over the entire 256×256 result matrix.
Figure 2. Result of applying HEL2 to fig. 1
tile may be obtained simply by extracting the 16×16-pixel subimage at, for example, any of the
four corners of the original image.
Extracting a Tile From a Noisy Texture Image
When a tessellation is not composed of exact copies of a tile but rather contains a certain amount
of noise (i.e., random black-to-white and white-to-black reversals of pixel color) it is more
difficult to analyze. In such cases a different method is required. The first step is to determine the
dimensions of the tile. This is accomplished with the correl function, which performs a sort of
circular autocorrelation. Suppose we are given the image of Fig. 3, which contains about 12%
noise over the whole image.
Figure 3. 256×256 tiled image with 12% corrupt pixels
correl determines periodicity in both the horizontal and vertical directions and, optionally,
displays a plot showing the periods. The plot in Fig. 4 shows a peak every 16 steps, and the
function reports that it has determined that the period is 16 in both the horizontal and vertical
directions. Thus, the image shown in Fig. 3 must be composed of 16×16 tiles.
Figure 4. correl plot showing the periodicity of Fig. 5
Once the dimensions of the tile have been determined, the image can be broken up into tile-sized
subimages. Each subimage is then used to make a test image the same size as the image being
analyzed. Finally, each test image is exclusive-OR’ed element-wise with the original image,
yielding a difference image. The tile that produces the difference image containing the smallest
number of 1’s is taken to be the closest estimate of the original tile. find_tile does all the
required operations. In general, if an image has some given percentage of random pixel color
reversals, each tile will also contain about this same percentage of affected pixels. This means
that we cannot be certain exactly what tile was used to create the image.3
Non-Square Swatches and Tiles
find_tile does not require either the tile or the whole image to be square. Fig. 5, for example, is a
computer-generated, but realistic, example of fabric texture. The estimated tile calculated by
find_tile is shown in fig. 6.
correl will correctly determine periodicity in the presence of ≥45% noise. At these levels an estimated tile is too
noisy to be usable as an approximation of the actual tile used to construct an image.
3
Figure 5. Computer-generated swatch with 15% noise pixels
Figure 6. Zoomed-in view of the estimated tile
Real-world Texture Analysis
With just find_tile texture analysis is restricted largely to the kind of well-behaved problem
exemplified by figs. 1, 3, and 5. A real-world challenge is to invent functions that can handle a
binary image like fig. 7, which was made from a photograph of an actual swatch of fabric woven
in the herringbone pattern. The current correl function is unable to determine the horizontal
periodicity of this swatch even though to the eye it is obvious that the pattern is repeated four
times across the image.
Figure 7. Binary image of a swatch of herringbone weave fabric
One could take a conventional image processing approach to the analysis of the image in fig. 7;
however, to do so would be to abandon the approach described here. The motivation for at least
trying to stay within the boolean framework is the utter simplicity of the key operations, most of
which can be realized as APL one-liners.
The Texture workspace contains the following variables, which are images of actual fabrics:
bricks, circles, hound, llamas, swirls, and zigzag. These are included to allow experiments that
demonstrate what the analysis functions can and can’t do with real fabric patterns (see pictures of
the fabric swatches in the Appendix).
Roll Your Own
The function testmake allows you to construct your own test images to experiment with. For
example, start by creating a tile with a random pattern:
tile←8 12 xrandm 0.5
or a tile with rotational symmetry, e.g.:
tile←8 12 sympat2 0.5
In either of these cases, tile will be an 8×12 matrix of 1’s and 0’s with approximately equal
numbers of each. Next, create a tessellated pattern that by default contains about 12% noise:
z←4 7 testmake tile
z will be a 4×7 array of tile partially obscured by noise. With correl, check to make sure z’s
periodicity can be determined :
0.9 correl z ⍝ 0.9 is the matching tolerance
Finally, get the estimated tile with
x←find_tile z
The result can be written out to a file with, e.g., writebmp. The implementation-dependent
function, disp, can be used to directly visualize the result. The quality of the estimated tile can be
given by a simple metric like (+/+/x=tile)÷×/⍴ tile, 1 being a perfect match.
The amount of noise a test image contains is determined by the right argument to xrandm in the
definition of testmake. It is set there to 0.12 (i.e., 12%), but it can generally be set somewhat
higher. Possible experiments include varying this value and observing whether find_tile
succeeds in finding a good approximation to tile. Another type of experiment illustrates the
effect of varying the relative number of white vs. black pixels in the tile. This is done by varying
the right argument of xrandm, sympat, sympat2, sympat4, or sympat4r between 0 and 1.
A clean tessellation can be created with the textile function. Tiles can be made with xrandm,
sympat, sympat2, sympat4, and sympat4r, as shown above, or selected from a small set of
tiles included as the variables m1-m7 in the Texture workspace. For example, a 3×3 tiled pattern
using m7 can be made with
x←3 3 textile m7
Pictures of the seven tiles can be found in the Appendix. These tiles can of course also be used
with testmake to create tessellations obscured by as much or as little noise as desired.
References
[1] Gérard Langlet. Towards the Ultimate APL-TOE. ACM SIGAPL Quote Quad, 23:1, July
1992.
[2] Michael Zaus. Crisp and Soft Computing with Hypercubical Calculus: New Approaches to
Modeling in Cognitive Science and Technology with Parity Logic, Fuzzy Logic, and
Evolutionary Computing: Studies in Fuzziness and Soft Computing, Vol. 27. Physica-Verlag
HD, 1999.
Appendix
Helical transform of square logical matrix ⍵ whose side is a power of 2
HEL2←{⍉↑ HEL¨↓ ⍉↑ HEL¨↓ ⍵ }
Cognitive transform of square logical matrix ⍵ whose side is a power of 2
COG2←{⍉↑ COG¨↓ ⍉↑ COG¨↓ ⍵ }
Helical transform of logical vector ⍵ whose length is a power of 2
HEL←{
(⍴ ⍵ )=1:⍵
(∇ lhalf ⍵ ),∇ (lhalf ⍵ )≠ rhalf ⍵
}
Cognitive transform of logical vector ⍵ whose length is a power of 2
COG←{
(⍴ ⍵ )=1:⍵
(∇ (rhalf ⍵ )≠ lhalf ⍵ ),∇ rhalf ⍵
}
Auxiliary functions required by the transforms
lhalf←{((⍴ ⍵ )÷2)↑ ⍵ }
rhalf←{((⍴ ⍵ )÷2)↓ ⍵ }
Find the tile of tessellation img, a rectangular matrix of arbitrary dimensions
z←find_tile img;r;c;pats;R;C;rows;cols;k;i;bgr;res;tot;min
r c←⍴ img
pats←cells img
R C←⍴ ⊃ pats[1]
⎕←R,C
rows cols←(⌊ r÷R),⌊ c÷C
⎕←rows,cols
k←⍳ ⍴ pats
min←r×c
:For i :In k
bgr←r c↑ (rows,cols)textile⊃ pats[i]
res←bgr≠ img
tot←+/+/res
:If tot<min
min←tot
z←⊃ pats[i]
:EndIf
:EndFor
cells←{
⎕IO←0
⎕ML←0
DIM←⍴ ⍵
dim←0.9 correl ⍵
r←1+0⌷ dim
c←1+1⌷ dim
R←0⌷ DIM
C←1⌷ DIM
,⍉↑ (⊂ R⍴ r↑ 1)⊂ [0]¨(C⍴ c↑ 1)⊂ [1]R C⍴ ⍵
}
Find the vertical and horizontal periods of binary image y
correl←{
⎕IO←1
ht←shad ⍵
hmax←⍺ ×⌈ /ht
h←(ht≥ hmax)/⍳ ⍴ ht
h←1↑ (h≥ 4)/h
⍝
vt←shad⍉⍵
vmax←⍺ ×⌈ /vt
v←(vt≥ vmax)/⍳ ⍴ vt
v←1↑ (v≥ 4)/v
⍝
⍝ Optional correlation plot
⍝ vt matplot ht
⍝
v,h
}
z←shad y;⎕IO;s;shf;n;i
⎕IO←1
s←2⌷ ⍴ y
z←⍳ 0
shf←y
n←⌊ s÷2
:For i :In ⍳ n
shf←¯1⌽shf
z←z,+/+/shf=y
:EndFor
matplot h;⎕USING;sp;vw
⎕USING←',sharpplot.dll' ',system.drawing.dll' ⍝ Bring in the
⍝ .Net namespaces needed by SharpPlot
⍝ create a chart
sp←⎕NEW Causeway.SharpPlot
sp.FrameStyle←Causeway.FrameStyles.Boxed
sp.Heading←'Correlation'
sp.SetKeyText⊂ 'Vertical' 'Horizontal' ⍝ enclosed because a
⍝ single argument of two strings
sp.YAxisStyle←Causeway.YAxisStyles.(ForceZero+PlainAxis)
sp.SetYTickMarks 2 1 ⍝ two arguments here (major internal and
⍝ minor interval), so no enclose
sp.DrawLineGraph⊂ v h
⍝ again, there is a single argument which
⍝ is a list of lists of y values - (sp.DrawLineGraph v h) would
⍝ take v as y values and h as x values
⍝ view it
vw←⎕NEW Causeway.SharpPlotViewer
vw.SharpPlot←sp
vw.Show ⍬
Create a tiling whose dimensions are ⍺ with tile ⍵ (two different versions)
textile←{⍎⍤ 1⍕⍺ ⍴ ⊂ ⍵ }
textile←{⊃ ⍪ /,/⍺ ⍴ ⊂ ⍵ }
Display array bits
disp bits
'bm'⎕WC'bitmap'('Bits'bits)('Cmap'(2 3⍴ 255 255 255 0 0 0))
'f'⎕WC'form'('Picture' 'bm')
Write array bits to .bmp file x
bits writebmp file;bm
'bm'⎕WC'bitmap'('Bits'bits)('Cmap'(2 3⍴ 255 255 255 0 0 0))
bm.File←mypath,file
2 ⎕NQ'bm' 'FileWrite'
Read .bmp file file into array argb
argb←readbmp file;bmp
'bmp'⎕WC'Bitmap'(mypath,file,'.bmp')
argb←255=256 256 256⊤ bmp.CBits
argb←argb[1;;]
z←mypath
z←'C:\Users\your user name\Desktop\'
Create tessellations for experiments (approximately 12% noise across each image)
testmake←{
t←⍺ textile ⍵
bad←(⍴ t)xrandm 0.12
t≠ bad
}
z←x xrandm y;n
n←×/x
z←x⍴ (n?n)≤ y×n
z←dim sympat p;t
t←⌈ dim÷2
z←t xrandm p
z←z,⌽z
z←z⍪ ⊖z
z←dim sympat2 p;t
t←⌈ dim÷2
z←t xrandm p
z←z,⌽z
z←z⍪ ⊖z
z←dim sympat4 p;q1;q2;q3;q4
q1←(⌈ (dim,dim)÷2)xrandm 0.5
q2←rot90 q1
q3←rot90 q2
q4←rot90 q3
z←(q2,q1)⍪ q3,q4
z←dim sympat4r p;q1;q2;q3;q4
q1←(⌈ (dim,dim)÷2)xrandm 0.5
q1←q1=⍉q1
q2←rot90 q1
q3←rot90 q2
q4←rot90 q3
z←(q2,q1)⍪ q3,q4
z←rot90 m;i
dim←⍴ m
z←(⌽dim)⍴ 0
:For i :In ⍳ dim[1]
z[;i]←⌽m[i;]
:EndFor
Tile motifs included as variables in textile.dws:
m1
m2
m3
m4
m5
m6
Fabric patterns included as variables in the Texture workspace
bricks
circles
hound
swirls
zigzag
llamas
m7