diff options
-rw-r--r-- | go.mod | 2 | ||||
-rw-r--r-- | go.sum | 4 | ||||
-rw-r--r-- | preprocmulti.go | 12 | ||||
-rw-r--r-- | sauvola.go | 36 | ||||
-rw-r--r-- | test_helpers.go | 2 | ||||
-rw-r--r-- | testdata/pg1_integralsauvola_k0.3_w19.png | bin | 19456 -> 19618 bytes | |||
-rw-r--r-- | testdata/pg1_integralsauvola_k0.5_w19.png | bin | 18241 -> 18439 bytes | |||
-rw-r--r-- | testdata/pg1_integralsauvola_k0.5_w41.png | bin | 18260 -> 18284 bytes | |||
-rw-r--r-- | util.go | 12 | ||||
-rw-r--r-- | wipesides.go | 27 | ||||
-rw-r--r-- | wipesides_test.go | 13 |
11 files changed, 56 insertions, 52 deletions
@@ -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 ) @@ -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)) @@ -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 Binary files differindex bdf5712..00aa1d8 100644 --- a/testdata/pg1_integralsauvola_k0.3_w19.png +++ b/testdata/pg1_integralsauvola_k0.3_w19.png diff --git a/testdata/pg1_integralsauvola_k0.5_w19.png b/testdata/pg1_integralsauvola_k0.5_w19.png Binary files differindex 5db2d9a..77a7194 100644 --- a/testdata/pg1_integralsauvola_k0.5_w19.png +++ b/testdata/pg1_integralsauvola_k0.5_w19.png diff --git a/testdata/pg1_integralsauvola_k0.5_w41.png b/testdata/pg1_integralsauvola_k0.5_w41.png Binary files differindex 050d037..5ddc854 100644 --- a/testdata/pg1_integralsauvola_k0.5_w41.png +++ b/testdata/pg1_integralsauvola_k0.5_w41.png @@ -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) } |