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
はいつものお姉さんです。
実行してみる。
ふむ。何か物足りない。そうだ、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君の画像の中心が来るようにしています。
実行結果です。
透過処理とか難しいので、省いたらなんか変。
まとめ
cgoは楽しいですね。opencvをやりたいけど、Cで書きたくない場合はオススメです。しかし、キャストとかC
付けるのは面倒です。次は動画の顔認識をやってみたいですね。