๐Ÿ“ฆ deluan / lookup

๐Ÿ“„ image_binary.go ยท 124 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124package lookup

import (
	"image"
	"image/color"
)

type channelType int

const (
	gray channelType = iota
	red
	green
	blue
)

// Minimal Sum-Tables required for any image for NCC or FNCC algorithm.
//
// 1) Integral Image
// 2) Integral ^ 2 Image (Image Energy)
// 3) Zero Mean Image (image where each pixel subtracted with image mean value)
type imageBinaryChannel struct {
	channelType   channelType
	zeroMeanImage []float64
	integralImage *integralImage
	width         int
	height        int
}

// Standard deviation, no sqrt and no mean
func (c *imageBinaryChannel) dev2nRect(x1, y1, x2, y2 int) float64 {
	return c.integralImage.dev2nRect(x1, y1, x2, y2)
}

// Same as Dev2nRect, for the whole image
func (c *imageBinaryChannel) dev2n() float64 {
	return c.integralImage.dev2nRect(0, 0, c.width-1, c.height-1)
}

// Container for ImageBinaryChannels (one for each channel)
// It auto-detects if the image is RGB or GrayScale
type imageBinary struct {
	channels []*imageBinaryChannel
	width    int
	height   int
	size     int
}

func newImageBinary(img image.Image) *imageBinary {
	max := img.Bounds().Max
	ib := &imageBinary{
		width:  max.X,
		height: max.Y,
		size:   max.X * max.Y,
	}
	if _, ok := img.(*image.Gray); ok {
		c := newImageBinaryChannel(img, gray)
		ib.channels = append(ib.channels, c)
	} else {
		ib.channels = newImageBinaryChannels(img, red, green, blue)
	}
	return ib
}

func newImageBinaryChannel(img image.Image, channelType channelType) *imageBinaryChannel {
	max := img.Bounds().Max
	ibc := &imageBinaryChannel{
		channelType: channelType,
		width:       max.X,
		height:      max.Y,
	}
	ibc.integralImage = newIntegralImage(img)
	ibc.zeroMeanImage = createZeroMeanImage(img, ibc.integralImage.mean)

	return ibc
}

// Extract one or more color channels from image, and wraps them in ImageBinaryChannels
func newImageBinaryChannels(imgSrc image.Image, colorChannelTypes ...channelType) []*imageBinaryChannel {
	channels := make([]*imageBinaryChannel, 3)
	max := imgSrc.Bounds().Max
	w, h := max.X, max.Y
	for i, channelType := range colorChannelTypes {
		colorChannel := image.NewGray(image.Rectangle{Max: image.Point{w, h}})
		for x := 0; x < w; x++ {
			for y := 0; y < h; y++ {
				colorPixel := imgSrc.At(x, y).(color.NRGBA)
				var c uint8
				switch channelType {
				case red:
					c = colorPixel.R
				case green:
					c = colorPixel.G
				case blue:
					c = colorPixel.B
				}
				grayPixel := color.Gray{Y: c}
				colorChannel.Set(x, y, grayPixel)
			}
		}
		channels[i] = newImageBinaryChannel(colorChannel, channelType)
	}
	return channels
}

// Zero Mean Image is an image where every pixel in the image is subtracted by
// mean value of the image. Mean value of the image is sum of all pixels values
// divided by number of pixels.
func createZeroMeanImage(img image.Image, mean float64) []float64 {
	cx := img.Bounds().Max.X
	cy := img.Bounds().Max.Y
	zeroMeanImage := make([]float64, cx*cy)
	offset := 0
	originalGray := img.(*image.Gray).Pix
	for y := 0; y < cy; y++ {
		for x := 0; x < cx; x++ {
			pixel := float64(originalGray[offset])
			zeroMeanImage[offset] = pixel - mean
			offset++
		}
	}
	return zeroMeanImage
}