├── .gitignore
├── LICENSE
├── README.md
├── encode.go
├── example
├── HorizontalMirror.jpg
├── adjustlight.jpg
├── negativeFilmEffect.jpg
├── sunsetEffect.jpg
├── test.jpg
└── verticalMirror.jpg
├── go.mod
├── io.go
├── process.go
├── recog.go
├── recog_test.go
├── testpic
├── aa.png
├── cc.png
└── dd.jpg
└── util.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Comdex
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # imgo
2 | golang图像处理工具库,图像相似度计算,图像二值化(golang image process lib)
3 |
4 | 目前只支持jpg,png
5 |
6 | [](http://godoc.org/github.com/Comdex/imgo)
7 |
8 | ### 安装
9 |
10 | ```shell
11 | go get github.com/Comdex/imgo
12 | ```
13 |
14 | ### 示例
15 |
16 | ```go
17 | package main
18 |
19 | import(
20 | "github.com/Comdex/imgo"
21 | )
22 |
23 | func main(){
24 | //如果读取出错会panic,返回图像矩阵img
25 | //img[height][width][4],height为图像高度,width为图像宽度
26 | //img[height][width][4]为第height行第width列上像素点的RGBA数值数组,值范围为0-255
27 | //如img[150][20][0]是150行20列处像素的红色值,img[150][20][1]是150行20列处像素的绿
28 | //色值,img[150][20][2]是150行20列处像素的蓝色值,img[150][20][3]是150行20列处像素
29 | //的alpha数值,一般用作不透明度参数,如果一个像素的alpha通道数值为0%,那它就是完全透明的.
30 | img:=imgo.MustRead("example/test.jpg")
31 |
32 | //对原图像矩阵进行日落效果处理
33 | img2:=imgo.SunsetEffect(img)
34 |
35 | //保存为jpeg,100为质量,1-100
36 | err:=imgo.SaveAsJPEG("example/new.jpg",img2,100)
37 | if err!=nil {
38 | panic(err)
39 | }
40 | }
41 | ```
42 |
43 | 计算两张图片的余弦相似度
44 | ```go
45 | cos,err:=imgo.CosineSimilarity("test1.jpg","test2.jpg")
46 | if err!=nil{
47 | panic(err)
48 | }
49 | fmt.Println(cos)
50 | ```
51 |
52 | 2015.8.23 update :添加使用感知哈希算法的GetFingerprint函数获取图片的“指纹”字符串
53 | ```go
54 | fp,err:=imgo.GetFingerprint("test1.jpg")
55 | if err!=nil {
56 | panic(err)
57 | }
58 | fmt.Println(fp)//输出64位的01字符串
59 | ```
60 |
61 | ### 效果
62 |
63 |
64 |
65 |  原图 |
66 |  横向镜像imgo.HorizontalMirror |
67 |
68 |
69 |  日落imgo.SunsetEffect |
70 |  负片imgo.NegativeFilmEffect |
71 |
72 |
73 |  调整亮度imgo.AdjustBrightness |
74 |  垂直镜像imgo.VerticalMirror |
75 |
76 |
77 |
78 | 更多api及帮助请访问:http://godoc.org/github.com/Comdex/imgo
79 |
80 | ### 版权
81 |
82 | 本项目采用[MIT](http://opensource.org/licenses/MIT)开源授权许可证,完整的授权说明可在[LICENSE](LICENSE)文件中找到。
83 |
84 |
--------------------------------------------------------------------------------
/encode.go:
--------------------------------------------------------------------------------
1 | package imgo
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "image"
7 | "image/png"
8 | "io/ioutil"
9 | )
10 |
11 | //Img2Base64 produce a base64 string from a image file.
12 | func Img2Base64(filepath string) (encodeString string, err error) {
13 | data, err := ioutil.ReadFile(filepath)
14 | if err != nil {
15 | return
16 | }
17 | encodeString = base64.StdEncoding.EncodeToString(data)
18 | return
19 | }
20 |
21 | //Base64ToImg create a image file named dstFile from base64 encodeString.
22 | func Base64ToImg(encodeString string, dstFile string) error {
23 | data, err := base64.StdEncoding.DecodeString(encodeString)
24 | if err != nil {
25 | return err
26 | }
27 | err2 := ioutil.WriteFile(dstFile, data, 0666)
28 | if err2 != nil {
29 | return err2
30 | }
31 | return nil
32 | }
33 |
34 | //Img2base64ByGoImage produce a base64 string from Image interface.
35 | func Img2Base64ByGoImage(img image.Image) (encodeString string, err error) {
36 | var w bytes.Buffer
37 | encodeErr := png.Encode(&w, img)
38 | if encodeErr != nil {
39 | return "", encodeErr
40 | }
41 |
42 | encodeString = base64.StdEncoding.EncodeToString(w.Bytes())
43 | return
44 | }
45 |
--------------------------------------------------------------------------------
/example/HorizontalMirror.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/example/HorizontalMirror.jpg
--------------------------------------------------------------------------------
/example/adjustlight.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/example/adjustlight.jpg
--------------------------------------------------------------------------------
/example/negativeFilmEffect.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/example/negativeFilmEffect.jpg
--------------------------------------------------------------------------------
/example/sunsetEffect.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/example/sunsetEffect.jpg
--------------------------------------------------------------------------------
/example/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/example/test.jpg
--------------------------------------------------------------------------------
/example/verticalMirror.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/example/verticalMirror.jpg
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module "github.com/Comdex/imgo"
2 |
3 | go 1.12
4 |
--------------------------------------------------------------------------------
/io.go:
--------------------------------------------------------------------------------
1 | package imgo
2 |
3 | import (
4 | "errors"
5 | "os"
6 |
7 | "image/jpeg"
8 | "image/png"
9 |
10 | "image"
11 | "image/color"
12 | )
13 |
14 | func GetImageHeight(img image.Image) int {
15 | return img.Bounds().Max.Y
16 | }
17 |
18 | func GetImageWidth(img image.Image) int {
19 | return img.Bounds().Max.X
20 | }
21 |
22 | // decode a image and retrun golang image interface
23 | func DecodeImage(filePath string) (img image.Image, err error) {
24 | reader, err := os.Open(filePath)
25 | if err != nil {
26 | return nil, err
27 | }
28 | defer reader.Close()
29 |
30 | img, _, err = image.Decode(reader)
31 |
32 | return
33 | }
34 |
35 | //convert image to NRGBA
36 | func convertToNRGBA(src image.Image) *image.NRGBA {
37 | srcBounds := src.Bounds()
38 | dstBounds := srcBounds.Sub(srcBounds.Min)
39 |
40 | dst := image.NewNRGBA(dstBounds)
41 |
42 | dstMinX := dstBounds.Min.X
43 | dstMinY := dstBounds.Min.Y
44 |
45 | srcMinX := srcBounds.Min.X
46 | srcMinY := srcBounds.Min.Y
47 | srcMaxX := srcBounds.Max.X
48 | srcMaxY := srcBounds.Max.Y
49 |
50 | switch src0 := src.(type) {
51 |
52 | case *image.NRGBA:
53 | rowSize := srcBounds.Dx() * 4
54 | numRows := srcBounds.Dy()
55 |
56 | i0 := dst.PixOffset(dstMinX, dstMinY)
57 | j0 := src0.PixOffset(srcMinX, srcMinY)
58 |
59 | di := dst.Stride
60 | dj := src0.Stride
61 |
62 | for row := 0; row < numRows; row++ {
63 | copy(dst.Pix[i0:i0+rowSize], src0.Pix[j0:j0+rowSize])
64 | i0 += di
65 | j0 += dj
66 | }
67 |
68 | case *image.NRGBA64:
69 | i0 := dst.PixOffset(dstMinX, dstMinY)
70 | for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
71 | for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
72 |
73 | j := src0.PixOffset(x, y)
74 |
75 | dst.Pix[i+0] = src0.Pix[j+0]
76 | dst.Pix[i+1] = src0.Pix[j+2]
77 | dst.Pix[i+2] = src0.Pix[j+4]
78 | dst.Pix[i+3] = src0.Pix[j+6]
79 |
80 | }
81 | }
82 |
83 | case *image.RGBA:
84 | i0 := dst.PixOffset(dstMinX, dstMinY)
85 | for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
86 | for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
87 |
88 | j := src0.PixOffset(x, y)
89 | a := src0.Pix[j+3]
90 | dst.Pix[i+3] = a
91 |
92 | switch a {
93 | case 0:
94 | dst.Pix[i+0] = 0
95 | dst.Pix[i+1] = 0
96 | dst.Pix[i+2] = 0
97 | case 0xff:
98 | dst.Pix[i+0] = src0.Pix[j+0]
99 | dst.Pix[i+1] = src0.Pix[j+1]
100 | dst.Pix[i+2] = src0.Pix[j+2]
101 | default:
102 | dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
103 | dst.Pix[i+1] = uint8(uint16(src0.Pix[j+1]) * 0xff / uint16(a))
104 | dst.Pix[i+2] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
105 | }
106 | }
107 | }
108 |
109 | case *image.RGBA64:
110 | i0 := dst.PixOffset(dstMinX, dstMinY)
111 | for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
112 | for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
113 |
114 | j := src0.PixOffset(x, y)
115 | a := src0.Pix[j+6]
116 | dst.Pix[i+3] = a
117 |
118 | switch a {
119 | case 0:
120 | dst.Pix[i+0] = 0
121 | dst.Pix[i+1] = 0
122 | dst.Pix[i+2] = 0
123 | case 0xff:
124 | dst.Pix[i+0] = src0.Pix[j+0]
125 | dst.Pix[i+1] = src0.Pix[j+2]
126 | dst.Pix[i+2] = src0.Pix[j+4]
127 | default:
128 | dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
129 | dst.Pix[i+1] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
130 | dst.Pix[i+2] = uint8(uint16(src0.Pix[j+4]) * 0xff / uint16(a))
131 | }
132 | }
133 | }
134 |
135 | case *image.Gray:
136 | i0 := dst.PixOffset(dstMinX, dstMinY)
137 | for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
138 | for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
139 |
140 | j := src0.PixOffset(x, y)
141 | c := src0.Pix[j]
142 | dst.Pix[i+0] = c
143 | dst.Pix[i+1] = c
144 | dst.Pix[i+2] = c
145 | dst.Pix[i+3] = 0xff
146 |
147 | }
148 | }
149 |
150 | case *image.Gray16:
151 | i0 := dst.PixOffset(dstMinX, dstMinY)
152 | for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
153 | for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
154 |
155 | j := src0.PixOffset(x, y)
156 | c := src0.Pix[j]
157 | dst.Pix[i+0] = c
158 | dst.Pix[i+1] = c
159 | dst.Pix[i+2] = c
160 | dst.Pix[i+3] = 0xff
161 |
162 | }
163 | }
164 |
165 | case *image.YCbCr:
166 | i0 := dst.PixOffset(dstMinX, dstMinY)
167 | for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
168 | for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
169 |
170 | yj := src0.YOffset(x, y)
171 | cj := src0.COffset(x, y)
172 | r, g, b := color.YCbCrToRGB(src0.Y[yj], src0.Cb[cj], src0.Cr[cj])
173 |
174 | dst.Pix[i+0] = r
175 | dst.Pix[i+1] = g
176 | dst.Pix[i+2] = b
177 | dst.Pix[i+3] = 0xff
178 |
179 | }
180 | }
181 |
182 | default:
183 | i0 := dst.PixOffset(dstMinX, dstMinY)
184 | for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
185 | for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
186 |
187 | c := color.NRGBAModel.Convert(src.At(x, y)).(color.NRGBA)
188 |
189 | dst.Pix[i+0] = c.R
190 | dst.Pix[i+1] = c.G
191 | dst.Pix[i+2] = c.B
192 | dst.Pix[i+3] = c.A
193 |
194 | }
195 | }
196 | }
197 |
198 | return dst
199 | }
200 |
201 | // read a image return a image matrix by path or image
202 | func Read(imgOrPath interface{}) (imgMatrix [][][]uint8, err error) {
203 | var img image.Image
204 | switch imgOrPath.(type) {
205 | case string:
206 | img, err = DecodeImage(imgOrPath.(string))
207 | if err != nil {
208 | return
209 | }
210 | case image.Image:
211 | img = imgOrPath.(image.Image)
212 | default:
213 | err = errors.New("Incoming parameter error!")
214 | return
215 | }
216 |
217 | bounds := img.Bounds()
218 | width := bounds.Max.X //width
219 | height := bounds.Max.Y //height
220 |
221 | src := convertToNRGBA(img)
222 | imgMatrix = NewRGBAMatrix(height, width)
223 |
224 | for i := 0; i < height; i++ {
225 | for j := 0; j < width; j++ {
226 | c := src.At(j, i)
227 | r, g, b, a := c.RGBA()
228 | imgMatrix[i][j][0] = uint8(r)
229 | imgMatrix[i][j][1] = uint8(g)
230 | imgMatrix[i][j][2] = uint8(b)
231 | imgMatrix[i][j][3] = uint8(a)
232 |
233 | }
234 | }
235 | return
236 | }
237 |
238 | // read a image return a image matrix , if appear an error it will panic
239 | func MustRead(filepath string) (imgMatrix [][][]uint8) {
240 | img, decodeErr := DecodeImage(filepath)
241 | if decodeErr != nil {
242 | panic(decodeErr)
243 | }
244 |
245 | bounds := img.Bounds()
246 | width := bounds.Max.X
247 | height := bounds.Max.Y
248 |
249 | src := convertToNRGBA(img)
250 | imgMatrix = NewRGBAMatrix(height, width)
251 |
252 | for i := 0; i < height; i++ {
253 | for j := 0; j < width; j++ {
254 | c := src.At(j, i)
255 | r, g, b, a := c.RGBA()
256 | imgMatrix[i][j][0] = uint8(r)
257 | imgMatrix[i][j][1] = uint8(g)
258 | imgMatrix[i][j][2] = uint8(b)
259 | imgMatrix[i][j][3] = uint8(a)
260 |
261 | }
262 | }
263 | return
264 | }
265 |
266 | // save a image matrix as a png , if unsuccessful it will return a error
267 | func SaveAsPNG(filepath string, imgMatrix [][][]uint8) error {
268 | height := len(imgMatrix)
269 | width := len(imgMatrix[0])
270 |
271 | if height == 0 || width == 0 {
272 | return errors.New("The input of matrix is illegal!")
273 | }
274 |
275 | nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
276 |
277 | for i := 0; i < height; i++ {
278 | for j := 0; j < width; j++ {
279 | nrgba.SetNRGBA(j, i, color.NRGBA{imgMatrix[i][j][0], imgMatrix[i][j][1], imgMatrix[i][j][2], imgMatrix[i][j][3]})
280 | }
281 | }
282 | outfile, err := os.Create(filepath)
283 | if err != nil {
284 | return err
285 | }
286 | defer outfile.Close()
287 |
288 | png.Encode(outfile, nrgba)
289 |
290 | return nil
291 | }
292 |
293 | // save a image matrix as a jpeg,if unsuccessful it will return a error,quality must be 1 to 100
294 | func SaveAsJPEG(filepath string, imgMatrix [][][]uint8, quality int) error {
295 | height := len(imgMatrix)
296 | width := len(imgMatrix[0])
297 |
298 | if height == 0 || width == 0 {
299 | return errors.New("The input of matrix is illegal!")
300 | }
301 |
302 | if quality < 1 {
303 | quality = 1
304 | } else if quality > 100 {
305 | quality = 100
306 | }
307 |
308 | nrgba := image.NewNRGBA(image.Rect(0, 0, width, height))
309 |
310 | for i := 0; i < height; i++ {
311 | for j := 0; j < width; j++ {
312 | nrgba.SetNRGBA(j, i, color.NRGBA{imgMatrix[i][j][0], imgMatrix[i][j][1], imgMatrix[i][j][2], imgMatrix[i][j][3]})
313 | }
314 | }
315 | outfile, err := os.Create(filepath)
316 | if err != nil {
317 | return err
318 | }
319 | defer outfile.Close()
320 |
321 | jpeg.Encode(outfile, nrgba, &jpeg.Options{Quality: quality})
322 |
323 | return nil
324 | }
325 |
--------------------------------------------------------------------------------
/process.go:
--------------------------------------------------------------------------------
1 | package imgo
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | //input a image matrix as src , return a image matrix by sunseteffect process
8 | func SunsetEffect(src [][][]uint8) [][][]uint8 {
9 |
10 | height := len(src)
11 | width := len(src[0])
12 | imgMatrix := NewRGBAMatrix(height,width)
13 | copy(imgMatrix,src)
14 |
15 | for i:=0;i 1.0 {
221 | err = errors.New("the opacity is illegal!")
222 | }
223 |
224 | for i:=0;i= v {
35 | vector[i] = 1
36 | } else {
37 | vector[i] = 0
38 | }
39 | i++
40 | }
41 |
42 | return vector, nil
43 | }
44 |
45 | //calculate Cosine Similarity of two images, input two file path
46 | func CosineSimilarity(src1 string, src2 string) (cossimi float64, err error) {
47 | myx, err1 := convertImg2Vector(src1)
48 | if err1 != nil {
49 | err = err1
50 | return
51 | }
52 |
53 | myy, err2 := convertImg2Vector(src2)
54 | if err2 != nil {
55 | err = err2
56 | return
57 | }
58 |
59 | cos1 := Dot(myx, myy)
60 | cos21 := math.Sqrt(Dot(myx, myx))
61 | cos22 := math.Sqrt(Dot(myy, myy))
62 |
63 | cossimi = cos1 / (cos21 * cos22)
64 | return
65 | }
66 |
67 | //binaryzation process of image matrix , threshold can use 127 to test
68 | func Binaryzation(src [][][]uint8, threshold int) [][][]uint8 {
69 | imgMatrix := RGB2Gray(src)
70 |
71 | height := len(imgMatrix)
72 | width := len(imgMatrix[0])
73 | for i := 0; i < height; i++ {
74 | for j := 0; j < width; j++ {
75 | var rgb int = int(imgMatrix[i][j][0]) + int(imgMatrix[i][j][1]) + int(imgMatrix[i][j][2])
76 | if rgb > threshold {
77 | rgb = 255
78 | } else {
79 | rgb = 0
80 | }
81 | imgMatrix[i][j][0] = uint8(rgb)
82 | imgMatrix[i][j][1] = uint8(rgb)
83 | imgMatrix[i][j][2] = uint8(rgb)
84 | }
85 | }
86 |
87 | return imgMatrix
88 | }
89 |
90 | //GetFingerprint use Perceptual Hash Algorithm to get fingerprint from a pircture
91 | func GetFingerprint(src string) (fp string, err error) {
92 | imgMatrix, err1 := ResizeForMatrix(src, 8, 8)
93 | if err1 != nil {
94 | return "", err1
95 | }
96 |
97 | //convert RGB to Gray
98 | h, w := len(imgMatrix), len(imgMatrix[0])
99 | gray := make([]byte, w*h)
100 | for x := 0; x < w; x++ {
101 | for y := 0; y < h; y++ {
102 | gray[x+y*8] = byte((imgMatrix[x][y][0]*30 + imgMatrix[x][y][1]*59 + imgMatrix[x][y][2]*11) / 100)
103 | }
104 | }
105 |
106 | //calculate average value of color of picture
107 | sum := 0
108 | for _, v := range gray {
109 | sum += int(v)
110 | }
111 | avg := byte(sum / len(gray))
112 |
113 | var buffer bytes.Buffer
114 | for _, v := range gray {
115 | if avg >= v {
116 | buffer.WriteByte('1')
117 | } else {
118 | buffer.WriteByte('0')
119 | }
120 | }
121 | fp = buffer.String()
122 | return
123 | }
124 |
--------------------------------------------------------------------------------
/recog_test.go:
--------------------------------------------------------------------------------
1 | package imgo
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestCosineSimilarity(t *testing.T) {
8 | cos, err := CosineSimilarity("testpic/aa.png", "testpic/cc.png")
9 |
10 | if err != nil {
11 | t.Error(err)
12 | }
13 |
14 | t.Log(cos)
15 | if cos > 0.5 {
16 | t.Fail()
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/testpic/aa.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/testpic/aa.png
--------------------------------------------------------------------------------
/testpic/cc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/testpic/cc.png
--------------------------------------------------------------------------------
/testpic/dd.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Comdex/imgo/bb8d436f1e5aa252afdd166111cfcc55130beede/testpic/dd.jpg
--------------------------------------------------------------------------------
/util.go:
--------------------------------------------------------------------------------
1 | //package imgo is a golang image process lib
2 | package imgo
3 |
4 | import (
5 | "errors"
6 | "math"
7 | "image"
8 | "runtime"
9 | )
10 |
11 | type resamplingFilter struct {
12 | Support float64
13 | Kernel func(float64) float64
14 | }
15 |
16 | func ResizeForMatrix(filepath string, width int, height int)(imgMatrix [][][]uint8 , err error){
17 | img,err1:=DecodeImage(filepath)
18 |
19 | if err1 != nil {
20 | err = err1
21 | return
22 | }
23 |
24 | nrgba:=convertToNRGBA(img)
25 | src:=Resize(nrgba,width,height)
26 |
27 | imgMatrix = NewRGBAMatrix(height,width)
28 |
29 |
30 | for i:=0;i goMaxProcs {
137 | numGoroutines = goMaxProcs
138 | }
139 | if numGoroutines > dstW {
140 | numGoroutines = dstW
141 | }
142 | partSize := dstW / numGoroutines
143 |
144 | doneChan := make(chan bool, numGoroutines)
145 |
146 | for part := 0; part < numGoroutines; part++ {
147 | partStart := part * partSize
148 | partEnd := (part + 1) * partSize
149 | if part == numGoroutines-1 {
150 | partEnd = dstW
151 | }
152 |
153 | go func(partStart, partEnd int) {
154 |
155 | for dstX := partStart; dstX < partEnd; dstX++ {
156 | fX := float64(srcMinX) + (float64(dstX)+0.5)*dX - 0.5
157 |
158 | startX := int(math.Ceil(fX - rX))
159 | if startX < srcMinX {
160 | startX = srcMinX
161 | }
162 | endX := int(math.Floor(fX + rX))
163 | if endX > srcMaxX-1 {
164 | endX = srcMaxX - 1
165 | }
166 |
167 | // cache weights
168 | weightSum := 0.0
169 | weights := make([]float64, int(rX+2)*2)
170 | for x := startX; x <= endX; x++ {
171 | w := filter.Kernel((float64(x) - fX) / scaleX)
172 | weightSum += w
173 | weights[x-startX] = w
174 | }
175 |
176 | for dstY := 0; dstY < dstH; dstY++ {
177 | srcY := srcMinY + dstY
178 |
179 | r, g, b, a := 0.0, 0.0, 0.0, 0.0
180 | for x := startX; x <= endX; x++ {
181 | weight := weights[x-startX]
182 | i := src.PixOffset(x, srcY)
183 | r += float64(src.Pix[i+0]) * weight
184 | g += float64(src.Pix[i+1]) * weight
185 | b += float64(src.Pix[i+2]) * weight
186 | a += float64(src.Pix[i+3]) * weight
187 | }
188 |
189 | r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
190 | g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
191 | b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
192 | a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
193 |
194 | j := dst.PixOffset(dstX, dstY)
195 | dst.Pix[j+0] = uint8(r + 0.5)
196 | dst.Pix[j+1] = uint8(g + 0.5)
197 | dst.Pix[j+2] = uint8(b + 0.5)
198 | dst.Pix[j+3] = uint8(a + 0.5)
199 | }
200 | }
201 |
202 | doneChan <- true
203 | }(partStart, partEnd)
204 |
205 | }
206 |
207 | // wait for goroutines to finish
208 | for part := 0; part < numGoroutines; part++ {
209 | <-doneChan
210 | }
211 |
212 | return dst
213 | }
214 |
215 | func resizeVertical(src *image.NRGBA, height int, filter resamplingFilter) *image.NRGBA {
216 | srcBounds := src.Bounds()
217 | srcW := srcBounds.Dx()
218 | srcH := srcBounds.Dy()
219 | srcMinX := srcBounds.Min.X
220 | srcMinY := srcBounds.Min.Y
221 | srcMaxY := srcBounds.Max.Y
222 |
223 | dstW := srcW
224 | dstH := height
225 |
226 | dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
227 |
228 | dY := float64(srcH) / float64(dstH)
229 | scaleY := math.Max(dY, 1.0)
230 | rY := math.Ceil(scaleY * filter.Support)
231 |
232 | // divide image to parts for parallel processing
233 | numGoroutines := runtime.NumCPU()
234 | goMaxProcs := runtime.GOMAXPROCS(0)
235 | if numGoroutines > goMaxProcs {
236 | numGoroutines = goMaxProcs
237 | }
238 | if numGoroutines > dstH {
239 | numGoroutines = dstH
240 | }
241 | partSize := dstH / numGoroutines
242 |
243 | doneChan := make(chan bool, numGoroutines)
244 |
245 | for part := 0; part < numGoroutines; part++ {
246 | partStart := part * partSize
247 | partEnd := (part + 1) * partSize
248 | if part == numGoroutines-1 {
249 | partEnd = dstH
250 | }
251 |
252 | go func(partStart, partEnd int) {
253 |
254 | for dstY := partStart; dstY < partEnd; dstY++ {
255 | fY := float64(srcMinY) + (float64(dstY)+0.5)*dY - 0.5
256 |
257 | startY := int(math.Ceil(fY - rY))
258 | if startY < srcMinY {
259 | startY = srcMinY
260 | }
261 | endY := int(math.Floor(fY + rY))
262 | if endY > srcMaxY-1 {
263 | endY = srcMaxY - 1
264 | }
265 |
266 | // cache weights
267 | weightSum := 0.0
268 | weights := make([]float64, int(rY+2)*2)
269 | for y := startY; y <= endY; y++ {
270 | w := filter.Kernel((float64(y) - fY) / scaleY)
271 | weightSum += w
272 | weights[y-startY] = w
273 | }
274 |
275 | for dstX := 0; dstX < dstW; dstX++ {
276 | srcX := srcMinX + dstX
277 |
278 | r, g, b, a := 0.0, 0.0, 0.0, 0.0
279 | for y := startY; y <= endY; y++ {
280 | weight := weights[y-startY]
281 | i := src.PixOffset(srcX, y)
282 | r += float64(src.Pix[i+0]) * weight
283 | g += float64(src.Pix[i+1]) * weight
284 | b += float64(src.Pix[i+2]) * weight
285 | a += float64(src.Pix[i+3]) * weight
286 | }
287 |
288 | r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
289 | g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
290 | b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
291 | a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
292 |
293 | j := dst.PixOffset(dstX, dstY)
294 | dst.Pix[j+0] = uint8(r + 0.5)
295 | dst.Pix[j+1] = uint8(g + 0.5)
296 | dst.Pix[j+2] = uint8(b + 0.5)
297 | dst.Pix[j+3] = uint8(a + 0.5)
298 | }
299 | }
300 |
301 | doneChan <- true
302 | }(partStart, partEnd)
303 |
304 | }
305 |
306 | // wait for goroutines to finish
307 | for part := 0; part < numGoroutines; part++ {
308 | <-doneChan
309 | }
310 |
311 | return dst
312 | }
313 |
314 | // fast nearest-neighbor resize, no filtering
315 | func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA {
316 | dstW, dstH := width, height
317 |
318 | srcBounds := src.Bounds()
319 | srcW := srcBounds.Dx()
320 | srcH := srcBounds.Dy()
321 | srcMinX := srcBounds.Min.X
322 | srcMinY := srcBounds.Min.Y
323 | srcMaxX := srcBounds.Max.X
324 | srcMaxY := srcBounds.Max.Y
325 |
326 | dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
327 |
328 | dx := float64(srcW) / float64(dstW)
329 | dy := float64(srcH) / float64(dstH)
330 |
331 | // divide image to parts for parallel processing
332 | numGoroutines := runtime.NumCPU()
333 | goMaxProcs := runtime.GOMAXPROCS(0)
334 | if numGoroutines > goMaxProcs {
335 | numGoroutines = goMaxProcs
336 | }
337 | if numGoroutines > dstH {
338 | numGoroutines = dstH
339 | }
340 | partSize := dstH / numGoroutines
341 |
342 | doneChan := make(chan bool, numGoroutines)
343 |
344 | for part := 0; part < numGoroutines; part++ {
345 | partStart := part * partSize
346 | partEnd := (part + 1) * partSize
347 | if part == numGoroutines-1 {
348 | partEnd = dstH
349 | }
350 |
351 | go func(partStart, partEnd int) {
352 |
353 | for dstY := partStart; dstY < partEnd; dstY++ {
354 | fy := float64(srcMinY) + (float64(dstY)+0.5)*dy - 0.5
355 |
356 | for dstX := 0; dstX < dstW; dstX++ {
357 | fx := float64(srcMinX) + (float64(dstX)+0.5)*dx - 0.5
358 |
359 | srcX := int(math.Min(math.Max(math.Floor(fx+0.5), float64(srcMinX)), float64(srcMaxX)))
360 | srcY := int(math.Min(math.Max(math.Floor(fy+0.5), float64(srcMinY)), float64(srcMaxY)))
361 |
362 | srcOffset := src.PixOffset(srcX, srcY)
363 | dstOffset := dst.PixOffset(dstX, dstY)
364 |
365 | dst.Pix[dstOffset+0] = src.Pix[srcOffset+0]
366 | dst.Pix[dstOffset+1] = src.Pix[srcOffset+1]
367 | dst.Pix[dstOffset+2] = src.Pix[srcOffset+2]
368 | dst.Pix[dstOffset+3] = src.Pix[srcOffset+3]
369 | }
370 | }
371 |
372 | doneChan <- true
373 | }(partStart, partEnd)
374 | }
375 |
376 | // wait for goroutines to finish
377 | for part := 0; part < numGoroutines; part++ {
378 | <-doneChan
379 | }
380 |
381 | return dst
382 | }
383 |
384 | // create a three dimenson slice
385 | func New3DSlice(x int , y int , z int)(theSlice [][][]uint8){
386 | theSlice = make([][][]uint8,x,x)
387 | for i := 0; i < x; i++ {
388 | s2 := make([][]uint8, y, y)
389 | for j:=0 ; j < y; j++ {
390 | s3 := make([]uint8,z,z)
391 | s2[j] = s3
392 | }
393 | theSlice[i] = s2
394 | }
395 | return
396 | }
397 |
398 | // create a new rgba matrix
399 | func NewRGBAMatrix(height int,width int)(rgbaMatrix [][][]uint8){
400 | rgbaMatrix = New3DSlice(height,width,4)
401 | return
402 | }
403 |
404 | func Matrix2Vector(imgMatrix [][][]uint8)(vector []uint8){
405 | h:=len(imgMatrix)
406 | w:=len(imgMatrix[0])
407 | r:=len(imgMatrix[0][0])
408 | length:=h*w*r
409 |
410 | vector = make([]uint8,length)
411 |
412 | for i:=0; i