Go言語で顔認識してみた


## はじめに
Go Advent Calendarの12月1日分の記事です。
はい、すいません。もう12月2日ですが、寝るまでが12月1日ということで。
今回は、cgoの練習ということで、OpenCVをいじってみました。

## OpenCVのインストール
私はMacを使っているので、homebrewで入れました。

```
$ brew install opencv
```

しばらくhomebrewを使っていなかったので、アップデートしようとしたら、エラーが沢山でました。その辺は以下を見て解決しました。

* https://github.com/mxcl/homebrew/issues/11448

## やり方を探す

とりあえず、「opencv golang」でググると、便利そうなバインディングが見つかりました。

* https://code.google.com/p/go-opencv/

`go get`してみよう。

```
go get code.google.com/p/go-opencv/trunk/opencv
```

コンパイルに失敗する。

```
# code.google.com/p/go-opencv/opencv
./cxcore.go:218:5: struct size calculation error off=60 bytesize=40
```

うむ、良くわからん。ググろう。
この辺とか出てきたけど、よくわからないので、やめよう。

* https://code.google.com/p/go/issues/detail?id=3505

時間がないので、バインディングを使う方法はやめました。

## 直にcgoを使う

またもや「opencv golang」ググります。
良さげな記事を発見しました。

* http://codeblag.wordpress.com/2013/03/09/how-to-use-opencv-with-go-on-linux/

説明の英語には目もくれず、Go言語のソースコードを見ます。

```
package main
 
import (
    "fmt"
)
 
//#cgo pkg-config: opencv
//#include <cv.h>
//#include <highgui.h>
import "C"
import "unsafe"
 
func main() {
    fmt.Println("Hello World!")
    text := C.CString("Hello World!")
    defer C.free(unsafe.Pointer(text))
    C.cvNamedWindow(text, 1)
    img := unsafe.Pointer(C.cvCreateImage(C.cvSize(640, 480), C.IPL_DEPTH_8U, 1))
    C.cvSet(img, C.cvScalar(0, 0, 0, 0), nil)
    var font C.CvFont
    C.cvInitFont(&font, C.CV_FONT_HERSHEY_SIMPLEX|C.CV_FONT_ITALIC,
        1.0, 1.0, 0, 1, 8)
    C.cvPutText(img, text, C.cvPoint(200, 400), &font,
        C.cvScalar(255, 255, 0, 0))
    C.cvShowImage(text, img)
    C.cvWaitKey(0)
}
```
実行すると、`Hello, World`と出ます。
ふむふむ！顔認識をやってみよう。

## 顔認識

cgoの使い方はふんわり分かったので、次はopencvで顔認識をするサンプルをGoogle先生で調べました。

* http://www.aianet.ne.jp/~asada/prog_doc/opencv/opencv_obj_det_img.htm

なかなか、良さげな記事を発見しました。静止画の顔認識をやってみます。
C言語のコードの前に`C`を付けていく簡単なお仕事をやってみます。

```
# command-line-arguments
./sample.go:21: not enough arguments in call to _Cfunc_cvLoad
./sample.go:21: cannot convert _Cfunc_cvLoad(_Cfunc_CString("haarcascade_frontalface_default.xml")) (type unsafe.Pointer) to type C.CvHaarClassifierCascade
./sample.go:21: invalid indirect of _Ctype_CvHaarClassifierCascade(_Cfunc_cvLoad(_Cfunc_CString("haarcascade_frontalface_default.xml"))) (type C.CvHaarClassif
ierCascade)
./sample.go:25: cannot use tarImg (type *_Ctype_IplImage) as type unsafe.Pointer in function argument
./sample.go:25: not enough arguments in call to _Cfunc_cvHaarDetectObjects
./sample.go:28: undefined: CvRect
./sample.go:28: cannot use i (type int) as type C.int in function argument
./sample.go:30: not enough arguments in call to _Cfunc_cvRectangle
./sample.go:36: cannot use tarImg (type *_Ctype_IplImage) as type unsafe.Pointer in function argument
./sample.go:37: not enough arguments in call to _Cfunc_cvNamedWindow
./sample.go:37: too many errors
```

なんかいっぱいエラーでた。
どうやら、省略されている引数があるようです。
あとはキャストに失敗している部分があるようです。

エラーを解決していくと、以下のようになりました。


```face_detection1.go

package main

//#cgo pkg-config: opencv
//#include <cv.h>
//#include <highgui.h>
import "C"

import (
	"flag"
	"unsafe"
)

var (
	filePath = flag.String("f", "lena.jpg", "file path")
)

func main() {
	flag.Parse()

	tarImg := C.cvLoadImage(C.CString(*filePath), C.CV_LOAD_IMAGE_ANYDEPTH|C.CV_LOAD_IMAGE_ANYCOLOR)

	cvHCC := (*C.CvHaarClassifierCascade)(C.cvLoad(C.CString("haarcascade_frontalface_default.xml"), (*C.CvMemStorage)(nil), (*C.char)(nil), (**C.char)(nil)))

	cvMStr := C.cvCreateMemStorage(0)

	face := C.cvHaarDetectObjects(
		unsafe.Pointer(tarImg),
		cvHCC,
		cvMStr,
		1.11,
		3,
		0,
		C.cvSize(0, 0),
		C.cvSize(0, 0),
	)

	for i := C.int(0); i < face.total; i++ {
		faceRect := (*C.CvRect)(unsafe.Pointer(C.cvGetSeqElem(face, i)))	
		C.cvRectangle(
			unsafe.Pointer(tarImg),
			C.cvPoint(faceRect.x, faceRect.y),
			C.cvPoint(faceRect.x+faceRect.width, faceRect.y+faceRect.height),
			C.cvScalar(0, 0, 255, 0),
			3,
			C.CV_AA,
			0,
		)
	}

	C.cvNamedWindow(C.CString("face_detect"), C.CV_WINDOW_AUTOSIZE)
	C.cvShowImage(C.CString("face_detect"), unsafe.Pointer(tarImg))

	C.cvWaitKey(0)

	C.cvDestroyWindow(C.CString("face_detect"))
}

```

`haarcascade_frontalface_default.xml`は顔認識の学習データらしく、ググると見つけれます。`lena.jpg`はいつものお姉さんです。

実行してみる。

![face_detection1.jpg](https://qiita-image-store.s3.amazonaws.com/0/14952/aebb6c4e-baf2-3efb-4237-8501f18a43e3.jpeg "face_detection1.jpg")


ふむ。何か物足りない。そうだ、Gopher君だ！

画像の上に画像を重ねる方法を探そうと思い、Google先生に聞きました。

* http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q13101143038

Yahoo!知恵袋で質問している方がいました。ナイス！知恵袋の正しい使い方ですね。

ふむふむ。ROIは`Region of Interes`の略で、指定した矩形領域にしか作用させないようにする機能なのかな？

早速、やってみましょう。

```face_detection2.go
package main

//#cgo pkg-config: opencv
//#include <cv.h>
//#include <highgui.h>
import "C"

import (
	"flag"
	"unsafe"
)

var (
	filePath = flag.String("f", "lena.jpg", "file path")
)

func main() {
	flag.Parse()

	gopherImg := C.cvLoadImage(C.CString("gopher_head.png"), C.CV_LOAD_IMAGE_ANYDEPTH|C.CV_LOAD_IMAGE_ANYCOLOR)
	tarImg := C.cvLoadImage(C.CString(*filePath), C.CV_LOAD_IMAGE_ANYDEPTH|C.CV_LOAD_IMAGE_ANYCOLOR)

	cvHCC := (*C.CvHaarClassifierCascade)(C.cvLoad(C.CString("haarcascade_frontalface_default.xml"), (*C.CvMemStorage)(nil), (*C.char)(nil), (**C.char)(nil)))

	cvMStr := C.cvCreateMemStorage(0)

	face := C.cvHaarDetectObjects(
		unsafe.Pointer(tarImg),
		cvHCC,
		cvMStr,
		1.11,
		3,
		0,
		C.cvSize(0, 0),
		C.cvSize(0, 0),
	)

	for i := C.int(0); i < face.total; i++ {
		faceRect := (*C.CvRect)(unsafe.Pointer(C.cvGetSeqElem(face, i)))
		roi := C.cvRect(
			faceRect.x+faceRect.width/2-gopherImg.width/2,
			faceRect.y+faceRect.height/2-gopherImg.height/2,
			gopherImg.width,
			gopherImg.height,
		)
		C.cvSetImageROI(tarImg, roi)
		C.cvCopy(unsafe.Pointer(gopherImg), unsafe.Pointer(tarImg), unsafe.Pointer(nil))
		C.cvResetImageROI(tarImg)
	}

	C.cvNamedWindow(C.CString("face_detect"), C.CV_WINDOW_AUTOSIZE)
	C.cvShowImage(C.CString("face_detect"), unsafe.Pointer(tarImg))

	C.cvWaitKey(0)

	C.cvDestroyWindow(C.CString("face_detect"))
}
```

変わったのは、Gopher君の画像の読込みと以下の部分です。

```
roi := C.cvRect(
	faceRect.x+faceRect.width/2-gopherImg.width/2,
         faceRect.y+faceRect.height/2-gopherImg.height/2,
         gopherImg.width,
         gopherImg.height,
)
C.cvSetImageROI(tarImg, roi)
C.cvCopy(unsafe.Pointer(gopherImg), unsafe.Pointer(tarImg), unsafe.Pointer(nil))
C.cvResetImageROI(tarImg)
```

顔認識された矩形領域の中心にGopher君の画像の中心が来るようにしています。

実行結果です。

![face_detection2.jpg](https://qiita-image-store.s3.amazonaws.com/0/14952/15ff1304-a4ad-90d1-104a-9fa1f7563c57.jpeg "face_detection2.jpg")

透過処理とか難しいので、省いたらなんか変。

## まとめ
cgoは楽しいですね。opencvをやりたいけど、Cで書きたくない場合はオススメです。しかし、キャストとか`C`付けるのは面倒です。次は動画の顔認識をやってみたいですね。