Skip to content

Instantly share code, notes, and snippets.

@ieee0824
Last active November 30, 2017 15:44
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save ieee0824/518653c3d9e7376e05fb8aae7a85a1fb to your computer and use it in GitHub Desktop.
Golang as Image processing language

Adovent Calendar 1日目の記事です。
Go言語で行う画像処理の話をしたいと思います。

様々な画像処理手法

  • ImageMagickを使う
    • 様々な言語でC言語のwrapperライブラリが有り幅広く使われている
    • 昔からあるので使用実績が広い
    • 半年ほど前任意のコマンドを実行できるようにする脆弱性が見つかり多くのエンジニアを悩ませた
  • phthon, ruby, perl, PHPなど
    • 裏側はC言語などで作られたライブラリを使用する
    • mac -> linuxなどの開発が少し億劫になる
  • Javaで頑張る
  • OpenCVを使う
    • C/C++, Java, Pythonに対応したライブラリである
    • ImageMagick同様様々な言語で使えるようになっている
    • 環境構築が大掛かりになる
  • Go言語で書く
    • 公式のパッケージで image.Image インターフェイスが用意されている
    • クロスコンパイルができるのでpure Goで作れば他の環境に置き換えるのが比較的楽
    • 並列処理が簡単にかけるのでIO待ちとかを潰しやすい

Go言語を画像処理に使うのに優れている点

  • 優秀なGC
  • 優秀な並列処理機能
  • 優秀な画像処理パッケージ群

優秀なGC

Go言語のGCは優秀でほとんどプログラムを止めること無くメモリを管理してくれる

優秀な並列処理機能

ゴルーチンという機能を備えており非同期的な処理が強い 例えばワーカーを作って順次何かの画像からPNGにして保存する関数だとこう

func convertPng(wg *sync.WaitGroup, queue chan image.Image){
    defer wg.Done()
    for {
        img, ok := <- queue// queueがcloseされるとokがfalseになる
        if !ok {
            return
        }
        file, err := os.Create(fmt.Sprintf("%v.png", time.Now().UnixNano()))
        if err != nil {
            panic(err)
        }
        err = png.Encode(file, img)
        if err != nil {
            panic(err)
        }
    }
}

func main(){
    var wg sync.WaitGroup
    queue := make(chan image.Image, 16)
    
    // cpuの数ぶんワーカーを生成する
    for i := 0; i < runtime.NumCPU(); i ++ {
        wg.Add(1)
        go convertPng(&wg, queue)
    }
    
    /*
     * 適当にワーカーに画像を投げる処理を書く
     */
    close(queue)
    wg.Wait()
}

ここでは単純にPNGで保存しているがリサイズ処理とかにしても良い

優秀な画像処理パッケージ群

pure Go, C言語ベースのパッケージ問わず使えるものが結構多い
pure Goなパッケージだけでも意外と困らない事が多い

  • Go言語として使える画像フォーマット
    • jpeg
    • png
    • gif
    • webp
    • bmp
  • 便利な画像処理パッケージ
    • github.com/nfnt/resize
      • pure Goである
      • 画像のリサイズができる
      • リサイズアルゴリズムの対応が多い
        • Nearest-Neighbor
        • Bilinear
        • Bicubic
        • Mitchell-Netravali
        • Lanczos2
        • Lanczos3
    • github.com/DAddYE/vips
    • github.com/pixiv/go-libjpeg
      • libjpeg wrapperである
      • buildinのライブラリより速い
    • github.com/lucasb-eyer/go-colorful
      • golangでhsvとかを扱うのに使う
      • 画像認識とかでいらないデータを削ったりするのに便利

画像リサイズサーバーの単純な実装

最後にローカルにある画像をリサイズしてbrowserに表示する画像サーバーの実装を載せておく

package main

import (
	"bytes"
	"encoding/json"
	"errors"
	"flag"
	"image"
	"image/jpeg"
	"image/png"
	"net/http"
	"os"
	"path/filepath"
	"runtime"
	"strconv"

	"github.com/nfnt/resize"
)

var (
	port   = flag.String("p", "8081", "port")
	imgDir = flag.String("i", "./img", "img dir")
)

func getImage(path string) (image.Image, string, error) {
	path, err := filepath.Abs(path)
	if err != nil {
		return nil, "", err
	}
	file, err := os.Open(path)
	if err != nil {
		return nil, "", err
	}
	return image.Decode(file)
}

func getSize(req *http.Request) (uint, uint, error) {
	query := req.URL.Query()

	w := query.Get("w")
	if w == "" {
		w = "0"
	}
	wid, err := strconv.Atoi(w)
	if err != nil {
		return 0, 0, err
	}
	h := query.Get("h")
	if h == "" {
		h = "0"
	}
	hig, err := strconv.Atoi(h)

	if err != nil {
		return 0, 0, err
	}
	return uint(wid), uint(hig), nil
}

func errorHandle(wr http.ResponseWriter, err error) {
	wr.Header().Set("Content-Type", "application/json")
	wr.WriteHeader(404)
	errJSON, _ := json.Marshal(map[string]string{
		"error": err.Error(),
	})
	wr.Write(errJSON)
}

func resizeImage(wr http.ResponseWriter, req *http.Request) {
	//query := req.URL.Query()
	path := *imgDir + req.URL.Path
	img, t, err := getImage(path)
	if err != nil {
		errorHandle(wr, err)
		return
	}

	w, h, err := getSize(req)
	if err != nil {
		errorHandle(wr, err)
		return
	}
	resizeImg := resize.Resize(w, h, img, resize.Lanczos3)

	out := new(bytes.Buffer)

	switch t {
	case "png":
		err = png.Encode(out, resizeImg)
		if err != nil {
			errorHandle(wr, err)
			return
		}

		wr.Header().Set("Content-Type", "image/png")
	case "jpeg":
		err = jpeg.Encode(out, resizeImg, nil)
		if err != nil {
			errorHandle(wr, err)
			return
		}

		wr.Header().Set("Content-Type", "image/jpeg")
	default:
		errorHandle(wr, errors.New("Unsupported image format"))
		return
	}

	wr.Write(out.Bytes())
}

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	http.HandleFunc("/", resizeImage)
	http.ListenAndServe(":"+*port, nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment