Skip to content

Commit b03703c

Browse files
committed
Add optimized Gaussian blur to completed filters, optimize Binary filter
1 parent d820d0a commit b03703c

12 files changed

+153
-425
lines changed

README.md

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,19 @@ Various image processing functions written in Golang
44

55
This module is a base for https://github.com/julyskies/brille
66

7-
### Filters to add
8-
9-
1. Bilateral filter (static / dynamic)
10-
2. Gaussian blur (static)
11-
3. Image rotation for any given angle
12-
137
### Available filters
148

159
These filters are ready to be used and were optimized compared to previous implementations
1610

17-
- Binary
11+
- Binary **(optimized, uses WaitGroup)**
1812
- Box blur (dynamic)
1913
- Brightness
2014
- Contrast
2115
- Eight colors (color reduction filter)
2216
- Emboss filter (edge detection, static)
2317
- Flip image (horizontal, vertical)
2418
- Gamma correction
19+
- Gaussian blur (dynamic) **(optimized, uses WaitGroup)**
2520
- Grayscale (average, luminance)
2621
- Hue rotate
2722
- Inversion
@@ -38,7 +33,6 @@ These filters are ready to be used and were optimized compared to previous imple
3833
These filters are not ready yet
3934

4035
- Bilateral filter (static / dynamic)
41-
- Gaussian blur (dynamic)
4236
- Rotate image by any given angle
4337

4438
### License

filters/bilateral-slow.go

Lines changed: 0 additions & 69 deletions
This file was deleted.

filters/binary-optimized.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package filters
2+
3+
import (
4+
"math"
5+
"runtime"
6+
"sync"
7+
"time"
8+
)
9+
10+
func Binary(path string, threshold uint8) {
11+
img, format, openMS, convertMS := open(path)
12+
now := math.Round(float64(time.Now().UnixNano()) / 1000000)
13+
14+
pixLen := len(img.Pix)
15+
threads := runtime.NumCPU()
16+
pixPerThreadRaw := float64(pixLen) / float64(threads)
17+
pixPerThread := int(pixPerThreadRaw + (float64(threads) - math.Mod(pixPerThreadRaw, 4.0)))
18+
19+
var wg sync.WaitGroup
20+
21+
processing := func(startIndex int) {
22+
defer wg.Done()
23+
endIndex := clampMax(startIndex+pixPerThread, pixLen)
24+
for i := startIndex; i < endIndex; i += 4 {
25+
average := uint8((int(img.Pix[i]) + int(img.Pix[i+1]) + int(img.Pix[i+2])) / 3)
26+
channel := uint8(255)
27+
if average < threshold {
28+
channel = 0
29+
}
30+
img.Pix[i], img.Pix[i+1], img.Pix[i+2] = channel, channel, channel
31+
}
32+
}
33+
34+
for t := 0; t < threads; t += 1 {
35+
wg.Add(1)
36+
go processing(pixPerThread * t)
37+
}
38+
39+
wg.Wait()
40+
41+
processMS := int(math.Round(float64(time.Now().UnixNano())/1000000) - now)
42+
saveMS := save(img, format)
43+
sum := openMS + convertMS + processMS + saveMS
44+
println("open", openMS, "convert", convertMS, "process", processMS, "save", saveMS, "sum", sum)
45+
}

filters/binary.go

Lines changed: 0 additions & 23 deletions
This file was deleted.

in-progress/gaussian-blur-emf.go renamed to filters/gaussian-blur-optimized.go

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package progress
1+
package filters
22

33
import (
44
"math"
@@ -9,8 +9,39 @@ import (
99
"go-image-processing/utilities"
1010
)
1111

12-
// Gaussian blur: even more faster (sync.WaitGroup) - top 1
13-
func GaussianBlurEMF(path string, sigma float64) {
12+
const K float64 = 6
13+
14+
func clampMax[T float64 | int | uint8](value, max T) T {
15+
if value > max {
16+
return max
17+
}
18+
return value
19+
}
20+
21+
func createKernel(sigma float64) []float64 {
22+
dim := math.Max(3.0, K*sigma)
23+
sqrtSigmaPi2 := math.Sqrt(math.Pi*2.0) * sigma
24+
s2 := 2.0 * sigma * sigma
25+
sum := 0.0
26+
kDim := dim
27+
if int(kDim)%2 == 0 {
28+
kDim = dim - 1
29+
}
30+
kernel := make([]float64, int(kDim))
31+
half := int(len(kernel) / 2)
32+
i := -half
33+
for j := 0; j < len(kernel); j += 1 {
34+
kernel[j] = math.Exp(-float64(i*i)/(s2)) / sqrtSigmaPi2
35+
sum += kernel[j]
36+
i += 1
37+
}
38+
for k := 0; k < int(kDim); k += 1 {
39+
kernel[k] /= sum
40+
}
41+
return kernel
42+
}
43+
44+
func GaussianBlur(path string, sigma float64) {
1445
if sigma < 0 {
1546
sigma *= -1
1647
}
@@ -75,20 +106,17 @@ func GaussianBlurEMF(path string, sigma float64) {
75106
wg.Add(1)
76107
go processing(pixPerThread*t, "horizontal")
77108
}
78-
79109
wg.Wait()
80110

81111
// vertical
82112
for t := 0; t < threads; t += 1 {
83113
wg.Add(1)
84114
go processing(pixPerThread*t, "vertical")
85115
}
86-
87116
wg.Wait()
88117

89118
processMS := int(math.Round(float64(time.Now().UnixNano())/1000000) - now)
90-
saveMS := save(img, format, 1)
119+
saveMS := save(img, format)
91120
sum := openMS + convertMS + processMS + saveMS
92121
println("open", openMS, "convert", convertMS, "process", processMS, "save", saveMS, "sum", sum)
93-
println("threads", threads)
94122
}

in-progress/binary-ef.go renamed to in-progress/binary-ch.go

Lines changed: 26 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"math"
1111
"os"
1212
"runtime"
13-
"sync"
1413
"time"
1514
)
1615

@@ -56,48 +55,45 @@ func save(img *image.RGBA, format string, iteration int) int {
5655
return int(math.Round(float64(time.Now().UnixNano())/1000000) - now)
5756
}
5857

59-
func processing(path string, threshold uint8, iteration int) int {
58+
// Binary filter that uses channels - slightly slower than WaitGroup
59+
func BinaryCH(path string, threshold uint8) {
6060
img, format, openMS, convertMS := open(path)
6161
now := math.Round(float64(time.Now().UnixNano()) / 1000000)
6262

63-
sa := len(img.Pix) / 4
63+
pixLen := len(img.Pix)
64+
threads := runtime.NumCPU()
65+
pixPerThreadRaw := float64(pixLen) / float64(threads)
66+
pixPerThread := int(pixPerThreadRaw + (float64(threads) - math.Mod(pixPerThreadRaw, 4.0)))
6467

65-
var wg sync.WaitGroup
66-
wg.Add(4)
67-
68-
process := func(startIndex, endIndex int, threshold uint8, pixels *[]uint8) {
69-
defer wg.Done()
70-
pxs := *pixels
68+
processing := func(startIndex int, ch chan int, thread int) {
69+
endIndex := clampMax(startIndex+pixPerThread, pixLen)
7170
for i := startIndex; i < endIndex; i += 4 {
72-
average := uint8((int(pxs[i]) + int(pxs[i+1]) + int(pxs[i+2])) / 3)
71+
average := uint8((int(img.Pix[i]) + int(img.Pix[i+1]) + int(img.Pix[i+2])) / 3)
7372
channel := uint8(255)
7473
if average < threshold {
7574
channel = 0
7675
}
77-
pxs[i], pxs[i+1], pxs[i+2] = channel, channel, channel
76+
img.Pix[i], img.Pix[i+1], img.Pix[i+2] = channel, channel, channel
7877
}
78+
ch <- thread
7979
}
8080

81-
go process(0, sa, threshold, &img.Pix)
82-
// go process(sa, len(img.Pix), threshold, &img.Pix)
83-
go process(sa, sa*2, threshold, &img.Pix)
84-
go process(sa*2, sa*3, threshold, &img.Pix)
85-
go process(sa*3, len(img.Pix), threshold, &img.Pix)
86-
87-
wg.Wait()
81+
ch := make(chan int)
82+
done := make([]int, 0)
83+
for t := 0; t < threads; t += 1 {
84+
go processing(pixPerThread*t, ch, t)
85+
}
86+
for {
87+
result := <-ch
88+
done = append(done, result)
89+
if len(done) == threads {
90+
close(ch)
91+
break
92+
}
93+
}
8894

8995
processMS := int(math.Round(float64(time.Now().UnixNano())/1000000) - now)
90-
saveMS := save(img, format, iteration)
96+
saveMS := save(img, format, 1)
9197
sum := openMS + convertMS + processMS + saveMS
92-
// println("f open", openMS, "convert", convertMS, "process", processMS, "save", saveMS, "sum", sum)
93-
return sum
94-
}
95-
96-
func BinaryEF(path string, threshold uint8) {
97-
iterations := 100
98-
total := 0
99-
for i := 0; i < iterations; i += 1 {
100-
total += processing(path, threshold, i)
101-
}
102-
println("avg", total/iterations, "CPUs:", runtime.NumCPU())
98+
println("f open", openMS, "convert", convertMS, "process", processMS, "save", saveMS, "sum", sum)
10399
}

in-progress/binary-ef-slices.go

Lines changed: 0 additions & 57 deletions
This file was deleted.

in-progress/gaussian-blur-daf.go renamed to in-progress/gaussian-blur-ch-slow.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"go-image-processing/utilities"
99
)
1010

11-
// Gaussian blur: different approach faster (channels) - worst performance
12-
func GaussianBlurDAF(path string, sigma float64) {
11+
// Gaussian blur that uses channels - slower version
12+
func GaussianBlurCHSlow(path string, sigma float64) {
1313
if sigma < 0 {
1414
sigma *= -1
1515
}

0 commit comments

Comments
 (0)