昨日のお昼休憩中に暇だったのでこんなツールを作りました。
見た目のインパクトからかプチバズりになり恐縮しております。
今回はどんな風にGoで開発したのか?というところを書いていきます。
pigoについて
開発するきっかけになった顔検出ライブラリです。
github.com
このライブラリはREADMEにも書いてあるように、「OpenCVインスコするのダルいからGoで顔検出ライブラリ作ったわ」という感じの分かりやすい思想に基づいて作成されました。(※尚、Webカメラを使ったリアルタイム検出にはOpenCVやpythonのインスコが必要になります。
実際に使ってみると、多少に検出誤差はあるもののほぼ顔を検出してくれて「おもすれー!」と感嘆。
多少暗い画像や、画質が荒かったり、顔が小さすぎるとちょっと検出が厳しい面はありました。
開発
さて、開発してみようと思ったのですが、まずpigoでどのようにコード書けばいいのか悩みました。
以下のようにREADMEに書かれているexampleを見てください。
cascadeFile, err := ioutil.ReadFile("/path/to/cascade/file")
if err != nil {
log.Fatalf("Error reading the cascade file: %v", err)
}
src, err := pigo.GetImage("/path/to/image")
if err != nil {
log.Fatalf("Cannot open the image file: %v", err)
}
pixels := pigo.RgbToGrayscale(src)
cols, rows := src.Bounds().Max.X, src.Bounds().Max.Y
cParams := pigo.CascadeParams{
MinSize: fd.minSize,
MaxSize: fd.maxSize,
ShiftFactor: fd.shiftFactor,
ScaleFactor: fd.scaleFactor,
ImageParams: pigo.ImageParams{
Pixels: pixels,
Rows: rows,
Cols: cols,
Dim: cols,
},
}
pigo := pigo.NewPigo()
classifier, err := pigo.Unpack(cascadeFile)
if err != nil {
log.Fatalf("Error reading the cascade file: %s", err)
}
angle := 0.0
dets := classifier.RunCascade(cParams, angle)
dets = classifier.ClusterDetections(dets, 0.2)
これ読むと、なんとなくニュアンスは伝わるのですが、「fd.minSizeとかのfdってなんやねん」とか色々疑問点が沸いてきました。
そこで、コマンド版のコードを覗いてみることに👀
pigo/main.go at master · esimov/pigo · GitHub
ここ読むとfdとは何ぞやとか色々理解が得られるので読むのをオススメします。
さて、読んでみるとpigoは以下のような流れで検出を行います。
- 顔検出に使うデータを読み込む
- 顔検出対象になる画像を読み込む
- 検出結果の取得
これをコードで書き起こしてみましょう。
package sample
import (
"github.com/esimov/pigo/core"
"io/ioutil"
"log"
)
func main() {
cascadeFile, err := ioutil.ReadFile("/go/src/github.com/esimov/pigo/data/facefinder")
if err != nil {
log.Fatalf("Error reading the cascade file: %v", err)
}
src, err := pigo.GetImage("/path/input.jpg")
if err != nil {
log.Fatalf("Cannot open the image file: %v", err)
}
pixels := pigo.RgbToGrayscale(src)
cols, rows := src.Bounds().Max.X, src.Bounds().Max.Y
cParams := pigo.CascadeParams{
MinSize: 20,
MaxSize: 1000,
ShiftFactor: 0.1,
ScaleFactor: 1.1,
ImageParams: pigo.ImageParams{
Pixels: pixels,
Rows: rows,
Cols: cols,
Dim: cols,
},
}
pigo := pigo.NewPigo()
classifier, err := pigo.Unpack(cascadeFile)
if err != nil {
log.Fatalf("Error reading the cascade file: %s", err)
}
angle := 0.0
dets := classifier.RunCascade(cParams, angle)
}
こんなコードになります。
ここまでで、検出した結果が dets
に格納されているはずなので、この結果から顔にthinking_faceを貼っ付けるところまでやってみましょう。
package main
import (
"github.com/esimov/pigo/core"
"github.com/fogleman/gg"
"github.com/nfnt/resize"
"image"
"image/jpeg"
"image/png"
"io/ioutil"
"log"
"os"
"path/filepath"
)
type detectionResult struct {
coords []image.Rectangle
}
var dc *gg.Context
func main() {
cascadeFile, err := ioutil.ReadFile("/go/src/github.com/esimov/pigo/data/facefinder")
if err != nil {
log.Fatalf("Error reading the cascade file: %v", err)
}
src, err := pigo.GetImage("/path/input.jpg")
if err != nil {
log.Fatalf("Cannot open the image file: %v", err)
}
pixels := pigo.RgbToGrayscale(src)
cols, rows := src.Bounds().Max.X, src.Bounds().Max.Y
cParams := pigo.CascadeParams{
MinSize: 20,
MaxSize: 1000,
ShiftFactor: 0.1,
ScaleFactor: 1.1,
ImageParams: pigo.ImageParams{
Pixels: pixels,
Rows: rows,
Cols: cols,
Dim: cols,
},
}
pigo := pigo.NewPigo()
classifier, err := pigo.Unpack(cascadeFile)
if err != nil {
log.Fatalf("Error reading the cascade file: %s", err)
}
angle := 0.0
dets := classifier.RunCascade(cParams, angle)
dets = classifier.ClusterDetections(dets, 0.2)
dc = gg.NewContext(cols, rows)
dc.DrawImage(src, 0, 0)
err = drawThinkingFace(dets)
if err != nil {
log.Fatalf("Error creating the image output: %s", err)
}
}
func drawThinkingFace(faces []pigo.Detection) error {
var qThresh float32 = 5.0
thinkingFace, _ := os.Open("/path/thinking_face.png")
thinkingFaceImg, _, err := image.Decode(thinkingFace)
for _, face := range faces {
if face.Q > qThresh {
resizeXY := int(float32(face.Scale) * 1.5)
resizeThinkingFaceImg := resize.Resize(uint(resizeXY), uint(resizeXY), thinkingFaceImg, resize.Lanczos3)
dc.DrawImage(resizeThinkingFaceImg, face.Col-face.Scale/2, face.Row-face.Scale/2)
}
}
img := dc.Image()
output, err := os.OpenFile("/path/output.jpg", os.O_CREATE|os.O_RDWR, 0755)
if err != nil {
return err
}
ext := filepath.Ext(output.Name())
switch ext {
case ".jpg", ".jpeg":
jpeg.Encode(output, img, &jpeg.Options{Quality: 100})
case ".png":
png.Encode(output, img)
}
return err
}
こんな感じになりました。
さっきより微妙にボリューミーになりましたが、大きく違うのは drawThinkingFace
があるところですかね。
基本的には dets
で取得された顔の情報から、適宜座標やサイズを良い感じにして🤔を貼り付けてます。
これをサンプルにパラメータを良い感じに弄ると良い感じに出力されるので、最高に良い感じのツールですね。
というわけで、pigoはまだWebカメラを使ったリアルタイム検出とかも機能としてはあるので、興味のあるGopherはやってみてアウトプットしてみてください!!!
Good ThinkingFace Life!!!!!!🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔🤔