Help us understand the problem. What is going on with this article?

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付けるのは面倒です。次は動画の顔認識をやってみたいですね。

tenntenn
Go engineer / Gopher artist
mercari
フリマアプリ「メルカリ」を、グローバルで開発しています。
https://tech.mercari.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away