Miyabiarts.net

一年に一度の更新頻度

カテゴリーアーカイブ: Programming

Panoramioからの写真収集

写真共有サイトPanoramio(http://www.panoramio.com/)は、ユーザがアップロードした写真を地図(Google Maps)上に重畳して表示するサービスです。
APIが用意されており、プログラムからアクセスすることが出来るため、今回はPythonを用いて写真を収集するコードを掲載します。
なお、基本的にLinuxを想定しており、画像のダウンロードにはwgetが必要となります。

Panoramioでは、APIに任意の範囲(経度・緯度)を与えることにより、その範囲内の画像を収集することができます。
ただし、全世界を含むような範囲を指定したら、全ての写真を収集できるというわけでなく、指定した範囲の大きさに応じてある程度収集する写真が選別されます。

下のコードでは、output_jsonに写真のメタデータをJSON形式で保存します。
メタデータには、写真のタイトルや写真が撮影された経度・緯度情報が保存されています。
また、output_image_dirに写真そのものをダウンロードすることができます。
各写真のファイル名は、写真のIDとなり、メタデータと対応付いています。
これらの保存先の変数と、経度・緯度範囲を示すminx、maxx、miny、maxyを指定してやることにより写真を収集します。
今回のコードでは、名古屋周辺をデフォルトの値として指定しています。


#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib
import json
import os

# 出力パラメータ
# 写真データ(JSON形式)
output_json = 'output.json'
# 写真(画像)を収集するかのフラグ
isCollectImage = True
# 写真(画像)の出力先ディレクトリ
output_image_dir = './images/'


# 収集パラメータの設定
# 収集する写真の種類(public/full/ユーザID)
type = 'public'
# 収集する範囲を設定
# 経度
minx = 136.56143
maxx = 137.38540

# 緯度
miny = 34.85550
maxy = 35.52887


# 写真のサイズ(small/medium/large)
size = 'medium'
# 収集するループ1回の写真の収集枚数(写真を分割収集するため)
span = 100


# 緯度・経度の大小関係を合わせる
if minx > maxx:
    minx, maxx = maxx, minx

if miny > maxy:
    miny, maxy = maxy, miny

# panoramioのURL
url = 'http://www.panoramio.com/map/get_panoramas.php'

# 収集結果
photos = []

i = 0
while True:
    # パラメータを設定
    params = urllib.urlencode( { 'set' : type,
                                 'minx' : minx,
                                 'miny' : miny,
                                 'maxx' : maxx,
                                 'maxy' : maxy,
                                 'size' : size,
                                 'from' : span * i,
                                 'to' : span * ( i + 1 ) } )

    # panoramioにリクエスト
    f = urllib.urlopen( url + '?' +  params )
    buf = f.read()
    j = json.loads( buf )

    photos += j[ 'photos' ]

    if j[ 'has_more' ] == False:
        break

    i += 1

# JSON形式でファイル出力
fp = open( output_json, 'w' )
fp.write( str( photos ) )
fp.close()

# 画像を収集
if isCollectImage == True:
    for photo in photos:
        print photo[ 'photo_url' ]
        cmd = 'wget ' + photo['photo_file_url' ] + ' -O ' + output_image_dir + '/' + str( photo['photo_id'] ) + '.jpg'
        os.system( cmd )

おまけ:写真のテキストタグの収集

以上のコードで、写真、およびメタデータを収集することができるのですが、写真に付随しているテキストタグは収集することができません。
そのため、無理やりですが、写真のIDを用いて、写真が掲載されているWebページのHTMLを解析することでテキストタグを取得するコードを示します。
下のコードでは、関数宣言のみなので、実際に使うときには呼び出すようにしてください。
また、HTML解析にはBeautifulSoupを用いているため、必要に応じてインストールしてください。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib
from BeautifulSoup import BeautifulSoup

# idの写真のテキストタグを取得
def get_tag( id ):
    # panoramioの写真ページアクセス
    # HTMLを取得
    url = 'http://www.panoramio.com/photo/' + str( id )
    f = urllib.urlopen( url )

    # HTML解析
    soup = BeautifulSoup( f.read() )

    # タグを取得
    taglst = []
    tags = soup.findAll( 'ul', {'id':'tags'} )
    for tag in tags:
        for t in tag.findAll( 'a' ):
            if t[ 'href' ] != '#':
                taglst.append(  t.contents[ 0 ].strip() )
    return taglst

以上がPanoramioからの写真収集となります。
気をつけることとして、一度に大量の写真を取得しようとして、Paranomioに負荷をかけ過ぎにようにしてください。

広告

OpenCVを用いた特徴点対応付け

OpenCV2.2になって、C++インタフェースが随分整理されましたので、色々と触っていました。
主に特徴点周りのクラスを見ていたのですが、画像から特徴点を検出し、画像間で対応付けるコードを簡単に書けるようになりました。
以下に2画像間の特徴点の対応付けのコードを掲載します。

#include <opencv2/opencv.hpp>
//
int main()
{
	// 画像1の読み込み
	cv::Mat_<cv::Vec3b> img1 = cv::imread( "in1.jpg" );

	// 画像2の読み込み
	cv::Mat_<cv::Vec3b> img2 = cv::imread( "in2.jpg" );

	// SIFT用のデフォルトパラメータを設定
	double threshold = cv::SIFT::DetectorParams::GET_DEFAULT_THRESHOLD();
	double edge_threshold = cv::SIFT::DetectorParams::GET_DEFAULT_EDGE_THRESHOLD();
	double magnification = cv::SIFT::DescriptorParams::GET_DEFAULT_MAGNIFICATION();

	// SIFTの特徴点検出・特徴量抽出器を用意
	cv::SiftFeatureDetector detector( threshold, edge_threshold );
	cv::SiftDescriptorExtractor extractor( magnification );
	/*
	// SURFの場合
	cv::SurfFeatureDetector detector();
	cv::SurfDescriptorExtractor extractor();
	*/

	// 画像1から特徴点を検出
	std::vector<cv::KeyPoint> keypoints1;
	detector.detect( img1, keypoints1 );

	// 画像2から特徴点を検出
	std::vector<cv::KeyPoint> keypoints2;
	detector.detect( img2, keypoints2 );

	// 画像1の特徴点における特徴量を抽出
	cv::Mat descriptor1;
	extractor.compute( img1, keypoints1, descriptor1 );

	// 画像2の特徴点における特徴量を抽出
	cv::Mat descriptor2;
	extractor.compute( img2, keypoints2, descriptor2 );

	// 対応点を求める
	std::vector<cv::DMatch> matches;

	// L2距離を用いた全探索対応付け
	cv::BruteForceMatcher< cv::L2 > matcher;
	// Flannベースの対応付け
//	cv::FlannBasedMatcher matcher;

	// 特徴量比較による特徴点の対応付け
	matcher.match( descriptor1, descriptor2, matches );

	// 結果を描画
	cv::Mat_<cv::Vec3b> result;
	cv::drawMatches( img1, keypoints1, img2, keypoints2, matches, result );

	// 結果を出力
	cv::imwrite( "result.jpg", result );

	return 0;
}

といった感じで、簡単なコードだと思いませんか?

簡単にやっていることを説明します。
各画像から、SIFTにより特徴点を検出し、その特徴量を抽出します。
特徴点の検出は、cv::FeatureDetectorを継承したcv::SiftFeatureDectetorを、特徴量の抽出は、cv::DescriptorExtractorを継承したcv::SiftDescriptorExtractorを用いることでそれぞれ行うことが出来ます。
コード中のコメントを入れ替えれば、SIFTからSURFに簡単に切り替えることもできます。
その他の特徴点検出・特徴量抽出手法についても同じです。
そして、特徴量間の対応付けを行います。
こちらも対応付けに用いる手法(cv::DescriptorMatcher)を、定義するクラスで簡単に切り替えることができます。
今回は、L2距離基準を用いた全探索対応付けを行うcv::BruteForceMatcherを用いています。
最後に、特徴点とその対応をcv::drawMatchesにより描画することで、結果を得ることができます。

テスト画像

テスト画像は、以下の2種類を用いています。
左側が原画像で、右側は原画像を45度回転しています。
これらの画像の特徴点対応付けを行います。



特徴点対応付け結果

下の画像がそれぞれの対応付け結果です。
各画像中の円で表されるものが特徴点で、特徴点間に引かれている線分が特徴点の対応を示しています。
1つ目の画像は、特徴点自体があまり出ておらず、対応付けもそれほど精度良くないようです。
2つ目の画像は、特徴点が多く検出され、誤対応もありますが、大半は精度良く対応付いています。

特徴点対応付けの確認

特徴点の対応付けが正しく行われているか、同じ画像同士を対応付けることにより確認しております。
下の画像を見れば、同じ特徴点が対応付いている(対応を表す線が平行である)ことが分かります。

というわけで、随分簡単に特徴点対応付けができるようになりました。
もちろん精度などの問題も残されているのでパラメータなどの調整が必要となってきます。

分からないことや不明なことがあれば、コメントください。

OpenCVでの画素へのアクセス方法

tsunotterで、

「OpenCVで任意のピクセルの画素値に直接どうやってアクセスする? #opencv」

という質問があったので、私のOpenCVでの画素へのアクセス方法を書いておきます。

私は、基本的にOpenCVはC++インタフェースを使っているので、画像はcv::Mat_で扱っています。
画素へのアクセスは、以下の2つの方法を使い分けており、普段は1つ目で、処理を速くしたい場合は2つ目の方法を用いています。
サンプルコードでは、400×400のRGB画像の全画素を赤くするもので、やっていることはcv::Fillと同じです。

任意の画素へのアクセス

単純にcv::Mat_::operator()( y, x )を用いて、画素にアクセスする手法です。
xとyが逆にならないように気をつけてください。
また、cols=width、rows=heightとなっています。

#include <opencv2/opencv.hpp>

int main()
{
  // 400x400 RGB画像を作成
  cv::Mat_<cv::Vec3b> img( cvCreateImage( cvSize( 400, 400 ), IPL_DEPTH_8U, 3 ) );

  // 全画素を赤くする
  for( int y = 0 ; y < img.rows ; ++y )
    {
      for( int x = 0 ; x < img.cols ; ++x )
	{
          // 画素へアクセス
	  img( y, x )[ 0 ] = 0;
	  img( y, x )[ 1 ] = 0;
	  img( y, x )[ 2 ] = 0xff;
	}
    }
  // 画像出力
  cv::imwrite( "result.bmp", img );
  return 0;
}

当然ですが、cv::Vec3bをunsigned charに変えることで、グレースケール画像も扱えます。

任意の画素へのアクセス(高速版)

1つ目の方法だと、1つの画素にアクセスするたびにcv::Mat_::operator ( y, x )を呼び出すオーバヘッドがかかるため、ポインタ操作でなるべくオーバヘッドを減らす方法です。
スキャンラインで画像を走査する際に、スキャンラインの先頭画素のポインタを取得した上で、後はポインタのインクリメンタルによりアクセスする画素を進めます。
気をつけることは、ポインタを進めすぎるとバッファ・オーバランが起こるので、アクセス範囲には十分気をつけてください。

#include <opencv2/opencv.hpp>;

int main()
{
  // 400x400 RGB画像を作成
  cv::Mat_<cv::Vec3> img( cvCreateImage( cvSize( 400, 400 ), IPL_DEPTH_8U, 3 ) );

  // 全画素を赤くする
  for( int y = 0 ; y < img.rows ; ++y )
    {
      // 先頭の画素のポインタを取得
      cv::Vec3b *p = &img( y, 0 );
      for( int x = 0 ; x < img.cols ; ++x )
	{
           // 画素へアクセス
	  ( *p )[ 0 ] = 0;
	  ( *p )[ 1 ] = 0;
	  ( *p )[ 2 ] = 0xff;
     // 次の画素へ進める
	  p++;
	}
    }
  // 画像出力
  cv::imwrite( "result.bmp", img );
  return 0;
}

cv::Vec3bをunsigned charに変えることで、グレースケール画像も扱えます。
このとき、画素へのアクセスに用いるポインタは型情報も持っているため、ポインタのインクリメンタルで画素単位で進めることができ、1画素が何バイトで構成されているかを意識しないで良いので楽ですし、間違いが少なくなります。

他にもやり方は色々ありますが、私は以上の方法で画素にアクセスすることが多いです。

特徴点検出・特徴量抽出プログラム

明日開催される第3回 名古屋CV・PRML勉強会 & 第6回 関西CV・PRML勉強会のテーマは「局所特徴量」です。
局所特徴量は、書いたものそのままですが、画像の局所的な領域から抽出した特徴量です。
局所特徴量は最近10年のコンピュータ・ビジョンの分野で非常に発達したものであり、画像の識別や、画像間の対応付けといった分野に大きく貢献しています。

今回は、勉強会のついでですが、OpenCVで実装されている局所特徴量(特徴点検出・特徴量抽出)をコマンドラインから呼び出すプログラムを作成しました。

特徴点検出・特徴量抽出プログラム

使い方については、同梱したreadme.txtに書いてありますが、入力した画像から各アルゴリズムを用いて、特徴点を描画した画像ファイル、および特徴量を記述したテキストファイルを出力します。
基本的な使い方は、以下の通りです。

[cc lang=”bash” width=”600″]
features.exe -t アルゴリズム -i 入力画像 -o 出力画像 -r 出力テキストファイル
[/cc]

使用できるアルゴリズムは、SIFT/SURF/MSER/Star/FAST/GoodFeatureです。

例として、SIFTを用いて特徴点を抽出し、描画した画像は下のようになります。 

この画像では、各特徴点を中心とし、そのスケールで円を描いています。

OpenCVを使ってコードを書けば、同じことはすぐできるのですが、プロトタイピング段階でアルゴリズムごとにどのような特徴点が出るかを確認したいときにでも使って下さい。