summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick White <git@njw.name>2020-07-23 23:09:43 +0100
committerNick White <git@njw.name>2020-07-23 23:09:43 +0100
commit1ef9df29fb2d5bb6585e0d0ed99e13a900289e7b (patch)
tree124ddc2d71e3090fa131468cdf22af076a177063
parent6a36400351bea5052431bb1feace358fa67a5cf9 (diff)
Update to v0.2.1 of integralimg, and improve various things
- Improve integral sauvola by rounding threshold correctly - The Sauvola functions can now process any image.Image, not just an image.Gray - Add ImageWindower interface and use it to generalise wipesides.go - Rely on Bounds() for image bounds rather than implementation- specific stuff in integralimg Note that a couple of the wipesides tests are now failing. It's possible that this is due to fixed or introduced bugs (let's hope the former) changing sensible thresholds. Will need to look into this and sort it.
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--preprocmulti.go12
-rw-r--r--sauvola.go36
-rw-r--r--test_helpers.go2
-rw-r--r--testdata/pg1_integralsauvola_k0.3_w19.pngbin19456 -> 19618 bytes
-rw-r--r--testdata/pg1_integralsauvola_k0.5_w19.pngbin18241 -> 18439 bytes
-rw-r--r--testdata/pg1_integralsauvola_k0.5_w41.pngbin18260 -> 18284 bytes
-rw-r--r--util.go12
-rw-r--r--wipesides.go27
-rw-r--r--wipesides_test.go13
11 files changed, 56 insertions, 52 deletions
diff --git a/go.mod b/go.mod
index cd9d133..b2c2c0c 100644
--- a/go.mod
+++ b/go.mod
@@ -7,5 +7,5 @@ require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/wcharczuk/go-chart v2.0.2-0.20191206192251-962b9abdec2b+incompatible
golang.org/x/image v0.0.0-20200618115811-c13761719519 // indirect
- rescribe.xyz/integralimg v0.1.1
+ rescribe.xyz/integralimg v0.2.1
)
diff --git a/go.sum b/go.sum
index 7b10a68..d1d33ca 100644
--- a/go.sum
+++ b/go.sum
@@ -16,5 +16,5 @@ golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20181205014116-22934f0fdb62/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-rescribe.xyz/integralimg v0.1.1 h1:riLayPKKM5bs/ZyIcYhbUs4qP8wQOWc+tMxOF3vuscI=
-rescribe.xyz/integralimg v0.1.1/go.mod h1:2euyPigpyIixTWO6JtFEhDp/3YKA6yy+d8g17oL0L0s=
+rescribe.xyz/integralimg v0.2.1 h1:OhYifujtlVGYm/WfLl8ios/bouzIWcwuzbRyCZQiab0=
+rescribe.xyz/integralimg v0.2.1/go.mod h1:2euyPigpyIixTWO6JtFEhDp/3YKA6yy+d8g17oL0L0s=
diff --git a/preprocmulti.go b/preprocmulti.go
index e671108..25d35ea 100644
--- a/preprocmulti.go
+++ b/preprocmulti.go
@@ -49,10 +49,8 @@ func PreProcMulti(inPath string, ksizes []float64, binType string, binWsize int,
if err != nil {
return donePaths, err
}
- b := img.Bounds()
- gray := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy()))
- draw.Draw(gray, b, img, b.Min, draw.Src)
+ b := img.Bounds()
if binWsize == 0 {
binWsize = autowsize(b)
}
@@ -61,11 +59,15 @@ func PreProcMulti(inPath string, ksizes []float64, binType string, binWsize int,
binWsize++
}
+ intImg := integralimg.NewImage(b)
+ draw.Draw(intImg, b, img, b.Min, draw.Src)
+ intSqImg := integralimg.NewSqImage(b)
+ draw.Draw(intSqImg, b, img, b.Min, draw.Src)
+
var clean, threshimg image.Image
- integrals := integralimg.ToAllIntegralImg(gray)
for _, k := range ksizes {
- threshimg = PreCalcedSauvola(integrals, gray, k, binWsize)
+ threshimg = PreCalcedSauvola(*intImg, *intSqImg, img, k, binWsize)
if binType == "zeroinv" {
threshimg, err = BinToZeroInv(threshimg.(*image.Gray), img.(*image.RGBA))
diff --git a/sauvola.go b/sauvola.go
index ee7c870..9eef01b 100644
--- a/sauvola.go
+++ b/sauvola.go
@@ -41,38 +41,32 @@ func Sauvola(img image.Image, ksize float64, windowsize int) *image.Gray {
// "Efficient Implementation of Local Adaptive Thresholding Techniques Using Integral Images"
// and
// https://stackoverflow.com/questions/13110733/computing-image-integral
-func IntegralSauvola(img *image.Gray, ksize float64, windowsize int) *image.Gray {
+func IntegralSauvola(img image.Image, ksize float64, windowsize int) *image.Gray {
b := img.Bounds()
- new := image.NewGray(b)
-
- integrals := integralimg.ToAllIntegralImg(img)
- for y := b.Min.Y; y < b.Max.Y; y++ {
- for x := b.Min.X; x < b.Max.X; x++ {
- m, dev := integrals.MeanStdDevWindow(x, y, windowsize)
- threshold := m * (1 + ksize*((dev/128)-1))
- if img.GrayAt(x, y).Y < uint8(threshold) {
- new.SetGray(x, y, color.Gray{0})
- } else {
- new.SetGray(x, y, color.Gray{255})
- }
- }
- }
+ intImg := integralimg.NewImage(b)
+ draw.Draw(intImg, b, img, b.Min, draw.Src)
+ intSqImg := integralimg.NewSqImage(b)
+ draw.Draw(intSqImg, b, img, b.Min, draw.Src)
- return new
+ return PreCalcedSauvola(*intImg, *intSqImg, img, ksize, windowsize)
}
// PreCalcedSauvola Implements Sauvola's algorithm using precalculated Integral Images
-func PreCalcedSauvola(integrals integralimg.WithSq, img *image.Gray, ksize float64, windowsize int) *image.Gray {
- // TODO: have this be the root function that the other two reference
+func PreCalcedSauvola(intImg integralimg.Image, intSqImg integralimg.SqImage, img image.Image, ksize float64, windowsize int) *image.Gray {
b := img.Bounds()
+ gray := image.NewGray(b)
+ draw.Draw(gray, b, img, b.Min, draw.Src)
new := image.NewGray(b)
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
- m, dev := integrals.MeanStdDevWindow(x, y, windowsize)
- threshold := m * (1 + ksize*((dev/128)-1))
- if img.GrayAt(x, y).Y < uint8(threshold) {
+ m, dev := integralimg.MeanStdDevWindow(intImg, intSqImg, x, y, windowsize)
+ // Divide by 255 to adjust from Gray16 used by integralimg to 8 bit Gray
+ m8 := m / 255
+ dev8 := dev / 255
+ threshold := m8 * (1 + ksize*((dev8/128)-1))
+ if gray.GrayAt(x, y).Y < uint8(math.Round(threshold)) {
new.SetGray(x, y, color.Gray{0})
} else {
new.SetGray(x, y, color.Gray{255})
diff --git a/test_helpers.go b/test_helpers.go
index 97a43dd..d36ef67 100644
--- a/test_helpers.go
+++ b/test_helpers.go
@@ -30,7 +30,7 @@ func decode(s string) (*image.Gray, error) {
return gray, nil
}
-func imgsequal(img1 *image.Gray, img2 *image.Gray) bool {
+func imgsequal(img1, img2 image.Image) bool {
b := img1.Bounds()
if !b.Eq(img2.Bounds()) {
return false
diff --git a/testdata/pg1_integralsauvola_k0.3_w19.png b/testdata/pg1_integralsauvola_k0.3_w19.png
index bdf5712..00aa1d8 100644
--- a/testdata/pg1_integralsauvola_k0.3_w19.png
+++ b/testdata/pg1_integralsauvola_k0.3_w19.png
Binary files differ
diff --git a/testdata/pg1_integralsauvola_k0.5_w19.png b/testdata/pg1_integralsauvola_k0.5_w19.png
index 5db2d9a..77a7194 100644
--- a/testdata/pg1_integralsauvola_k0.5_w19.png
+++ b/testdata/pg1_integralsauvola_k0.5_w19.png
Binary files differ
diff --git a/testdata/pg1_integralsauvola_k0.5_w41.png b/testdata/pg1_integralsauvola_k0.5_w41.png
index 050d037..5ddc854 100644
--- a/testdata/pg1_integralsauvola_k0.5_w41.png
+++ b/testdata/pg1_integralsauvola_k0.5_w41.png
Binary files differ
diff --git a/util.go b/util.go
index 304b209..1f8c9a5 100644
--- a/util.go
+++ b/util.go
@@ -8,14 +8,14 @@ import (
"errors"
"image"
"math"
+
+ "rescribe.xyz/integralimg"
)
-type UsefulImg interface {
- // TODO: name better; maybe verb, x-er
- // TODO: implement these for regular image, and use them to make
- // image functions generic for integral and non- images
- MeanWindow()
- MeanStdDevWindow()
+type ImageWindower interface {
+ image.Image
+ GetWindow(x, y, size int) integralimg.Window
+ GetVerticalWindow(x, width int) integralimg.Window
}
func mean(i []int) float64 {
diff --git a/wipesides.go b/wipesides.go
index 26b7d25..e87d209 100644
--- a/wipesides.go
+++ b/wipesides.go
@@ -4,7 +4,6 @@
package preproc
-// TODO: switch to an interface rather than integralimg.I
// TODO: optionally return the edges chosen
import (
@@ -21,7 +20,7 @@ import (
)
// returns the proportion of the given window that is black pixels
-func proportion(i integralimg.I, x int, size int) float64 {
+func proportion(i ImageWindower, x int, size int) float64 {
w := i.GetVerticalWindow(x, size)
return w.Proportion()
}
@@ -30,7 +29,7 @@ func proportion(i integralimg.I, x int, size int) float64 {
// find the one with the lowest proportion of black pixels.
// if there are multiple lines with the same proportion (e.g. zero),
// choose the middle one.
-func findbestedge(img integralimg.I, x int, w int) int {
+func findbestedge(img ImageWindower, x int, w int) int {
var best float64
var bestxs []int
@@ -59,8 +58,8 @@ func findbestedge(img integralimg.I, x int, w int) int {
// findedges finds the edges of the main content, by moving a window of wsize
// from near the middle of the image to the left and right, stopping when it reaches
// a point at which there is a lower proportion of black pixels than thresh.
-func findedges(img integralimg.I, wsize int, thresh float64) (int, int) {
- maxx := len(img[0]) - 1
+func findedges(img ImageWindower, wsize int, thresh float64) (int, int) {
+ maxx := img.Bounds().Dx() - 1
var lowedge, highedge int = 0, maxx
// don't start at the middle, as this will fail for 2 column layouts,
@@ -88,8 +87,8 @@ func findedges(img integralimg.I, wsize int, thresh float64) (int, int) {
// but working from the outside of the image inwards, rather than from the
// middle outwards.
// TODO: test what difference this makes
-func findedgesOutin(img integralimg.I, wsize int, thresh float64) (int, int) {
- maxx := len(img[0]) - 1
+func findedgesOutin(img ImageWindower, wsize int, thresh float64) (int, int) {
+ maxx := img.Bounds().Dx() - 1
var lowedge, highedge int = 0, maxx
for x := maxx-wsize; x > 0; x-- {
@@ -165,8 +164,10 @@ func sideways(img *image.Gray) *image.Gray {
// Wipe fills the sections of image which fall outside the content
// area with white, providing the content area is above min %
func Wipe(img *image.Gray, wsize int, thresh float64, min int) *image.Gray {
- integral := integralimg.ToIntegralImg(img)
- lowedge, highedge := findedges(integral, wsize, thresh)
+ b := img.Bounds()
+ intImg := integralimg.NewImage(b)
+ draw.Draw(intImg, b, img, b.Min, draw.Src)
+ lowedge, highedge := findedges(*intImg, wsize, thresh)
if toonarrow(img, lowedge, highedge, min) {
return img
}
@@ -177,9 +178,11 @@ func Wipe(img *image.Gray, wsize int, thresh float64, min int) *image.Gray {
// content area with white, providing the content area is above min %
func VWipe(img *image.Gray, wsize int, thresh float64, min int) *image.Gray {
rotimg := sideways(img)
- integral := integralimg.ToIntegralImg(rotimg)
+ b := rotimg.Bounds()
+ intImg := integralimg.NewImage(b)
+ draw.Draw(intImg, b, rotimg, b.Min, draw.Src)
// TODO: test whether there are any places where Outin makes a real difference
- lowedge, highedge:= findedgesOutin(integral, wsize, thresh)
+ lowedge, highedge:= findedgesOutin(*intImg, wsize, thresh)
if toonarrow(img, lowedge, highedge, min) {
return img
}
@@ -200,10 +203,10 @@ func VWipe(img *image.Gray, wsize int, thresh float64, min int) *image.Gray {
// vmin: minimum % of content area height to consider valid.
func WipeFile(inPath string, outPath string, hwsize int, hthresh float64, hmin int, vwsize int, vthresh float64, vmin int) error {
f, err := os.Open(inPath)
- defer f.Close()
if err != nil {
return errors.New(fmt.Sprintf("Could not open file %s: %v", inPath, err))
}
+ defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
return errors.New(fmt.Sprintf("Could not decode image: %v", err))
diff --git a/wipesides_test.go b/wipesides_test.go
index d6a9af3..c6ff711 100644
--- a/wipesides_test.go
+++ b/wipesides_test.go
@@ -11,6 +11,7 @@ package preproc
import (
"fmt"
"image"
+ "image/draw"
"image/png"
"os"
"testing"
@@ -77,8 +78,10 @@ func TestWipeSides(t *testing.T) {
if err != nil {
t.Fatalf("Could not open file %s: %v\n", c.filename, err)
}
- integral := integralimg.ToIntegralImg(img)
- leftedge, rightedge := findedges(integral, c.wsize, c.thresh)
+ b := img.Bounds()
+ integral := integralimg.NewImage(b)
+ draw.Draw(integral, b, img, b.Min, draw.Src)
+ leftedge, rightedge := findedges(*integral, c.wsize, c.thresh)
if leftedge < c.minleft {
t.Errorf("Left edge %d < minimum %d", leftedge, c.minleft)
}
@@ -113,8 +116,10 @@ func TestWipeSides(t *testing.T) {
if err != nil {
t.Fatalf("Could not open file %s: %v\n", c.filename, err)
}
- integral := integralimg.ToIntegralImg(sideways(img))
- topedge, bottomedge := findedges(integral, c.wsize, c.thresh)
+ b := img.Bounds()
+ intImg := integralimg.NewImage(b)
+ draw.Draw(intImg, b, img, b.Min, draw.Src)
+ topedge, bottomedge := findedges(*intImg, c.wsize, c.thresh)
if topedge < c.mintop {
t.Errorf("Top edge %d < minimum %d", topedge, c.mintop)
}