/
otsu.go
109 lines (92 loc) · 2.74 KB
/
otsu.go
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
package bot
import (
"fmt"
"gopkg.in/gographics/imagick.v3/imagick"
)
type OtsuArgs struct {
ImageURL string `default:"" description:"URL to the image to process. Leave blank to automatically attempt to find an image."`
Invert bool `default:"false" description:"Invert the colors."`
}
func (args OtsuArgs) GetImageURL() string {
return args.ImageURL
}
// Otsu turns the image black and white by applying an adaptive threshold using Otsu's method
func Otsu(wand *imagick.MagickWand, args OtsuArgs) ([]*imagick.MagickWand, error) {
numOfPixels := 0
histogram := map[int]int{}
pixelIterator := wand.NewPixelIterator()
for y := 0; y < int(wand.GetImageHeight()); y++ {
pixels := pixelIterator.GetNextIteratorRow()
if len(pixels) == 0 {
break
}
for _, pixel := range pixels {
gray := 0
alpha := pixel.GetAlpha()
if alpha != 1 {
if alpha == 0 {
pixel.SetColor("#FFFFFF")
gray = 255
}
pixel.SetAlpha(1)
}
if gray != 255 {
red := pixel.GetRed() * 255
green := pixel.GetGreen() * 255
blue := pixel.GetBlue() * 255
gray = int(0.2126*red + 0.7152*green + 0.0722*blue)
hex := fmt.Sprintf("#%02x%02x%02x", gray, gray, gray)
pixel.SetColor(hex)
}
histogram[gray] += 1
numOfPixels++
}
err := pixelIterator.SyncIterator()
if err != nil {
return nil, fmt.Errorf("error writing colours back to image when converting to grayscale: %w", err)
}
}
sum := 0
for i := 0; i < 256; i++ {
sum += i * histogram[i]
}
sumBackground, weightBackground, weightForeground, threshold, maxVariance := 0, 0, 0, 0, 0
for i := 0; i < 256; i++ {
weightBackground += histogram[i]
if weightBackground == 0 {
continue
}
weightForeground = numOfPixels - weightBackground
if weightForeground == 0 {
continue
}
sumBackground += i * histogram[i]
meanBackground := sumBackground / weightBackground
meanForeground := (sum - sumBackground) / weightForeground
betweenVariance := weightBackground * weightForeground * (meanBackground - meanForeground) * (meanBackground - meanForeground)
if betweenVariance > maxVariance {
maxVariance = betweenVariance
threshold = i
}
}
pixelIterator = wand.NewPixelIterator()
for y := 0; y < int(wand.GetImageHeight()); y++ {
pixels := pixelIterator.GetNextIteratorRow()
if len(pixels) == 0 {
break
}
for _, pixel := range pixels {
red := int(pixel.GetRed() * 255)
if (args.Invert && red > threshold) || (!args.Invert && red < threshold) {
pixel.SetColor("#000000")
} else {
pixel.SetColor("#FFFFFF")
}
}
err := pixelIterator.SyncIterator()
if err != nil {
return nil, fmt.Errorf("error writing colours back to image when converting to black and white: %w", err)
}
}
return []*imagick.MagickWand{wand}, nil
}