Saturday, October 10, 2015

MOCO'Sキッチンのフォントに似せるβ版

お久しぶりです。
今回は昔に作ったまな板の話です。

2012年に料理好きの友人にMOCO'Sキッチン風のまな板を作成してプレゼントしました。
このときに使用したフォントはなんですか?という質問をもらいました。
検索してみると同じように悩んでいる人が多いようです。

結論からいうとキッチンは元ネタの画像を加工して使用していました。
英語のフォントは本当に適当です。

そのまま答えるのも申し訳ないので似たフォントを探してみました。
その結果が以下の画像です。


mocos

オリジナルの「キッチン」の部分はかなり特徴的なフォントです。
 ここには、以下のフォントを使用させていただきました。

マキナス: http://moji-waku.com/makinas/index.html

「MOCO S」の部分はSimplified Arabicで「'」はArial Unicodeを使用しました。

マジマジと見ると違う部分が目立ちます。

課題は、「チ」と「ン」のフラットさの再現でしょうか。

正直フォントで頑張って再現するより、自分でラインを引いたほうが早い気がしてきました。
今度はMOCO'Sキッチン風フォントの作成に調整しても面白いかもしれませんね。

それでは今回はここまでです。

Tuesday, March 10, 2015

RICHO THETAの簡単なAndroidアプリ

RICHO THETAを最初見たときは,あんまりピンと来なかったのですが使ってみると便利で面白いですね.
そんなTHETAをいじる機会があったので,簡単なAndroid用のプログラムを作ってみました.

SDKは下記のサイトからDLできます.

RICHO THETA Developpers
https://developers.theta360.com/ja/


SDKに付属しているサンプルプログラムは私にとって複雑で,何をしているのかわかりにくいものでした.
そこで,SDKのサンプルとリファレンスを頼りに,以下のようなシャッターを切るだけの簡単なサンプルを作りました.

SimpleTHETA_01

このサンプルはCONNECT Buttonを押してTHETAに接続し,SHOOT Buttonを押してシャッターを切るというプログラムです.
切断するときはDISCONNECT Buttonを押します.
THETAのステイタスからGUIを動かすサンプルとして,各操作の状況をTHETA statusというtextviewに描画しています.

それではこのプログラムの説明を行います.

まずは,以下のように,インターネットのpermissionを取ります.
この記述は,AndroidManifest.xmlに記述します.
<uses-permission android:name="android.permission.INTERNET"/>
つぎに,GUIのレイアウトを設定します.デフォルトではactivity_main.xmlに記述します.
    <LinearLayout
    android:id="@+id/linearLayout1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true"
    android:layout_marginLeft="28dp"
    android:layout_marginTop="64dp"
    android:orientation="vertical" >
        
        <LinearLayout
        android:id="@+id/linearLayout2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        
         <TextView
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="THETA status:" />    
             
         <TextView
         android:id="@+id/status"
         android:layout_width="wrap_content"
         android:layout_height="match_parent"
         android:text="Status" />
        
        </LinearLayout>
        
        <LinearLayout
        android:id="@+id/linearLayout3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
        
         <Button
         android:id="@+id/connect"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="CONNECT" />
         
         <Button
         android:id="@+id/disconnect"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="DISCONNECT" />
         
         <Button
         android:id="@+id/shoot"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="SHOOT" />
        
        </LinearLayout>
 
    </LinearLayout>

これまでで,準備が整ったのでMainActivity.javaに処理を記述します.
この処理は基本的に各ボタンのListnerを設定して,そのボタンに合わせて処理を行います.
気を付ける点は以下の2つです.
  1. THETAとのやりとりはINTERNETを使うので,AsyncTaskなどを使って別スレッドで行う.
  2. GUIを別スレッドから操作するときは,Handlerを使う.
この点だけ気を付ければ特別難しい処理はないと思います.
各ボタンを押すとConnectTask,DisConnectTask,ShootTaskが実行されます.

ConnectTaskでは,指定したIPアドレスのTHETAに接続を行い,成功したらCONNECTED,失敗したらERRORと表示します.
DisConnectTaskでは,TETAとの接続を断ち,成功したらDISCONNECTED,失敗したらエラーログを表示します.
ShootTaskでは,シャッターを切り,成功したらSHOOT,失敗したら各エラーを表示します.

public class MainActivity extends Activity {
 
 PtpipInitiator camera;
 private static final String IP_ADDRESS = "192.168.1.1";
 private TextView txt_theta_status;
 private Button btn_theta_connect;
 private Button btn_theta_disconnect;
 private Button btn_theta_shoot;
 
 private Handler handler = null;

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  
  txt_theta_status = (TextView) findViewById(R.id.status);
  btn_theta_connect = (Button) findViewById(R.id.connect);
  btn_theta_disconnect = (Button) findViewById(R.id.disconnect);
  btn_theta_shoot = (Button) findViewById(R.id.shoot);
        
  btn_theta_connect.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new ConnectTask().execute();
       }
  });
        
         btn_theta_disconnect.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new DisConnectTask().execute();
       }
         });
        
         btn_theta_shoot.setOnClickListener(new View.OnClickListener() {
       @Override
       public void onClick(View v) {
        new ShootTask().execute();
   }
         });
        
         handler = new Handler();
 }
 
 
 private class ConnectTask extends AsyncTask {
     @Override
     protected String doInBackground(String... params) {
         try {
             camera = new PtpipInitiator(IP_ADDRESS);
             
             handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("CONNECTED");
              }
             });
             
             
         } catch (Throwable e) {
             e.printStackTrace();
             
             handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("ERROR");
              }
             });
         }
         return null;
     }
 }
 
 private class DisConnectTask extends AsyncTask {
  @Override
  protected Boolean doInBackground(Void... params) {

   try {
    PtpipInitiator.close();
    
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("DISCONNECTED");
              }
             });
    
    return true;

   } catch (Throwable throwable) {
    final String errorLog = Log.getStackTraceString(throwable);
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText(errorLog);
              }
             });

    
    return false;
   }
  }
 }

 private static enum ShootResult {
  SUCCESS, FAIL_CAMERA_DISCONNECTED, FAIL_STORE_FULL, FAIL_DEVICE_BUSY
 }
 
 private class ShootTask extends AsyncTask {

  @Override
  protected ShootResult doInBackground(Void... params) {
   try {
    PtpipInitiator camera = new PtpipInitiator(IP_ADDRESS);
    camera.initiateCapture();
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("SHOOT");
              }
             });
    return ShootResult.SUCCESS;

   } catch (IOException e) {
    handler.post(new Runnable() {
              @Override
              public void run() {
               txt_theta_status.setText("FAIL_CAMERA_DISCONNECTED");
              }
             });
    return ShootResult.FAIL_CAMERA_DISCONNECTED;
   } catch (ThetaException e) {
    if (Response.RESPONSE_CODE_STORE_FULL == e.getStatus()) {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_STORE_FULL");
               }
              });
     return ShootResult.FAIL_STORE_FULL;
    } else if (Response.RESPONSE_CODE_DEVICE_BUSY == e.getStatus()) {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_DEVICE_BUSY");
               }
              });
     return ShootResult.FAIL_DEVICE_BUSY;
    } else {
     handler.post(new Runnable() {
               @Override
               public void run() {
                txt_theta_status.setText("FAIL_CAMERA_DISCONNECTED");
               }
              });
     return ShootResult.FAIL_CAMERA_DISCONNECTED;
    }
   }
  }

 }

以上で,AndroidアプリからTHETAのシャッターを切る方法の説明を終わります.

Tuesday, March 3, 2015

Raspberry Pi 2のケースを自作する

Raspberry Pi 2を手に入れたのでケースを作ってみました.
市販のケースを買うのもいいですが,ケースを自作するのも楽しさの一つだと思います.

調べてみるとケースは3Dプリンタやレーザーカッターで作っるのがメジャーなようです.
3Dプリンタで作ると市販の射出成形のケースに見劣りしそうだったので,レーザーカッターをメインに使いました.
レーザーカッターは基本的に板を切るものなので,積層や箱組で立体的にケースを作るという手法が多く見られました.
今回,私は板を金属の支柱で支えることでケースを作ることにしました.
シンプルですが,各コネクタやIOへのアクセスを考えると悪くない選択だと思います.

Raspberry Pi2とケースの部品は以下のようになります.
各部品は,自作品や在庫品を使用しています.
ここを見てくれた方の参考になるよう,同等の市販品を参考に載せました.
もっと安いものもありますが,趣味に使うものなので良い物を選んでいます.

RaspberryPi2 Case veiw4

天板と下板のデータはここからDLできます.
DXF,ISG,STLで用意したので興味のある方は是非使ってみてください.
下板のパーツのRaspberry Pi2固定用の穴はM2.6のタップでメネジをたてて下さい.
それができない場合は,φ2.6の穴に拡張して,ナットで固定してください.
その場合は,ナットの厚み分,プラネジを長くしてください.
また,下板-天板接続用ネジの下面側のネジはナットよりも背の高いものを使用してください.

Drawing of Raspberry Pi2 Case


それでは,これらの部品を組み立てていきます.
まずは,下板にプラスティックの支柱とM2.6のプラネジを使って固定します.
さらに,アルミ製の支柱を下板に止めます.

RaspberryPi2 Case veiw2

最後に,天板を固定すれば完成です.
天板には,お決まりのロゴを彫刻しました.
すりガラス風のアクリルを使っているので,ロゴがはっきりと見えます.
すりガラス風のロゴを彫刻する場合は, すりガラスのような加工が入った面に彫刻するほうがきれいにロゴが見えると思います.

RaspberryPi2 Case veiw1

横から見ると以下のようになります.
各コネクタやIOは隠していないのでちゃんとアクセスできます.

RaspberryPi2 Case veiw3

簡単な作りですが,それなりに見栄えのするものになったと思います.

Thursday, February 19, 2015

Python版OpenCVで色抽出を行う

今回は,Python版のOpenCVを使ってHSV色空間で色抽出をする方法をメモしておきます.

環境は以下のとおりです.
Python 2.6
OpenCV 2.4.6

更新履歴
  • 2015/03/12
    しきい値処理におけるTHRESH_BINARYとTHRESH_BINARY_INVが逆になっているのを修正しました.

それでは早速スクリプトを見ていきます.

import cv2

def extract_color( src, h_th_low, h_th_up, s_th, v_th ):

    hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
    h, s, v = cv2.split(hsv)

    if h_th_low > h_th_up:
        ret, h_dst_1 = cv2.threshold(h, h_th_low, 255, cv2.THRESH_BINARY) 
        ret, h_dst_2 = cv2.threshold(h, h_th_up,  255, cv2.THRESH_BINARY_INV)
        
        dst = cv2.bitwise_or(h_dst_1, h_dst_2)

    else:
        ret, dst = cv2.threshold(h,   h_th_low, 255, cv2.THRESH_TOZERO) 
        ret, dst = cv2.threshold(dst, h_th_up,  255, cv2.THRESH_TOZERO_INV)

        ret, dst = cv2.threshold(dst, 0, 255, cv2.THRESH_BINARY)
        
    ret, s_dst = cv2.threshold(s, s_th, 255, cv2.THRESH_BINARY)
    ret, v_dst = cv2.threshold(v, v_th, 255, cv2.THRESH_BINARY)

    dst = cv2.bitwise_and(dst, s_dst)
    dst = cv2.bitwise_and(dst, v_dst)

    return dst

色抽出する処理を関数にまとめてみました.
この関数の戻り値と引数は以下のようになります.黒を除外して色相で抜きたい色を指定するイメージで処理を行います.
出力画像 extract_color( 入力画像, 色相のしきい値(下), 色相のしきい値(上), 彩度のしきい値, 明度のしきい値 )
関数の内部の処理を説明します.
入力画像はRGBカラーモデルであることを想定し,HSV色空間に変換します.
その後,HSVの各チャンネルを別々の画像にわけます.
hsv = cv2.cvtColor(src, cv2.COLOR_BGR2HSV)
h, s, v = cv2.split(hsv)
赤色を抽出する場合は,色相が360°をまたぐ可能性があるため,ここで処理を分けます.
色相のしきい値(下)がしきい値(上)より大きいというのは,赤のように350°から10°までというような場合を想定しています.
360°を境界として2回しきい値処理を行った後に各画像を合わせることで抽出します.
まず,以下のようなcv2.threshold関数を使って,しきい値処理を行います.
cv2.threshold(画像, しきい値, 最大値, しきい値処理のタイプ)
しきい値処理のタイプは以下のものを使用します.

cv2.THRESH_BINARY: しきい値より大きい値は最大値,それ以外は0
cv2.THRESH_BINARY_INV: しきい値より大きい値は0,それ以外は最大値

その後,各画像のORを取ることで,2枚の画像を合わせます.
    if h_th_low > h_th_up:
        ret, h_dst_1 = cv2.threshold(h, h_th_low, 255, cv2.THRESH_BINARY) 
        ret, h_dst_2 = cv2.threshold(h, h_th_up,  255, cv2.THRESH_BINARY_INV)
        
        dst = cv2.bitwise_or(h_dst_1, h_dst_2)
次に,360°をまたがない場合を考えます.
ここでは,しきい値処理に以下のものを使用します.

cv2.THRESH_TOZERO: しきい値よりより大きい値はそのまま,それ以外は0
cv2.THRESH_TOZERO_INV: しきい値よりより大きい値は0,それ以外はそのまま

まず,しきい値(下)の値を0にし,その後,しきい値(上)より大きい値を0にすることで特定の範囲の値を抽出できます.
    else:
        ret, dst = cv2.threshold(h,   h_th_low, 255, cv2.THRESH_TOZERO) 
        ret, dst = cv2.threshold(dst, h_th_up,  255, cv2.THRESH_TOZERO_INV)

        ret, dst = cv2.threshold(dst, 0, 255, cv2.THRESH_BINARY)
色相については,ここまでで処理できたので,残りの明度と彩度についても処理を行います.
ここでは,黒に近い色を排除するだけでよいので,下限値のみのしきい値処理を行います.
    ret, s_dst = cv2.threshold(s, s_th, 255, cv2.THRESH_BINARY)
    ret, v_dst = cv2.threshold(v, v_th, 255, cv2.THRESH_BINARY)
そして最後に,各画像のANDをとり,各しきい値を満たす画素を抽出します.
    dst = cv2.bitwise_and(dst, s_dst)
    dst = cv2.bitwise_and(dst, v_dst)
これで,任意の色を抽出するextract_color関数が完成しました.

それでは,この関数を使って色抽出を行ってみます.
基本的なWebカメラの扱い使い方は,ここを見てください.
if __name__=="__main__":

    capture = cv2.VideoCapture(0)
    
    if capture.isOpened() is False:

        raise("IO Error")

    cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)
    cv2.namedWindow("Red",     cv2.WINDOW_AUTOSIZE)
    cv2.namedWindow("Yellow",  cv2.WINDOW_AUTOSIZE)

    while True:

        ret, image = capture.read()
        if ret == False:
            continue

        red_image    = extract_color(image, 170, 5,  190, 200)
        yellow_image = extract_color(image, 10,  25, 50,  50)

        cv2.imshow("Capture", image)
        cv2.imshow("Red",     red_image)
        cv2.imshow("Yellow",  yellow_image)
       
        if cv2.waitKey(33) >= 0:
            cv2.imwrite("image.png", image)
            cv2.imwrite("red_image.png", red_image)
            cv2.imwrite("yellow_image.png", yellow_image)
            break

    cv2.destroyAllWindows()
このスクリプトの実行結果は以下のようになります.
まずは,入力画像です.
image
次に,赤色の抽出結果です.
red_image
最後に,黄色の抽出結果です.
yellow_image

この例では,赤色と黄色をそれぞれ抽出して画像を表示しています.
各画像を見ると,それぞれの色で抽出出来ていることがわかります.
基本的な処理は既に説明していますが,OpenCVではHSVの各チャンネルは以下のような値域となっていることに注意してください.

H(色相):0~180
S(彩度):0~255
V(明度):0~255

以上でHSV色空間で色抽出をする方法の説明を終わります.  

Python版OpenCV関連の記事:
Python版OpenCVでWebカメラの画像を取得する
Python版OpenCVで色抽出を行う

参考文献:
「Miscellaneous Image Transformations」『OpenCV 2.4.9.0 documentation』<http://docs.opencv.org/modules/imgproc/doc/miscellaneous_transformations.html> (2015/02/20アクセス)
「Basic Operations on Images」『OpenCV 3.0.0-dev documentation』<http://docs.opencv.org/trunk/doc/py_tutorials/py_core/py_basic_ops/py_basic_ops.html> (2015/02/20アクセス) 

Friday, February 13, 2015

Python版OpenCVでWebカメラの画像を取得する

OpenCVを使うときは主にC++を使っているのですが,Pythonでも使えると何かと便利なので使い方を調べてみました.

今回は,Webカメラから画像を取得する方法をメモしておきます.
ちなみにPython 2.6を使っている関係で,OpenCV 2.4.6を使っています.

それでは早速スクリプトを見ていきます.
import cv2

if __name__=="__main__":

    capture = cv2.VideoCapture(0)
    
    if capture.isOpened() is False:
        raise("IO Error")

    cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)

    while True:

        ret, image = capture.read()

        if ret == False:
            continue

        cv2.imshow("Capture", image)
       
        if cv2.waitKey(33) >= 0:
            cv2.imwrite("image.png", image)
            break

    cv2.destroyAllWindows()

まずは,0番のカメラを引数にしてVideoCaptureクラスをインスタンス化します.
capture = cv2.VideoCapture(0)
次に,isOpenedメソッドを使ってWebカメラに接続できているか確認します.
もしできていなければIOエラーをだします.
if capture.isOpened() is False:
        raise("IO Error")
Webカメラで取得した画像を表示するCaptureという名前のウィンドウを設定します.
cv2.WINDOW_AUTOSIZEを指定すると,表示する画像に応じてウィンドウの大きさが自動的に決まります.
cv2.namedWindow("Capture", cv2.WINDOW_AUTOSIZE)
この先の処理は後ほど説明するキー入力があるまで,処理をwhileループで繰り返します.
まずは,画像をWebカメラから取得します.
取得できればimageにnumpyのndarray形式で画像が,retにbool形式でTrueが格納されます.
ret, image = capture.read()
もし,retにFalseが入っていればこの後の処理を飛ばします.
if ret == False:
            continue
取得した画像を先ほど生成したウィンドウCaptureに表示します.
cv2.imshow("Capture", image)
最後に,cv2.waitKeyを呼び出します.引数はウェイト時間 msです.0を指定すると無限にキー入力を待ちます.
ちなみにcv2.watiKeyはイベント処理を扱うので定期的に呼び出す必要があることに注意してください.
これを書かないとキー入力を受け取れないだけでなく,画像もウィンドウに表示されません.
ここでは,何かキー入力があった場合,image.pngというPNGファイルに画像を保存します.
if cv2.waitKey(33) >= 0:
            cv2.imwrite("image.png", image)
            break
キーが押された場合に,全てのウィンドウを破壊して終わります.
cv2.destroyAllWindows()
以上でWebカメラの画像取得方法の説明を終わります.  

Python版OpenCV関連の記事:
Python版OpenCVでWebカメラの画像を取得する
Python版OpenCVで色抽出を行う

参考文献:
「User Interface」『OpenCV 2.4.9.0 documentation』<http://docs.opencv.org/modules/highgui/doc/user_interface.html> (2015/02/14アクセス)
「Reading and Writing Images and Video」『OpenCV 2.4.9.0 documentation』<http://docs.opencv.org/modules/highgui/doc/reading_and_writing_images_and_video.html> (2015/02/14アクセス) 

Sunday, February 8, 2015

ggplot2を使ってみる

最近,機械学習の勉強会でRをいじる機会がありました.
Rは機械学習や統計に関するパッケーが充実しているのは大変魅力的です.
そして,ggplot2という強力なグラフの描画用のパッケーがすごいです.
Excelではこんな見栄えのよいグラフを作れないよなと感動してしまいます.
今日はそんなRのggplot2で基本的な図の描画をしてみます.

まずggplot2をインストールしていない場合は,以下の用なコードでインストールしてください.
コードを入力するとCRAN mirrorというウィンドウが現れるので,Japanあたりのミラーを指定してください.
install.packages("ggplot2", dependencies = TRUE)
package ‘ggplot2’ successfully unpacked and MD5 sums checked
というメッセージがでればインストール成功です.
インストールが無事終わったら,以下のようにライブラリをロードします.
library('ggplot2')
まずは,データフレームを作ります.
set.seed(1)
x.1 <- rnorm(1000)
y.1 <- rnorm(1000)
label.1 <- ifelse(rbinom(1000, size = 1, prob = 0.5), TRUE, FALSE)

data.1 <- data.frame(X = x.1, Y = y.1, L = label.1)
x.1とy.1には正規分布に従う乱数を1000個ずつ生成して格納します.
label.1には,二項分布に従う乱数を生成し,1ならTRUE,0ならFALSEを格納します.
これらのベクトルからデータフレーム data.1を作成します.

データフレームの準備が出来たので,まずはXを0.4ずつに区切ってヒストグラムを描画します.
ggplot()にデータフレームと使用するデータのラベルを指定し,描画する環境を設定します.
その環境にgeom_histogram()で生成したヒストグラムを重ねることで,グラフを描画します.
hist.1 <- ggplot(data.1, aes(x = X)) + geom_histogram(binwidth = 0.4)
hist.1

次に,geom_density()を使って確率密度関数を描画します.
dens.1 <- ggplot(data.1, aes(x = X)) + geom_density()
dens.1

最後に,散布図を描画します.
x軸にラベルXのデータを,y軸にラベルYのデータ,各点の色にラベルLのデータを使用します.
散布図はgeom_point()関数で描画できます.
scatter.1 <- ggplot(data.1, aes(x = X, y = Y, color = L)) + geom_point()
scatter.1

ラベルごとに色が変わっており,さらに凡例もしっかりとついています.
軸のラベルや凡例のラベルを変更するには以下のように,labs()を使用します.
scatter.1 <- scatter.1 + labs(color = 'Label')

このように,よくあるグラフを簡単に,しかも見栄え良く描画することができます.

今度は,別のデータフレームを使って,任意の関数の描画方法を見てみましょう.
x.2 = runif(1000)
y.2 <- x.2 + rnorm(1000, sd = 0.05)

data.2 <- data.frame(X = x.2, Y = y.2)
y = xの1次関数に従うデータを生成します.
x.2には一様乱数を1000個生成して格納しています.
y.2にはx.2の値に標準偏差0.05,平均0のガウスノイズを加えたデータを格納しています.
まずは,このデータの散布図を描画します.
scatter.2 <- ggplot(data.2, aes(x = X, y = Y, color = L)) + geom_point()
scatter.2

次に,任意の関数をこの散布図に重ねてみます.
描画するのは,このデータの回帰直線を描画します.
回帰直線はlm()を使って以下のように簡単に求めることが出来ます.
fit <- with(data.2, lm(Y ~ X))
intercept <- coef(fit)[1]
slope <- coef(fit)[2]
この回帰直線をstat_function()関数の引数funに,ラムダ式を使って回帰直線の1次関数の計算式を与えます.
先ほどの散布図に重ねるには,この直線をscatter.2に足し合わせることで重ねることが出来ます.
scatter.2 <- scatter.2 + 
stat_function(colour = "orange", fun = function(x) slope * x + intercept, size = 1)
scatter.2

この回帰直線はy = xの関数にノイズを加えたものなので,回帰直線はy = xに近いことがわかります.
最後に,scatter.2を画像ファイルに保存します.
ggsave(plot = scatter.2, filename = "scatter_2.png", height = 8, width = 8)
以上のように,簡単に見栄えのするグラフが描画できました.
正直,ggplot2だけでもRを使う価値があると思います.
三次元のグラフのように見栄えのするグラフも描画できるようですので,みなさんも是非使ってみてください.

参考文献:
Drew Conwayら 『入門 機械学習』オライリージャパン (2012/12/22)
「乱数Tips大全」『RjpWiki』 (2015/02/09アクセス)

Tuesday, February 3, 2015

OpenCVでチェスボードの交点を検出する

最近OpenCVを使うことが増えたので,メモ代わりに情報を残しておきます.

今回は,OpenCVを使ってチェスボードの交点を探します.

以下の,チェスボードの交点を探します.
チェスボードのデータをPDFで置いておきますので,よろしければA4用紙に印刷して使用ください.
chess_board_A4_7x10.pdf

chess_A4_7x10

チェスボードの交点位置を求めるプログラムは以下のようになります.

#include "opencv2/core/core.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"

bool find_chess_corners( cv::Mat image, 
                         cv::vector *corners, 
                         cv::Size pattern_size, cv::Size chess_size )
{
 bool is_find = cv::findChessboardCorners(image, pattern_size, *corners);

 if( is_find != true )
 {
  return false;
 }

 cv::Mat gray( image.rows, image.cols, CV_8UC1 );
 cv::cvtColor(image, gray, CV_BGR2GRAY);
 cv::cornerSubPix(gray, *corners, chess_size, 
                  cv::Size(-1, -1), 
                  cv::TermCriteria(CV_TERMCRIT_ITER | CV_TERMCRIT_EPS, 20, 0.01));

 return true;
}

int main(int argc, const char ** argv)
{
 cv::Mat src_img = cv::imread("chess.jpg");

 cv::namedWindow("src", CV_WINDOW_AUTOSIZE);

 cv::vector src_corners;
 find_chess_corners( src_img, &src_corners, cv::Size(10, 7), cv::Size(20, 20) );
 cv::drawChessboardCorners( src_img, cv::Size(10, 7), ( cv::Mat )src_corners, true);

 cv::imshow("src", src_img);
 
 if( cv::waitKey(0) == 'q' )
 {
  cv::imwrite( "dst.jpg", src_img);
  return 0;
 }
} 

Visual Studioで実行する場合は,以下の記述をプログラムの最初に追記することで,必要なライブラリがリンクされます.
*2410は2.4.10版のことですので,ご自分の環境に合わせて書き換えてください.
#ifdef DEBUG
#pragma comment(lib,  "opencv_calib3d2410.lib")
#pragma comment(lib,  "opencv_core2410.lib")
#pragma comment(lib,  "opencv_highgui2410.lib")
#pragma comment(lib,  "opencv_imgproc2410.lib")
#else
#pragma comment(lib,  "opencv_calib3d2410d.lib")
#pragma comment(lib,  "opencv_core2410d.lib")
#pragma comment(lib,  "opencv_highgui2410d.lib")
#pragma comment(lib,  "opencv_imgproc2410d.lib")
#endif

このプログラムを実行すると,以下のようにチェスボードの交点を見つけることができます.

src

それではプログラムの説明を行います.
このプログラムは,読み込んだjpeg画像からチェスボードの交点を探します.
交点を探す処理はfind_chess_corners関数で行っています.
この関数の引数は以下のようになります.
bool fined_chess_corners( cv::mat カラー画像, 
                          cv::vector<cv::Point2f> 交点を格納する配列, 
                          cv::Size チェスボードの交点数(行, 列), 
                          cv::Size 探索ウィンドウの半分の大きさ) 
この関数内では,以下のような2つの関数を使って交点位置を求めています.

・cv::findChessboardCorners関数
チェスボードの交点は,cv::findChessboardCorners関数で見つけることができます.
チェスボードがあれば真を返し,なければ偽を返します.
そして,検出したチェスボードのコーナーが配列に格納されます.
cv::findChessboardCorners関数の引数は以下のようになります.
bool findChessboardCorners( cv::mat カラーまたは8bitグレースケール画像, 
                            cv::Size チェスボードの交点数(行, 列), 
                            cv::vector<cv::Point2f> 交点を格納する配列, 
                            int 処理のフラグ) 

cv::cornerSubPix関数
cv::cornerSubPix関数を使うことで検出した交点位置の精度をあげることができます.
cv::cornerSubPix関数の引数は以下のようになります.
対象外の探索領域の半分の大きさにcv::Size(-1, -1)を指定すると,対象外領域はないということを指定できます.
この関数はコーナー位置の高精度化処理を繰り返し行うので,その最大反復回数と要求精度のどちらか,または両方を設定します.
void cornerSubPix( cv::mat 8bitグレースケール画像, 
                   cv::vector<cv::Point2f> 交点を格納する配列, 
                   cv::Size 探索ウィンドウの半分の大きさ, 
                   cv::Size 対象外の探索領域の半分の大きさ, 
                   cv::TermCritera 繰り返し処理の終了条件) 
私もはまったのですが,cv::cornerSubPix関数に与える画像は1chのグレースケール画像である必要があるので注意してください.

これで,チェスボードのコーナー点を求めることができました.
今回は以上となります.

参考文献:
OpenCV 2 プログラミングブック制作チーム『OpenCV 2 プログラミングブック OpenCV 2.2/2.3対応』マイナビ (2011/12/27)
「特徴検出」『opencv 2.2 documetation』<http://opencv.jp/opencv-2svn/cpp/feature_detection.html> (2015/02/03アクセス)
「カメラキャリブレーションと3次元再構成」『opencv 2.2 documetation』<http://opencv.jp/opencv-2svn/cpp/camera_calibration_and_3d_reconstruction.html> (2015/02/03アクセス)