func newAtFuncGray(p *image.Gray) AtFunc { return func(x, y int) (r, g, b, a uint32) { i := p.PixOffset(x, y) yy := uint32(p.Pix[i]) yy |= yy << 8 return yy, yy, yy, 0xffff } }
// Interpolate uint8/pixel images. func interpolate1x8(src *image.Gray, dstW, dstH int) image.Image { srcRect := src.Bounds() srcW := srcRect.Dx() srcH := srcRect.Dy() ww, hh := uint64(dstW), uint64(dstH) dx, dy := uint64(srcW), uint64(srcH) n, sum := dx*dy, make([]uint64, dstW*dstH) for y := 0; y < srcH; y++ { pixOffset := src.PixOffset(0, y) for x := 0; x < srcW; x++ { // Get the source pixel. val64 := uint64(src.Pix[pixOffset]) pixOffset++ // Spread the source pixel over 1 or more destination rows. py := uint64(y) * hh for remy := hh; remy > 0; { qy := dy - (py % dy) if qy > remy { qy = remy } // Spread the source pixel over 1 or more destination columns. px := uint64(x) * ww index := (py/dy)*ww + (px / dx) for remx := ww; remx > 0; { qx := dx - (px % dx) if qx > remx { qx = remx } qxy := qx * qy sum[index] += val64 * qxy index++ px += qx remx -= qx } py += qy remy -= qy } } } dst := image.NewGray(image.Rect(0, 0, dstW, dstH)) index := 0 for y := 0; y < dstH; y++ { pixOffset := dst.PixOffset(0, y) for x := 0; x < dstW; x++ { dst.Pix[pixOffset] = uint8(sum[index] / n) pixOffset++ index++ } } return dst }
// grayToY stores the 8x8 region of m whose top-left corner is p in yBlock. func grayToY(m *image.Gray, p image.Point, yBlock *block) { b := m.Bounds() xmax := b.Max.X - 1 ymax := b.Max.Y - 1 pix := m.Pix for j := 0; j < 8; j++ { for i := 0; i < 8; i++ { idx := m.PixOffset(min(p.X+i, xmax), min(p.Y+j, ymax)) yBlock[8*j+i] = int32(pix[idx]) } } }
// Fill in missing pixels func interpolateMissingPixels(img image.Gray, mask []bool) { var wg sync.WaitGroup newMask := make([]bool, len(mask), len(mask)) copy(newMask, mask) couldNotFill := false imgWidth := img.Bounds().Max.X imgHeight := img.Bounds().Max.Y for x := 0; x < imgWidth; x++ { wg.Add(1) go func(x int) { for y := 0; y < imgHeight; y++ { imgPos := img.PixOffset(x, y) if mask[imgPos] == true { continue } var sum int cnt := 0 if x > 0 && mask[imgPos-1] { sum += int(img.Pix[imgPos-1]) cnt++ } if x < imgWidth-1 && mask[imgPos+1] { sum += int(img.Pix[imgPos+1]) cnt++ } if y > 0 && mask[imgPos-img.Stride] { sum += int(img.Pix[imgPos-img.Stride]) cnt++ } if y < imgHeight-1 && mask[imgPos+img.Stride] { sum += int(img.Pix[imgPos+img.Stride]) cnt++ } if cnt != 0 { img.Pix[imgPos] = uint8(sum / cnt) newMask[imgPos] = true } else { couldNotFill = true } } wg.Done() }(x) } wg.Wait() if couldNotFill { interpolateMissingPixels(img, newMask) } }
//AutoCorrelateProjections calculates a cross-correlation of each radial //projection with itself func AutoCorrelateProjections(projections image.Gray) []float64 { size := projections.Bounds().Size() width := size.X nbProj := size.Y out := make([]float64, nbProj*width) // for each projection for θ := 0; θ < nbProj; θ++ { // log.Println("θ:", θ) left := projections.PixOffset(0, θ) right := projections.PixOffset(width, θ) projection := projections.Pix[left:right] out = append(out, AutoCorrelateSeries(projection)...) } return out }
func meanVertical(src image.Gray, radius int) (dst image.Gray) { var wg sync.WaitGroup norm := float64(radius*2 + 1) dst = *image.NewGray(src.Bounds()) width, height := src.Bounds().Max.X, src.Bounds().Max.Y for x := 0; x < width; x++ { wg.Add(1) go func(x int) { total := 0.0 for ky := -radius; ky <= radius; ky++ { total += float64(src.Pix[src.PixOffset(x, inRange(ky, height))]) } dst.Pix[dst.PixOffset(x, 0)] = uint8(total / norm) for y := 1; y < height; y++ { total -= float64(src.Pix[src.PixOffset(x, inRange(y-radius-1, height))]) total += float64(src.Pix[src.PixOffset(x, inRange(y+radius, height))]) dst.Pix[dst.PixOffset(x, y)] = uint8(total / norm) } wg.Done() }(x) } wg.Wait() return }
func meanHorizontal(src image.Gray, radius int) (dst image.Gray) { var wg sync.WaitGroup norm := float64(radius*2 + 1) dst = *image.NewGray(src.Bounds()) width, height := src.Bounds().Max.X, src.Bounds().Max.Y for y := 0; y < height; y++ { wg.Add(1) go func(y int) { total := 0.0 for kx := -radius; kx <= radius; kx++ { total += float64(src.Pix[src.PixOffset(inRange(kx, width), y)]) } dst.Pix[dst.PixOffset(0, y)] = uint8(total / norm) for x := 1; x < width; x++ { total -= float64(src.Pix[src.PixOffset(inRange(x-radius-1, width), y)]) total += float64(src.Pix[src.PixOffset(inRange(x+radius, width), y)]) dst.Pix[dst.PixOffset(x, y)] = uint8(total / norm) } wg.Done() }(y) } wg.Wait() return }
func evaluateGrids(src image.Gray, grids []lineGrid) []lineGrid { for _, grid := range grids { hCount := len(grid.Horizontal) vCount := len(grid.Vertical) fragments := make([]lineFragment, hCount+vCount) firstVertLine := grid.Vertical[0] lastVertLine := grid.Vertical[vCount-1] for j, h := range grid.Horizontal { _, start := intersection(h, firstVertLine) _, end := intersection(h, lastVertLine) fragments[j] = lineFragment{start, end} } firstHorizLine := grid.Horizontal[0] lastHorizLine := grid.Horizontal[hCount-1] for j, h := range grid.Vertical { _, start := intersection(h, firstHorizLine) _, end := intersection(h, lastHorizLine) fragments[hCount+j] = lineFragment{start, end} } score := 0.0 for _, fragment := range fragments { points := pointsOnLineFragment(fragment) value := 1.0 / fragment.Length() for _, point := range points { if src.Pix[src.PixOffset(point.X, point.Y)] != 0 { score += value } } } grid.Score = grid.Score * score / float64(len(fragments)) } sort.Sort(lineGridByScore(grids)) return grids }
// copies and resizes src into dst's r func scaleDownTo(dst *image.Gray, r image.Rectangle, src *image.Gray) error { if src.Bounds().Dx()%r.Dx() != 0 || src.Bounds().Dy()%r.Dy() != 0 { return errors.New("Source image size not multiple of rectagle size") } dx := src.Bounds().Dx() / r.Dx() dy := src.Bounds().Dy() / r.Dy() area := dx * dy for y := r.Min.Y; y < r.Max.Y; y++ { for x := r.Min.X; x < r.Max.X; x++ { col := 0 for v := 0; v < dy; v++ { for u := 0; u < dx; u++ { offset := src.PixOffset((x-r.Min.X)*dx+u, (y-r.Min.Y)*dy+v) col += int(src.Pix[offset]) } } dst.SetGray(x, y, color.Gray{uint8(col / area)}) } } return nil }
func (p *perspectiveTrasnformation) warpPerspective(src image.Gray) image.Gray { var wg sync.WaitGroup maxX := 0.0 maxY := 0.0 for _, p := range p.dstPoints { maxX = math.Max(maxX, p.X) maxY = math.Max(maxY, p.Y) } dst := *image.NewGray(image.Rect(0, 0, int(maxX), int(maxY))) mask := make([]bool, len(dst.Pix), len(dst.Pix)) srcWidth := src.Bounds().Max.X srcHeight := src.Bounds().Max.Y for x := 0; x < srcWidth; x++ { wg.Add(1) go func(x int) { for y := 0; y < srcHeight; y++ { newX, newY := p.Project(float64(x), float64(y)) if newX < 0 || newX >= maxX || newY < 0 || newY >= maxY { continue } g := src.Pix[src.PixOffset(x, y)] dstPos := dst.PixOffset(int(newX), int(newY)) dst.Pix[dstPos] = g mask[dstPos] = true } wg.Done() }(x) } wg.Wait() interpolateMissingPixels(dst, mask) return dst }
func sdfize(img *image.Gray) { w := img.Bounds().Size().X h := img.Bounds().Size().Y g1 := make([]xys, w*h) g2 := make([]xys, w*h) for y := 0; y < h; y++ { for x := 0; x < w; x++ { a := img.Pix[img.PixOffset(x, y)] if a < 0x7F { g2[y*w+x] = infinity } else { g1[y*w+x] = infinity } } } generateSDF(int16(w), int16(h), g1) generateSDF(int16(w), int16(h), g2) for y := 0; y < h; y++ { for x := 0; x < w; x++ { pos := y*w + x dist1 := distance(g1[pos].x, g1[pos].y) dist2 := distance(g2[pos].x, g2[pos].y) dist := 2 * 128 * (dist1 - dist2) / float64(w) if dist < -128 { dist = -128 } else if dist > 127 { dist = 127 } c := uint8(128 + dist) img.SetGray(x, y, color.Gray{c}) } } }
// Scale the source image down to the destination image. // Preserves aspect ratio, leaving unused destination pixels untouched. // At present it just uses a simple box filter. // It might be possible to improve performance and clarity by making all // pixel fractions 1/16 and using essentially fixed-point arithmetic. func scaleDown(src SippImage, dst *image.Gray) { srcRect := src.Bounds() dstRect := dst.Bounds() //fmt.Println("srcRect:<", srcRect, ">") //fmt.Println("dstRect:<", dstRect, ">") srcWidth := srcRect.Dx() srcHeight := srcRect.Dy() dstWidth := dstRect.Dx() dstHeight := dstRect.Dy() //fmt.Println("srcWidth:<", srcWidth, ">") //fmt.Println("srcHeight:<", srcHeight, ">") //fmt.Println("dstWidth:<", dstWidth, ">") //fmt.Println("dstHeight:<", dstHeight, ">") srcAR := float64(srcWidth) / float64(srcHeight) dstAR := float64(dstWidth) / float64(dstHeight) //fmt.Println("srcAR:<", srcAR, ">") //fmt.Println("dstAR:<", dstAR, ">") var scale float64 var outWidth int var outHeight int if srcAR < dstAR { // scale vertically and use a horizontal offset scale = float64(srcHeight) / float64(dstHeight) outWidth = int(float64(srcWidth) / scale) outHeight = dstHeight //fmt.Println("Scaling vertically") } else { // scale horizontally and use a vertical offset scale = float64(srcWidth) / float64(dstWidth) outHeight = int(float64(srcHeight) / scale) outWidth = dstWidth //fmt.Println("Scaling horizontally") } //fmt.Println("scale:<", scale, ">") //fmt.Println("outWidth:<", outWidth, ">") //fmt.Println("outHeight:<", outHeight, ">") // One of the following will be 0. hoff := (dstWidth - outWidth) / 2 voff := (dstHeight - outHeight) / 2 // Scale 16-bit images down to 8. We incour the cost spuriously for 8-bit // images so that we can access the source polymorphically. var scaleBpp float64 = 1.0 if src.Bpp() == 16 { scaleBpp = 1.0 / 256.0 } //fmt.Println("scaleBpp:", scaleBpp) hfilter := preComputeFilter(scale, outWidth, srcWidth, scaleBpp) intrm := image.NewGray(image.Rect(0, 0, outWidth, srcHeight)) for inty := 0; inty < srcHeight; inty++ { // Apply the filter to the source row, generating an intermediate row for intx := 0; intx < outWidth; intx++ { var val float64 for i := 0; i < hfilter[intx].n; i++ { val = val + src.Val(hfilter[intx].idx+i, inty)*hfilter[intx].weights[i] } val = math.Floor(val + 0.5) if val > 255.0 { intrm.Pix[intrm.PixOffset(intx, inty)] = 255 } else { intrm.Pix[intrm.PixOffset(intx, inty)] = uint8(val) } } } vfilter := preComputeFilter(scale, outHeight, srcHeight, 1.0) for outx := 0; outx < outWidth; outx++ { // Apply the filter to the intermediate column, generating an output column for outy := 0; outy < outHeight; outy++ { var val float64 for i := 0; i < vfilter[outy].n; i++ { index := intrm.PixOffset(outx, vfilter[outy].idx+i) val = val + float64(intrm.Pix[index])*vfilter[outy].weights[i] } dst.Pix[dst.PixOffset(outx+hoff, outy+voff)] = uint8(math.Floor(val + 0.5)) } } }
func newSetFuncGray(p *image.Gray) SetFunc { return func(x, y int, r, g, b, a uint32) { i := p.PixOffset(x, y) p.Pix[i] = uint8(((299*r + 587*g + 114*b + 500) / 1000) >> 8) } }
func houghLines(src image.Gray, thetas []float64, threshold uint64, limit int) []polarLine { if thetas == nil { thetas = generateThetas(-math.Pi/2, math.Pi/2, math.Pi/180.0) } maxY, maxX := src.Bounds().Max.Y, src.Bounds().Max.X maxR := 2 * math.Hypot(float64(maxX), float64(maxY)) offset := maxR / 2 hAcc := make([][]uint64, int(maxR), int(maxR)) for i := range hAcc { hAcc[i] = make([]uint64, len(thetas), len(thetas)) } sin := make([]float64, len(thetas), len(thetas)) cos := make([]float64, len(thetas), len(thetas)) for i, th := range thetas { sin[i] = math.Sin(th) cos[i] = math.Cos(th) } var wg sync.WaitGroup for y := 0; y < maxY; y++ { wg.Add(1) go func(y int) { for x := 0; x < maxX; x++ { val := src.Pix[src.PixOffset(x, y)] if val == 0 { continue } for i := range thetas { r := float64(x)*cos[i] + float64(y)*sin[i] iry := int(r + offset) atomic.AddUint64(&hAcc[iry][i], 1) } } wg.Done() }(y) } wg.Wait() linesSet := make(map[string]bool) var lines []polarLine for i := range hAcc { r := i - int(offset) thetaOffset := 0.0 if r < 0 { thetaOffset = math.Pi r *= -1 } for j, count := range hAcc[i] { if count < 2 || count < threshold { continue } line := polarLine{ Theta: thetas[j] + thetaOffset, Distance: r, Count: count, } hash := line.HashKey() if !linesSet[hash] { linesSet[hash] = true lines = append(lines, line) } } } sort.Sort(polarLinesByCount(lines)) if limit > 0 && len(lines) > limit { lines = lines[:limit] } return lines }