Go言語で顔認識してみた

December 2, 2013

この記事はQiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。

はじめに

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

OpenCVのインストール

私はMacを使っているので、homebrewで入れました。

$ brew install opencv

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

やり方を探す

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

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

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

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

直にcgoを使う

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

説明の英語には目もくれず、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先生で調べました。

なかなか、良さげな記事を発見しました。静止画の顔認識をやってみます。 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

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

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

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

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

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

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

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

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

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

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

まとめ

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