diff options
-rw-r--r-- | binarize/integralimg.go | 116 | ||||
-rw-r--r-- | binarize/testdata/pg1.png | bin | 651071 -> 0 bytes | |||
-rw-r--r-- | integralimg/integralimg.go | 149 | ||||
-rw-r--r-- | preproc/cmd/binarize/main.go (renamed from binarize/cmd/binarize/main.go) | 6 | ||||
-rw-r--r-- | preproc/helpers_test.go | 56 | ||||
-rw-r--r-- | preproc/sauvola.go (renamed from binarize/sauvola.go) | 9 | ||||
-rw-r--r-- | preproc/sauvola_test.go (renamed from binarize/sauvola_test.go) | 48 | ||||
-rw-r--r-- | preproc/testdata/pg1.png | bin | 30803 -> 651071 bytes | |||
-rw-r--r-- | preproc/testdata/pg1_integralsauvola_k0.3_w19.png (renamed from binarize/testdata/pg1_integralsauvola_k0.3_w19.png) | bin | 19456 -> 19456 bytes | |||
-rw-r--r-- | preproc/testdata/pg1_integralsauvola_k0.5_w19.png (renamed from binarize/testdata/pg1_integralsauvola_k0.5_w19.png) | bin | 18241 -> 18241 bytes | |||
-rw-r--r-- | preproc/testdata/pg1_integralsauvola_k0.5_w41.png (renamed from binarize/testdata/pg1_integralsauvola_k0.5_w41.png) | bin | 18260 -> 18260 bytes | |||
-rw-r--r-- | preproc/testdata/pg1_sauvola_k0.3_w19.png (renamed from binarize/testdata/pg1_sauvola_k0.3_w19.png) | bin | 19447 -> 19447 bytes | |||
-rw-r--r-- | preproc/testdata/pg1_sauvola_k0.5_w19.png (renamed from binarize/testdata/pg1_sauvola_k0.5_w19.png) | bin | 18231 -> 18231 bytes | |||
-rw-r--r-- | preproc/testdata/pg1_sauvola_k0.5_w41.png (renamed from binarize/testdata/pg1_sauvola_k0.5_w41.png) | bin | 18275 -> 18275 bytes | |||
-rw-r--r-- | preproc/testdata/pg2.png | bin | 0 -> 30803 bytes | |||
-rw-r--r-- | preproc/testdata/pg2_integralwipesides_t0.02_w5.png (renamed from preproc/testdata/pg1_integralwipesides_t0.02_w5.png) | bin | 33595 -> 33595 bytes | |||
-rw-r--r-- | preproc/testdata/pg2_integralwipesides_t0.05_w25.png (renamed from preproc/testdata/pg1_integralwipesides_t0.05_w25.png) | bin | 33432 -> 33432 bytes | |||
-rw-r--r-- | preproc/testdata/pg2_integralwipesides_t0.05_w5.png (renamed from preproc/testdata/pg1_integralwipesides_t0.05_w5.png) | bin | 14546 -> 14546 bytes | |||
-rw-r--r-- | preproc/util.go (renamed from binarize/util.go) | 10 | ||||
-rw-r--r-- | preproc/wipesides.go | 4 | ||||
-rw-r--r-- | preproc/wipesides_test.go | 52 |
21 files changed, 228 insertions, 222 deletions
diff --git a/binarize/integralimg.go b/binarize/integralimg.go deleted file mode 100644 index 382b495..0000000 --- a/binarize/integralimg.go +++ /dev/null @@ -1,116 +0,0 @@ -package binarize - -import ( - "image" - "math" -) - -type integralwindow struct { - topleft uint64 - topright uint64 - bottomleft uint64 - bottomright uint64 - width int - height int -} - -func Integralimg(img *image.Gray) [][]uint64 { - b := img.Bounds() - var oldy, oldx, oldxy uint64 - var integral [][]uint64 - for y := b.Min.Y; y < b.Max.Y; y++ { - newrow := []uint64{} - for x := b.Min.X; x < b.Max.X; x++ { - oldx, oldy, oldxy = 0, 0, 0 - if x > 0 { - oldx = newrow[x-1] - } - if y > 0 { - oldy = integral[y-1][x] - } - if x > 0 && y > 0 { - oldxy = integral[y-1][x-1] - } - pixel := uint64(img.GrayAt(x, y).Y) - i := pixel + oldx + oldy - oldxy - newrow = append(newrow, i) - } - integral = append(integral, newrow) - } - return integral -} - -func integralimgsq(img *image.Gray) [][]uint64 { - b := img.Bounds() - var oldy, oldx, oldxy uint64 - var integral [][]uint64 - for y := b.Min.Y; y < b.Max.Y; y++ { - newrow := []uint64{} - for x := b.Min.X; x < b.Max.X; x++ { - oldx, oldy, oldxy = 0, 0, 0 - if x > 0 { - oldx = newrow[x-1] - } - if y > 0 { - oldy = integral[y-1][x] - } - if x > 0 && y > 0 { - oldxy = integral[y-1][x-1] - } - pixel := uint64(img.GrayAt(x, y).Y) - i := pixel * pixel + oldx + oldy - oldxy - newrow = append(newrow, i) - } - integral = append(integral, newrow) - } - return integral -} - -// this gets the values of the four corners of a window, which can -// be used to quickly calculate the mean of the area -func getintegralwindow(integral [][]uint64, x int, y int, size int) integralwindow { - step := size / 2 - - minx, miny := 0, 0 - maxy := len(integral)-1 - maxx := len(integral[0])-1 - - if y > (step+1) { - miny = y - step - 1 - } - if x > (step+1) { - minx = x - step - 1 - } - - if maxy > (y + step) { - maxy = y + step - } - if maxx > (x + step) { - maxx = x + step - } - - return integralwindow { integral[miny][minx], integral[miny][maxx], integral[maxy][minx], integral[maxy][maxx], maxx-minx, maxy-miny} -} - -func integralmean(integral [][]uint64, x int, y int, size int) float64 { - i := getintegralwindow(integral, x, y, size) - total := float64(i.bottomright + i.topleft - i.topright - i.bottomleft) - sqsize := float64(i.width) * float64(i.height) - return total / sqsize -} - -func integralmeanstddev(integral [][]uint64, integralsq [][]uint64, x int, y int, size int) (float64, float64) { - i := getintegralwindow(integral, x, y, size) - isq := getintegralwindow(integralsq, x, y, size) - - var total, sqtotal, sqsize float64 - - sqsize = float64(i.width) * float64(i.height) - - total = float64(i.bottomright + i.topleft - i.topright - i.bottomleft) - sqtotal = float64(isq.bottomright + isq.topleft - isq.topright - isq.bottomleft) - - mean := total / sqsize - variance := (sqtotal / sqsize) - (mean * mean) - return mean, math.Sqrt(variance) -} diff --git a/binarize/testdata/pg1.png b/binarize/testdata/pg1.png Binary files differdeleted file mode 100644 index 2bcc4b1..0000000 --- a/binarize/testdata/pg1.png +++ /dev/null diff --git a/integralimg/integralimg.go b/integralimg/integralimg.go new file mode 100644 index 0000000..31f3e53 --- /dev/null +++ b/integralimg/integralimg.go @@ -0,0 +1,149 @@ +package integralimg + +import ( + "image" + "math" +) + +// I is the Integral Image +type I [][]uint64 + +// Sq contains an Integral Image and its Square +type WithSq struct { + Img I + Sq I +} + +// Window is a part of an Integral Image +type Window struct { + topleft uint64 + topright uint64 + bottomleft uint64 + bottomright uint64 + width int + height int +} + +// ToIntegralImg creates an integral image +func ToIntegralImg(img *image.Gray) I { + var integral I + var oldy, oldx, oldxy uint64 + b := img.Bounds() + for y := b.Min.Y; y < b.Max.Y; y++ { + newrow := []uint64{} + for x := b.Min.X; x < b.Max.X; x++ { + oldx, oldy, oldxy = 0, 0, 0 + if x > 0 { + oldx = newrow[x-1] + } + if y > 0 { + oldy = integral[y-1][x] + } + if x > 0 && y > 0 { + oldxy = integral[y-1][x-1] + } + pixel := uint64(img.GrayAt(x, y).Y) + i := pixel + oldx + oldy - oldxy + newrow = append(newrow, i) + } + integral = append(integral, newrow) + } + return integral +} + +// ToSqIntegralImg creates an integral image of the square of all +// pixel values +func ToSqIntegralImg(img *image.Gray) I { + var integral I + var oldy, oldx, oldxy uint64 + b := img.Bounds() + for y := b.Min.Y; y < b.Max.Y; y++ { + newrow := []uint64{} + for x := b.Min.X; x < b.Max.X; x++ { + oldx, oldy, oldxy = 0, 0, 0 + if x > 0 { + oldx = newrow[x-1] + } + if y > 0 { + oldy = integral[y-1][x] + } + if x > 0 && y > 0 { + oldxy = integral[y-1][x-1] + } + pixel := uint64(img.GrayAt(x, y).Y) + i := pixel * pixel + oldx + oldy - oldxy + newrow = append(newrow, i) + } + integral = append(integral, newrow) + } + return integral +} + +// ToAllIntegralImg creates a WithSq containing a regular and +// squared Integral Image +func ToAllIntegralImg(img *image.Gray) WithSq { + var s WithSq + s.Img = ToIntegralImg(img) + s.Sq = ToSqIntegralImg(img) + return s +} + + +// GetWindow gets the values of the corners of a part of an +// Integral Image, plus the dimensions of the part, which can +// be used to quickly calculate the mean of the area +func (i I) GetWindow(x, y, size int) Window { + step := size / 2 + + minx, miny := 0, 0 + maxy := len(i)-1 + maxx := len(i[0])-1 + + if y > (step+1) { + miny = y - step - 1 + } + if x > (step+1) { + minx = x - step - 1 + } + + if maxy > (y + step) { + maxy = y + step + } + if maxx > (x + step) { + maxx = x + step + } + + return Window { i[miny][minx], i[miny][maxx], i[maxy][minx], i[maxy][maxx], maxx-minx, maxy-miny} +} + +// Sum returns the sum of all pixels in a Window +func (w Window) Sum() uint64 { + return w.bottomright + w.topleft - w.topright - w.bottomleft +} + +// Size returns the total size of a Window +func (w Window) Size() int { + return w.width * w.height +} + +// Mean returns the average value of pixels in a Window +func (w Window) Mean() float64 { + return float64(w.Sum()) / float64(w.Size()) +} + +// MeanWindow calculates the mean value of a section of an Integral +// Image +func (i I) MeanWindow(x, y, size int) float64 { + return i.GetWindow(x, y, size).Mean() +} + +// MeanStdDevWindow calculates the mean and standard deviation of +// a section on an Integral Image +func (i WithSq) MeanStdDevWindow(x, y, size int) (float64, float64) { + imean := i.Img.GetWindow(x, y, size).Mean() + smean := i.Sq.GetWindow(x, y, size).Mean() + + variance := smean - (imean * imean) + + return imean, math.Sqrt(variance) +} diff --git a/binarize/cmd/binarize/main.go b/preproc/cmd/binarize/main.go index bda3d93..c274f9c 100644 --- a/binarize/cmd/binarize/main.go +++ b/preproc/cmd/binarize/main.go @@ -10,7 +10,7 @@ import ( "log" "os" - "rescribe.xyz/go.git/binarize" + "rescribe.xyz/go.git/preproc" ) // TODO: do more testing to see how good this assumption is @@ -57,10 +57,10 @@ func main() { // TODO: come up with a way to set a good ksize automatically var thresh image.Image - thresh = binarize.IntegralSauvola(gray, *ksize, *wsize) + thresh = preproc.IntegralSauvola(gray, *ksize, *wsize) if *btype == "zeroinv" { - thresh, err = binarize.BinToZeroInv(thresh.(*image.Gray), img.(*image.RGBA)) + thresh, err = preproc.BinToZeroInv(thresh.(*image.Gray), img.(*image.RGBA)) if err != nil { log.Fatal(err) } diff --git a/preproc/helpers_test.go b/preproc/helpers_test.go new file mode 100644 index 0000000..326b59d --- /dev/null +++ b/preproc/helpers_test.go @@ -0,0 +1,56 @@ +package preproc + +// TODO: add different pages as test cases +// TODO: test non integral img version + +import ( + "flag" + "image" + "image/draw" + "image/png" + "os" +) + +var update = flag.Bool("update", false, "update golden files") + +func decode(s string) (*image.Gray, error) { + f, err := os.Open(s) + defer f.Close() + if err != nil { + return nil, err + } + img, err := png.Decode(f) + if err != nil { + return nil, err + } + b := img.Bounds() + gray := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy())) + draw.Draw(gray, b, img, b.Min, draw.Src) + return gray, nil +} + +func imgsequal(img1 *image.Gray, img2 *image.Gray) bool { + b := img1.Bounds() + if !b.Eq(img2.Bounds()) { + return false + } + for y := b.Min.Y; y < b.Max.Y; y++ { + for x := b.Min.X; x < b.Max.X; x++ { + r0, g0, b0, a0 := img1.At(x, y).RGBA() + r1, g1, b1, a1 := img2.At(x, y).RGBA() + if r0 != r1 { + return false + } + if g0 != g1 { + return false + } + if b0 != b1 { + return false + } + if a0 != a1 { + return false + } + } + } + return true +} diff --git a/binarize/sauvola.go b/preproc/sauvola.go index 6d9c1af..e93ea81 100644 --- a/binarize/sauvola.go +++ b/preproc/sauvola.go @@ -1,8 +1,10 @@ -package binarize +package preproc import ( "image" "image/color" + + "rescribe.xyz/go.git/integralimg" ) // Implements Sauvola's algorithm for text binarization, see paper @@ -35,12 +37,11 @@ func IntegralSauvola(img *image.Gray, ksize float64, windowsize int) *image.Gray b := img.Bounds() new := image.NewGray(b) - integral := Integralimg(img) - integralsq := integralimgsq(img) + 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 := integralmeanstddev(integral, integralsq, x, y, windowsize) + 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}) diff --git a/binarize/sauvola_test.go b/preproc/sauvola_test.go index 5faeb61..1397a4f 100644 --- a/binarize/sauvola_test.go +++ b/preproc/sauvola_test.go @@ -1,59 +1,13 @@ -package binarize +package preproc import ( - "flag" "fmt" "image" - "image/draw" "image/png" "os" "testing" ) -var update = flag.Bool("update", false, "update golden files") - -func decode(s string) (*image.Gray, error) { - f, err := os.Open(s) - defer f.Close() - if err != nil { - return nil, err - } - img, err := png.Decode(f) - if err != nil { - return nil, err - } - b := img.Bounds() - gray := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy())) - draw.Draw(gray, b, img, b.Min, draw.Src) - return gray, nil -} - -func imgsequal(img1 *image.Gray, img2 *image.Gray) bool { - b := img1.Bounds() - if ! b.Eq(img2.Bounds()) { - return false - } - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - r0, g0, b0, a0 := img1.At(x, y).RGBA() - r1, g1, b1, a1 := img2.At(x, y).RGBA() - if r0 != r1 { - return false - } - if g0 != g1 { - return false - } - if b0 != b1 { - return false - } - if a0 != a1 { - return false - } - } - } - return true -} - func TestBinarization(t *testing.T) { cases := []struct { name string diff --git a/preproc/testdata/pg1.png b/preproc/testdata/pg1.png Binary files differindex c7c4249..2bcc4b1 100644 --- a/preproc/testdata/pg1.png +++ b/preproc/testdata/pg1.png diff --git a/binarize/testdata/pg1_integralsauvola_k0.3_w19.png b/preproc/testdata/pg1_integralsauvola_k0.3_w19.png Binary files differindex bdf5712..bdf5712 100644 --- a/binarize/testdata/pg1_integralsauvola_k0.3_w19.png +++ b/preproc/testdata/pg1_integralsauvola_k0.3_w19.png diff --git a/binarize/testdata/pg1_integralsauvola_k0.5_w19.png b/preproc/testdata/pg1_integralsauvola_k0.5_w19.png Binary files differindex 5db2d9a..5db2d9a 100644 --- a/binarize/testdata/pg1_integralsauvola_k0.5_w19.png +++ b/preproc/testdata/pg1_integralsauvola_k0.5_w19.png diff --git a/binarize/testdata/pg1_integralsauvola_k0.5_w41.png b/preproc/testdata/pg1_integralsauvola_k0.5_w41.png Binary files differindex 050d037..050d037 100644 --- a/binarize/testdata/pg1_integralsauvola_k0.5_w41.png +++ b/preproc/testdata/pg1_integralsauvola_k0.5_w41.png diff --git a/binarize/testdata/pg1_sauvola_k0.3_w19.png b/preproc/testdata/pg1_sauvola_k0.3_w19.png Binary files differindex bcd595f..bcd595f 100644 --- a/binarize/testdata/pg1_sauvola_k0.3_w19.png +++ b/preproc/testdata/pg1_sauvola_k0.3_w19.png diff --git a/binarize/testdata/pg1_sauvola_k0.5_w19.png b/preproc/testdata/pg1_sauvola_k0.5_w19.png Binary files differindex 8de596c..8de596c 100644 --- a/binarize/testdata/pg1_sauvola_k0.5_w19.png +++ b/preproc/testdata/pg1_sauvola_k0.5_w19.png diff --git a/binarize/testdata/pg1_sauvola_k0.5_w41.png b/preproc/testdata/pg1_sauvola_k0.5_w41.png Binary files differindex b8f50e0..b8f50e0 100644 --- a/binarize/testdata/pg1_sauvola_k0.5_w41.png +++ b/preproc/testdata/pg1_sauvola_k0.5_w41.png diff --git a/preproc/testdata/pg2.png b/preproc/testdata/pg2.png Binary files differnew file mode 100644 index 0000000..c7c4249 --- /dev/null +++ b/preproc/testdata/pg2.png diff --git a/preproc/testdata/pg1_integralwipesides_t0.02_w5.png b/preproc/testdata/pg2_integralwipesides_t0.02_w5.png Binary files differindex 6b4ccb2..6b4ccb2 100644 --- a/preproc/testdata/pg1_integralwipesides_t0.02_w5.png +++ b/preproc/testdata/pg2_integralwipesides_t0.02_w5.png diff --git a/preproc/testdata/pg1_integralwipesides_t0.05_w25.png b/preproc/testdata/pg2_integralwipesides_t0.05_w25.png Binary files differindex 39dc88d..39dc88d 100644 --- a/preproc/testdata/pg1_integralwipesides_t0.05_w25.png +++ b/preproc/testdata/pg2_integralwipesides_t0.05_w25.png diff --git a/preproc/testdata/pg1_integralwipesides_t0.05_w5.png b/preproc/testdata/pg2_integralwipesides_t0.05_w5.png Binary files differindex 50df855..50df855 100644 --- a/preproc/testdata/pg1_integralwipesides_t0.05_w5.png +++ b/preproc/testdata/pg2_integralwipesides_t0.05_w5.png diff --git a/binarize/util.go b/preproc/util.go index ad641c9..5f8a9f1 100644 --- a/binarize/util.go +++ b/preproc/util.go @@ -1,4 +1,4 @@ -package binarize +package preproc import ( "errors" @@ -6,6 +6,14 @@ import ( "math" ) +// 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 +type UsefulImg interface { + MeanWindow() + MeanStdDevWindow() +} + func mean(i []int) float64 { sum := 0 for _, n := range i { diff --git a/preproc/wipesides.go b/preproc/wipesides.go index c773054..4806e93 100644 --- a/preproc/wipesides.go +++ b/preproc/wipesides.go @@ -7,7 +7,7 @@ import ( "image" "image/color" - "rescribe.xyz/go.git/binarize" + "rescribe.xyz/go.git/integralimg" ) type IntWindow struct { // TODO: put this in its own package @@ -126,7 +126,7 @@ func wipesides(img *image.Gray, lowedge int, highedge int) *image.Gray { // wipe fills the sections of image which fall outside the content // area with white func Wipe(img *image.Gray, wsize int, thresh float64) *image.Gray { - integral := binarize.Integralimg(img) + integral := integralimg.ToIntegralImg(img) lowedge, highedge := findedges(integral, wsize, thresh) return wipesides(img, lowedge, highedge) } diff --git a/preproc/wipesides_test.go b/preproc/wipesides_test.go index b0ada4e..f66f39b 100644 --- a/preproc/wipesides_test.go +++ b/preproc/wipesides_test.go @@ -4,59 +4,13 @@ package preproc // TODO: test non integral img version import ( - "flag" "fmt" "image" - "image/draw" "image/png" "os" "testing" ) -var update = flag.Bool("update", false, "update golden files") - -func decode(s string) (*image.Gray, error) { - f, err := os.Open(s) - defer f.Close() - if err != nil { - return nil, err - } - img, err := png.Decode(f) - if err != nil { - return nil, err - } - b := img.Bounds() - gray := image.NewGray(image.Rect(0, 0, b.Dx(), b.Dy())) - draw.Draw(gray, b, img, b.Min, draw.Src) - return gray, nil -} - -func imgsequal(img1 *image.Gray, img2 *image.Gray) bool { - b := img1.Bounds() - if !b.Eq(img2.Bounds()) { - return false - } - for y := b.Min.Y; y < b.Max.Y; y++ { - for x := b.Min.X; x < b.Max.X; x++ { - r0, g0, b0, a0 := img1.At(x, y).RGBA() - r1, g1, b1, a1 := img2.At(x, y).RGBA() - if r0 != r1 { - return false - } - if g0 != g1 { - return false - } - if b0 != b1 { - return false - } - if a0 != a1 { - return false - } - } - } - return true -} - func TestWipeSides(t *testing.T) { cases := []struct { name string @@ -65,9 +19,9 @@ func TestWipeSides(t *testing.T) { thresh float64 wsize int }{ - {"integralwipesides", "testdata/pg1.png", "testdata/pg1_integralwipesides_t0.02_w5.png", 0.02, 5}, - {"integralwipesides", "testdata/pg1.png", "testdata/pg1_integralwipesides_t0.05_w5.png", 0.05, 5}, - {"integralwipesides", "testdata/pg1.png", "testdata/pg1_integralwipesides_t0.05_w25.png", 0.05, 25}, + {"integralwipesides", "testdata/pg2.png", "testdata/pg2_integralwipesides_t0.02_w5.png", 0.02, 5}, + {"integralwipesides", "testdata/pg2.png", "testdata/pg2_integralwipesides_t0.05_w5.png", 0.05, 5}, + {"integralwipesides", "testdata/pg2.png", "testdata/pg2_integralwipesides_t0.05_w25.png", 0.05, 25}, } for _, c := range cases { |