├── .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 | [![GoDoc](http://godoc.org/github.com/Comdex/imgo?status.svg)](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 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |

原图

横向镜像imgo.HorizontalMirror

日落imgo.SunsetEffect

负片imgo.NegativeFilmEffect

调整亮度imgo.AdjustBrightness

垂直镜像imgo.VerticalMirror
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