Go
OpenCV
GoDay 1

Go言語で顔認識してみた

More than 5 years have passed since last update.


はじめに

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

なんかいっぱいエラーでた。

どうやら、省略されている引数があるようです。

あとはキャストに失敗している部分があるようです。

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


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

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

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

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

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


まとめ

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