まだプログラマーですが何か?

プログラマーネタ中心。たまに作成したウェブサービス関連の話も https://twitter.com/dotnsf

2015/03

前回の記事の後編です。Watson の画像認識機能そのものを紹介した前編はこちらを参照ください:
Watson の画像認識 API を使う(1/2)


前回は Watson Visual Recognition(画像認識)の機能を理解するために、実際にデモサイトを使って体験してみる、という内容を紹介しました。今回は IBM Bluemix から提供されている API を使って、実際に画像認識を行うサンプルコードを紹介します。前提条件として IBM Bluemix のアカウントが必要です。お持ちでない場合はこちらのサイトを参照して、30日間無料トライアルアカウントを取得してください:
http://ibm.biz/BMtrial

アカウントが取得できたら Bluemix にログインします。Visual Recognition サービスを使うためには、そのサービスに紐付けるランタイム(アプリケーション・サーバー)を指定する必要があるため、ランタイムを1つ作ります(既に Bluemix をお使いの方で、ランタイムも定義済みであれば、そのランタイムをそのままお使いいただいても構いません)。今回は Java のサンプルを紹介するので Java アプリケーションサーバーである Liberty Java を選択して作成します:
2015031101


なお、Visual Recognition API 自体は REST なので、ランタイムは Java でなくても構いませんが、本エントリでは Java のサンプルを紹介します。Visual Recognition の API ドキュメントはこちらを参照ください(ただし 2015/Mar/11 時点でパラメータ名が間違ってます、正しい使い方は以下のコードを参照ください)
visual-recognition : REST methods for IBM Visual Recognition


ランタイムが作成されたら、このランタイムに Visual Recognition サービスを紐付ける形で追加します:
2015031102


Visual Recognition サービスを使うための認証情報(ID とパスワード)を確認します。ランタイムを選択して環境変数 VCAP_SERVICES 内の credentials.username と credential.password の値を参照します:
2015031103


ここまでの作業で Visual Recognition サービスを使うために必要な準備と情報は整いました。後はコードを書くだけです。

今回紹介するサンプルでは (1) 画像をアップロードして、(2) その画像を Visual Recognition API で調べて、結果の JSON をそのまま出力する、というシンプルな例とします。

まずは (1) の部分を HTML で用意します。一切装飾してませんが、こんな感じでしょうか:
<html>
<head>
<title>Visual Recognition</title>
</head>
<body>
<form name="frm" method="post" enctype="multipart/form-data" action="./visualrecognition">
<input type="file" name="img_file"/><input type="submit" value="Upload" />
</form>
</body>
</html>


そして (2) の部分を Java サーブレットで作成します。(1) で /visualrecognition というアクションに multipart/form-data で画像ファイルを POST するように指定しているので、その内容を受け取って処理するような内容にしています。またファイルアップロードと HTTP アクセスを実装するためのライブラリ(Jakarta Commons の fileupload, codec, logging, httpclient3)を追加して使っています:
 :
 :
import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.servlet.*;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.methods.multipart.*;

public class VisualRecognitionServlet extends HttpServlet {
	private String username = "(Visual Recognition API の username の値)";
	private String password = "(Visual Recognition API の password の値)";

	@Override
	protected void doPost( HttpServletRequest req, HttpServletResponse res ) throws ServletException, IOException{
		String contenttype = "application/json; charset=UTF-8";
		String body = "";
		byte[] img_file = null;
		String out = "";

		ServletFileUpload upload = new ServletFileUpload();
		
		try{
			FileItemIterator iterator = upload.getItemIterator( req );
			while( iterator.hasNext() ){
				FileItemStream item = iterator.next();
				InputStream stream = item.openStream();
				
				if( item.isFormField() ){
				}else{
					String fieldname = item.getFieldName();
					if( fieldname.equals( "img_file" ) ){
						byte[] buffer = new byte[4*1024*1024]; //. 4MB上限
						ByteArrayOutputStream baos = new ByteArrayOutputStream();
						while( ( len = stream.read( buffer, 0, buffer.length ) ) != -1 ){
							baos.write( buffer, 0, len );
							size += len;
						}
						
						img_file = baos.toByteArray();
					}
				}
			}
			
			if( img_file != null && img_file.length > 0 ){
				HttpClient client = new HttpClient();
				byte[] b64data = Base64.encodeBase64( ( username + ":" + password ).getBytes() );

				PostMethod post = new PostMethod( "https://gateway.watsonplatform.net/visual-recognition-beta/api/v1/tag/recognize" );
				Part[] parts = new Part[]{ 
					//new StringPart( "labels_to_check", "{\"label_groups\":[\"Vehicle\"],\"labels\":[\"Mixed_Type\"]}" ),
					new FilePart( "imgFile", new ByteArrayPartSource( "imgFile.jpg", img_file ) )
				};
				
				post.setRequestHeader( "Authorization", "Basic " + new String( b64data ) );
				post.setRequestEntity( new MultipartRequestEntity( parts, post.getParams() ) );
				//post.setRequestHeader( "Content-Type", "multipart/form-data" ); //. 自分で設定してはダメ
				
				out = post.getResponseBodyAsString();
				//System.out.println( out );
			}
		}catch( Exception e ){
			e.printStackTrace();
		}
		
		res.setContentType( contenttype );
		res.setCharacterEncoding( "UTF-8" );
		res.getWriter().println( out );
	}
}

このコードは(ファイルアップロードでは一般的な)マルチパートで送られてくる画像データを受け取って、そのバイナリ情報をバイト配列の img_file 変数に格納します(この例では 4MB を上限としています)。

img_file の中身が正しく取得できていたら、Visual Recognition API を実行します。Basic 認証が必要なので、Visual Recognition サービスを追加したランタイムの環境変数 VCAP_SERVICES から credentials の username と password の値を取り出して、この例ではそれぞれ変数に格納しています(実際には動的に取得することをおすすめします)。Basic 認証のヘッダを作成すると同時に、この画像バイナリ情報をマルチパートで API のエンドポイント(https://gateway.watsonplatform.net/visual-recognition-beta/api/v1/tag/recognize)に POST で送ります。その際の指定方法は FilePart で変数名は imgFile、ファイル名は任意ですが拡張子が .jpg になるように(上例では "imgFile.jpg" で固定値)する必要があるそうです。

なお、このマルチパート部分には、画像のカテゴリがあらかじめわかっている時のためのカテゴリ指定を追加することも可能です。その場合は別の API エンドポイント: https://gateway.watsonplatform.net/visual-recognition-beta/api/v1/tag/labels を GET コールして得られる結果を絞る形で JSON を作り、StringPart で作成した文字列を指定します(上記例では青でコメントにしている部分です)。このサンプルでは使っていないので、特にカテゴリを指定せずに解析しています。

最後に API を実行した結果の文字列を取得して、"Content-Type: application/json" で出力する、というサンプルです。エラーハンドリングなど、甘い部分もまだありますが、おおまかな流れがご理解いただけると思います。


このプロジェクトアプリケーションを実行して、HTML ページにアクセスすると、画像ファイルを指定するこんなシンプルな画面になります:
2015031104


ローカルシステム内の画像ファイルを1つ指定してサーブレットを呼び出すと、上記のコードが実行され、Watson Visual Recognition API が実行され、その結果の JSON テキストが画面に出力されます:
2015031105


ちなみに指定した画像はこれ(木更津市のマンホール)でした。"Dog" かあ、惜しいなあ。でもタヌキは「イヌ科」だからほぼ正解と言えますよね! <ポジティブシンキング
kisarazu













IBM Bluemix を通じて IBM の人工知能サービスである Watson のデベロッパー向け API がいくつか提供されています。その1つの Visual Recognition(画像認識) API を紹介します。

前編である本エントリでは、この Watson Visual Recognition 機能がどのようなものかを理解していただくことを目的としています。そのために、まずは以下のデモ用サイトを参照してください:
Visual Recognition DEMO


リンク先をブラウザで開くと、"Select a sample image or Upload your own below" と書かれたテキストと、その下にいくつかの画像が並んでいるページが表示されます。その下にもパーツがありますが、最初は無視していただいて構いません:
2015031001


並んでいる画像から1つを選択してください。例えば右端の赤ちゃんの画像をクリックします。すると、処理が行われるので少し待ちますが、その結果、以下の様な画面になるはずです:
2015031002


先ほど画像が並んでいた部分の下部分が再描画され、左側に選択した画像が、そして右側に Output として画像認識結果が表示されます。結果を見ると一番上に "Baby 74%" と表示されています。続いて "Color 72%", "Photo 70%", "Human 69%", "Face 69%", ... と並んでいるのが分かります。

これが Watson の画像認識です。画像の情報だけを与えて、それが何の画像なのかを推測し、その確度と併せて返答してくれます。上記例ですと "Color" や "Photo" は認識結果としてはまだ曖昧な部分が含まれているとも言えますが、もっとも高い可能性として "Baby" の返答が出されていて、それ以外でも "Human" や "Face" といった結果が上位で返されており、かなり正しく画像を認識していることがわかります。

余談ですが、IBM Watson から提供される API の多くは、このように「確度と併せた結果」を返す仕様になっています。以前に本ブログでテキスト内の単語間の関連性を調べる Relationship Extraction API の説明をしましたが、そこでも同様の結果が返されていました:
IBM Bluemix の Watson Relationship Extraction API を使う


試しに違う画像も選んでみましょう。"Start Over" と書かれた箇所をクリックすると画像選択前の画面に戻るので、そこで異なる画像も選択してみます:
2015031003
↑Tiger(虎)

2015031004
↑Crowd(群衆)

2015031005
↑Windmill(風車)


いかがでしょう、かなり正確に認識してそうですよね??

でも当然こう考えますよね? 「これって仕込みじゃないの?」と。その疑問は当然です。自分の手持ち画像や、ネット上の画像ではどうなるか、調べてみたくなりますよね?

その場合は Start Over 後に一覧から画像を選択せずに、画面左下から画像を指定してください:
2015031006


自分の PC 内に保存されている画像を調べたいのであれば "Click or drop a JPG in this box." と書かれた箇所をクリックして画像を選択するか、あるいは画像ファイルを直接ドラッグ&ドロップします。 ネット上の画像を指定する場合は、その画像 URL を "Paste image url here..." と書かれた箇所に指定して Enter キーを押します。

今回は先日ラスベガスで行われたイベント IBM InterConnect2015 内の、エアロスミスのコンサート画像を見つけたので試してみます:
2015031007
↑Steven Tyler!!


この画像を "Click or drop a JPG in this box" で指定した結果がこちら:
2015031008


・・・うん、ある意味で正しい。間違いではない。少なくとも画像の色を正しく認識していることは確実。候補として "Gymnastics"(体操競技)があるのも、激しいステージにはそういう要素があると言えなくもない(苦しい? (^^;)。そして "Human" !

認識精度というか、こちらが期待する結果がピンポイントで得られるかどうかという点はともかく、全くの初見の画像に対してもそれなりの結果が得られている、とは言えると感じています。


なんとなく、Watson の画像認識機能がどのようなものか分かっていただけたのではないかと思っています。 ここで改めて説明しますが「この画像認識機能を IBM が創りだした」ことを伝えたかったのではなく、「IBM Bluemix を通じて、この画像認識機能が Web API として誰でも使えるように提供されている」ことをお伝えしたかったのでした。

ではその API はどうやって、どのようにして使うのか? を後編で説明します。お楽しみに。


(追記)
後編はこちら: Watson の画像認識 API を使う(1/2)


 

(2015/07/14 追記)
このエントリではビルドパックを使って Java8 ランタイムを動かす方法を紹介していますが、現在は標準の Liberty Java ランタイムが Java8 対応しています。詳しい手順はこちらを参照ください:
Bluemix で Java8 を使う
(2015/07/14 追記終わり)



新しい環境で、新たに IBM Bluemix 向けのアプリ開発環境を作っていてハマった落とし穴の解説です。同じ穴に引っかかる人を未然に防げれば何より。


新しい PC で新たに Java + Eclipse 環境を普通に構築して、で Java ウェブアプリケーションを1つ作りました。それを IBM Bluemix にデプロイ:
2015030901


問題なく成功:
2015030902


で動作確認・・・ したらエラー!
2015030901


一応、事前にローカルホストで動作確認できていたので、コーディング上のミスは考えにくい。なんだこれは?環境依存??だとしてもどんな環境???

よくわからないので画面のエラーメッセージを読んでみる。サーブレットクラスが corrupted で UnavailableException になってる。"Check that the class file was not renamed after it was compiled" って言われても、war ファイルをデプロイしただけだし・・・

・・・ん?コンパイル??

そういえば・・・、とデプロイ時の画面の標準出力内容を見直し:
2015030903


IBM Bluemix の Liberty Java では IBM Java 1.7.1 が使われていることがわかります。そして自分の開発環境を再確認すると:
2015030902

これだ!
新しい環境には JDK 1.8 しかインストールしてなかったので、JavaSE 1.8 でコンパイルしてた!

IBM Bluemix で Liberty Java を使う場合、少なくとも現時点では JDK 1.7 を導入して使うべきですね。JDK 1.8 をインストールすること自体はいいんですが、Bluemix 用にコンパイルする場合は 1.7 互換(以下)でのビルドが必要になります。

特に新たに開発環境をセットアップするようなケースでは(深く考えずに最新版だけをインストールしちゃうと NG なので)要注意です。


(おまけ)
IBM Bluemix で「どうしても JDK 1.8 / Java 8 を使いたい!」という場合は、CloudFoundry 用の buildpack を使って、以下のように指定すると OpenJDK 1.8 (と Apache Tomcat)が含まれた Java 実行環境をランタイムにすることができます:
# cf push XXX(アプリ名)XXX -p ****.war -b https://github.com/cloudfoundry/java-buildpack.git
(おまけ終わり)


いずれかの方法で、ランタイムとアプリケーションのコンパイルバージョンを合わせることができれば、デプロイしたアプリケーションを IBM Bluemix 上で正しく動かすことができるようになります:
2015030903

↑サンプルはここで紹介したアプリを IBM Bluemix 上で動作確認したものです。
Tomcat でリライト









ウェブサービスを開発・公開する上で、URL のリライトの問題があります。

例えば、アプリケーションとしては
 http://xxx.myserver.com/abc.php?country=Japan&pref=Chiba&city=Funabashi
というパラメータを受け取って処理するようなページやサービスがあったとします。

単に動かすだけなら、このパラメータ処理でいいのですが、SEO 対策や、単に長くて見難いという理由から、これを
 http://xxx.myserver.com/abc.php/Japan/Chiba/Funabashi
のような URL パターンで受け取りたいことが出てきます。

この例であれば、
 http://xxx.myserver.com/abc.php/AAA/BBB/CCC
という URL パターンを有効な URL として認識し、実際に処理する際には
 http://xxx.myserver.com/abc.php?country=AAA&pref=BBB&city=CCC
と内部的に書き直し(ReWrite)してから処理をする、ということになります。これが URL リライトです。

HTTP サーバーに Apache HTTPD を使っている場合は、この URL リライト機能が標準モジュールになっていて、mod_rewrite モジュールを有効にして、httpd.conf や .htaccess に変換ルールを記述するだけで URL リライトが実現できます。 ではウェブサーバーに Apache Tomcat など Apache HTTPD を経由しない Java アプリケーションサーバーを使っている場合のリライト処理はどうすればいいでしょう??


その答の1つが UrlRewriteFilter です。jar ファイル1つをプロジェクトに組み込み、専用の XML ファイルに変換ルールを記述することで、Java アプリケーションサーバーでも mod_rewrite のようなリライト処理を実現できるようになります:
urlrewritefilter00


実際に使ってみたサンプルを紹介します。まずは公式ページから urlrewritefilter-4.0.3.jar ファイルと、サンプルの urlrewrite.xml ファイルをダウンロードして保存します。
urlrewritefilter01


Java アプリケーションのプロジェクト内にこれらのファイルを配置します。まず urlrewritefilter-4.0.3.jar は WEB-INF/lib/ に、urlrewrite.xml は WEB-INF にそれぞれ保存します。次に web.xml を編集します:
urlrewritefilter02

WEB-INF/web.xml の </web-app> の直前に次の内容を追加して、このウェブアプリケーション内の全ての URL パターン(/*)に UrlRewriteFilter フィルターを適用することを宣言します:
  :
  :
<filter>
  <filter-name>UrlRewriteFilter</filter-name>
  <filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
</filter>
<filter-mapping>
  <filter-name>UrlRewriteFilter</filter-name>
  <url-pattern>/*</url-pattern>
  <dispatcher>REQUEST</dispatcher>
  <dispatcher>FORWARD</dispatcher>
</filter-mapping>

</webapp>

実際の変換パターンは WEB-INF/urlrewrite.xml ファイルの <urlrewrite> 要素内に記述します。以下の例では2つの変換パターンを記載しています:
<urlrewrite>
  :
  :
  <rule>
    <from>/urlrewrite/(.*)/(.*)/(.*)</from>
    <to>/urlrewrite?x=$1&y=$2&z=$3</to>
  </rule>
  <rule>
    <from>/urlrewrite/(.*)/(.*)</from>
    <to>/urlrewrite?a=$1&b=$2</to>
  </rule>
</urlrewrite>

前者では /urlrewrite/ 以下に / をセパレータとして3つのパラメータを受け取る、つまり
 /urlrewrite/XXX/YYY/ZZZ 
のような URL 指定があった場合に、内部的に
 /urlrewrite?x=XXX&y=YYY&z=ZZZ
のようにリライトして処理する、という変換パターンを記載しています。

後者では同様に2つのパラメータを受け取った時、つまり
 /urlrewrite/AAA/BBB
のような URL 指定があった場合に、内部的に 
 /urlrewrite?a=AAA&b=BBB
のようにリライトして処理する、という変換パターンを記載しています。

#上記例ではパラメータが2つの時と3つの時とで、異なるパラメータ変数に渡すようにしています。
 もちろんこの部分は定義次第です。


後はこれらのパラメータ(リライト後のパラメータ)に対応して処理するような /urlrewrite を(サーブレットなどで)実装すればよい、ということになりますね。




 

以前に IBM Bluemix を独自ドメインで利用する方法について紹介しました:
IBM Bluemix を独自ドメインで運用する


本エントリでは、特に SSL を使って IBM Bluemix を独自ドメイン運用するための注意点と手順を紹介します。


まず独自ドメインを使わない場合、IBM Bluemix 上のウェブアプリケーションには米国リージョンの場合で****.mybluemix.net(英国リージョンであれば ****.eu-gb.mybluemix.net)というホスト名が割り当てられます。**** 部分はアプリケーション作成時に指定したものが使われます。 以下、説明をシンプルにするため、米国リージョンでの利用を前提として説明を続けます)。

このホスト名を SSL で利用する場合の SSL 証明書は mybluemix.net 側で(つまり IBM 側で)用意されていることになります。特に **** 部分にどのような文字列が来るか想定できない前提で SSL を使うので、その証明書はワイルドカード対応のものが用意されている、ということになります。


さて、独自ドメインで IBM Bluemix を使う場合の注意点として、そのドメインの SSL 証明書は利用者が用意する必要があり、加えて上記の理由からその証明書はワイルドカード対応のものを用意する必要がある、ということになります。

現在、SSL 証明書を発行してくれるサービスは国内外でいくつかあります。ただ IBM Bluemix で利用するドメインの SSL 証明書を発行してもらうためには、ワイルドカード対応のものを発行してもらう必要があるのですが、全ての業者がワイルドカード対応の証明書を発行してくれるわけではありませんワイルドカード対応の SSL 証明書を発行してくれる業者の中から選んで注文する、という点に注意が必要です。


ただ、特定の限られた用途での利用など、いわゆる「オレオレ証明書」でもいい、という場合は、自分でワイルドカードに対応した SSL 証明書を用意することでも対応は可能です。ワイルドカードに対応した SSL 証明書を自分で発行する場合の手順はこちらを参照してください:
ワイルドなオレの証明

以下はこの方法で用意した SSL 鍵および証明書を使った前提で紹介を続けます(手順そのものは正式な証明書を使った場合も同様です)。

まず冒頭で紹介した「IBM Bluemix を独自ドメインで運用する」の手順を進めて、「組織の管理」メニューからドメインの追加を行います:
2015030601


追加する独自ドメイン(この例では "yellomix.net")を指定して「保存」します:
2015030602


保存後、「SSL 証明書」と書かれた列に証明書アップロードのためのボタンが表示されるので、ここをクリックします:
2015030603


証明書ファイルと秘密鍵ファイルを指定してアップロードします。もし中間証明書ファイルをお持ちで、追加したい場合は中間証明書ファイルも指定します。最後に「アップロード」ボタンをクリックします:
2015030604


先ほどのボタンがグリーンに変わっていれば証明書ファイルのアップロードは完了です:
2015030605


では独自ドメインでアプリケーション・サーバーを1つ作成して、SSL でアクセスする、という動作確認を行ってみます。まずは Bluemix 上に(動作確認なので中身はあってもなくてもいいと思いますが)ランタイムまたはボイラープレートから作成したランタイムを作ります。その際にドメイン情報として、追加した独自ドメインを指定するようにします:
2015030606


ランタイムアプリケーションを作成すると、経路の情報としてホスト名が表示されます。先ほど独自ドメインを指定したので独自ドメインにアプリ名が付与されたホスト名になっているはずです:
2015030607


DNS 側でもこのホスト名の名前解決ができるような設定をしておきます。このランタイムに付けた名前(dotnsf-ibm-wordpress)の CNAME として、元々デフォルトで割り振られるはずだった名前(dotnsf-ibm-wordpress.mybluemix.net)を参照するように指定します。この部分は各々でお使いの DNS ツールの方法に従ってください:
2015030608


これで設定は完了しています。確認のため実際にブラウザを使って、作成したランタイムのホスト名に対して https:// でアクセスしてみましょう。オレオレ証明書を使った場合は、ブラウザの種類にもよりますが、「接続の安全性を確認できない」云々、といった確認画面になると思います。ある意味、正しく SSL が動いている証拠と言えます:
2015030609


理解した上で許可すれば SSL で(httpsで)見れるようになります。このホスト名を直接指定した形では SSL 証明書を作っていませんでしたが、ワイルドカード指定で作成した SSL 証明書が IBM Bluemix に正しくインポートされて動いていることが確認できました:
2015030610





















 

このページのトップヘ