のどあめ

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

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ライフを送りましょう!

OpenCV 3.0.0 beta で Dense samplingする

さて,先日の記事OpenCV 3.0.0 alpha で Dense samplingする - のどあめを書いたと思ったら,すぐにOpenCV 3.0.0 betaが出てしまいました.
(リリース予定くらいは確認すべきでした)

もちろん,研究室のうちのグループ(の私だけ)は,早速OpenCV 3.0.0 betaに切り替えました.しかし,先日実装したDenseSamplingのコードは,betaではコンパイルが通りません.
そこで,今回は先日の記事と同じようにOpenCV 3.0.0 betaでもDense Samplingの実装をしたいと思います.

Dense Samplingの実装(OpenCV 3.0.0 beta版)

前回のdetectImpl関数をdetectAndComputeに変更します.
detectなどはこの関数を内部で呼んでいるみたいです.
compute関数(DescriptorExtractor的な関数?)をoverrideしてassertを吐かせる方がいい気もしますが,今回は省略.

DenseFeatureDetector.h

#pragma once

#include <opencv2/opencv.hpp>
namespace cv {
	class DenseFeatureDetector : public cv::FeatureDetector {
	public:
		struct Param{
			int xStep = 4;
			int yStep = 4;

			float initFeatureScale = 1.0f;
			int scaleLevels = 1;
			float featureScaleMul = 1.2f;

			int imgBoundX = 0;
			int imgBoundY = 0;

			bool varyXyStepWithScale = true;
			bool varyImgBoundWithScale = false;
			Param(){}
		}param;

		DenseFeatureDetector(){}
		DenseFeatureDetector(const Param& p)
			:param(p)
		{
		}

		static cv::AlgorithmInfo& _info();
		virtual cv::AlgorithmInfo* info()const override;

		virtual void detectAndCompute( 
				cv::InputArray image, cv::InputArray mask,
				std::vector<cv::KeyPoint>& keypoints,
				cv::OutputArray descriptors,
				bool useProvidedKeypoints=false );

		static cv::Ptr<FeatureDetector> create(const Param& p=Param()){
			return cv::Ptr<FeatureDetector>(new DenseFeatureDetector(p));
		}
	};
}

DenseFeatureDetector.cpp

#include "DenseFeatureDetector.h"

namespace cv{
#define CV_INIT_ALGORITHM(classname, algname, memberinit) \
    static inline ::cv::Algorithm* create##classname##_hidden() \
    { \
        return new classname; \
    } \
    \
    static inline ::cv::Ptr< ::cv::Algorithm> create##classname##_ptr_hidden() \
    { \
        return ::cv::makePtr<classname>(); \
    } \
    \
    static inline ::cv::AlgorithmInfo& classname##_info() \
    { \
        static ::cv::AlgorithmInfo classname##_info_var(algname, create##classname##_hidden); \
        return classname##_info_var; \
    } \
    \
    static ::cv::AlgorithmInfo& classname##_info_auto = classname##_info(); \
    \
    ::cv::AlgorithmInfo* classname::info() const \
    { \
        static volatile bool initialized = false; \
        \
        if( !initialized ) \
        { \
            initialized = true; \
            classname obj; \
            memberinit; \
        } \
        return &classname##_info(); \
    }

	CV_INIT_ALGORITHM(DenseFeatureDetector, "Dense",);


	void DenseFeatureDetector::detectAndCompute( 
			cv::InputArray image, cv::InputArray mask,
			std::vector<cv::KeyPoint>& keypoints,
			cv::OutputArray descriptors,
			bool useProvidedKeypoints)
	{
		if (!useProvidedKeypoints) keypoints.clear();


		cv::Mat maskMat = mask.empty() ? cv::Mat(image.size(), CV_8UC1, cv::Scalar(255)) : mask.getMat();
		cv::Mat imageMat = image.getMat();

		int width = imageMat.cols;
		int height = imageMat.rows;

		int nowBoundX = param.imgBoundX;
		int nowBoundY = param.imgBoundY;

		int nowXStep = param.xStep;
		int nowYStep = param.yStep;

		float nowFeatureSize = param.initFeatureScale;

		for (int s = 0; s < param.scaleLevels; s++){
			for (int y = nowBoundY; y < height; y += nowYStep){
				unsigned char* maskline = maskMat.ptr<unsigned char>(y);

				for (int x = nowBoundX; x < width; x += nowYStep){
					if (maskline[x] > 128){
						cv::KeyPoint kp;
						kp.pt = cv::Point(x, y);
						kp.size = nowFeatureSize;
						kp.response = 100.0f;
						keypoints.push_back(kp);
					}
				}
			}

			if (param.varyImgBoundWithScale){
				nowBoundX *= param.featureScaleMul;
				nowBoundY *= param.featureScaleMul;
			}
			if (param.varyXyStepWithScale){
				nowXStep *= param.featureScaleMul;
				nowYStep *= param.featureScaleMul;
			}
			nowFeatureSize *= param.featureScaleMul;
		}
	}
}

基本的には関数名以外変える必要はありません.
これで,先日の記事のコードを動かすことで同様の結果が得られます.

今回のまとめ

  • 大きな仕様変更がなくてよかった.