forked from nipraxis/textbook
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathotsu_threshold.Rmd
136 lines (109 loc) · 3.73 KB
/
otsu_threshold.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
---
jupyter:
orphan: true
jupytext:
notebook_metadata_filter: all,-language_info
split_at_heading: true
text_representation:
extension: .Rmd
format_name: rmarkdown
format_version: '1.2'
jupytext_version: 1.13.7
kernelspec:
display_name: Python 3
language: python
name: python3
---
# Otsu’s method for binarizing images
This page has some notes explaining [Otsu’s
method](https://en.wikipedia.org/wiki/Otsu%27s_method) for binarizing grayscale
images.
The best source on the method is the [original
paper](http://ieeexplore.ieee.org/stamp/stamp.jsp?arnumber=4310076). At the
time I wrote this page, the Wikipedia article is too messy to be useful.
Conceptually, Otsu’s method proceeds like this:
* create the 1D histogram of image values, where the histogram has $L$ bins.
The histogram is $L$ bin counts $\vec{c} = [c_1, c_2, ... c_L]$, where $c_i$
is the number of values falling in bin $i$. The histogram has bin centers
$\vec{v} = [v_1, v_2, ..., v_L]$, where $v_i$ is the image value
corresponding to the center of bin $i$;
* for every bin number $k \in [1, 2, 3, ..., L-1]$, divide the histogram at
that bin to form a *left histogram* and a *right histogram*, where the left
histogram has counts, centers $[c_1, ... c_k], [v_1, ... v_k]$, and the
right histogram has counts, centers $[c_{k+1} ... c_L], [v_{k+1} .. v_L]$;
* calculate the mean corresponding to the values in the left and right
histogram:
$$
n_k^{left} = \sum_{i=1}^{k} c_i \\
\mu_k^{left} = \frac{1}{n_k^{left}} \sum_{i=1}^{k} c_i v_i \\
n_k^{right} = \sum_{i={k+1}}^{L} c_i \\
\mu_k^{right} = \frac{1}{n_k^{right}} \sum_{i={k+1}}^{L} c_i v_i
$$
* calculate the sum of squared deviations from the left and right means:
$$
\mathrm{SSD}_k^{left} = \sum_{i=1}^{k} c_i (v_i - \mu_k^{left}) \\
\mathrm{SSD}_k^{right} = \sum_{i={k+1}}^{L} c_i (v_i - \mu_k^{right}) \\
\mathrm{SSD}_k^{total} = SSD_k^{left} + SSD_k^{right}
$$
* find the bin number $k$ that minimizes $\mathrm{SSD}_k^{total}$:
$$
z = \mathrm{argmin}_k \mathrm{SSD}_k^{total}
$$
* the binarizing threshold for the image is the value corresponding to this
bin $z$:
$$
t = v_z
$$
Here is Otsu’s threshold in action. First we load an image:
```{python}
# Get the image file from the web.
import nipraxis
camera_fname = nipraxis.fetch_file('camera.txt')
camera_fname
```
```{python}
# Loat the file, show the image.
import numpy as np
import matplotlib.pyplot as plt
cameraman_data = np.loadtxt(camera_fname)
cameraman = np.reshape(cameraman_data, (512, 512))
plt.imshow(cameraman.T, cmap='gray')
```
Make a histogram:
```{python}
cameraman_1d = cameraman.ravel()
n_bins = 128
plt.hist(cameraman_1d, bins=n_bins)
counts, edges = np.histogram(cameraman, bins=n_bins)
bin_centers = edges[:-1] + np.diff(edges) / 2.
```
Calculate the threshold:
```{python}
def ssd(counts, centers):
""" Sum of squared deviations from mean """
n = np.sum(counts)
mu = np.sum(centers * counts) / n
return np.sum(counts * ((centers - mu) ** 2))
```
```{python}
total_ssds = []
for bin_no in range(1, n_bins):
left_ssd = ssd(counts[:bin_no], bin_centers[:bin_no])
right_ssd = ssd(counts[bin_no:], bin_centers[bin_no:])
total_ssds.append(left_ssd + right_ssd)
z = np.argmin(total_ssds)
t = bin_centers[z]
print('Otsu bin (z):', z)
print('Otsu threshold (c[z]):', bin_centers[z])
```
This gives the same result as the [scikit-image](http://scikit-image.org/) implementation:
```{python}
from skimage.filters import threshold_otsu
threshold_otsu(cameraman, n_bins)
np.allclose(threshold_otsu(cameraman, n_bins), t)
```
The original image binarized with this threshold:
```{python}
binarized = cameraman > t
plt.imshow(binarized.T, cmap='gray')
```