Hatena::Groupcoders

ラシウラ出張所 このページをアンテナに追加 RSSフィード

2007/10/22

[][] すごく遅まきながらOpenCVを使ってみた  すごく遅まきながらOpenCVを使ってみた - ラシウラ出張所 を含むブックマーク はてなブックマーク -  すごく遅まきながらOpenCVを使ってみた - ラシウラ出張所  すごく遅まきながらOpenCVを使ってみた - ラシウラ出張所 のブックマークコメント

しかも動くが(OpenCVの使い方が)完璧理解できてないという状態。

デフォルトPythonインタフェースが入ってるのはうれしいですが、使い方がよくわからないのが難しかった。とくにイメージ間操作が。

OpenCVが出たとき作られてた顔にイメージを当てるcgiを書いてみた。とりあえず、sample/python/facedetect.pyを元に、image2次元配列っぽく操作できるところまではできた。


#!/usr/bin/python

from wsgiref.handlers import CGIHandler
import os.path as path
import urllib2
import hashlib
import opencv.cv as cv
import opencv.highgui as highgui

class OpenCVGateway:
    def __call__(self, env, res):
        url = env["QUERY_STRING"]
        remote = urllib2.urlopen(url)
        content_type = remote.info().get("content-type")

        if not content_type.startswith("image/"):
            #return self._transfer_data(res, remote, content_type)
            return self._redirect(res, url)

        imagename = self._image_name(url, content_type)
        cachename = self._cache_name(imagename)
        resultname = self._converted_name(imagename)

        if not path.exists(resultname):
            if not path.exists(cachename):
                self._make_cache(cachename, remote)
                pass
            self._convert(cachename, resultname)
            pass

        #return self._transfer_image(res, resultname, content_type)
        redirect_url = self._redirect_url(env, resultname)
        return self._redirect(res, redirect_url)

   def _convert(self, cachename, resultname):
        # from sample/python/facedetect.py
        image_scale = 1.3
        storage = cv.cvCreateMemStorage(0)
        cascade_name = "haarcascade_frontalface_alt.xml"
        cascade = cv.cvLoadHaarClassifierCascade(cascade_name, cv.cvSize(1, 1))
        min_size = cv.cvSize(20, 20)
        haar_scale = 1.2
        min_neighbors = 2
        haar_flags = 0

        image = highgui.cvLoadImage(cachename)
        stampimage = highgui.cvLoadImage("stamp.png")
        maskimage = highgui.cvLoadImage("stamp.png", 0)
        modified = cv.cvCreateImage(cv.cvSize(image.width, image.height),
                                    image.depth, image.nChannels)
        cv.cvCopy(image, modified)

        gray = cv.cvCreateImage(cv.cvSize(image.width, image.height), 8, 1)
        small = cv.cvCreateImage(cv.cvSize(
            cv.cvRound(image.width / image_scale),
            cv.cvRound(image.height / image_scale)), 8, 1)

        cv.cvCvtColor(image, gray, cv.CV_BGR2GRAY)
        cv.cvResize(gray, small, cv.CV_INTER_LINEAR)
        cv.cvEqualizeHist(small, small)
        cv.cvClearMemStorage(storage)

        faces = cv.cvHaarDetectObjects(small, cascade, storage,
                                       haar_scale, min_neighbors, haar_flags,
                                       min_size)

        if not faces:
            highgui.cvSaveImage(resultname, image)
            return
        for rect in faces:
            top_left = cv.cvPoint(int(rect.x * image_scale),
                                  int(rect.y * image_scale))
            bottom_right = cv.cvPoint(int((rect.x + rect.width) * image_scale),
                                      int((rect.y + rect.height) * image_scale))


            # draw red rect around the face
            #cv.cvRectangle(modified, top_left, bottom_right,
            #               cv.CV_RGB(255, 0, 0), 3, 8, 0)

            # draw smaller image over the face
            stamp = cv.cvCreateImage(cv.cvSize(int(rect.width * image_scale),
                                               int(rect.height * image_scale)),
                                     stampimage.depth, stampimage.nChannels)
            cv.cvResize(stampimage, stamp, cv.CV_INTER_LINEAR)
            mask = cv.cvCreateImage(cv.cvSize(int(rect.width * image_scale),
                                              int(rect.height * image_scale)),
                                     maskimage.depth, maskimage.nChannels)
            cv.cvResize(maskimage, mask, cv.CV_INTER_LINEAR)

            # how to use cvCopy?
            for i in xrange(stamp.width):
                for j in xrange(stamp.height):
                    if mask[i, j] == 0:
                        continue
                    modified[top_left.x + i, top_left.y + j] = stamp[i, j]
                    pass
                pass
            pass

        highgui.cvSaveImage(resultname, modified)
        pass
    
    def _image_name(self, url, content_type):
        format = content_type[len("image/"):]
        filename = hashlib.sha1(url).hexdigest()
        return "%s.%s" % (filename, format)

    def _cache_name(self, image_name):
        return "cache/%s" % (image_name,)

    def _converted_name(self, image_name):
        return "converted/%s" % (image_name,)

    def _make_cache(self, filename, remote):
        cache = open(filename, "w")
        for line in remote.readlines():
            cache.write(line)
            pass
        cache.close()
        pass

    def _redirect_url(self, env, file):
        scheme = env["wsgi.url_scheme"]
        host = env["HTTP_HOST"]
        port = env["SERVER_PORT"]
        dirname = path.dirname(env["SCRIPT_NAME"])
        return "%s://%s:%s%s/%s" % (scheme, host, port, dirname, file)

    def _redirect(self, res, url):
        res("307 temporary redirect", [("Location", url)])
        return []

    # obsoleted
    def _transfer_data(self, res, fp, content_type):
        res("200 OK", [("Content-Type", content_type)])
        for line in fp.readlines():
            yield line
            pass
        pass

    # obsoleted
    def _transfer_image(self, res, filename, content_type):
        fp = open(filename)
        return self._transfer_data(res, fp, content_type)
    pass

CGIHandler().run(OpenCVGateway())

このcgiのほかに、cacheとconvertedのディレクトリ、スタンプ用のstamp.pngと、OpenCV付属のhaarcascade_frontalface_alt.xmlも必要。

長いけど、OpenCV部分は_convertメソッド内で閉じてます。ほかはWSGIの処理で、その中でURLの?以降の文字列をそのままリソースをとってきて、画像でなければそこにリダイレクト画像なら保存して変換、変換したファイルリダイレクトしてます。

_convert部分は、facedetectほぼそのまま、なぜscale変換をしてるのかもよくわかってない。検出できた顔部分のrectから、stampをそのサイズにリサイズするまではいいが、それを埋め込むときどうするかで悩む。もしかして行列操作なのかな。