FindThreshold¶
Status: Stable
documented, exercised by the test suite and/or worked examples, with no known limitations recorded.
Description¶
FindThreshold[image]
gives a threshold separating the image into two classes, by Otsu's method: the level maximising the BETWEEN-class variance w0 w1 (mu0 - mu1)^2, which is algebraically the same as minimising the weighted within-class variance but needs only one incremental pass over a 256-bin histogram. A colour image is reduced to Rec. 601 luminance first. Returns unevaluated for an image whose pixels are all identical, since no threshold splits one cluster into two.
Examples (28)¶
Every input below was run against the current Mathilda build and its output recorded.
Basic Examples (9)¶
In[1]:= FindThreshold[Image[{{0., 0., 1.}, {0., 1., 1.}}]]
Out[1]= 0.00196078
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]:= rgb = Image[Table[{N[i/16], N[j/16], 0.5}, {i, 1, 16}, {j, 1, 16}], "Real"];
In[4]:= bit = Image[Table[Boole[Mod[i + j, 2] == 0], {i, 1, 8}, {j, 1, 8}]];
In[5]:= byte = Image[Table[Mod[i*13 + j*7, 256], {i, 1, 16}, {j, 1, 16}]];
In[6]:= FindThreshold[chk]
Out[6]= 0.00196078
In[7]:= FindThreshold[rgb]
Out[7]= 0.523529
In[8]:= FindThreshold[bit]
Out[8]= 0.00196078
In[9]:= FindThreshold[byte]
Out[9]= 0.519608
Scope (12)¶
In[10]:= disk = Image[Table[N[Boole[(i - 8.5)^2 + (j - 8.5)^2 <= 25]], {i, 1, 16}, {j, 1, 16}], "Real"];
In[11]:= ramp = Image[Table[N[(j - 1)/15], {i, 1, 16}, {j, 1, 16}], "Real"];
In[12]:= zone = Image[Table[N[(1 + Cos[((i - 16)^2 + (j - 16)^2)/40.])/2], {i, 1, 32}, {j, 1, 32}], "Real"];
In[13]:= noise = Image[Table[N[Mod[i*37 + j*17, 101]]/101, {i, 1, 32}, {j, 1, 32}], "Real"];
In[14]:= rgb = Image[Table[{N[i/16], N[j/16], 0.5}, {i, 1, 16}, {j, 1, 16}], "Real"];
In[15]:= 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[16]:= FindThreshold[disk]
Out[16]= 0.00196078
In[17]:= FindThreshold[ramp]
Out[17]= 0.468627
In[18]:= FindThreshold[zone]
Out[18]= 0.488235
In[19]:= FindThreshold[noise]
Out[19]= 0.488235
In[20]:= FindThreshold[sky]
Out[20]= 0.539216
In[21]:= FindThreshold[Import[Export["/tmp/mathilda_ex.png", rgb]]]
Out[21]= 0.523529
Applications (2)¶
In[22]:= 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[23]:= Table[FindThreshold[GaussianFilter[chk, r]], {r, 1, 3}]
Out[23]= {0.194118, 0.460784, 0.382353}
Neat Examples (2)¶
In[24]:= zone = Image[Table[N[(1 + Cos[((i - 16)^2 + (j - 16)^2)/40.])/2], {i, 1, 32}, {j, 1, 32}], "Real"];
In[25]:= FindThreshold[zone]
Out[25]= 0.488235
Applications (3)¶
In[26]:= FindThreshold[Image[{{0.2, 0.3, 0.7, 0.8}}]]
Out[26]= 0.303922
In[27]:= Binarize[Image[{{0.2, 0.3, 0.7, 0.8}}], FindThreshold[Image[{{0.2, 0.3, 0.7, 0.8}}]]]
Out[27]= -Image-
In[28]:= FindThreshold[Image[{{0.5, 0.5}, {0.5, 0.5}}]]
Out[28]= FindThreshold[Image[NDArray[{{0.5, 0.5}, {0.5, 0.5}}], "Real"]]
Implementation notes¶
Algorithm. builtin_findthreshold returns the single level that best
separates an image into two classes by Otsu's method. It reduces the image to
a Rec. 601 luminance plane (img_grey_plane) and calls img_otsu, which bins
the pixels into a 256-bin histogram (IMG_OTSU_BINS), with out-of-range values
clamped into the extreme bins rather than dropped so they still vote. It then
sweeps the threshold t over the bins, maintaining incrementally the class
weight w0 and first moment sum0, and maximises the between-class variance
sb = w0 · w1 · (mu0 − mu1)² — algebraically equivalent to minimising the
weighted within-class variance but computable in one incremental pass. A NaN
pixel aborts (it has no bin); a strictly-greater comparison keeps the lowest
winning bin, so ties are deterministic. The returned threshold sits at the
upper edge of the winning bin, (best_bin + 0.5)/255, so a pixel in that bin
falls on the low side. An image whose pixels are all identical has only one
occupied cluster and no split exists, so the routine returns false and the head
is left unevaluated.
Data structures. A fixed 256-element double histogram on the stack; a
single flat luminance double plane loaded from the image. No per-threshold
allocation — the sweep is O(bins).
Complexity / limits. O(width · height) to build the histogram plus
O(bins) for the sweep; the threshold is a double on the same unit scale as
ImageData. Returns unevaluated for a degenerate (single-value) image. The same
img_otsu backs Binarize's default threshold and EdgeDetect's high
threshold.
Attributes: Protected.
References¶
See also: Image
- N. Otsu, A threshold selection method from gray-level histograms, IEEE Trans. Systems, Man, and Cybernetics 9 (1979) 62-66.
- Source:
src/imagefilter.c - Specification:
docs/spec/builtins/image-processing.md - Tests:
tests/test_image.c
Notes & additional examples¶
Notes¶
FindThreshold[image] gives a threshold separating the image into two classes by
Otsu's method: the level maximising the between-class variance w0 w1 (mu0 −
mu1)², which is algebraically the same as minimising the weighted within-class
variance but needs only one incremental pass over a 256-bin histogram.
A colour image is reduced to Rec. 601 luminance first. The returned level sits at
the upper edge of its histogram bin and is on the same unit scale as ImageData,
so it feeds straight into Binarize[image, t]. It returns unevaluated for an
image whose pixels are all identical, since no threshold splits one cluster into
two. The same routine backs Binarize's default threshold and EdgeDetect's
hysteresis high threshold.