dotfiles を作ってみた

公開日: 2021年2月20日 最終更新: 2021年2月20日 執筆者: watamario15

大学の課題を全て出し終わり、ようやく自分の作業に取り組むことができるようになりました。無事に単位が取れていて、GPA もあれば良いのですが…

色々とやることはあるのですが、まずは dotfiles を作成することにしました。

dotfiles とは

検索すればすぐに分かるのですが、簡単に言うと環境構築を秒速で終わらせるためのファイル群です。Unix 系のソフトウェアの多くはテキストファイルを設定ファイルとして扱うものが多く、それをカスタマイズすることで自分好みに設定することができるようになっています。ここで、そのファイル群とそれを適切に配置したりその他の設定をしたりするスクリプトもセットで GitHub に上げておけば、新しい環境でも git clone してスクリプトを走らせるだけで即座に自分の環境が出来上がる、という訳です。

なお、dotfiles という名前は、設定ファイルの多くが名前がドット (.) で始まる「ドットファイル」であることが由来です。ちなみに、Unix 系システムにおいてドットファイルは隠しファイルとなります。

どんな dotfiles を作ったのか

これが私の dotfiles です: https://github.com/watamario15/dotfiles
機能や使い方などの説明は Readme.md にありますので、気になる方は読んでください。もちろん、install.sh.bashrc とかを参考にしてもらっても構いません(特に alias 周り)。まだまだ未熟ですが、少しずつ成長させて行けたらと思っています(この記事を読んでいる方で、もし「絶対これは設定すべき!」みたいなのがあれば issue 立てたり pull request 出したりして頂けると嬉しいです)。initialize.sh に付けた CASLII/COMETII のシミュレータをインストールする機能、阪大生(だけ)には割と需要ありそうですね(笑)

今回の投稿は以上です。ところで、推薦入試の合格発表があったそうですが、合格した皆さんおめでとうございます!もし合格してサークルを探している、という方で OUCC の活動に興味を持たれた方がいらっしゃれば、ぜひ Discord 新歓サーバー に参加してみてください(もちろん上回生も大歓迎!)。もちろん、これから一般入試だという方も全力で応援します!

GAN使用のアプリ制作における雑記

~あらすじ~

 GANを3か月前から学び始めたんですが、OUCCのAdvent Calendarの24日目?の記事を書くに当たって、自分が今までやったことがない技術を使用しようと思いまして、StackGANというGANを使用したアプリを制作しようとしました。具体的には大阪大学生協食堂の料理の写真とその料理名を学習して、入力された架空の料理名から架空の料理画像を生成するアプリです。結論から言いますと上手くいかなかったのですが、その間に得られた知見などの心に移りゆくよしなし事をそこはかとなく書きつくりました。

~GANって?~

GANについて軽く説明します。詳しくはググってください。

 GeneratorとDiscriminatorという2つの学習するモデルから構成される機械学習モデルです。小学校とかの先生と生徒の関係で例えると、生徒のGenerator君は答えを写した宿題を作成し、Discriminator先生は提出された答案が答えを写したものかきちんと解いて得られた答案かを見分けます。その見分けた結果、答えを写したことがばれて叱られたGenerator君は、学習してより自分で解いたかのように見える答案を作成します。Discriminator先生もきちんと解いてきた答案かどうかを見分けられる様に学習します。そのように一方が利するともう一方が損する関係によってお互いが高めあい、Generator君は自分で解いた答案と変わらない答案を作成することができるように成長します。つまり、Generatorモデルが最終的に本物そっくりのものが生成できるようになる学習の仕組みがGANというモデルです。

~StackGANって?~

 私も最近知ったのでよく知りません。言語から画像を生成する方法ないかなと探していたら発見したGANの一種のモデルです。これも詳しくはGoogle先生に教えを乞うか、私が参考にしたサイトを閲覧してください。(GANの説明に疲れて丸投げしたのでは無い)

 なお、レシピから料理を生成するCookGANというものがあるみたいですが、今回は料理名をラベルとして使用するので恐らく使えないです。

・何が駄目だったのか

 あまり制作にかける時間がなかったので原因究明はきちんとはされていないですが、早い話学習データが圧倒的に不足していました。もし、学習が上手くいかなくてどういうわけかこのページに行きついてしまったかわいそうな人にはこんな結論で申し訳ないです。それはさておき何が駄目だったかというと、130枚の、しかもラベルが重複しない、共通点としては器の形が似ているだけの画像で学習しようとしたことが無謀でした。そのデータ数でよくやろうと思ったなと言われそうですが、言い訳をすると入力する料理名に含まれる名詞は重複が多く何とかなるかなと思ってやってみた次第です。さらにGANの学習データを水増しするDifferentiable Augmentationという技術を発見して、もしかしたら出来るのでは?と愚考した次第です。

 Differentiable Augmentationについて軽く説明しますと、従来の画像のクラス分類学習では訓練データにちょっとした加工を加えることでデータの水増しを行うことができましたが、GANの学習ではさらにGeneratorの生成した画像に同様の加工を加えることで、より良いデータの水増し効果が得られるという技術がDifferentiable Augmentationです。間違っていたらすみません、詳しくはgoog(ry

・使用したデータの一例

 一枚目がピリ辛サーモン丼。美味で個人的なおすすめ。二枚目はマヨラーの友人によって犠牲となった親子丼。親子丼に何の恨みがあったのだろうか。なお、二枚目は学習には使用していません。

・結果

 StackGANのstage1の学習段階で上手くいきそうな気配がないのでやめました。

一応stage1の学習結果だけ載せておきます。

心が清い人には遠目で見るとかろうじて料理に見えるはず。

 これだけでは申し訳ないので、同じデータセットで学習したDCGANで生成したものを貼り付けておきます。

 右下以外はまんま存在する料理が生成されています。あ、そう くらいのつまらなさで申し訳ない。やはり、生成画像を人間がコントロールできるものを作った方が楽しいですね。

・損失関数について

 最近GANの学習にはHinge Lossを使用すれば上手くいくよという記事を見つけたので、これとは違うGANのアプリに実装すると劇的に学習が向上しました。なのでこのDCGANにもHinge Lossを適用したらより上手くいくかと思ったら、逆に学習しなくなってしまいました。Hinge Lossの取る値はReLUみたいに途中から一定の値になりますが、Binary Cross Entropyは少しの値の変化もLossの値に反映されるので、これが原因ではないかと考えています。

~終わりに~

 いかがでしたか?ろくな原因究明をしていない分、悪質なキュレーションサイトの方がよっぽど役に立つような内容でした。この山無し落ち無し意味なしのやおいページにお付き合い下さり有難うございました。そして1月になって記事を書いたことお許しください。

著者:AI班上月

昔作ったゲームを振り返る

これはアドベントカレンダー25日目の記事です。なんでトリなんて選んだんでしょうね。

特に書くこともないので昔作ったゲームでも紹介します。
1つ目はブロック崩し。
僕が初めて作ったゲームです。javascriptとhtmlで作りました。

2つのゲームを同時にクリアさせる必要があります。カーソルは1つなのでなかなか忙しいです。当てるなってやつに当てると画面が変化して見づらくなります。初めて作ったにしてはなかなかいい発想してたと思います。


2つ目は人生ゲーム。何番目に作ったのかは覚えてません。
小学生のころ考えた留置所と戦争に行くという要素を含んだものです。
ほかにも途中で無職になったり、事故を起こして裁判になったりと、およそ市販できないようなマスを用意しています。最近のマスがどんなものか知りませんが。大体所持金がマイナスになるというそれはひどい人生ゲームです。ちなみに画像は無職なのに車を買って事故を起こし、裁判で賠償金490万払ったところです。賠償金安すぎですね。

3つ目は陣取りゲームです。某イカのゲームが流行っていたので作りました。

右にあるいろいろな塗り方から選んで盤面を塗っていきます。緑色の場所しか選べず、盤面がすべて赤色か青色で塗られたらゲーム終了。色の多いほうが勝ちです。一番右にある4つはスペシャル技で、1種類だけ使えます。正方形が強かった気がします。

最後はトランプオセロ。これだけ大学に入ってから作ったものです。部室でトランプで遊んでた時にふと思いついたのでゲームにしたものです。

トランプでオセロをすることで、単純に表の数でなく数値の和で勝敗を決める戦略性、裏が同じ模様のトランプであることから両者が使える裏というおき方、裏返ったカードの色と数値を記憶する必要があるという神経衰弱要素、そしてジョーカーを使うことで場のカードを1枚ひっくり返せるという逆転要素が盛りだくさんです。自分の周りでは結構評価高かった気がします。

こうして振り返ってみるとなかなか頑張ってたなという気がします。今はこんな気力はないですけど、また面白そうなもの思いついたら形にしておきたいですね。たとえ日の目を浴びなくても、こうして思い出すことで自分も頑張ってたんだな、とか面白いこと考えてたな、とか懐かしい気持ちになれるなら形にした意味はあるんじゃないでしょうか。

SHARP Brain 用アプリケーションの作成方法

投稿: 2020年12月20日 最終更新: 2021年2月19日 投稿者: watamario15

この記事は OUCC Advent Calendar 2020 の 20 日目の記事です。

OS として Windows Embedded CE 6.0、CPU に ARM926EJ-S (ARMv5TEJ) を搭載する電子辞書 SHARP Brain 用アプリケーションの作成方法を簡単に解説していきます。これについてまとまっているサイトがあまりない印象なので。

注意点

  • ここで取り扱うものは、SHARP 公式の内容ではありません。普通、フリーズなどが起こった場合もリセットボタンを押せば元に戻りますが、万一何かが起こった場合も一切保証できませんので、自己責任で試してください
  • 新機種の多くには、SHARP 公式のソフトウェア以外を起動できなくするプロテクトが掛かっています。具体的には、ビジネスモデル (PW-SBx) を除く PW-Sx4 以降の機種がそれに該当します。

作成するソフト

この記事では、プログラミングより開発手順(開発環境やコンパイル方法など)の解説に重点を置くため、非常に単純なプログラムを使用します。OUCC_Advent_2020.cpp をダウンロードしてください。

開発環境

以下の 3 通りを解説していきます。

  • Microsoft eMbedded Visual C++ 4.0
  • Pocket GCC
  • CeGCC

電子辞書での実行方法について

内部ストレージまたは micro SD カードの「アプリ」フォルダに(なければ新規作成)、空のフォルダを作成し(フォルダ名はメニューに表示させたい任意の名前。日本語もOK)、その中にコンパイルして得られた実行ファイル(AppMain.exe になっていなければ AppMain.exe にリネーム)と、 index.din (ダミーの空ファイル) を入れてください。これで、注意点で記載したプロテクト付き機種でなければ「アクセサリー」->「追加コンテンツ」->「追加アプリ・音声」(機種によって違う可能性あり)に表示され、実行できるようになります。加えて、Sx1 世代以降の機種の場合、AppMain.cfg (ダミーの空ファイル) も加えることで高解像度状態のまま実行できるようになります。

なお、index.din 及び AppMain.cfg は空のテキストファイルを作成し、ファイル名を index.din, AppMain.cfg (もちろん元の拡張子 .txt は消す) に変更するだけで作成が可能です。ちなみに、ceOpener などの別ツールを導入済みなどで、 exe ファイルを直接実行する場合は以上の作業は不要です。どんな名前でもどこに置いても構いません。

Microsoft eMbedded Visual C++ 4.0

知る人ぞ知る、大昔(2000年辺り)の Windows CE 用 Microsoft 純正無料 IDE です。おそらくこれが最も使いやすいと思いますが、激古なので C++ の新しい機能はほぼ使えないことと、Windows 2000 と Windows XP でしか動作しないという難点があります。私は組み込み機器に C++ の最新機能など期待していない(笑)ことと、奇跡的に何とかギリギリ動作する Windows XP パソコンを所有しているという理由で現在主に使っています。ただし後述の CeGCC が使いこなせるようになったら乗り換えるかもしれません。

環境構築

まず、 Microsoft Download Center からダウンロードしてください。これは自動解凍式の圧縮ファイルになっているので、実行して適当な場所に解凍してください。それで得られたファイルの、setup.exe を実行し、あとは手順に従ってください。なお、途中でプロダクトキーを聞かれますが、先ほどのダウンロードページの「インストール方法」欄に記載されているものを入力すれば大丈夫です。

私がインストールしてから結構経っているので忘れたのですが(おいw)、もし起動時に SDK がないみたいなことを言われた場合は、先ほど実行した setup.exe と同じ場所にある SDK フォルダの setup.exe を実行してください。多分これでいけるはずです。

プロジェクト作成・ビルド

まず、左上の「ファイル(F)」から「新規作成」をクリックしてください。その後、下の画像のように設定してください。CPU に関して、 SHARP Brain は ARMv5TEJ ですが、このコンパイラにはないため ARMv4I を選択してください(SHARP Brain 以外の CE 端末用のコンパイルも行いたければ、ARMv4I に加えてその他の CPU を選択してもかまいません)。設定できたら「OK」を押してください。

ここでは、「空のプロジェクト」を選択してください。

次にプロジェクト概要の画面が出るので、OKを押してください。これでプロジェクトが作成されます。

その後、左を FileView に切り替え、Source Files を右クリックしてソースファイルを追加してください。分かりやすくなるよう、ソースファイルはプロジェクトフォルダ内に予め入れておくことをお勧めします。

ソースファイルが追加できたら、FileView から先ほど追加したファイルをダブルクリックすると、画面内にソースファイルが表示されます。ここでプログラムを編集することが可能です。今回はソースファイルを UTF-8 エンコードしたため、コメントの日本語が文字化けしていますが無視してください(気になるようなら Shift_JIS でエンコードし直してください)。というか何で WinCE は Unicode のみ対応なのに eVC4 はUnicode 非対応なのだろうか…

ここで、SHARP Brain で実行する際は AppMain.exe になっていた方が色々とうれしいので、デフォルトでこの名前になるように設定しておきます。「プロジェクト」->「設定」を開き、「リンク」タブに移動すると下のような画面になるので、画像のように出力ファイル名を編集して OK を押してください。

これで準備完了です!では、下の画像の赤丸で囲ったボタンを押してください。

ビルド終了後、以下のようなメッセージが出ますが無視してください。コンパイルエラーがなければコンパイル成功です。

プロジェクトフォルダの ARMV4IDbg に AppMain.exe が生成されているはずですので、電子辞書にコピーしてして実行してみてください。以下のように表示されれば成功です!

ちなみに、 この記事では扱いませんが Win32 (WCE ARMV4I) Debug と書かれているプルダウンメニューから、Release ビルドへの切り替えや他の CPU の選択(最初にチェックボックスを付けていた場合)が可能です。

Pocket GCC

例によって激古ですが、SHARP Brain 上でのセルフ開発が可能な GCC の Windows CE 移植版コンパイラです。上のコンパイラよりさらに機能は限られますが、Windows XP の骨董品 PC が不要でかつ扱い方が確立しているので紹介しておきます。

準備

手順が非常に多くここに書くのはあまりに大変なので、Brain Wiki のページ を参照してください。

一通り終わったら、OUCC_Advent_2020.cpp を win.cpp に改名したものと WINBUILD.BAT (右クリック -> リンク先を保存)を電子辞書内の同じフォルダに入れてください。この際、WINBUILD.BAT の2行目と3行目を環境に合わせて編集しておいてください。

その後、DOS窓Open からWINBUILD.BAT の所まで移動して(空白や日本語を含んでいてもクォーテーション不要で普通に cd でいけます)実行してください。するとそこに AppMain.exe が生成されるので、それを実行して前節の eVC4 の最後の画像のようになれば成功です!

ちなみに、この記事では説明しませんが WINBUILD.BAT は resource.rc があった場合、それも含めてコンパイルするようになっています(その際、34行目の -l commctrl は削除してください)。

CeGCC

つい先日知ったばかりで、私も未だに扱い方がよく分かっていないコンパイラです。しかし、最新の Debian (Ubuntu も OK) でコンパイル可能(WinXP 不要!)でかつ、なんと GCC 9.3.0 ベース!!!!(執筆時点で MinGW より新しい)という、今まで 2000 年前後のコンパイラを使っていた私には衝撃の見過ごせない特徴を持っていたので、紹介することにします。

前提

Windows Subsystem for Linux の Ubuntu で説明します。おそらく Debian や通常版の OS でも同じような方法でできるはずです。

なお、基本的に公式サイト(とはいえ有志の個人開発者様ですが)である cegcc/mingw32ce (kellermann.name) に記載の手順に沿って説明していきます。

環境構築

/etc/apt/sources.list ファイルに、以下の 1 行を追加します(元サイトとは少し違いますので注意)。

deb [trusted=yes] https://max.kellermann.name/debian cegcc_buster-default main

この作業にはテキストエディタを用いても構いませんが、今回はファイルの末尾に足すだけですので、以下のコマンドを実行すればよいです(1 行目は改行、2 行目は実際の追記)。

sudo bash -c 'echo >> /etc/apt/sources.list'
sudo bash -c 'echo "deb [trusted=yes] https://max.kellermann.name/debian cegcc_buster-default main" >> /etc/apt/sources.list'

その後、以下のコマンドを実行してパッケージをインストールしてください。

sudo apt update
sudo apt install gcc-arm-mingw32ce

終わったら、以下のコマンドを入力してください。

arm-mingw32ce-g++ -v

以下のような出力が得られれば環境構築は終了です。お疲れさまでした!コマンドが見つからないというエラーが出た場合は、正しくインストールできていませんので今までの手順をもう一度確認してください。

Using built-in specs.
COLLECT_GCC=arm-mingw32ce-g++
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/arm-mingw32ce/9.3.0/lto-wrapper
Target: arm-mingw32ce
Configured with: ../../../configure --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=arm-mingw32ce --disable-dependency-tracking --prefix=/usr --syscon
fdir=/etc --with-gcc --with-gnu-ld --with-gnu-as --enable-threads=win32 --disable-nls --enable-languages=c,c++ --disable-win32-registry --disable-multilib --dis
able-interwork --without-newlib --enable-checking --with-headers --disable-__cxa_atexit
Thread model: win32
gcc version 9.3.0 (GCC)

なお、 Cygwin 用は別の方がビルドしてくださっているようで、ここからダウンロードすれば使えるかもしれません(試していないため不明)。その他 Debian 系でない Linux で使用する場合は、ソースからのコンパイルが必要です。

ビルド

OUCC_Advent_2020.cpp を配置したディレクトリで、以下のコマンドを実行してください。

arm-mingw32ce-g++ -Wall -O2 -std=gnu++2a -march=armv5tej -mcpu=arm926ej-s -static -s -lcommctrl -o AppMain.exe OUCC_Advent_2020.cpp

成功すると AppMain.exe が生成されるので、電子辞書にコピーして実行してください。なぜか全画面表示にはなりませんが(ShowWindow(hWnd, SW_MAXIMIZE); が利いてないっぽい?)、下のようなウィンドウが表示されれば成功です!

追記

急ごしらえで sh スクリプトを作成しました。Build_CeGCC.sh を、コマンドラインオプションにソースファイル名を指定して実行すると AppMain.exe が出力されます。カレントディレクトリに resource.rc があればそれも併せてコンパイルします。

(2020年12月22日更新) 上記のコンパイルコマンド及び sh スクリプトを、新たな知見 (strip) に基づき更新しました。これにより、スタティックリンクを行うと実行ファイルが巨大化する、場合によって起動が遅くなったりメモリ使用量が増える問題は解消しました!!ShowWindow(hWnd, SW_MAXIMIZE); が利かない問題はまだ解消できていませんが、eVC4 から CeGCC への乗り換えが現実味を帯びて来たかも…?

おわりに

今回は、SHARP Brain 用ソフトウェア開発の方法をまとめました。もしかしたらこういうのは Brain Wiki を拡充する方針で書くべきだったのかもしれませんが(笑)、参考になれば幸いです。SHARP Brain のハックに興味がある方は、Brain での Linux 起動など、様々な挑戦が行われている Brain Hackers の Discord コミュニティがあります(ここに紹介があります)ので、そこに参加してみても面白いかもしれません。ただし、Brain Wiki 及び Brain Hackers は当部(OUCC; 大阪大学コンピュータクラブ) とは一切無関係ですのでご注意ください。

なお、今回は Windows API プログラミングについては一切触れていません。興味がある方は、Windows プログラミング入門 – Web/DB プログラミング徹底解説 が参考になると思います。ただし、C/C++ をそれなりに理解していることが求められます。また、最も原始的な方法となるため、比較的レベルが高くなります(高2の頃、Hello, world! にたどり着くまでに 1,2 週間かかりました)。さらには調べてもなかなか出てこない Windows CE の独自仕様も相当あるので(Command Bar が代表例)結構大変です。でも電子辞書で自作ソフトが動作する感動はなかなかなものです!

ちなみに、執筆者もいくつかの SHARP Brain 用ソフトウェアをオープンソースで公開しています。執筆地点では素因数分解プログラム超大量ファイル整理KN MemoPad 機能追加版 がありますので、もしよければ使ってみてください。Windows API プログラミングに関しても何か参考になるかもしれません。

では、ここまでお読みいただきありがとうございました!OUCC では、現在も部員を募集中です。興味がある方は、ぜひご気軽に Discord 新歓サーバー にご参加下さい!

Advent Calender 参加方法

前提

今回使用するサイトは、adventar.orgというものです。このサイトに登録するにあたり、

  • Google
  • GitHub
  • Twitter
  • Facebook

のいずれかのアカウントが必要になりますので、あらかじめご準備をお願いします。

参加登録

まず、指定されたURLを開きます。(2020年度のアドベントカレンダーのURLはこちら)

次に、赤円で囲んだマークをクリックして、アドベントカレンダーと連携させるアカウントを選択します。

そこからは、アカウントごとに連携させる手順があるので、各自で登録をお願いします。

無事にログインできたら、まだ枠が埋まっていない日の中から投稿したい日を選択します。

すると、選択した後に以下のようなウィンドウが現れるので、上の入力部分には投稿する記事の大まかな内容を記入し、下の入力部分には投稿する日になったときにそのサイトのURLを貼り付けます。

以上の手順によりアドベントカレンダーへの登録は完了となります。

院試終わりました

さて、お盆の前の8月上旬の今日この頃、筆者はとうとう院試が終了しました。

私が受けた院試は、大阪大学の大学院情報科学研究科というところで、8月1日と2日の二日に分けて行われました。

今年は今までの院試とは違って全体の問題量が少なくなっており、その分一問当たりの占める得点の重みがあったので、一問一問を大事にしながら問題を解いていきました。

他の大学の院試では、SQL( insert ~ from …)のような対策をしないといけないのですが、阪大の院試は広く浅く、IPA主催の情報処理技術者試験にも似た雰囲気のものだったと思われます。

合格発表が待ち遠しいですね!()

OUCCの主な活動を振り返る

はじめに

この記事はOUCC Advent Calender 2020 の12日目の記事です。

新入生へ向けてのアドベントカレンダーということで、今回はここ数年でこの クラブ が行った活動を、私の知りうる範囲で振り返りたいと思います。

・4月

4月は新入生歓迎(新歓)の時期ということで、課外活動オリエンテーションで、OUCCがしていることや開発したものを展示し、毎週金曜日には部室で説明会を行いました。

・5月

5月にはいちょう祭があり、OUCCの模擬店では、唐揚げや焼き鳥を販売しました。また展示では、部員が作ったゲームを多くのお客様にプレイしていただきました。
この時期あたりで、部員やOBさんが新入生に向けて講習会を開き、講習会に興味のある新入生や部員が講習会に参加しました。(※注)春の講習会の画像は去年のものです

・6月

6月には確定新歓が行われ、新入生は晴れて正式な部員となります。
去年の確定新歓では、部室でたこ焼きやお菓子を食べながら、新部員と既部員がテレビゲームやボードゲーム、雑談等をして交流しました。
また、OUCCでは2D、3DCG班や競技プログラミング班、WEB班、等があり、各部員は興味のある班活に入って班での活動も行います。

・7月

7月は下旬にテストがあるので、上旬の部会で夏休みの各予定を決め、テスト休みとなります。

・夏休み

8月上旬にテストが終わったらいよいよ夏休みです!!
私たちの部室は冷房設備がなく、夏場はサウナと化してしまうため、部員は基本在宅での作業を行います。基本的には個人個人でやりたいことをやりますが、人によっては班で活動したり、似たようなことをしたい同士で共同開発や勉強会を行います。
昨年の夏休みの主な活動としましては、KC3と 夏合宿 がありました。
また、希望者を募って夏コミにも行きます

・KC3

KC3とは、関西情報系学生団体交流会の略称で、関西の大学の情報系団体に所属する学生たちが交流を深める会です。
参加団体は大阪大学コンピュータクラブのほかに、立命館コンピュータクラブ、立命館大学情報理工学部PJ団体 RiG++、関西大学電気通信工学研究会、関西学院大学機巧堂、近畿大学電子計算機研究会、京大マイコンクラブ等があります。
去年の9月に行われたKC3では、各クラブの紹介と、各 クラブ による講習会がありました。また、最後には懇親会があり、他のクラブ の部員との交流ができます。
KC3について詳しくはこちらから

・夏合宿(開発合宿)

去年は夏合宿で福井に行きました!!
この合宿は開発合宿ということで、2つのチームに分かれ、待ちかね祭に向けてゲームの開発を行い、最終日には各チーム制作物を発表しました。

・10,11月

夏休みが終わり10月になるとまちかね祭の準備に向けて動き出します。
まちかね祭が近づいてくると、模擬店の買い出しの確認をしたり、展示するゲームの開発によるデスマーチが発生したりします。
そして、まちかね祭当日は部員が一丸となって展示・模擬店を行います。

・12,1月

この時期になると部室は冷蔵庫並の寒さになるので、部室にこたつが導入されます。部員たちは、各々の開発を進めたり、課題やテストに追われたりと思い思いのことをします。

・春休み

テストが終わり冬休みになると、部員はバイトをしたり、旅行に行ったり、勉強・開発したりと、それぞれ自分のやりたいことをやります。また、4回生の追いコンも開催しました。(今年は新型コロナウイルスの影響で、追いコンは3,4回生のみでの開催となりました)

追いコン(画像はイメージです)

・まとめ

以上がOUCCのおおまかな活動内容となります。
主な活動曜日は金曜日ですが、部室にはいつでも入れるので、金曜日以外に来て作業をする人もいます。また、月に1回ある部会もオンラインでの参加が可能なので、部室に来れない人でも参加できます。
これまで見てもらった通り、このクラブは活動日を柔軟に決めることができ、他のサークルとの掛け持ちも可能です。
今回は大まかな紹介のみとなりましたが、興味のある人はOUCCのTwitter等を見れば今後の活動が分かると思います。

・最後に

このクラブに限らず、サークルや部活に入ることは、他学科の人との交流や大学生活を何かに打ち込むという点で大変良いものなので、自粛期間がおわり各サークルが新歓を始めたら、いろいろなところを見て回ることをおすすめします。

if文をすっきりさせる

ちょっとしたプログラム記述方法を紹介しようと思います。例えば、次のようなメソッドを記述したとします。

        private void Example0(int x)
        {
            if (x%2==0) {
                // 処理
            }
        }

このif文は必要でしょうか?こんなことを聞くからには、もちろんNoです。ただ、if文が必要ないというのは語弊があります。正確には、処理をif文で囲む必要はありません。次のメソッドは全く同じ動作をします。

        private void Example1(int x)
        {
            if (x%2==1) {
                return ;
            }

            // 処理

        }

xが奇数の時、つまり偶数でないとき、return命令によってメソッド処理を終了させます。すると// 処理 の位置に達したときはxが偶数であることが確定しているので、if文による確認を行わず処理を記述することができます。

この記述方法のうれしいところは、鍵かっこで処理を囲む必要がなくなるところです。例では処理は1行しかありませんが、これが何十行と続くと、最後にぽつんと}が残ることになり、どの鍵かっこと対応しているのかわかりにくくなってしまいます。鍵かっこを使わないpythonのような言語であっても、インデントが右にいきすぎてしまい、プログラムが見にくくなること必至です。

        private bool Example2()
        {

             if (ReturnBooleanMethod1()) {
                // 処理
                if (ReturnBooleanMethod2()) {
                    // 処理
                    if (ReturnBooleanMethod3()) {
                        // 処理
                        if (ReturnBooleanMethod4()) {
                            // 処理
                            if (ReturnBooleanMethod5()) {
                                // 処理
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        }

この記述方法の問題点は、if文の条件がわかりにくくなることです。xが偶数の時処理をしたいのに、if文の条件式は否定である「xが奇数であるか」の確認を行っています。人によっては処理の流れが追いづらいと感じたり、条件によっては否定の記述が難しい場合があります。1つ前の例では、順にメソッドの条件を満たした場合処理をし、次の確認事項を調べて・・・という流れがわかると思います。この記述方法で書いた場合は次のようになります。

       private bool Example3()
        {
            if (!ReturnBooleanMethod1()) {
                return false;
            }
            // 処理
            if (!ReturnBooleanMethod2()) {
                return false;
            }
            // 処理
            if (!ReturnBooleanMethod3()) {
                return false;
            }
            // 処理
            if (!ReturnBooleanMethod4()) {
                return false;
            }
            // 処理
            if (!ReturnBooleanMethod5()) {
                return false;
            }
            // 処理
            return true;
        }

インデントやかっこは見やすくなりましたが、何となくどういう場合にReturnBooleanMethod4()の確認を行うのかわかりにくいですよね?少なくとも僕にはわかりにくいです。また、メソッド内の最初と最後以外でreturnによって抜けると、処理していると思っていたコードが、実はその前にreturnしていて処理していない!ふざけんな!というような事態になったりするので注意です。

この記述方法は、メソッドだけでなくforループからの脱出(break,continue)でも使えます。また、if … else break のような場合にも、breakする条件を記述することで、そのあとの処理をif文で囲む必要がなくなります。また、elseが消えてますよね。次のコードはbreak文の例を紹介するためだけに書いたどうでもいい動作をするプログラムです。

        private void Example4()
        {
            int sum = 0;
            for (int i=0;i<10;i++) {
                if (i<=5) {
                    sum += i;
                }
                else {
                    break;
                }
            }

            for (int i = 0; i < 10; i++) {
                if (i>5) {
                    break;
                }
                sum += i;
            }

        }

{}で1行とらないような記述方法をとるなら行数は変わりませんが、処理が複数ある場合は{}で1行とる人がほとんどだと思うので、そこそこ使えると思います。

プログラムの見やすい記述方法、書きやすい記述方法は人それぞれだと思います。自分に合った記述方法でデバッグをしやすい、ストレスのたまらないプログラムを書きましょう。

OpenCVでMONO消しゴムのデザインをイタリアの国旗にする

~あらすじ~
道のり① 消しゴムの青いところを取得
道のり② MONO消しゴムを判別
道のり③ MONO消しゴムをイタリア化
結果
コード
参考・その他

・~あらすじ~

 クリスマスではないけど、一応アドベントカレンダーの名目で二日目の記事書かせてもらいますOUCCの2DCG班長です。最初は適当にunityの音声認識の記事でも書いてやり過ごそうと思ってたんですが、一日目の先代の部長が結構頑張ってたんで、それを見て急遽書く内容変更して、一日で出来るけどそこそこ難易度のあるテーマを考えました。シートン学園を見ながら3時間くらい悩んだ結果、PCの隣にあったMONO消しゴムのデザインがエストニアの国旗に似ているな~とふと気が付いて、現在に至ります。


・道のり① 消しゴムの青いところを取得

 コードが300行あって説明が大変なので要点をかいつまんで書きます。読んでも幸せになれない可能性があるので、青い鳥をお探しの方は結果まで飛ばした後、AMAZONにお買い求めください。

※実行環境はwindows10、Python3.8で、使用ライブラリはOpenCVとNumPyです。

・MONO消しゴムが写っている画像を読込み

・読み込んだ画像をガウス平滑化

・cv2.inRangeで青周辺の色域選択&選択部分取得

・cv2.dilateで白い部分を膨張させ、erodeで収縮

↑(これは白い部分の中に散見される黒い所を出来れば除去したいなという希望でやってます。)

・cv2.findContoursで輪郭を取得

・各々の輪郭についてcv2.contourAreaで輪郭領域内の面積を求め、小さすぎる領域や、大きすぎる領域を除外。

・一面真っ黒の画像をnp.zerosで生成。

・各輪郭について黒一色の画像にcv2.drawContoursで輪郭を描画

・cv2.momentsで輪郭で囲まれた各領域の重心を求める。

・cv2.floodFillで先ほど輪郭を描画した画像について重心を含む領域を塗りつぶし。

↑領域の中の黒い点を塗りつぶしで除去

・塗りつぶし画像のnp.averageが大きければ塗りつぶし失敗なので、cv2.bitwise_notで反転。

・こうして作成された塗りつぶされた画像(輪郭の数だけ存在)と輪郭情報をペアでリスト(名前:blue_area)に格納。

・消しゴムの黒い部分に関しても同様にして、リスト(名前:black_area)に格納。

輪郭を描画した画像

・道のり② MONO消しゴムを判別

MONO消しゴムは青から少し離れて黒の領域があるという性質を利用して消しゴムの柄の場所を特定していきます。

・black_areaとblue_areaの要素の全ての組み合わせについて、
     
  1. ①2つの塗りつぶされた画像の膨張を前述のdilateで行う。
  2.   
  3. ②cv2.bitwise_andで重なった領域を取得する
  4.   
  5. ③2つの画像の重なった領域がnp.averageが0より大きいかを見て存在を確認し、存在すればその組み合わせを除外(一つの消しゴムの青い領域と黒い領域が接することは無いため。)
  6. ④np.concatenateで2つの輪郭領域情報のリストを結合する。

  7. ⑤cv2.minAreaRectで二つの領域に外接する最小の長方形を取得。
  8.   
  9. ⑥2つの塗りつぶされた領域の面積を前述の方法で求める。
  10.   
  11. ⑦長方形の面積を求める。
  12.   
  13. ⑧長方形の面積が塗りつぶされた面積に対し比較的大きかったり小さければその組み合わせを除外(青と黒の領域が離れている組み合わせを除外)
  14.   
  15. ⑨除外されずに残った組み合わせの先ほど作成した長方形を保持。
青い部分の抽出された画像(blue_areaの要素の一つ)と黒い部分の抽出された画像(black_areaの要素の一つ)をOR演算で合成し、外接矩形を表示した画像。心の清い人には外接しているように見えるはず。長方形の面積が白い部分の面積より比較的大きいため、この組み合わせは正しくないと判別する。

・保持された全ての長方形と保持された他の全ての長方形との組み合わせしていき、一方の長方形がもう一方の長方形にほとんど含まれているとき、大きい方の長方形を除外。

↑この判別法は、長方形の領域を塗りつぶし、AND演算を行って重なった部分の比率がどれくらいあるかで判別しています。

・残った領域一つ一つについて、その領域の部分だけをマスキングして切り取る。(読み込んだ画像と長方形の中身を塗りつぶした画像のAND演算)

↑なお、この過程で次元の低い長方形塗りつぶしの画像はcv2.COLOR_GRAY2BGRで3次元に変換。

画像にマスキングをかけて消しゴム一個だけ取得した画像

hitomatagiさんのコードにかけて特徴量マッチングし、コード中のgoodの個数がある一定水準を超えないものを除外。

特徴点を結んでいった画像

ここまでクリアしたものををMONO消しゴムと認定します。


・道のり③ MONO消しゴムをイタリア化

ここから色付けをしていきます。…とその前にもう3時近いので寝ます。おやすみなさい~

おはようございました。現在12時です。どうやら記事を書いていたのは夢落ちではないそうですね。さて…

・各長方形を塗りつぶした画像と読み込んだ画像の青い部分を取得した画像のANDを取る。

・AND演算して出来た画像の白い部分が存在するピクセルの座標と、同じ座標の読み込まれた画像の場所の色を緑に変更。(2重ループ文で一つ一つのピクセルを処理)

・同様にして黒の部分を赤に変更

完成!


・結果

イタリアと化したMONO消しゴム
他の画像での実行結果

 とても精度が悪いですね。影は仕方ないとしても、文字の部分や、ノイズは頑張ればどうにかなる部分です。でも作者は途中で力尽きました。許して下さい。


・コード

RTAして書いたコードなので絶対見ない方がいいですよ。呪われます。コード整理をしてなくてもOKで、呪い耐性がある方だけどうぞ。


import  cv2
import  numpy as np
import compare #引用したコードを記載した場所

def main():
    sample=cv2.imread("./data/monoEraser.jpg")
    testImg=cv2.imread("./data/test1.jpg")
    height, width, channels = testImg.shape
    image_size = height * width
    testImg_b= cv2.GaussianBlur(testImg, (9, 9), 2)
    hsv=cv2.cvtColor(testImg_b,cv2.COLOR_BGR2HSV)
    lower = np.array([110, 50, 50])
    upper = np.array([240, 255, 255])
    frame_mask = cv2.inRange(hsv, lower, upper)  

    img2,blueArea = getColorArea(frame_mask, testImg)
    lower = np.array([0, 0, 0])
    upper = np.array([180, 255, 50])
    frame_mask2=cv2.inRange(hsv,lower,upper)

    kernel = np.ones((5, 5), dtype=np.uint8)

    frame_mask2 = cv2.dilate(frame_mask2, kernel)
    frame_mask2 = cv2.erode(frame_mask2, kernel)


    testImg_g=cv2.cvtColor(testImg,cv2.COLOR_BGR2GRAY)

    img_canny=cv2.Canny(frame_mask2,300,400)


    img,blackArea=getColorArea(frame_mask2, testImg)
    rects=[]
    for i, blue_a in enumerate(blueArea) :
        for j,black_a in enumerate(blackArea):
            b,rect=checkAreaRatio(testImg,blue_a,black_a)
            if(b):
                rects.append(rect)
    deleteRects=[]
    for i, rect1 in enumerate(rects):
        for j,rect2 in enumerate(rects):
            if(i>j):
                k= checkContain(rect1,rect2,height,width)
                if(k==1):
                    deleteRects.append(rect2)
                elif(k==2):
                    deleteRects.append(rect1)
    for rect1 in deleteRects:
        rects.remove(rect1)
    deleteRects=[]
    for rect1 in rects:
        if(not compareTrait(sample,img,rect1)):
            deleteRects.append(rect1)
    for rect1 in deleteRects:
        rects.remove(rect1)
    for rect1 in rects:
        img=changeColorItaly(img,rect1)

    cv2.imshow("sample2", img)
    cv2.imwrite("./data/output6.png", img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
def changeColorItaly(img,rect):
    height, width, channels = img.shape
    mask = np.zeros((height, width), dtype=np.uint8)
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    mask1 = np.copy(mask)
    mask1 = cv2.drawContours(mask1, [box], 0, 255, 1)
    mask1 = fillArea(mask1, int(rect[0][0] + 2), int(rect[0][1] + 2))
    mask2 = np.copy(mask)
    mask2 = cv2.drawContours(mask2, [box], 0, 255, 1)
    mask2 = fillArea(mask2, int(rect[0][0] + 2), int(rect[0][1] + 2))
    hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    lower = np.array([110, 50, 50])
    upper = np.array([240, 255, 255])
    frame_mask = cv2.inRange(hsv, lower, upper)
    lower = np.array([0, 0, 0])
    upper = np.array([180, 255, 50])
    frame_mask2 = cv2.inRange(hsv, lower, upper)
    mask1=cv2.bitwise_and(frame_mask,mask1)
    mask2=cv2.bitwise_and(frame_mask2,mask2)
    mask1=cv2.cvtColor(mask1,cv2.COLOR_GRAY2BGR)
    mask2=cv2.cvtColor(mask2,cv2.COLOR_GRAY2BGR)
    for x in range(height):
        for y in range(width):
            b, g, r = mask1[x, y]
            if (b, g, r) == (0, 0, 0):
                continue
            img[x, y] = 99, 135, 0
    for x in range(height):
        for y in range(width):
            b, g, r = mask2[x, y]
            if (b, g, r) == (0, 0, 0):
                continue
            img[x, y] = 57, 41, 206
    return img

def compareTrait(sample,img,rect):
    height, width, channels = img.shape
    mask = np.zeros((height, width), dtype=np.uint8)
    box = cv2.boxPoints(rect)
    box = np.int0(box)
    mask1 = np.copy(mask)
    mask1 = cv2.drawContours(mask1, [box], 0, 255, 1)
    mask1=fillArea(mask1,int(rect[0][0]+2),int(rect[0][1]+2))

    mask1=cv2.cvtColor(mask1,cv2.COLOR_GRAY2BGR)
    img2=cv2.bitwise_and(img,mask1)

    return compare.compare(sample,img2)

def checkContain(rect1,rect2,height,width):#領域の中に領域があるかチェック
    mask = np.zeros((height , width ), dtype=np.uint8)
    box=cv2.boxPoints(rect1)
    box = np.int0(box)
    mask1 = np.copy(mask)
    mask1 = cv2.drawContours(mask1, [box], 0, 255, 1)
    mask1=fillArea(mask1,int(rect1[0][0]+2),int(rect1[0][1]+2))
    box = cv2.boxPoints(rect2)
    box = np.int0(box)
    mask2 = np.copy(mask)
    mask2 = cv2.drawContours(mask2, [box], 0, 255, 1)
    mask2 = fillArea(mask2, int(rect2[0][0] + rect2[1][0] / 2),int( rect2[0][1] + rect2[1][1] / 2))
    mask3=cv2.bitwise_and(mask1,mask2)
    contours, hierarchy = cv2.findContours(mask3, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

    if(len(contours)<=0):
        return  0
    area3 = cv2.contourArea(contours[0])

    area1=rect1[1][0]*rect1[1][1]
    area2 = rect2[1][0] * rect2[1][1]
    ratio=0.9
    if(area3>area1*ratio):
        return  1
    elif(area3>area2*ratio):
        return  2
    else:
        return 0

def checkAreaRatio(img,mask1,mask2):
    mask1,cnt_mask1=mask1
    mask2, cnt_mask2 = mask2
    kernel = np.ones((5, 5), dtype=np.uint8)
    mask4=cv2.dilate(mask1,kernel)
    mask5=cv2.dilate(mask2,kernel)
    mask6=cv2.bitwise_and(mask4,mask5)

    if(np.average(mask6)>0):#隣り合っている領域を除外
        return False,None
    area1=cv2.contourArea(cnt_mask1)
    area2=cv2.contourArea(cnt_mask2)
    cnt_mask3=np.concatenate([cnt_mask1,cnt_mask2])

    rect = cv2.minAreaRect(cnt_mask3)
    area3= rect[1][0]*rect[1][1]
    if (area3 > (area1 + area2) * 3 or area3<(area1 + area2) * 1.6):#比率で除外
        return False,None
    #box = cv2.boxPoints(rect)
    #box = np.int0(box)
    #img2=np.copy(img)
    #img2 = cv2.drawContours(img2, [box], 0, (0, 0, 255), 2)

    return  True,rect

def getColorArea(frame_mask, draw_img, draw=False):
    ret, testImg_g2 = cv2.threshold(frame_mask, 100, 255, cv2.THRESH_BINARY)

    contours, hierarchy = cv2.findContours(testImg_g2, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    height, width, channels = draw_img.shape

    img=draw_img
    if(draw):
        img = cv2.drawContours(draw_img, contours, -1, (0, 0, 255, 255), 2, cv2.LINE_AA)
    cont_area=[]

    for i, contour in enumerate(contours):
        area = cv2.contourArea(contour)
        image_size=height*width
        if area < 500:
            continue
        if image_size * 0.99 < area:
            continue
        epsilon = 0.02 * cv2.arcLength(contour, True)
        approx = cv2.approxPolyDP(contour, epsilon, True)
        if(draw):
            cv2.drawContours(img,[approx], -1,  (255, 0, 255), 2)

        mask = np.zeros((height, width), dtype=np.uint8)
        cv2.drawContours(mask, [approx], -1, (255, 0, 255), 2)
        M = cv2.moments(contour)
        mask= fillArea(mask,int(M['m10']/M['m00']),int(M['m01']/M['m00']))#引数に重心を入れている


        cont_area.append((mask,contour))




    return  img,cont_area
def fillArea(img,startx,starty,color=(0,0,255)):
    channels=0
    height=0
    width=0
    if(img.ndim==2):
        channels=1
        height, width = img.shape
    elif(img.ndim==3):
        height, width, channels = img.shape
    mask = np.zeros((height+2, width+2), dtype=np.uint8)#+2しないとエラー
    if(channels==3):
        pass
    else:
        color=255
    retval, img2, mask, rect = cv2.floodFill(img, mask, seedPoint=(startx, starty), newVal=color)
    if(np.average(img2)>100):#もし塗りつぶしが多ければ
        img2=cv2.bitwise_not(img2)#反転
    return img2

if __name__ == '__main__':
    main()

・参考・その他

・OpenCV 3とPython 3で特徴量マッチング(A-KAZE, KNN):

https://qiita.com/hitomatagi/items/caac014b7ab246faf6b1

・OpenCV 2.2 C++ リファレンス:

http://opencv.jp/opencv-2svn/cpp/index.html

・OpenCV-Python チュートリアル :

http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/index.html

筆者:OUCC 2DCG・AI班長 上月

Unityでノベルゲームを制作中

はじめに

この記事はOUCC Advent Calender 2019の23日目の記事です(完成したのは12/31です)

パワポケから野球要素を抜いたノベルゲームみたいなのを作りたかったので、12月から土台作りを始めました。
土台作りといっても基礎となる部分は以下のサイトを参考に作成しました。

今回はこれをパワポケ風に近づけるために追加した機能を書きたいと思います。

1 キャラの立ち位置を二か所にする

パワポケでは左右にキャラが立って、会話が展開されるので、まずはキャラを左右に立たせるようにしました。このとき、3人目のキャラを追加するときは、既にいるどちらか二人のうちの一人を消して、3人目のキャラを追加するようにしました。

Gif画像をUpしたかったのですが、容量的に上げられませんでした…

2 背景画像を変える

#bg_imageで背景を変えられるようにしました。

3 真ん中に画像が出るようにする

背景と同じ要領で真ん中に画像が出るようにしました。背景との違いは#center_imageで真ん中に画像が出現して、#center_image_offでその画像を見えなくするといったところです。

4 選択肢の内容をメッセージボックスに表示する

選択肢のボタンを表示させて、ボタンに触れるとそのボタンのテキストの内容をメッセージボックスに表示するようにしました。

5 その他機能

そのほかに追加した機能としては、BGMを変える機能や、SEを鳴らす機能、コマンドで使用している’#’をメッセージボックスに表示させるためのエスケープシーケンスを実装しました。

6 まとめ

以上述べたような機能を実装しましたが、パワポケ風にするためには、パラメーターの追加やフラグ管理など、追加しなければならないことが沢山あるのでこれからも追加していきたいと思います。
今回は容量の都合上、gif画像を使えなかったので、次に投稿するときはgif画像を上げられるようなブログで書きたいなと思います。

お借りした素材

キャラクターの素材:いらすとや様
背景素材:あやえも研究所様
出演:部室に侵入した猫様