├── .env.sample ├── .gitignore ├── LICENSE.🍣.md ├── README.md ├── callapi └── detect.go ├── imgprocessing └── img.go ├── models ├── facelandmark.go └── faceparts.go ├── picture ├── document │ ├── TeikyoLena.png │ └── TeikyomeAIKATSU.jpg └── material │ ├── teikyo-kyo.png │ └── teikyo-tei.png ├── server.go ├── test ├── Lena.jpg ├── dice.png └── monariza.jpg └── util └── loadenv.go /.env.sample: -------------------------------------------------------------------------------- 1 | URL= 2 | KEY1= 3 | KEY2= 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### https://raw.github.com/github/gitignore/9e018473e80cf262d25841facbbd69e7f1a4ef41/Go.gitignore 2 | 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # myenv 17 | .env 18 | -------------------------------------------------------------------------------- /LICENSE.🍣.md: -------------------------------------------------------------------------------- 1 | # "THE SUSHI-WARE LICENSE" 2 | 3 | wrote this file. 4 | 5 | As long as you retain this notice you can do whatever you want 6 | with this stuff. If we meet some day, and you think this stuff 7 | is worth it, you can buy me a **sushi 🍣** in return. 8 | 9 | (This license is based on ["THE BEER-WARE LICENSE" (Revision 42)]. 10 | Thanks a lot, Poul-Henning Kamp ;) 11 | 12 | ["THE BEER-WARE LICENSE" (Revision 42)]: https://people.freebsd.org/~phk/ 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![SUSHI-WARE LICENSE](https://img.shields.io/badge/license-SUSHI--WARE%F0%9F%8D%A3-blue.svg)](https://github.com/MakeNowJust/sushi-ware) 2 | 3 | # TeikyoGenerator 4 | "提供目"(Teikyo-me) is japanese traditional funny picture for TV captured things. 5 | 6 | Japanese TV has fund provider. And japanese TV maker introduce the company for appreciation. So sometimes that caption be coverd with some people eyes. 7 | 8 | ex) 9 | ![](./picture/document/TeikyomeAIKATSU.jpg) 10 | 11 | This repository generate this "提供目". 12 | 13 | ## Installation 14 | To install Teikyo generator, you need to install Go and Go workspaces. 15 | 16 | And to enable Face API in Azure. 17 | 18 | https://azure.microsoft.com/ja-jp/services/cognitive-services/face/ 19 | 20 | 1. `go get` this repository 21 | ``` 22 | $ go get github.com/hatobus/Teikyo 23 | ``` 24 | 25 | 2. To write `.env` file 26 | .env file is like this 27 | ``` 28 | URL=[Your request URL with Azure] 29 | KEY1=[Azure Key1] 30 | KEY2=[Azure Key2] 31 | ``` 32 | 3. To start `server.go` 33 | ``` 34 | go run server.go 35 | ``` 36 | Default port is `8080`. If it already used. To change other ports. 37 | 4. Do POST Request 38 | 39 | - curl 40 | ``` 41 | curl http://localhost:8080/detect \ 42 | -F "upload[]=@path/to/img1.jpg" \ 43 | -F "upload[]=@path/to/img2.jpg" \ 44 | -H "Content-Type: multipart/form-data" 45 | ``` 46 | - httpie 47 | ``` 48 | http -f POST http://localhost:8080/detect \ 49 | upload[]@/path/to/img1.jpg \ 50 | upload[]@/path/to/img3.jpg 51 | ``` 52 | 53 | Source image must be `jpeg` file. (Other format will be error) 54 | 55 | 5. To generate pictures 56 | 57 | Output image generate to `picture/output` and name is `output[num].png` 58 | 59 | example 60 | 61 | ![lena](./picture/document/TeikyoLena.png) 62 | 63 | ## Used 64 | - Azure 65 | - FaceAPI 66 | - Golang 67 | - godotenv 68 | - gin 69 | 70 | 71 | -------------------------------------------------------------------------------- /callapi/detect.go: -------------------------------------------------------------------------------- 1 | package callapi 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "image/jpeg" 7 | "io/ioutil" 8 | "log" 9 | "mime/multipart" 10 | "net/http" 11 | "net/url" 12 | "os" 13 | "path" 14 | "path/filepath" 15 | 16 | "github.com/hatobus/Teikyo/models" 17 | ) 18 | 19 | func DetectFace(fstream multipart.File) ([]models.FaceParts, error) { 20 | 21 | exe, _ := os.Getwd() 22 | savedir := filepath.Join(exe, "picture", "output") 23 | 24 | // ディレクトリの確認 25 | // ディレクトリがなかった場合は作成 26 | // それでもディレクトリを作成できなかったらエラー 27 | if f, err := os.Stat(savedir); os.IsNotExist(err) || !f.IsDir() { 28 | if err := os.MkdirAll(savedir, 0777); err != nil { 29 | return nil, err 30 | } 31 | } 32 | 33 | // 画像をデコード 34 | imgBin, err := jpegToBin(fstream) 35 | if err != nil { 36 | log.Println(err) 37 | return nil, err 38 | } 39 | 40 | // 叩くURLは以下のようにするのでこれを満たすように作成 41 | // https://[location].api.cognitive.microsoft.com/face/v1.0/detect[?returnFaceId][&returnFaceLandmarks][&returnFaceAttributes] 42 | 43 | URL := os.Getenv("URL") 44 | subscriptionKey := os.Getenv("KEY1") 45 | 46 | u, err := url.Parse(URL) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // クエリパラメータはreturnFaceLandmarkdが必要なのでtrueにしておく 52 | query := url.Values{ 53 | "returnFaceLandmarks": {"true"}, 54 | } 55 | 56 | // URLの生成 57 | u.Path = path.Join(u.Path, "detect") 58 | u.RawQuery = query.Encode() 59 | log.Println(u.String()) 60 | 61 | req, err := http.NewRequest("POST", u.String(), bytes.NewBuffer(imgBin)) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | req.Header.Set("Content-Type", "application/octet-stream") 67 | req.Header.Set("Ocp-Apim-Subscription-Key", subscriptionKey) 68 | 69 | client := &http.Client{} 70 | resp, err := client.Do(req) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | defer resp.Body.Close() 76 | 77 | body, err := ioutil.ReadAll(resp.Body) 78 | if err != nil { 79 | return nil, err 80 | } 81 | 82 | var facelandmark []models.FaceParts 83 | err = json.Unmarshal(body, &facelandmark) 84 | log.Println(string(body)) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | return facelandmark, nil 90 | } 91 | 92 | func jpegToBin(fstream multipart.File) ([]byte, error) { 93 | buf := new(bytes.Buffer) 94 | // どうやらファイルの先頭までシークをしなければいけなかったっぽい 95 | // https://stackoverflow.com/questions/32193395/golang-io-reader-issue-with-jpeg-decode-returning-eof 96 | fstream.Seek(0, 0) 97 | 98 | img, err := jpeg.Decode(fstream) 99 | if err != nil { 100 | // シークしないとunexpected EOFで落ちる 101 | return buf.Bytes(), err 102 | } 103 | 104 | if err = jpeg.Encode(buf, img, nil); err != nil { 105 | return buf.Bytes(), err 106 | } 107 | 108 | return buf.Bytes(), nil 109 | } 110 | -------------------------------------------------------------------------------- /imgprocessing/img.go: -------------------------------------------------------------------------------- 1 | package imageprocessing 2 | 3 | import ( 4 | "image" 5 | "image/draw" 6 | "image/png" 7 | "math" 8 | "mime/multipart" 9 | "os" 10 | "path/filepath" 11 | "strconv" 12 | 13 | "github.com/hatobus/Teikyo/models" 14 | "github.com/nfnt/resize" 15 | ) 16 | 17 | func GenTeikyo(fstream multipart.File, parts *models.Landmark, multi bool, n int, cnt int) error { 18 | 19 | exe, _ := os.Getwd() 20 | 21 | goPath := os.Getenv("GOPATH") 22 | imgPath := filepath.Join(goPath, "src", "github.com", "hatobus", "Teikyo", "picture", "material") 23 | outputfile := filepath.Join(exe, "picture", "output", "output"+strconv.Itoa(n)+".png") 24 | 25 | t, _ := os.Open(filepath.Join(imgPath, "teikyo-tei.png")) 26 | defer t.Close() 27 | 28 | k, _ := os.Open(filepath.Join(imgPath, "teikyo-kyo.png")) 29 | defer k.Close() 30 | 31 | if _, err := fstream.Seek(0, 0); err != nil { 32 | return err 33 | } 34 | 35 | Tei, _, err := image.Decode(t) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | Kyo, _, err := image.Decode(k) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | dstimg, _, err := image.Decode(fstream) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | if cnt > 0 && multi { 51 | t, err := os.Open(outputfile) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | dstimg, _, err = image.Decode(t) 57 | if err != nil { 58 | return err 59 | } 60 | } 61 | 62 | // 大きさの微調整 63 | RightEyeSizeWidth := parts.EyeRight.BottomX - parts.EyeRight.TopX + 15 64 | RightEyeSizeHeight := parts.EyeRight.BottomY - parts.EyeRight.TopY + 15 65 | 66 | LeftEyeSizeWidth := parts.EyeLeft.BottomX - parts.EyeLeft.TopX + 15 67 | LeftEyeSizeHeight := parts.EyeLeft.BottomY - parts.EyeLeft.TopY + 15 68 | 69 | // 目の周りを覆うように大きさをリサイズ 70 | LTei := resize.Resize( 71 | uint(math.Abs(LeftEyeSizeWidth)), 72 | uint(math.Abs(LeftEyeSizeHeight)), 73 | Tei, 74 | resize.Lanczos3) 75 | 76 | RKyo := resize.Resize( 77 | uint(math.Abs(RightEyeSizeWidth)), 78 | uint(math.Abs(RightEyeSizeHeight)), 79 | Kyo, 80 | resize.Lanczos3) 81 | 82 | // References this 83 | // http://dempatow.hatenablog.com 84 | 85 | // 描画を開始する場所 86 | TeistartPoint := image.Point{int(parts.EyeLeft.TopX) - 10, int(parts.EyeLeft.TopY)} 87 | KyostartPoint := image.Point{int(parts.EyeRight.TopX) - 10, int(parts.EyeRight.TopY)} 88 | 89 | // 画像を入れる部分を作る 90 | TeiRectangle := image.Rectangle{TeistartPoint, TeistartPoint.Add(Tei.Bounds().Size())} 91 | KyoRectangle := image.Rectangle{KyostartPoint, KyostartPoint.Add(Kyo.Bounds().Size())} 92 | DstRectangle := image.Rectangle{image.Point{0, 0}, dstimg.Bounds().Size()} 93 | 94 | // 実際に描画、draw.Overで透過したまま重ねることができる 95 | rgba := image.NewRGBA(DstRectangle) 96 | draw.Draw(rgba, DstRectangle, dstimg, image.Pt(0, 0), draw.Src) 97 | draw.Draw(rgba, TeiRectangle, LTei, image.Pt(0, 0), draw.Over) 98 | draw.Draw(rgba, KyoRectangle, RKyo, image.Pt(0, 0), draw.Over) 99 | 100 | outfile, err := os.Create(outputfile) 101 | defer outfile.Close() 102 | if err != nil { 103 | return err 104 | } 105 | 106 | png.Encode(outfile, rgba) 107 | 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /models/facelandmark.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type Landmark struct { 4 | EyeRight Righteye 5 | EyeLeft Lefteye 6 | } 7 | 8 | type Righteye struct { 9 | TopX float64 10 | TopY float64 11 | BottomX float64 12 | BottomY float64 13 | } 14 | 15 | type Lefteye struct { 16 | TopX float64 17 | TopY float64 18 | BottomX float64 19 | BottomY float64 20 | } 21 | -------------------------------------------------------------------------------- /models/faceparts.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | type FaceParts struct { 4 | FaceID string `json:"faceId"` 5 | FaceRectangle struct { 6 | Top int `json:"top"` 7 | Left int `json:"left"` 8 | Width int `json:"width"` 9 | Height int `json:"height"` 10 | } `json:"faceRectangle"` 11 | FaceLandmarks struct { 12 | PupilLeft struct { 13 | X float64 `json:"x"` 14 | Y float64 `json:"y"` 15 | } `json:"pupilLeft"` 16 | PupilRight struct { 17 | X float64 `json:"x"` 18 | Y float64 `json:"y"` 19 | } `json:"pupilRight"` 20 | NoseTip struct { 21 | X float64 `json:"x"` 22 | Y float64 `json:"y"` 23 | } `json:"noseTip"` 24 | MouthLeft struct { 25 | X float64 `json:"x"` 26 | Y float64 `json:"y"` 27 | } `json:"mouthLeft"` 28 | MouthRight struct { 29 | X float64 `json:"x"` 30 | Y float64 `json:"y"` 31 | } `json:"mouthRight"` 32 | EyebrowLeftOuter struct { 33 | X float64 `json:"x"` 34 | Y float64 `json:"y"` 35 | } `json:"eyebrowLeftOuter"` 36 | EyebrowLeftInner struct { 37 | X float64 `json:"x"` 38 | Y float64 `json:"y"` 39 | } `json:"eyebrowLeftInner"` 40 | EyeLeftOuter struct { 41 | X float64 `json:"x"` 42 | Y float64 `json:"y"` 43 | } `json:"eyeLeftOuter"` 44 | EyeLeftTop struct { 45 | X float64 `json:"x"` 46 | Y float64 `json:"y"` 47 | } `json:"eyeLeftTop"` 48 | EyeLeftBottom struct { 49 | X float64 `json:"x"` 50 | Y float64 `json:"y"` 51 | } `json:"eyeLeftBottom"` 52 | EyeLeftInner struct { 53 | X float64 `json:"x"` 54 | Y float64 `json:"y"` 55 | } `json:"eyeLeftInner"` 56 | EyebrowRightInner struct { 57 | X float64 `json:"x"` 58 | Y float64 `json:"y"` 59 | } `json:"eyebrowRightInner"` 60 | EyebrowRightOuter struct { 61 | X float64 `json:"x"` 62 | Y float64 `json:"y"` 63 | } `json:"eyebrowRightOuter"` 64 | EyeRightInner struct { 65 | X float64 `json:"x"` 66 | Y float64 `json:"y"` 67 | } `json:"eyeRightInner"` 68 | EyeRightTop struct { 69 | X float64 `json:"x"` 70 | Y float64 `json:"y"` 71 | } `json:"eyeRightTop"` 72 | EyeRightBottom struct { 73 | X float64 `json:"x"` 74 | Y float64 `json:"y"` 75 | } `json:"eyeRightBottom"` 76 | EyeRightOuter struct { 77 | X float64 `json:"x"` 78 | Y float64 `json:"y"` 79 | } `json:"eyeRightOuter"` 80 | NoseRootLeft struct { 81 | X float64 `json:"x"` 82 | Y float64 `json:"y"` 83 | } `json:"noseRootLeft"` 84 | NoseRootRight struct { 85 | X float64 `json:"x"` 86 | Y float64 `json:"y"` 87 | } `json:"noseRootRight"` 88 | NoseLeftAlarTop struct { 89 | X float64 `json:"x"` 90 | Y float64 `json:"y"` 91 | } `json:"noseLeftAlarTop"` 92 | NoseRightAlarTop struct { 93 | X float64 `json:"x"` 94 | Y float64 `json:"y"` 95 | } `json:"noseRightAlarTop"` 96 | NoseLeftAlarOutTip struct { 97 | X float64 `json:"x"` 98 | Y float64 `json:"y"` 99 | } `json:"noseLeftAlarOutTip"` 100 | NoseRightAlarOutTip struct { 101 | X float64 `json:"x"` 102 | Y float64 `json:"y"` 103 | } `json:"noseRightAlarOutTip"` 104 | UpperLipTop struct { 105 | X float64 `json:"x"` 106 | Y float64 `json:"y"` 107 | } `json:"upperLipTop"` 108 | UpperLipBottom struct { 109 | X float64 `json:"x"` 110 | Y float64 `json:"y"` 111 | } `json:"upperLipBottom"` 112 | UnderLipTop struct { 113 | X float64 `json:"x"` 114 | Y float64 `json:"y"` 115 | } `json:"underLipTop"` 116 | UnderLipBottom struct { 117 | X float64 `json:"x"` 118 | Y float64 `json:"y"` 119 | } `json:"underLipBottom"` 120 | } `json:"faceLandmarks"` 121 | } 122 | 123 | func (fp FaceParts) ToLandmark() *Landmark { 124 | LM := &Landmark{} 125 | 126 | LM.EyeRight.TopX = fp.FaceLandmarks.EyebrowRightInner.X 127 | LM.EyeRight.TopY = fp.FaceLandmarks.EyebrowRightInner.Y 128 | 129 | LM.EyeRight.BottomX = fp.FaceLandmarks.EyebrowRightOuter.X 130 | LM.EyeRight.BottomY = fp.FaceLandmarks.EyeRightBottom.Y 131 | 132 | LM.EyeLeft.TopX = fp.FaceLandmarks.EyebrowLeftOuter.X 133 | LM.EyeLeft.TopY = fp.FaceLandmarks.EyebrowLeftOuter.Y 134 | 135 | LM.EyeLeft.BottomX = fp.FaceLandmarks.EyebrowLeftInner.X 136 | LM.EyeLeft.BottomY = fp.FaceLandmarks.EyeLeftBottom.Y 137 | 138 | return LM 139 | } 140 | -------------------------------------------------------------------------------- /picture/document/TeikyoLena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatobus/Teikyo/053bb9ad3a00bd1a11b2b3b145b9e126f20a6607/picture/document/TeikyoLena.png -------------------------------------------------------------------------------- /picture/document/TeikyomeAIKATSU.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatobus/Teikyo/053bb9ad3a00bd1a11b2b3b145b9e126f20a6607/picture/document/TeikyomeAIKATSU.jpg -------------------------------------------------------------------------------- /picture/material/teikyo-kyo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatobus/Teikyo/053bb9ad3a00bd1a11b2b3b145b9e126f20a6607/picture/material/teikyo-kyo.png -------------------------------------------------------------------------------- /picture/material/teikyo-tei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatobus/Teikyo/053bb9ad3a00bd1a11b2b3b145b9e126f20a6607/picture/material/teikyo-tei.png -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "image" 6 | "io" 7 | "log" 8 | "net/http" 9 | 10 | _ "image/gif" 11 | _ "image/jpeg" 12 | _ "image/png" 13 | 14 | "github.com/gin-gonic/gin" 15 | "github.com/hatobus/Teikyo/callapi" 16 | img "github.com/hatobus/Teikyo/imgprocessing" 17 | "github.com/hatobus/Teikyo/util" 18 | ) 19 | 20 | func main() { 21 | r := gin.Default() 22 | 23 | err := util.Loadenv() 24 | if err != nil { 25 | panic(err) 26 | } 27 | 28 | r.POST("/detect", createTeikyohandler) 29 | 30 | r.Run(":8080") 31 | } 32 | 33 | func createTeikyohandler(c *gin.Context) { 34 | 35 | form, _ := c.MultipartForm() 36 | files := form.File["upload[]"] 37 | 38 | errch := make(map[string]string, len(form.File)) 39 | b := new(bytes.Buffer) 40 | 41 | var mul bool 42 | 43 | for i, file := range files { 44 | log.Println(file.Filename) 45 | 46 | f, err := file.Open() 47 | defer f.Close() 48 | 49 | // 一回DecodeConfigでファイルをいじるとファイルが壊れるために 50 | // 別のbufにコピーをして回避しておく 51 | io.Copy(b, f) 52 | 53 | _, format, err := image.DecodeConfig(b) 54 | if err != nil { 55 | errch[file.Filename] = err.Error() 56 | b.Reset() 57 | break 58 | } else if format != "jpeg" { 59 | errch[file.Filename] = "Filetype must be jpeg" 60 | b.Reset() 61 | break 62 | } 63 | 64 | b.Reset() 65 | 66 | landmark, err := callapi.DetectFace(f) 67 | if err != nil { 68 | errch[file.Filename] = err.Error() 69 | break 70 | } 71 | 72 | if len(landmark) == 0 { 73 | errch[file.Filename] = "Human not found." 74 | } 75 | 76 | mul = false 77 | 78 | if len(landmark) > 1 { 79 | mul = true 80 | } 81 | 82 | for k, L := range landmark { 83 | LM := L.ToLandmark() 84 | err := img.GenTeikyo(f, LM, mul, i, k) 85 | if err != nil { 86 | errch[file.Filename] = err.Error() 87 | } 88 | } 89 | 90 | } 91 | 92 | c.JSON(http.StatusOK, gin.H{ 93 | "errors": errch, 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /test/Lena.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatobus/Teikyo/053bb9ad3a00bd1a11b2b3b145b9e126f20a6607/test/Lena.jpg -------------------------------------------------------------------------------- /test/dice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatobus/Teikyo/053bb9ad3a00bd1a11b2b3b145b9e126f20a6607/test/dice.png -------------------------------------------------------------------------------- /test/monariza.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hatobus/Teikyo/053bb9ad3a00bd1a11b2b3b145b9e126f20a6607/test/monariza.jpg -------------------------------------------------------------------------------- /util/loadenv.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/joho/godotenv" 7 | ) 8 | 9 | func Loadenv() error { 10 | err := godotenv.Load() 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | return nil 15 | } 16 | --------------------------------------------------------------------------------