ZawaWorks’s diary

プログラミング技術メモ

Processingとゲームコントローラを接続しよう!

Processingアドベントカレンダー2020 4日目です.

今回はGame Control Plusライブラリを紹介します.

lagers.org.uk

このライブラリを用いることで,XboxPS4・Joyconなどのゲームコントローラを使って,Processingで作成したゲームをプレイすることができます.

目標

コントローラのボタンを押して値を取得 f:id:ZawaWorks:20201204210530j:plain

環境

  • Mac OS Catalina 10.15.7
  • Processing 3.5.4
  • ファイティングパッド6B(メガドライブミニのコントローラ)

サンプルコード

gist.github.com

解説

Game Control Plusライブラリで取得できる入力の種類は,Button, Slider, Hatの三種類があります.

入力 説明
Button Aボタンやスタートボタンなど,押しボタン
Slider ジョイスティックや十字キーのような移動で使うボタン
Hat ジョイパッドやジョイスティックの上部などにある特別な入力

入力の説明

Button

Buttonは押しボタンを扱うクラスです.pressed()を使うとそのボタンを押しているかどうか検出してくれます.

 //テストコード
void draw() {
  for (int i = 0; i < device.getNumberOfButtons(); i++) {
    ControlButton button = device.getButton(i);
    if (button.pressed())println(i);//押したボタンの数字を表示
  }
}

Slider

Sliderジョイスティックや十字キーを扱うクラスです.-1.0から1.0の範囲で値を返します.

 //テストコード
void draw() {
  ControlSlider sliderX = device.getSlider("x");
  ControlSlider sliderY = device.getSlider("y");
  print("x: " + (int)sliderX.getValue());
  print(", ");
  println("y: " + (int)sliderY.getValue());
}

Hat

Hatはジョイパッドやジョイスティックの上部などにある特別な入力です.

今回使ったコントローラにはHatに対応する入力はありませんでした.

↓JoyConの場合,スティックボタンの取得に使えるらしいです.

note.com

checkControlType()

コントローラについている入力がどの種類なのか調べるのがサンプルのcheckControlType()です.これを使えばPS4のコントローラのような押しボタンや十字キー,スティックが複数ついているデバイスでも,どの種類の入力がどの値に対応しているか調べることができます.

今回は以下のような結果が出ました.(明らかに入力の数が実際のコントローラにあるものより多いのはなぜ……?) f:id:ZawaWorks:20201204210601p:plain

作品

今回のライブラリを用いて自分は「ソーシャルディスタンスでもできる乳首当てゲーム」を開発しました!

おわりに

Processingで作成したゲームをゲームコントローラで操作したいと昔から思ってました.

このライブラリの存在は早く知りたかったですね……

より詳しい内容は参考記事に書いてあります!ぜひご参照ください!

この記事でたくさんのProcessingユーザの方々に知ってもらえれば幸いです!

参考記事

hoshi-sano.hatenablog.com

mslabo.sakura.ne.jp

Processingで機械学習!? (RunwayML + Processing)

はじめに

Processingアドベントカレンダー2020 3日目の記事です.

RunwayMLとは,クリエイターのための機械学習ツールです.すでに複数の学習モデルが用意されており,それらを使うことで機械学習アート(勝手に命名を創作できます.

runwayml.com

このRunwayMLの良いところは,その出力結果をProcessingに送信できるという点です.すでにProcessingのサンプルが用意されており,今すぐ機械学習を用いたプログラム作成ができます.今回はRunwayMLと通信するためのセットアップの方法とサンプルの一部を紹介します.

目標

youtu.be

試した環境

  • Mac OS Catalina 10.15.7
  • RunwayML 0.15.0
  • Processing 3.5.4

セットアップ

1. RunwayMLソフトウェアをダウンロード

RunwayMLはWeb上で体験することが可能です.しかし,Processingとの通信には専用ソフトウェアをダウンロードする必要があります. ダウンロードは以下のリンクから行うことができます. runwayml.com

2. RunwayMLにサインアップ

RunwayMLのソフトウェアを入れたらサインアップします

3. Processingにライブラリをインポートする

今回使うサンプルを使うにはRunwayライブラリOSCライブラリが必要です. 「ライブラリを追加」からそれらを検索し,インストールしてください. f:id:ZawaWorks:20201203222017p:plain

サンプルを動かしてみよう!

今回はポーズキャプチャするプログラムを使ってみます. libraries > examples > OSC > PoseNetWebcam > PoseNetWebcam.pdeです 場所がわからない方は以下のリンク先にあるコードをコピペしてください.

github.com

1. RunwayMLでポーズキャプチャモデルを探す

f:id:ZawaWorks:20201203221831p:plain f:id:ZawaWorks:20201203221849p:plain

2. ポーズキャプチャモデルを起動する

モデルを選択すると以下の画像のような画面が表示されます. 右下にあるRunボタンを押すとポーズキャプチャが実行します. f:id:ZawaWorks:20201203221910p:plain f:id:ZawaWorks:20201203220253p:plain

3. PoseNetWebcam.pdeを実行する

そしてPoseNetWebcam.pdeを実行します.

するとProcessingの実行画面にポーズキャプチャの様子が描画されます.

youtu.be

コードの解説

PoseNetWebcam.pde起動時にProcessingとRunwayMLはこんな感じで通信しています. f:id:ZawaWorks:20201203224016j:plain runway.query()runwayDataEvent()runwayInfoEvent()runwayErrorEvent()Runwayライブラリに標準搭載している関数たちです.

基本的にrunwayDataEvent()で,RunwayMLが送信した基本的なデータ(今回は体パーツの座標)を非同期で取得しています.

この命令は他の学習モデルにも適用することができます.

他のサンプル

他には顔のパーツ(FaceLadmark)を取得することもできます. f:id:ZawaWorks:20201203224605p:plain また,画像を写実的に表現することもでき,それは以下の記事で細かく解説していました.

valed.press

おわりに

自分は機械学習に詳しくないので,このような作品を思いついても制作することができませんでした.RuwayMLによってProcessingにもその技術を応用できるようになり,創作するコンテンツの幅が広がるような気がします.

この記事を読んだ方々もぜひ機械学習を用いた創作にチャレンジしてみてください!

Unity+Vuforia: ARで画像と動画を表示する

今回はUnity とVuforiaを使ってマーカー上に画像と動画の表示方法をまとめました!

目標

youtu.be

目次

環境

  • Mac Catalina ver10.15.3
  • Unity 2019.3.9.f1
  • Vuforia Engine 9.0

事前準備

教材

drive.google.com

UnityとVuforiaのセットアップ

UnityとVuforiaの準備は以下の記事に書いてあるので,こちらの準備が終わってから進んでください.

note.com

画像の表示

1. Assetsの中にimagesフォルダを作りcat.jpegをいれる

f:id:ZawaWorks:20200423031751j:plain

2. catTexture TypeSprite (2D and UI)にする

f:id:ZawaWorks:20200423031745j:plain

3. catImageTargetの子オブジェクトにする

f:id:ZawaWorks:20200423031741j:plain

4. catの角度を調整して,マーカーと動画が平行になるようにする

f:id:ZawaWorks:20200423040619j:plain

5. 再生する

youtu.be

動画の表示

1. Assetsの中にvideosフォルダを作りcat.mp4をいれる

f:id:ZawaWorks:20200423031732j:plain

2. videosの中にRender TextureMaterialを作成する

f:id:ZawaWorks:20200423031737j:plain

3. testMaterialtestTextureを貼り付ける

f:id:ZawaWorks:20200423031727j:plain

4. ImageTarget の下に Quad を作成する

f:id:ZawaWorks:20200423031723j:plain

5. QuadVideoPlayerを追加する

f:id:ZawaWorks:20200423031719j:plain

6. Quadcat.mp4, testTexture, testMaterialを追加する

f:id:ZawaWorks:20200423031715j:plain

7. Quadの角度を調整して,マーカーと動画が平行になるようにする

f:id:ZawaWorks:20200423040540j:plain

8. 再生する

youtu.be

おまけ: 動画の縦横比を変える

先ほどの手順だけでは,動画とQuadの縦横比があっていないため,黒い部分ができてしまいます.

ここではQuadの縦横比を動画と同じにして,黒い部分ができない方法を教えます.

1. AssetsGetVideoAspectRatioEditor.csを追加する

f:id:ZawaWorks:20200423031710j:plain

2. VideoPlayerを右クリックしてGet Aspect Ratio for Meshを押します

f:id:ZawaWorks:20200423031706j:plain

3. Render ModeMaterial Overrideにする

f:id:ZawaWorks:20200423031702j:plain

4. 再生する

youtu.be

参考資料

www.sejuku.net

ekulabo.com

unitycoder.com

Processing: 円と円の交点を求めよう

目次

はじめに

目標

今回は円と円の交点を求めてみます

サンプルコード

動画のコードは以下のようになります 記事の後半では、この中のgetCirclesCrossPoints()という関数について解説していきます

gist.github.com

解説

数式で考えてみる

2つの円の方程式を用いて考えます

円の中心座標が(x_1, y_1)で半径がr_1の円の方程式


(x-x_1)^2+(y-y_1)^2=r_1^2\cdots(1)

中心座標が(x_2,  y_2)で半径がr_2の円の方程式


(x-x_2)^2+(y-y_2)^2=r_2^2\cdots(2)

このとき(2)-(1)をすると

2(x_2-x_1)x + 2(y_2-y_1)y + x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2=0\cdots(3)

この(3)は円の交点を通る直線の方程式になっています f:id:ZawaWorks:20200106014311j:plain あとは(3)の直線と(1)または(2)の円との交点を求めれば、円と円の交点を求めることができます 直線と円の交点の求め方は以下の記事を参考にしてください

zawaworks.hatenablog.com

コードにしてみよう

a=2(x_2-x_1)\\b = 2(y_2-y_1)\\c=x_1^2-x_2^2+y_1^2-y_2^2+r_2^2-r_1^2

とおきます。それを円と直線の交点を求める関数に代入すれば円と円の交点が返ってくるようになっています。

//円と円の交点を取得する関数
PVector[] getCirclesCrossPoints(float x1, float y1, float r1, float x2, float y2, float r2) {

  float a = 2*(x2 - x1);
  float b = 2*(y2 - y1);
  float c = sq(x1)-sq(x2)+sq(y1)-sq(y2)+sq(r2)-sq(r1);
  return getLineCircleCrossPoints(a, b, c, x1, y1, r1);
}

//直線(ax + by + c = 0)と円(中心座標(circleX, circleY), 半径 r)の交点を取得する関数
PVector[] getLineCircleCrossPoints(float a, float b, float c, float circleX, float circleY, float r) {
    //中身はこちらの記事を読んでください
    //http://zawaworks.hatenablog.com/entry/2019/12/04/012717?_ga=2.20645616.952110635.1578237427-1521756013.1576935957
}

Processing: 2番目の画面でクラスを使うときの注意

Processingは通常画面が一つしか作成されませんが,PAppletクラスを継承するクラスを作れば画面を複数作成することができます. f:id:ZawaWorks:20191207161923j:plain

問題

新しくTestクラスを作りました.これは画面の背景を黒くするクラスです.

このクラスをSecondWindowdraw()に書きましたが,背景が黒くなりません.

f:id:ZawaWorks:20191207162005j:plain

解決方法

これは以下のようにSecondWindow内にPApplet型の変数を用意して,applet.background(0)とすれば2番目の画面の背景色を変更することができました. f:id:ZawaWorks:20191207162716j:plain

今回のコードは,すべてこちらにあります. 2番目の画面内でクラスを使うときの注意 · GitHub

解説

f:id:ZawaWorks:20191207165911j:plain PAppletクラスは画面を作成し,setup()draw()background()を管理しているクラスです. Processingでは起動するとPAppletクラスが呼ばれて画面を作成し,その中身はsize()background()などで変更されます. SecondWindowクラスはそれを継承して,新しい画面を作成し元のPAppletクラスと同じ機能(setup()background()など)を備えています.

background()ellipse()などの命令を自分の作ったクラス(今回はTestクラス)で呼ぶと,それは1番目のPAppletの命令を呼んでしまうので,2番目のPAppletSecondWindow)の中身には何も変化が起きません.

これは,クラスの内で呼ばれるbackground()などの命令が,どのPAppletの命令なのかを指定することで解決することができるのです.

ちなみに

クラス内でmouseXmouseYとしても,1番目の画面のマウス座標を取得してしまうので,これもapplet.mouseXのように指定する必要があります.

class Test {
  PApplet applet;

  Test(PApplet _applet) {
    applet = _applet;
  }

  void display() {
    applet.background(0);
    applet.fill(255);
    applet.ellipse(applet.mouseX, applet.mouseY, 20, 20);
  }
}

参考文献

3846masa.hatenablog.jp

Processing: 2点を通る直線と円の交点を求めよう

目標


Processing: 2点を通る直線と円の交点を求めよう

今回は以下のようにある2点を通る直線と円の交点を求めるgetCrossPoints()という関数を作ります.

//交点を取得する関数
PVector[] getCrossPoints(float x1, float y1, float x2, float y2, float circleX, float circleY, float r) {

  //ax + by + c = 0 の定数項
  float a = y2-y1;
  float b = x1-x2;
  float c = -a*x1-b*y1;

  //円の中心から直線までの距離
  //mag(a, b) = √a^2+b^2
  float d = abs((a*circleX+b*circleY+c)/mag(a, b));

  //直線の垂線とX軸と平行な線がなす角度θ
  float theta = atan2(b, a);

  if (d > r) {
    return null;
  } else if (d == r) {
    PVector[] point = new PVector[1];

    //場合わけ
    if (a*circleX+b*circleY+c > 0)theta += PI;

    float crossX = r*cos(theta)+circleX;
    float crossY = r*sin(theta)+circleY;

    point[0] = new PVector(crossX, crossY);
    return point;
  } else {

    PVector[] crossPoint = new PVector[2];
    float[]crossX = new float[2];
    float[]crossY = new float[2];

    //alphaとbetaの角度を求める
    float alpha, beta, phi;
    phi = acos(d/r);
    alpha = theta - phi;
    beta = theta + phi;

    //場合わけ
    if (a*circleX+b*circleY+c > 0) {
      alpha += PI;
      beta += PI;
    }

    //交点の座標を求める
    crossX[0] = r*cos(alpha) + circleX;
    crossY[0] = r*sin(alpha) + circleY;

    crossX[1] = r*cos(beta) + circleX;
    crossY[1] = r*sin(beta) + circleY;


    for (int i = 0; i < crossPoint.length; i++)
      crossPoint[i] = new PVector(crossX[i], crossY[i]);

    return crossPoint;
  }
}

動画のプログラムは,こちらを実行すれば体験できます.

円と線の交点を求めたプログラム · GitHub

2点を通る直線と円の交点の求め方

ここからは直線と円の交点をどうやって求めているのか解説したいと思います

直線と円の交点があるかを確認

f:id:ZawaWorks:20191203233704j:plain まず「直線と円が交わっているのか?」を確認しないといけません. 確認の方法は至って簡単で,直線と円の中心の距離をdとしたときに,r < dだったら直線が円から離れているので交わっていません. r = dのときはちょうど円と直線が接しています.そして,r > dだったら直線と円は2つの交点を持ちます.

dの求め方

d点と直線の距離の公式を使うことで,求めることが可能です.

点(x_0, y_0)と直線(ax_0+by_0+c = 0)の距離の公式


d=\dfrac{|ax_0+by_0+c|}{\sqrt{a^2+b^2}}

コードにすると……

 //円の中心から直線までの距離
 //mag(a, b) = √a^2+b^2
 float d = abs((a*circleX+b*circleY+c)/mag(a, b));

if  (d > r) {
 //交点が0のとき
}else if (d == r) {
 //交点が1つのとき
}else{
 //交点が2つのとき
}

直線と円が接しているとき

まず直線と円が接しているときに交点の座標をどのように求めるのかを解説していきます.

交点の座標の求め方

f:id:ZawaWorks:20191203234227j:plain 例えば円の中心が原点だったときのことを考えます. 円と直線の距離をdとし,直線の垂線がX軸と平行な線となす角をθとすると交点の座標は

float x = r*cos(theta);
float y = r*sin(theta);

となります.

円の中心が(centerX, centerY)だった場合は平行移動させればいいので

float x = r*cos(theta) + centerX;
float y = r*sin(theta) + centerY;

とすれば,交点の座標を求めることができます.

θの求め方

f:id:ZawaWorks:20191203234921j:plain 直線と円との距離は,円の半径と同じであるため求めるのが簡単です.一方,θの値はどうやって求めればいいのでしょうか.

円と接する直線の方程式をax + by + c = 0とすると,その垂線の傾きはb/aとなることが知られています. そして,傾きはtan()を使って表すこともでき,tan(θ) = b/aとなります. これを満たすθを求める命令がatan2()です.

float theta = atan2(b, a);

これでθの値を求めることができました.

atan2()の罠

f:id:ZawaWorks:20191203235520j:plain 先ほど紹介したθの求め方だけだと,正しい交点の座標を求めることはできません.それは.θの値を直線の方程式のaとbを使うことで角度を求めているので,その値に円と直線の位置関係が反映されていないからです. これは,a*centerX + b*centerY + cが0より大きいか小さいかで場合わけすることで対処することができます(理由は後日記述します).

float theta = atan2(b, a);
 if (a*circleX+b*circleY+c > 0) theta += PI;

このようにa*circleX+b*circleY+c > 0のときにθに180°足すと,円と直線の位置関係が反映されて,正しい交点の座標を求めることができます.

コードにすると……

これまで説明したことをコードにすると以下のようになります.

float theta = atan2(b, a);

//場合わけ
if (a*circleX+b*circleY+c > 0)theta += PI;

float crossX = r*cos(theta)+circleX;
float crossY = r*sin(theta)+circleY;

直線と円の交点が2つのとき

次に直線と円の交点が2つのときに交点の座標をどのように求めるのかを解説していきます.

交点の座標の求め方

f:id:ZawaWorks:20191204000947j:plain 直線と円が接しているときと同じ考え方で求めます.円の中心と交点を通る直線X軸と平行な線のなす角をそれぞれα, βとすると

float crossX1 = r*cos(alpha) + centerX;
float crossY1 = r*sin(alpha) + centerY;

float crossX2 = r*cos(beta) + centerX;
float crossY2 = r*sin(beta) + centerY;

として求めることができます.

αとβの求め方

f:id:ZawaWorks:20191204001750j:plain 直線の垂線AがX軸と平行な線となす角をθとし,「円の中心を通る垂線A」が「円の中心と交点を通る直線B, C」となす角をそれぞれγδとすると

float alpha = theta - gamma;
float beta = theta + delta;

として求めることができます.

γ,δの求め方

f:id:ZawaWorks:20191204002409j:plain 「円の中心と交点の直線」と「円の中心から直線の垂線」を引きます. そしてできた直角三角形を見てみると,それぞれ合同であることが分かります. つまり,φ = γ = δとなるφの値を求めるだけで済みます.

直角三角形であることを利用すればφを求めることは簡単です. r, d, φは,cos(φ) = d/rという関係があります.

これはatan()のときと同じようにacos()という命令を使えば,求めることが可能です.

float phi = acos(d/r);

これでphiの値を求めることができました.

コードにすると……

これまで説明したことをコードにすると以下のようになります. (atan2()の場合わけも忘れないように!)

phi = acos(d/r);
alpha = theta - phi;
beta = theta + phi;

//場合わけ
if (a*circleX+b*circleY+c > 0) {
   alpha += PI;
   beta += PI;
}

//交点の座標を求める
crossX[0] = r*cos(alpha) + circleX;
crossY[0] = r*sin(alpha) + circleY;

crossX[1] = r*cos(beta) + circleX;
crossY[1] = r*sin(beta) + circleY;

おわりに

今回は,2点を通る直線と円の交点の求め方について解説しました.この記事の知識をプログラム作品に応用してもらえたら幸いです.

Python : pandasでdf['hoge']が使えないときの対処法

はじめに

自分の研究でデータ分析をするためにpandasを使い始めました.pandasはcsv(tsv)ファイルをdf = pd.read_csv('hoge.csv')(またはread_table)で読み込んで,df['hoge'] という形で指定したカラム(列)を配列として抽出できます.しかし,あるときなぜかそれができませんでした.そこで,そのときの対処法をここでまとめました.

今日使うデータの説明

今回使うデータは以下の通りです.

hoge.csv

id sex name
0 M Yuta
1 F Rikka
2 M Sho

ちなみに表の一番上にあるid, sex, nameのタイトルが書かれているカラムのことをヘッダーといいます.

コード

df pd.read_csv('hoge.csv')

print(df['id'])
print(df['sex'])
print(df['name'])

これがうまくいけば以下のような結果が返ってきます.

実行結果

[0, 1, 2]
[`M`, `F`, `M`]
[`Yuta`, `Rikka`, `Sho`]

read_csvかread_tableかを確認

pandasには 'read_csv' と read_table という二つの命令があります.これらは前者がcsvファイルを読み込むときに使い,後者が read_table ファイルを読み込むときに使います.

read_csv read_table
csv専用 tsv専用

そのため,read_csv('hoge.tsv')read_table('hoge.csv') とすると,ヘッダーが読み込めなくなり,df['id']と呼んでも[0, 1, 2] の配列は返ってきません. ちゃんとread_csv('hoge.csv')read_table('hoge.tsv')になっているか確認しましょう.

index_col=0 またはheader=0を入れる

たとえばread_csv('hoge.csv')をやってもdf['id']が出ない場合,hoge.csvが「ヘッダーがないcsvファイル」として読み込まれている可能性があります.csvファイルには「ヘッダーあり」の場合とありますと「ヘッダーなし」の場合があります.df['id']でカラムを抽出できるのは,ヘッダーありcsvの場合なので,ヘッダーがないcsvだとできません.このとき,ヘッダーありのcsvとして読み込むためには,以下のように書きます.

df = pd.read_csv('hoge.csv', index_col=0)
df = pd.read_csv('hoge.csv', header=0)

index_col = 0は「0行目をindexとして使いますよ」という変数で,header=0は「0行名をヘッダーにするよ」という命令です.これを指定あげることで,「0行目がヘッダーのcsvファイル」として読み込むことができます.