LocalAdaptiveBinarize¶
Status: Stable
documented, exercised by the test suite and/or worked examples, with no known limitations recorded.
Description¶
LocalAdaptiveBinarize[image, r]
binarizes by comparing each pixel to the MEAN of its own (2r+1)x(2r+1) neighbourhood, and LocalAdaptiveBinarize[image, r, {c1, c2, c3}] to c1*mean + c2*stddev + c3. A global threshold cannot binarize unevenly lit content, and that is not a tuning problem: if one half of a page is darker than the other, no single number separates ink from paper in both halves at once. Mean alone (the default {1, 0, 0}) is Bradley's method; a negative c2 is Sauvola's, tightening the threshold where the neighbourhood is busy. Summed-area tables make the window statistics O(1) per pixel regardless of r -- without them a radius-16 window would be 1089 taps per pixel. The result is typed "Bit", since it is binary by construction. Colour is reduced to luminance first.
Examples (45)¶
Every input below was run against the current Mathilda build and its output recorded.
Basic Examples (5)¶
In[1]:= ImageData[LocalAdaptiveBinarize[Image[{{0.2, 0.3, 0.9}, {0.2, 0.8, 0.9}, {0.1, 0.2, 0.3}}], 1]]
Out[1]= {{0.0, 0.0, 1.0}, {0.0, 1.0, 1.0}, {0.0, 0.0, 0.0}}
In[2]:= chk = Image[Table[If[Mod[Quotient[i - 1, 2] + Quotient[j - 1, 2], 2] == 0, 0., 1.], {i, 1, 16}, {j, 1, 16}], "Real"];
In[3]:= LocalAdaptiveBinarize[chk, 2]
Out[3]= -Image-
In[4]:= ImageDimensions[LocalAdaptiveBinarize[chk, 2]]
Out[4]= {16, 16}
In[5]:= ImageType[LocalAdaptiveBinarize[chk, 1]]
Out[5]= "Bit"
Scope (22)¶
In[6]:= chk = Image[Table[If[Mod[Quotient[i - 1, 2] + Quotient[j - 1, 2], 2] == 0, 0., 1.], {i, 1, 16}, {j, 1, 16}], "Real"];
In[7]:= disk = Image[Table[N[Boole[(i - 8.5)^2 + (j - 8.5)^2 <= 25]], {i, 1, 16}, {j, 1, 16}], "Real"];
In[8]:= ramp = Image[Table[N[(j - 1)/15], {i, 1, 16}, {j, 1, 16}], "Real"];
In[9]:= zone = Image[Table[N[(1 + Cos[((i - 16)^2 + (j - 16)^2)/40.])/2], {i, 1, 32}, {j, 1, 32}], "Real"];
In[10]:= noise = Image[Table[N[Mod[i*37 + j*17, 101]]/101, {i, 1, 32}, {j, 1, 32}], "Real"];
In[11]:= rgb = Image[Table[{N[i/16], N[j/16], 0.5}, {i, 1, 16}, {j, 1, 16}], "Real"];
In[12]:= sky = Image[Table[{N[0.15 + 0.7 (16 - i)/16], N[0.35 + 0.45 (16 - i)/16], N[0.85 - 0.35 (16 - i)/16]}, {i, 1, 16}, {j, 1, 24}], "Real"];
In[13]:= bit = Image[Table[Boole[Mod[i + j, 2] == 0], {i, 1, 8}, {j, 1, 8}]];
In[14]:= byte = Image[Table[Mod[i*13 + j*7, 256], {i, 1, 16}, {j, 1, 16}]];
In[15]:= vol = Image3D[Table[N[Mod[z*7 + y*13 + x*3, 97]]/97, {z, 1, 8}, {y, 1, 10}, {x, 1, 12}], "Real"];
In[16]:= LocalAdaptiveBinarize[disk, 1]
Out[16]= -Image-
In[17]:= LocalAdaptiveBinarize[ramp, 2]
Out[17]= -Image-
In[18]:= LocalAdaptiveBinarize[zone, 2]
Out[18]= -Image-
In[19]:= LocalAdaptiveBinarize[noise, 3]
Out[19]= -Image-
In[20]:= LocalAdaptiveBinarize[rgb, 1]
Out[20]= -Image-
In[21]:= LocalAdaptiveBinarize[sky, 2]
Out[21]= -Image-
In[22]:= LocalAdaptiveBinarize[bit, 1]
Out[22]= -Image-
In[23]:= LocalAdaptiveBinarize[byte, 2]
Out[23]= -Image-
In[24]:= LocalAdaptiveBinarize[vol, 1]
Out[24]= -Image-
In[25]:= ImageChannels[LocalAdaptiveBinarize[rgb, 2]]
Out[25]= 1
In[26]:= ImageDimensions[LocalAdaptiveBinarize[vol, 1]]
Out[26]= {12, 10, 8}
In[27]:= LocalAdaptiveBinarize[chk, 4]
Out[27]= -Image-
Applications (6)¶
In[28]:= zone = Image[Table[N[(1 + Cos[((i - 16)^2 + (j - 16)^2)/40.])/2], {i, 1, 32}, {j, 1, 32}], "Real"];
In[29]:= noise = Image[Table[N[Mod[i*37 + j*17, 101]]/101, {i, 1, 32}, {j, 1, 32}], "Real"];
In[30]:= rgb = Image[Table[{N[i/16], N[j/16], 0.5}, {i, 1, 16}, {j, 1, 16}], "Real"];
In[31]:= Binarize[LocalAdaptiveBinarize[noise, 2]]
Out[31]= -Image-
In[32]:= EdgeDetect[LocalAdaptiveBinarize[zone, 2]]
Out[32]= -Image-
In[33]:= ImageDimensions[LocalAdaptiveBinarize[Import[Export["/tmp/mathilda_ex.png", rgb]], 2]]
Out[33]= {16, 16}
Properties & Relations (6)¶
In[34]:= chk = Image[Table[If[Mod[Quotient[i - 1, 2] + Quotient[j - 1, 2], 2] == 0, 0., 1.], {i, 1, 16}, {j, 1, 16}], "Real"];
In[35]:= vol = Image3D[Table[N[Mod[z*7 + y*13 + x*3, 97]]/97, {z, 1, 8}, {y, 1, 10}, {x, 1, 12}], "Real"];
In[36]:= ImageDimensions[LocalAdaptiveBinarize[chk, 3]] === ImageDimensions[chk]
Out[36]= True
In[37]:= Max[Flatten[ImageData[LocalAdaptiveBinarize[chk, 2]]]] <= 1.0
Out[37]= True
In[38]:= Min[Flatten[ImageData[LocalAdaptiveBinarize[chk, 2]]]] >= 0.0
Out[38]= True
In[39]:= ImageDimensions[LocalAdaptiveBinarize[vol, 2]] === ImageDimensions[vol]
Out[39]= True
Neat Examples (3)¶
In[40]:= zone = Image[Table[N[(1 + Cos[((i - 16)^2 + (j - 16)^2)/40.])/2], {i, 1, 32}, {j, 1, 32}], "Real"];
In[41]:= LocalAdaptiveBinarize[zone, 4]
Out[41]= -Image-
In[42]:= LocalAdaptiveBinarize[zone, 1]
Out[42]= -Image-
Applications (3)¶
The result is binary, typed Bit
In[43]:= ImageType[LocalAdaptiveBinarize[Image[{{0., 0., 1.}, {0., 1., 1.}, {1., 1., 1.}}], 1]]
Out[43]= "Bit"
Each pixel vs its local mean
In[44]:= ImageData[LocalAdaptiveBinarize[Image[{{0., 0., 1.}, {0., 1., 1.}, {1., 1., 1.}}], 1]]
Out[44]= {{0.0, 0.0, 1.0}, {0.0, 1.0, 1.0}, {1.0, 1.0, 0.0}}
A negative c2 is Sauvola's rule
In[45]:= ImageData[LocalAdaptiveBinarize[Image[{{0.2, 0.9}, {0.3, 0.8}}], 1, {1, -0.2, 0.}]]
Out[45]= {{0.0, 1.0}, {0.0, 1.0}}
Implementation notes¶
Algorithm. builtin_localadaptivebinarize thresholds each pixel against a statistic of its
own (2r+1) × (2r+1) neighbourhood rather than a global level — the only way to binarize
unevenly lit content, since if one half of a page is darker than the other, no single number
separates ink from paper in both halves. The rule is g > c1·mean + c2·stddev + c3, with the
coefficients defaulting to {1, 0, 0} (Bradley's mean thresholding);
LocalAdaptiveBinarize[image, r, {c1, c2, c3}] sets them, and a negative c2 gives Sauvola's
method, tightening the threshold where the neighbourhood is busy.
The window statistics are O(1) per pixel via summed-area tables: a padded prefix-sum
table s1 for the mean, and a second table s2 of squares (built only when c2 ≠ 0, since
the default never needs the standard deviation) for the variance s2/area − mean², clamped at
0 before sqrt because cancellation can push a uniform window a hair negative. Without the
tables a radius-16 window would be 1089 taps per pixel.
Data structures. A grey plane, one or two (PH+1)(PW+1) summed-area tables, and a
width · height byte mask; the result is built by bit_image_from_mask as a "Bit" image —
the output is binary by construction, so it is typed "Bit" and handed back as a packed buffer
(every image head returns one — make check-image-packing).
Complexity / limits. O(pixels) regardless of radius. Radius 1–256; colour is reduced to
luminance first. The coefficient triple must be numeric.
Attributes: Protected.
References¶
See also: List, Binarize, ImagePad
- Source:
src/imagefilter.c - Specification:
docs/spec/builtins/image-processing.md - Tests:
tests/test_image.c
Notes & additional examples¶
Notes¶
LocalAdaptiveBinarize[image, r] binarizes by comparing each pixel to the mean of its own
(2r+1) × (2r+1) neighbourhood; LocalAdaptiveBinarize[image, r, {c1, c2, c3}] compares to
c1·mean + c2·stddev + c3. A global threshold cannot binarize unevenly lit content: if one
half of a page is darker than the other, no single number separates ink from paper in both
halves at once.
Mean alone (the default {1, 0, 0}) is Bradley's method; a negative c2 is Sauvola's,
tightening the threshold where the neighbourhood is busy. Summed-area tables make the window
statistics O(1) per pixel regardless of r. The result is typed "Bit", and colour is
reduced to luminance first.