のどあめ

ググってもなかなか出てこなかった情報を垂れ流すブログ。

開発合宿で作ったアプリをリリース && 地名から緯度経度を取得するAPIの比較

5月の連休中に、友人と温泉旅館でswift開発合宿をしました。
そこで作ったアプリを少しずつ肉付けして、本日ようやくAppleの審査を通過してリリースできました。

作ったアプリ: どこでも展望台

どこでも展望台

どこでも展望台

  • Yohei Iseki
  • ナビゲーション
  • 無料

アプリ説明用画像

f:id:ykicisk:20160611215048p:plain

どこでも展望台について

アプリ「どこでも展望台」は、画像を見てもらえばだいたいわかると思いますが、 登録したランドマークの方角と距離をARで表示するアプリです。

主に展望台あっちに富士山が見えるはず・・・みたいな時に利用することを想定しています。
無料なのでぜひ使ってみてくださいね!

このアプリでは、サイドメニューから地名を入力してランドマークを登録するのですが、 この地名→緯度経度を取得するところで結構右往左往してしまいました。

ということで今回は、どこでも展望台を開発する家庭で得られた 地名から緯度経度を取得するAPIの比較について紹介します。

地名からの緯度経度情報を取得方法の検討

結論から先にいうと、Google Maps APIのPlaces API Web Serviceを使うのが一番よいです。

以下、検討の詳細です。

ジオコーディングと逆ジオコーディング

「地名 緯度経度 取得」みたいなクエリでググると、 ジオコーディングと逆ジオコーディングなるものがあることがわかりました。

ざっくりいうと

  • ジオコーディング: 住所→緯度経度の変換
  • 逆ジオコーディング: 緯度経度→住所の変換

のことです。

今回は、ジオコーディングっぽいですが、住所ではなく地名を直接緯度経度に変換することが目的です。
今回扱う地名→緯度経度の変換の名称は結局わからないままでした。

地名→緯度経度の変換方法

今回は、swiftでiosアプリを作ることを前提としています。また、お金は出したくないです。
この制約の下で使える地名→緯度経度の変換方法について以下の4つを検討しました。
※ 順番は検討した順番

比較した内容

検討では、以下の観点について比較を行いました。
※ 何れも2016/06/09時点の比較です

  1. カバレッジ・精度(いくつかの地名クエリについて、正しい結果が返せるか)
  2. APIの制限(1日何回つかえるか)

また、性質の違いがわかりやすいクエリとして、「東京タワー」「富士山」「六本木一丁目駅」「ランドマークタワー」の結果も記載します。
(結果自体をのせるのはダメっぽいので成功・失敗についてのみ記載)

結果

概要

方法 カバレッジ・精度 APIの制限 備考
Geocoding API 5秒に1回以上のアクセスはNG 1クエリに対して1Result。
レスポンスがやや遅い(3〜5秒)
CLGeocoder 制限はあるが具体的な数値は書いてない -
YOLP コンテンツジオコーダAPI 1日50000回以下(実際は不明?) -
Google Maps API 1日100回。ただしクレカ登録で1日15000回までUP クレカ登録ではお金は発生しない

詳細

Geocoding API

  • APIのトップに仕様・利用規約が書かれています
    • 裏でGoogle Maps APIを叩いている?ので精度はかなり良いようです。
    • 5秒に1回以上のアクセスは禁止しています。

地名→緯度経度の変換例

クエリ 成功 or 失敗 備考
東京タワー -
富士山 -
六本木一丁目駅 -
ランドマークタワー -

CLGeocoder

  • 純正のジオコーダ。基本iphone限定になってしまう。
  • 精度はかなりイマイチな感じ。今後に期待。
  • API制限についてはドキュメントに以下のように記載されています。

    Geocoding requests are rate-limited for each app, so making too many requests in a short period of time may cause some of the requests to fail.

地名→緯度経度の変換例

クエリ 成功 or 失敗 備考
東京タワー -
富士山 -
六本木一丁目駅 麻布十番一丁目がHIT
ランドマークタワー HITしない。
横浜ランドマークタワー」ならHIT

YOLP コンテンツジオコーダAPI

  • 住所検索とランドマークの検索ができる
  • YOLPの「ランドマーク」に定義されていないもの(山など)は取れない?
  • 利用制限については、APIの合計で50000回 && API単体制限回数があるとのことですが、 コンテンツジオコーダ自体のAPI制限についての記載は見当たりませんでした。
クエリ 成功 or 失敗 備考
東京タワー -
富士山 富士山駅がHIT
六本木一丁目駅 六本木駅がHIT。
ランドマークタワー HITしない。
横浜ランドマークタワー」だと横浜駅がHIT

Google Maps API

使用制限は、Google 周辺検索サービスと Google プレイス テキスト検索サービスとで共通ですが、テキスト検索サービスには 10 倍の乗数が適用されます。つまり、テキスト検索リクエスト 1 回で、リクエスト 10 回分の割り当て量を使用することになります。Google Maps API for Work の契約の一部として Google Places API を購入した場合、乗数は異なります。詳しくは、Google Maps API for Work のドキュメントをご覧ください。

クエリ 成功 or 失敗 備考
東京タワー -
富士山 -
六本木一丁目駅 -
ランドマークタワー -

考察

地名から緯度経度情報が欲しい時、最低限の精度を満たすのは、Geocoding APIGoogle Maps APIだと思います。

ただし、Geocoding APIは元データがGoogle Maps APIですし、 大量のAPI利用はできないので、普通にGoogle Maps APIを使っておけば良さそうです。

結論

地名から緯度経度情報がほしい時は、今のところGoogle Maps API一択で良いと思います。

何れの方法を使うにしても、必ず利用規約をよく読んで正しく使いましょう。
私の場合は、Google Maps APIに以下の制限があるのに後で気づいて実装やり直しが何回か発生してしまいました。。

  • 地図表示を使わない場合はpowered by Googleのロゴを表示する
  • 緯度経度はアプリに保存するのはNG(例外あり)
  • 利用規約とプライバシーポリシーにGoogle Map APIを使用していることを明記

MessagePackでgolang-python間でデータ受け渡しする

データをバイナリファイルとして保存すると読み込みが早くて良いですが、 OSや言語が変わると読み込むことができません。

研究室時代には、この問題に対処するためにJSONとバイナリを保存しておいて、 バイナリファイルの読み込みに失敗したらJSONを読み込むという意味不明な実装をしていました。

しかし、最近同僚にMessagePackというものがあることを教えてもらいました。

公式サイトからの引用

MessagePack is an efficient binary serialization format.
It lets you exchange data among multiple languages like JSON. But it's faster and smaller.

つまり、これを使えば万事解決するわけですね。 (同僚いわく、MessagePackを知らないエンジニアはモグリだとか・・・はい。)

今回は、試しにMessagePack(とredis)を使ってgoとpython間でデータのやりとりに挑戦します。

なお、あとで紹介するコードはこちらのgithubにも公開しています。 github.com

準備

データの書き出し先としてredisを使うので、Redisのインストールして起動しておきます。

$ redis-server

また、pythonとgoでMessagePackを扱うパッケージをインストールします。

$ pip install msgpack-python
$ go get gopkg.in/vmihailenco/msgpack.v2

さらに、goでRedisを扱うパッケージを入れます。

$ go get gopkg.in/redis.v3

redigoというパッケージもありますが、 こちらのほうが使いやすいです(個人的に)。名前に惑わされてはいけません。

実装

  • python or goの一方でRedisにList, Mapを書き込む
  • 他方でRedisからList, Mapを受け取る

というプログラムを実装します。

python → go

put.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import msgpack
import redis


if __name__ == "__main__":
    r = redis.StrictRedis(host='localhost', port=6379, db=0)
    # packing
    packed_ls_int = msgpack.packb(range(10))
    packed_ls_float = msgpack.packb([-1.0, 2.02, 5.04])
    packed_dic = msgpack.packb({u'pykey1': u'pyval1', u'pykey2': u'pyval2'})
    # put
    r.set("py_ls_int", packed_ls_int)
    r.set("py_ls_float", packed_ls_float)
    r.set("py_map", packed_dic)

get.go

package main

import (
    "fmt"
    "gopkg.in/redis.v3"
    "gopkg.in/vmihailenco/msgpack.v2"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    redis_client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })
    var unpacked_map map[string]string
    var unpacked_ls_int []int
    var unpacked_ls_float []float64
    {
        msg, msgpack_err := redis_client.Get("py_map").Bytes()
        check(msgpack_err)
        msgpack.Unmarshal(msg, &unpacked_map)
    }
    {
        msg, msgpack_err := redis_client.Get("py_ls_int").Bytes()
        check(msgpack_err)
        msgpack.Unmarshal(msg, &unpacked_ls_int)
    }
    {
        msg, msgpack_err := redis_client.Get("py_ls_float").Bytes()
        check(msgpack_err)
        msgpack.Unmarshal(msg, &unpacked_ls_float)
    }
    fmt.Println(unpacked_map)
    fmt.Println(unpacked_ls_int)
    fmt.Println(unpacked_ls_float)
}

実行結果

$ python put.py
$ go run get.go
map[pykey1:pyval1 pykey2:pyval2]
[0 1 2 3 4 5 6 7 8 9]
[-1 2.02 5.04]

go -> python

put.go

package main

import (
    "gopkg.in/redis.v3"
    "gopkg.in/vmihailenco/msgpack.v2"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    redis_client := redis.NewClient(&redis.Options{
        Addr:     "localhost:6379",
        Password: "", // no password set
        DB:       0,  // use default DB
    })
    dic := map[string]string{"gokey1": "goval1", "gokey2": "goval2"}
    ls_int := []int{1, 3, 5, 8}
    ls_float := []float64{2.3, 6.4, 5.5}
    {
        msg, msgpack_err := msgpack.Marshal(dic)
        check(msgpack_err)
        check(redis_client.Set("go_map", msg, 0).Err())
    }
    {
        msg, msgpack_err := msgpack.Marshal(ls_int)
        check(msgpack_err)
        check(redis_client.Set("go_ls_int", msg, 0).Err())
    }
    {
        msg, msgpack_err := msgpack.Marshal(ls_float)
        check(msgpack_err)
        check(redis_client.Set("go_ls_float", msg, 0).Err())
    }
}

get.py

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import msgpack
import redis


if __name__ == "__main__":
    r = redis.StrictRedis(host='localhost', port=6379, db=0)

    packed = r.get("go_map")
    dic = msgpack.unpackb(packed, encoding='utf-8')
    packed = r.get("go_ls_int")
    ls_int = msgpack.unpackb(packed, encoding='utf-8')
    packed = r.get("go_ls_float")
    ls_float = msgpack.unpackb(packed, encoding='utf-8')

    print dic
    print ls_int
    print ls_float

実行結果

$ go run put.go
$ python get.py
{u'gokey2': u'goval2', u'gokey1': u'goval1'}
[1, 3, 5, 8]
[2.3, 6.4, 5.5]

まとめ

複雑な構造体などはパーサを定義する必要があるみたいですが、 リストやマップ程度であればこのように簡単に受け渡しできます。

試してはいませんが、他言語間でもいい感じに使えるのではないでしょうか。

くれぐれも俺俺フォーマットなんて作らず、MessagePackを使っていきましょう!

golang + anacondaでTwitter Streamimg APIを使う

goのTwitter APIライブラリの一つとして、anacondaがあります。

最近まで(?)Streaming APIには対応されていなかったようですが、 ソースを見る限りすでにStreaming APIに対応するための実装があるようです。

GoDocを見ながら適当に実装してみたらちゃんと動いたので、 今回はその方法について書きます。

準備

$ go get github.com/ChimeraCoder/anaconda

実装

github.com

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "github.com/ChimeraCoder/anaconda"
    "io/ioutil"
)

type ApiConf struct {
    ConsumerKey       string `json:"consumer_key"`
    ConsumerSecret    string `json:"consumer_secret"`
    AccessToken       string `json:"access_token"`
    AccessTokenSecret string `json:"access_token_secret"`
}

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    var apiConf ApiConf
    {
        apiConfPath := flag.String("conf", "config.json", "API Config File")
        flag.Parse()
        data, err_file := ioutil.ReadFile(*apiConfPath)
        check(err_file)
        err_json := json.Unmarshal(data, &apiConf)
        check(err_json)
    }

    anaconda.SetConsumerKey(apiConf.ConsumerKey)
    anaconda.SetConsumerSecret(apiConf.ConsumerSecret)
    api := anaconda.NewTwitterApi(apiConf.AccessToken, apiConf.AccessTokenSecret)

    twitterStream := api.PublicStreamSample(nil)
    for {
        x := <-twitterStream.C
        switch tweet := x.(type) {
        case anaconda.Tweet:
            fmt.Println(tweet.Text)
            fmt.Println("-----------")
        case anaconda.StatusDeletionNotice:
            // pass
        default:
            fmt.Printf("unknown type(%T) : %v \n", x, x)
        }
    }
}

簡単に説明すると、twitterStream.Cからどんどんツイートを取得して表示するだけです。

twitterStream.Canaconda.Tweet型(ツイート本体)と anaconda.StatusDeletionNotice型(ツイート消されてますNotice?)が流れてきます。 消されたツイートはどうでもよいので今回はスルーしています。

goを使うとJsonパースが楽なのも良いですね。

実行

$ go run main.go -conf=config.json

追記:環境によっては-conf="config.json"とする必要があるそうです

世界中のツイート(の一部)がどんどん流れてきます。 これを使って何かおもしろいことができればよいですね!

OpenCV 3.0で人物追跡したい!

最近社内でライトニングトークなるものがありました。 そのうち画像を使う機会がありそうで復習したかった半分、 OpenCV3.0使ってみたかった半分で単カメラの人物追跡をテーマに軽く解説をしてきました。

研究室時代ではいろんな論文の実装を組み込んで何とか実装していた単カメラ人物追跡ですが、 OpenCV3.0を使えば、100行くらいで人物追跡できます。素晴らしい限りです。

今回は、その実装について説明したいと思います。

ただし、以下の点には注意です。

  • 理論などはすべて省略します
  • 高速化は考えません(本当は大事)
  • 使っている技術は古いです(2005-2006くらいの技術)

用いた環境

  • C++11
  • OpenCV 3.0
  • boost 1.58 (ファイル読み込みのみ)

人物追跡

ここでいう人物追跡とは、単一のカメラ映像に写る人物の軌跡を取得することです。 人物検出をして、その結果を追跡することで実現できます。

人物検出

画像の中から人物の矩形を取得します。

OpenCVでは、cv::HOGDescriptor::getDefaultPeopleDetectorというズバリなものが用意されていますので、 今回はこれを利用します。

(人物)追跡

人物検出の結果を起点に、そのものが映像中に動いた奇跡を追跡します。 (人物以外でも使えます)

OpenCVでは、cv::Trackerというインタフェースが用意されていますので、 今回はこれを利用します。

実装

実装はこんな感じです。 ykicisk/tracking · GitHub

#include <iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/tracking/tracker.hpp>
#include <boost/filesystem.hpp>

const cv::Size MAX_DETECT_SIZE = cv::Size(100, 200);
const int MAX_MISS_FRAME = 10;
const double MIN_NEW_DETECT_INTERSECTION_RATE = 0.5;

class MyTracker {
private:
    static int next_id;
    int id;
    int n_miss_frame = 0;
    cv::Rect2d rect;
    cv::Ptr<cv::Tracker> cv_tracker;
public:
    // フレーム画像と追跡対象(Rect)で初期化
    MyTracker(const cv::Mat& _frame, const cv::Rect2d& _rect) 
        : id(next_id++), rect(_rect) {
        cv_tracker = cv::Tracker::create("BOOSTING"); //  or "MIL"
        cv_tracker->init(_frame, _rect);
    }
    // 次フレームを入力にして、追跡対象の追跡(true)
    // MAX_MISS_FRAME以上検出が登録されていない場合は追跡失敗(false)
    bool update(const cv::Mat& _frame){
        n_miss_frame++;
        return cv_tracker->update(_frame, rect) && n_miss_frame < MAX_MISS_FRAME;
    }
    // 新しい検出(Rect)を登録。現在位置と近ければ受理してn_miss_frameをリセット(true)
    // そうでなければ(false)
    bool registerNewDetect(const cv::Rect2d& _new_detect){
        double intersection_rate = 1.0 * (_new_detect & rect).area() / (_new_detect | rect).area();
        bool is_registered = intersection_rate > MIN_NEW_DETECT_INTERSECTION_RATE;
        if (is_registered) n_miss_frame = 0;
        return is_registered;
    }
    // trackerの現在地を_imageに書き込む
    void draw(cv::Mat& _image) const{
        cv::rectangle(_image, rect, cv::Scalar(255, 0, 0), 2, 1);
        cv::putText(_image, cv::format("%03d", id), cv::Point(rect.x + 5, rect.y + 17), 
                cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255,0,0), 1, CV_AA);
    }
};
int MyTracker::next_id = 0;


int main(int argc, char* argv[]){
    if(argc != 2){
        std::cout << "usage: " << argv[0] << " videodir" << std::endl;
        exit(1);
    }
    // フレーム画像列のパスを取得
    namespace fs = boost::filesystem;
    std::vector<std::string> frame_paths;
    for(auto it = fs::directory_iterator(argv[1]); it != fs::directory_iterator(); ++it){
        frame_paths.push_back(it->path().string());
    }
    // detector, trackerの宣言
    cv::HOGDescriptor detector;
    detector.setSVMDetector(cv::HOGDescriptor::getDefaultPeopleDetector());
    std::vector<MyTracker> trackers;
    // 1フレームずつループ
    for (auto& frame_path : frame_paths){
        std::cout << "frame : " << frame_path << std::endl;
        cv::Mat frame = cv::imread(frame_path);
        // 人物検出
        std::vector<cv::Rect> detections;
        detector.detectMultiScale(frame, detections);
        // trackerの更新(追跡に失敗したら削除)
        for (auto t_it = trackers.begin(); t_it != trackers.end();){
            t_it = (t_it->update(frame)) ? std::next(t_it) : trackers.erase(t_it);
        }
        // 新しい検出があればそれを起点にtrackerを作成。(既存Trackerに重なる検出は無視)
        for(auto& d_rect : detections){
            if (d_rect.size().area() > MAX_DETECT_SIZE.area()) continue;
            bool exists = std::any_of(trackers.begin(), trackers.end(), 
                    [&d_rect](MyTracker& t){return t.registerNewDetect(d_rect);});
            if(!exists) trackers.push_back(MyTracker(frame, d_rect));
        }
        // 人物追跡と人物検出の結果を表示
        cv::Mat image = frame.clone();
        for(auto& t : trackers) t.draw(image);
        for(auto& d_rect : detections) cv::rectangle(image, d_rect, cv::Scalar(0, 255, 0), 2, 1);
        cv::imshow("demo", image);
        cv::waitKey(1);
    }
    return 0;
}

動画のフレームごとに人物検出と追跡を繰り返して実現します。

そのままだとTrackerがどんどん増えていくので、 Trackerと重なる検出がでてこなくなったTrackerは消すようにしています。

実験

PETS2009 S2.L1のView001で使って動かしてみました。

緑が人物検出の結果、青が追跡結果です。

考察

思ったよりイマイチですね。

人物検出の誤検出・未検出について

OpenCV3.0のdetectMultiScaleを使った人物検出ではこれが限界だと思います。

もっとリッチな人物検出手法をつかったり、 シーンの知識(どの当たりにどんな大きさの人が写るなど)を利用すればもっと検出がよくなると思います。

追跡ミスについて

汎用追跡器を使ったこともありますが、オクルージョンにめちゃくちゃ弱いです。 真ん中のポールを通過した人はもれなくおかしなことになっていますね。

これに解決するためには、もっと人物に特化した追跡器を使ったり、 関連付けられた人物画像を一旦バラバラにして最適化で同一人物をくっつけなおしたりすると良いと思います。

まとめ

  • OpenCV3.0を使えば簡単に単カメラ人物追跡できる
  • 結果はいまいち。実際のカメラは更にいろいろあるので更におかしい事が起こる
  • 人物追跡にかぎらず、この辺りの分野は闇が深い

次回があれば、今回取得した人物画像列について、 複数カメラでの対応付けを行う方法について説明します。

でも次回はありません。

Web画像検索で画像を収集する

沢山の画像を利用して何やらしたい時があります.
たいていはImageNetやFlickrで収集すれば良いのですが, 時には普通のWebで画像検索した結果を利用したいこともあります.

以前に,Web画像検索した結果を収集したい時の最適解はなんだろうと検討したことがありましたので, その結果をメモついでに記事にしました.

画像収集に利用するサービスの検討

Google画像検索API

制限が厳しくなり,同じクエリに対して10枚×10ページの合計100枚までしか取得できないようです. 画像を収集するという意味では使いにくいですね.

基本的な使い方は

Bing Search API

同じクエリでも1000件以上収集できます(上限は確認していません).
無料枠でも月5000回Getできるので画像収集として使えますね.
今回はBing Search APIが最適解という結論に至りました.

API利用までの流れなどは以下の記事で詳しく紹介されています.
Bing Search APIを使ってWeb検索を行うには(Json編) | garicchi.com

画像収集スクリプト(Python)

今回はWindowsで利用したのでcp932とか書いてますが, 違う環境の場合は適宜修正してください.

# coding: utf-8
import sys, os
import argparse
import json
import urllib
import urllib2
import requests

#proxy setting
proxy_dict =  {"http":"your.proxy:8080"}
proxy = urllib2.ProxyHandler(proxy_dict)
opener = urllib2.build_opener(proxy)
urllib2.install_opener(opener)

#primary account key
api_key="*************************************"

def download_urllist(urllist,outpath):
    for url in urllist:
        try:
            print"url:"+url
            with open(outpath+"/"+os.path.basename(url),'wb') as f:
                img=urllib2.urlopen(url,timeout=5).read()
                f.write(img)
        except urllib2.URLError:
            print("URLError")
        except IOError:
            print("IOError")
        except UnicodeEncodeError:
            print("EncodeError")
        except OSError:
            print("OSError")

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("-q","--query", required=True, 
            help="query")
    parser.add_argument("-o","--outpath", required=True,
            help="output path")
    args = parser.parse_args()


    #Bing Image Search
    query = args.query.decode('cp932').encode('utf-8')
    query = urllib2.quote(query)

    step = 20
    num = 50
    
    url_param_dict={
            "Query":"'%s'"%(query),
            "Market":"'ja-JP'",
            "Adult":"'Off'",
            }
    url_param_base = urllib.urlencode(url_param_dict)
    url_param_base = url_param_base + "&$format=json&$top=%d&$skip="%(num)


    for skip in range(0,num*step,num):
        url_param = url_param_base + str(skip)
        url="https://api.datamarket.azure.com/Bing/Search/Image?"+url_param
        print url

        response = requests.get(url, 
                            auth=(api_key, api_key), 
                            headers={'User-Agent': 'My API Robot'},
                            proxies=proxy_dict)
        response=response.json()

        urllist=[item['MediaUrl'] for item in response['d']['results']]
        download_urllist(urllist,args.outpath)

画像収集スクリプトは以下の記事を参考にしました.
Bing Search API を使いたいと思ったのでPythonでラッパーを作ってみた - [[ともっくす alloc] init]

使い方は

python collect_images.py -q Query -o outputdir

です.

-qで指定したクエリについて,step*num件の画像をダウンロードします.
ダウンロードした結果にはゴミも多いので,いい感じにクリーニングして利用しましょう.

OpenCV 3.0 で cvSegmentFGMask

OpenCV 3.0.0 では,IplImage,legacy関数などの1.x系のサポートがなくなっています.
つまりcvHogehogeみたいな関数は使えません.
たいていの機能はcv::Hogehogeみたいな関数が実装されているのですが,一部の関数は代替関数がなくて残念なことになる場合があります.

私の環境でも,一部のプログラムで使っていたcvSegmentFGMaskが使えなくなり,困っていました.
cvSegmentFGMaskは,ノイズっぽいmask画像をいい感じにまとめてくれる関数です.

この度cvSegmentFGMaskをOpenCV 3.0で使えるように1.x系のソースを参考にして2.x系で実装しました.

cvSegmentFGMaskの2.0系実装

cvSegmentFGMask.cpp

#include <opencv2/opencv.hpp>

namespace cv {
  void SegmentFGMask(InputArray src, OutputArray dst, bool poly1Hull0, float perimScale, Point offset=Point(0,0))
  {
    Mat mask = src.getMat().clone();
    if(mask.type() != CV_8UC1) throw std::runtime_error("src.type() != CV_8UC1");

    Mat new_mask = Mat::zeros(mask.size(), mask.type());

    std::vector<std::vector<Point>> contours;
    findContours(mask, contours,RETR_EXTERNAL, CHAIN_APPROX_SIMPLE, offset);

    const Scalar white(255);
    for (int i = 0; i < contours.size(); i++){
      double len = cv::arcLength(contours[i], true);
      double q = (mask.rows + mask.cols) / perimScale; 

      if (len >= q){
        std::vector<std::vector<Point>> tmp_contours(1);
        if (poly1Hull0){
          cv::approxPolyDP(contours[i], tmp_contours[0], 2.0, true);
        } else{
          tmp_contours[0] = contours[i];
        }
        drawContours(new_mask, tmp_contours, 0, white, -1, 8,cv::noArray(),-1,Point(-offset.x,-offset.y));
      }
    }
    new_mask.copyTo(dst);
  }
}

int main(){
  cv::VideoCapture video("./WalkByShop1cor.mpg");
  cv::BackgroundSubtractorMOG bgs;

  while(1){
    cv::Mat image;
    video >> image;

    if(image.empty()) break;
    cv::Mat mask;
    bgs(image,mask);
    cv::Mat segmented_mask;
    cv::SegmentFGMask(mask,segmented_mask,true,16.0f);
    {
      cv::Mat tmp;
      cv::hconcat(mask,segmented_mask,tmp);
      cv::imshow("mask",tmp);
      char k = cv::waitKey(1);
      if(k == 'q') break;
    }
  }
  return 0;
}

結果

左が普通の背景差分.右がcv::SegmentFGMaskをかけたものです.
f:id:ykicisk:20150203000821p:plain

今回のまとめ

必要がなければOpenCV 2.x 系に戻しましょう.

VisualStudioの理不尽な問題(の一部)に対処する

私は普段のプログラミング(C++)でVisual Studioを良く使っています.
なんだかんだ言ってもIntelliSenseや高機能なデバッガは非常に便利です.今後Visual Studioオープンソース化するということなので,楽しみであります.
参考:Microsoft takes .NET open source and cross-platform, adds new development capabilities with Visual Studio 2015, .NET 2015 and Visual Studio Online | News Center

しかし,Visual Studioを使っていると時々理不尽な問題にぶち当たり,どうしていいかわからなくなる時があります.
最近こうした問題について後輩から何度か質問されたので,その辺りをまとめました.
今回対象とするのは最新版のVisual Studio2013のみですが,古いバージョンでも同じようなことが可能かもしれません.

Visual Studioの理不尽な問題

今回扱うVisual Studioの理不尽な問題は以下のとおりです.

以上3点です.これは酷い・・・
VisualStudioを使い始めでこのような問題にぶち当たったら投げ出す人が多いと思います.

いつの間にかIntelliSenseが効かなっている

特にActive Directoryを導入した環境でVisual Studioを使っている時に良く発生する気がします.こちらに関しては,フォールバック位置を変更することで対処する事ができる場合があります.

オプション→テキスト エディター→C/C++→詳細について,
常にフォールバック位置を使用を「True」
フォールバック位置をC直下などActive Directoryの管理下出ないフォルダに指定すればOKです.
指定したあとは,Visual Studioを再起動する必要があります.

また,最初からプロジェクトをローカルに作ってしまっても大丈夫だと思います.

ソースコードが元のバージョンと異なります」と言われてデバッグ時にブレークポイントで止まらない

こちら(Visual Studioでブレーク出来ない問題)のページによると,昔からある厄介なバグのようです.こちらはデバッグ情報の形式を変更することで対処することが出来る場合があります.

プロジェクトのプロパティ→C++→全般について
デバッグ情報の形式を「C7 互換 (/Z7)」に変更します.

理由はよくわかりませんが,これで大抵治ります.

ソースコードを編集したのにデバッグ時に反映されない

これに関しては,「最小リビルドを有効にする」というコンパイルオプションが悪さをしている場合があります.

プロジェクトのプロパティ→C++→コード生成について
最小リビルドを有効にするを「いいえ (/Gm-)」にします.

今回のまとめ

今回はVisual Studioの理不尽な問題(の一部)の解決法について紹介しました.
他にも,#includeをするときに<だと補完されるのにダブルクオーテーションだと補完されない等いろいろ細かい問題もありますが,それらについてはまた機会があれば紹介したいと思います.

文句をタラタラ言っていますが,最近はPythonを書くこともできますし,なんだかんだ言ってもVisual Studioは便利なツールです.
ぜひ使いこなして快適なVisual Studioライフを送りましょう!