4Sとその前後を振り返る(卒論配属〜院試まで)

この記事ではタイトルの通り4S(卒論配属〜院試まで)を振り返ります。研究以外にも、ちょっと五月祭の話が混じります。

内容としてはほぼ日頃のツイートに散りばめられていたようなものですが、一旦まとめておこうという意図です。

2023年2月以前

12月頭ごろ、「半導体って研究テーマとしてどうなんだろう」というようなツイートをしたところ、当時FFではあったものの一切関わりのなかった前田先生からリプが飛んできて、なんやかんやで研究室見学に行くことになりました。

この時前田先生から聞いた話が面白そうだと感じ、半導体系の研究室も視野に入れるようになりました。「研究室に入ったらすぐにでも研究を始めて夏には国際学会に行けるかも!」という文言に釣られたのもありますが。

これ以前は先述のツイートにある通り半導体に対して「覚えられない・難しい」と感じていたため、これがなければ普通に情報系に行っていたと思います。

余談ですが、これがEEIC2022にとって初めての研究室見学ということで学年slackで一緒に行く人を募集したところ、最終的に20人くらいで押しかけることになりました。「友達も何人か一緒に行くことになると思う」と伝えていたもののこの人数は想定外だったようで色々大変そうでした。

 

これ以降EEIC2022の中でも各研究室にアポを取っての見学が活発になっていき、自分は最終的に15個くらい見学しました。研究室見学は複数の研究室がまとめて実施したりもするので、実際に見学したことになる研究室の数としてはおそらくもう少し多くなります。自分は日程が合って外出する気になる限り参加したので、他の人はもっと少なかったようです。

 

2023年2月

月末に、研究室見学の希望調査が始まりました。自分は最後まで情報系に進むかデバイス系に進むかで迷っていましたが、色々悩んだ結果デバイス系に進むことに決めました。色々な研究室を見学したからこその決断だと思いますし、学生の間のことだけでなくその先の進路まで考えた結果でした。

 

2023年3月

院試のために6月くらいまでにTOEFLのスコアを取得する必要があったのですが、学科の五月祭を取り仕切っていた都合上その辺りの時期は忙しいことがわかっていたので、月末に試験日を設定してTOEFLの対策をしていました。

 

月末に配属が決まり、無事第一志望の研究室に入れることになりました。前田先生に「デバイス系やるなら読んでおくと良い教科書」を教えてもらい、早速ポチりました。3Sでは半導体系の講義を取っていましたが、3Aでは情報系に履修を振っていたので講義で扱った内容すら何も覚えていませんでした。3月末から4月頭にかけて読み進める予定でしたが、帰省していたのもあって全然進みませんでした。

 

2023年4月

ここからRTAが始まりました。

研究室での最初の顔合わせ的なイベントが4/3(月曜)にあったのですが、この日に前田先生から「この論文を金曜までに読んで要約・発表してみてね」と5ページくらいの軽めの論文を渡されました。当然まだ教科書を読み終わっていない自分は、1ミリも内容がわかっていないまま英語の論文を読む羽目になり、かなりキツかったです。今となっては慣れてきたのでそこまで負担はないのですが、やはり最初の一歩は重いです。同じく前田先生に指導してもらっている同期はスラスラやっていたので、これまで積み重ねてきた努力の差を感じました。

ここから4月の中旬くらいまでかけて、研究室/測定室の立ち上げ(自分たちが前田先生の指導する学生第一号なので)を行いつつ測定器の使い方を学んでいきました。研究対象のデバイス自体は共同研究先から提供されていたので、4月末にはそのデバイスの測定・解析という形で研究が本格的に開始しました。

頑張って6月にある学会(ナノエピ:投稿締め切りは5/12)への投稿を目指そうみたいな話もこの辺りからすでに出ていました。ここだけ聞くと正気とは思えないですね。

 

自分は学科のイベ長(五月祭へのEEICの出展責任者のようなもの)もやっていたのですが、この時期はちょうど五月祭直前ということもあってそちらがかなり忙しく(五月祭当日:5/13,14に近づくほど指数関数的にイベ長としてのタスクが増えていきました)、研究との並行は大変でした。

 

EEICは4Sで指定された講義の中から4単位以上取得することが卒業の条件になっているため、講義は必ず2つ以上取ることになります。研究も五月祭もかなり忙しかったので、講義は意識的に軽めのものを取りました。

流石に4単位分ちょうどの履修にすると一つでも落としたら留年になってしまうので、安全マージンも考えて

・情報セキュリティ

・宇宙電気電子システム工学

半導体物性工学

の3コマを取りました。

 

2023年5月

ついに五月祭が近づいてきました。研究自体はいい感じに学会に出せそうなデータが月初の段階で出ていた(←!?)のですが、学会の予稿を書くのが初めてというのもあり、そちらに難航していました。先生から「多分予稿提出締め切りは2週間くらい延びる(学会というのは往々にしてそういうものらしいです)」と聞いていたので安心して五月祭準備に打ち込んでいましたが、結局1週間しか延びませんでした。五月祭が5/13,14で、本来の締切が5/12なので、五月祭が終わってから1週間以内に予稿を書き上げないと間に合わないペースです。「1週間しか延びなかった」という報せを聞いた時は笑うしかなかったですね。なんだかんだ間に合いましたが、五月祭の最中も頭の隅の方でずっと「五月祭が終わったら締切に追われる日々が......」というのが浮かんでました。

 

無事6月開催の学会(ナノエピ)の予稿を提出したと思ったら、先生から「これに追加データを入れて8月の国際学会(LEC:予稿は6月締切)に行こう」と言われました。本当にイケるのか自分ではまだわからなかったのですが、先生がそう言うならと頑張ってみることにしました。国際学会なので当然予稿も英語で書かなければならないのですが、春休みにTOEFL対策をしていたことが若干活きた気がします。研究室に入る前に前田先生が言っていた「もしかしたら夏の国際学会に行けるかも」というのが本当になるとは思っていませんでした。

 

2023年6月

参加する学会(ナノエピ)は2分間のショートプレゼン&ポスター発表という形式の学会なので、月初の方はポスターを頑張って作っていました。ポスターって作るの難しくないですか?

6月15-17日はついに学会の本番です。なんとか無事に乗り切り、なんと発表奨励賞という賞までいただくことができました。研究成果が評価されるとやっぱり嬉しいですね。今後も頑張ります。

会期中は、夜に学生だけの飲み会が企画されており、同世代で同じような分野を研究している学生と交流することができました。学会はこういった交流の場にも価値があるのだということを身をもって感じました。

当然というか何というか、流石に参加していたB4は自分たちだけだったようです。

また、ナノエピの期間中に次の学会(LEC)の予稿締切が設定されていたので、飲み会終わりにホテルの部屋でせっせと予稿を書いて提出していました。結局LECの予稿締切はここから延びたのでこの日に頑張る必要はなかったです......

秋にある応用物理学会にも出したいということで、月末はそちらの予稿も書いていました。流石に3回目ともなると予稿の書き方にも慣れてきて、初めての頃とは比べ物にならないペースで書くことができました。なんでこの時期に予稿の書き方にすでに慣れてきているんでしょうね......

 

2023年7月

EMSという学会にはぜひ出したいということで、中旬はその予稿を書いていました。

下旬は、8月にある国際学会(LEC)に向けてスライド作りと発表練習に励んでいました。履修していた講義の期末レポートも重なり、かなり忙しかったです。

 

2023年8月

ついに国際学会(LEC:アメリカのシカゴで開催)に挑戦しました。発表自体は用意した原稿が頭から飛ぶこともなくなんとかやり切れたのですが、その後の質疑で英語が聞き取れず失敗しました。もっと英語ができれば発表も余裕を持ってできたし質疑もきちんと対応できたはずなので、今後は英語もちゃんとやっていこうと思いました。

シカゴは良い街で、年度末に研究室見学をしていたときにどこかの先生が言っていた「学会とかで色々な土地に行ったり色々な人に会えるのが研究モチベーションになる」というのは本当だなと思いました。

飛行機の都合もあり、8/5の夜に羽田を出て8/12の夜に帰ってくるスケジュールでしたが、電気系専攻の院試は8/28にあり、みんな8月頭くらいからは対策を始めるそうなので、自分でも「院試勉強はどうするんだろうな?」と思っていました。

結局、帰国した翌日と翌々日(8/13,14)はコミケに参加していたので結局院試勉強は8/15からになりました(親から「弟をコミケに連れて行ってやってくれ」と頼まれたので......)。自分でもよく体力が保ったなと思います。

電気系専攻の院試の問題自体は過去問を見てもそこまで難しいことが聞かれるわけではなく、基礎的な内容を漏れなく押さえていれば大丈夫そうでした。大丈夫だと信じさせてください......(これを書いている時点ではまだ結果が出ていません)

 

まとめ

ということで、4Sとその前後を振り返りました。やることがめちゃくちゃ大変でしたが、本当に色々と貴重な経験ができて良かったです。自分も頑張りましたが、それだけでなく周りの人の助けやタイミングの良さもあってのことなので、感謝を忘れずこれからも頑張っていきたいと思います。

 

 

ノベルゲーム風の画面をカメラ映像とマイク入力からリアルタイムで作成する

この記事は、EEIC Advent Calendar 2022 の 22 日目の記事として書かれました。

こんにちは。むなです。このアドカレの初日ぶりですね。

EEIC では 3 年後期に「電気電子情報実験・演習第二」という科目が開講されています。この実験は 30 種目くらいある中からいくつか選んで参加するのですが、この記事では、その中の「OpenCV/OpenGL による映像処理」という種目で私が作成したアプリケーションについて書いていきます。アドカレ初日にお雑煮の記事を投げたらちょっと浮いてしまったので、技術系っぽい記事も一つくらい投げておこうかなと。


実験概要

この実験種目は1ヶ月くらいかけて行い、OpenCV/OpenGLに関する基礎知識を3週間ほどで詰め込んだ後に1週間で自由課題の制作に取り組みます。そしてその自由課題のレギュレーションはなんと「OpenCVOpenGLの両方、もしくはいずれか一方を利用していること」というもののみ。驚きの緩さです。私はノベルゲームが好きなので、それに因んだものを作ることにしました。


制作物のコード

https://github.com/Muna-akki/NovelGameMaker
まだるっこしい説明はいらないという人は、上記のリポジトリにコード一式を置いてあるので手元に持ってきて素材を用意すれば実際に動かせると思います(環境依存のエラーは吐くかもですが)。README.mdに大体書いてあります。


制作物の概要

今回の制作物がどのようなものかは、以下の動画を見てもらうのが早いと思います。この動画を作った後にコードを書き直したため挙動が少し実際のコードとは違うかもですが、概ねこのような形で動きます。(この動画を撮った時は一週間で書いたコードを動かしていたので挙動が不安定&この記事を書くにあたってコードを一新したので......)

 


動画内でも説明している通り、このアプリケーションは以下のように動きます。キーボード操作を排除したあたりがこだわりポイントです。

  • カメラに映る顔の位置によって、画面内のキャラクターの立ち位置を決定
  • カメラに映る表情によってキャラクターの表情を決定
  • カメラに映る手が立てている指の本数によって背景が変化
  • カメラに映る手の裏表でキャラクター変更を制御
  • マイクに入力された音声をセリフとして表示
  • 「さようなら」「さよなら」を認識すると別れの挨拶を発声(この部分はLinuxではうまく動かない可能性が高いです)


以降では、このアプリケーションを作成する道筋に沿って書いていきたいと思います。ただし、全てに対して深入りして説明しようとするととんでもない長さになるので、詳細な部分は省略します(その結果、ライブラリ紹介コーナーみたいになりましたが)。また、作り方に興味がなければ読み飛ばして問題ないです。


1. 素材集め

これを実現させるにあたって、まず最初のハードルは画像素材を入手する部分です。自分で絵が描ければよかったのですが、できないものはしょうがないのでフリー素材を探します(絵が描けたとしても一週間でコードも絵もは無理だろうというのはありますが)。ということで、以下のサイトから素材を借用しています。ありがとうございます。何か問題がありましたらご連絡ください。

キャラクター立ち絵

らぬきの立ち絵保管庫:http://ranuking.ko-me.com/

背景

KNT graphics:矢神ニーソ:http://kntgraphics.web.fc2.com/

メッセージウインドウ/システムアイコン

空想曲線:https://kopacurve.blog.fc2.com/

フォント

ふぉんときゅーとがーる。:http://font.cutegirl.jp/



2. カメラからの画像取得

本実験のテーマであるOpenCVの出番ですね。コード内ではcapture.pyあたりに大体まとめてあります。

import cv2 as cv
cap = cv.VideoCapture(0)

こんな感じでカメラから画像を取得する準備をして、カメラ幅やフレームレートなどの各種定数を cap にセットしたのち、

ret, input_frame = cap.read()

でフレームを取得します。input_frame は所詮 python の配列でしかないので、さまざまな処理が簡単に行えます。
フレームを一回取得するごとに画像認識等を走らせ出力画像を更新することで、リアルタイムでノベルゲーム風の画面を生成していきます。


3. 出力画像の準備

出力として表示されるノベルゲーム風画面の用意をします。これはcreateoutput.pyあたりにまとまってます。といっても、やることは

  • 使用する画像のパスを指定
  • 画像を読み込み、使用可能にする

くらいです。ここで用意した画像を下から背景→立ち絵→メッセージウインドウ→テキストの順で重ね、重ねる位置や画像の種類、テキストを変更することで出力を変えます。透過込みでPNG画像を重ねるのはこのサイトを、画像に日本語を重ねるのはこのサイトを参考にしました。 出力はまたしてもOpenCV

cv.imshow(output_window_name, output_frame)

とすれば画面上にウインドウが出てきて output_frame に格納した画像が表示されます。


4. 取得した画像で顔認識

機能の一つ目、「カメラに映る顔の位置によって、画面内のキャラクターの立ち位置を決定」の部分です。これはface.pyあたりにまとまってます。今回、顔の位置を判定するために使用するのはカスケード分類器です。Haar-cascadeOpenCVにデフォルトで搭載されている顔判定で、入力画像のどこに顔が存在するかを非常に高速で判定してくれます。このサイトあたりから調べるとなんとなく原理はわかるんじゃないかなと思います。

# 分類のためのモデルの読み込み
cascade_frontalface = cv.CascadeClassifier("model/haarcascade_frontalface_alt.xml")
# 顔の位置判定
faces = cascade_frontalface.detectMultiScale(入力画像, 1.1, 2, cv.CASCADE_SCALE_IMAGE, (20,20))

この時、高速化のために入力画像は先述の input_frame を白黒に変換して縮小したものを使用しています(縮小も白黒化も OpenCV 内に関数があります)。いくつか設定してある定数は顔検出の精度等を制御するパラメータで、実際使う環境でいい感じになるように調整します。詳しくはググってください。

ともあれ、これで faces の中に検出された顔の座標等が格納されるので、その情報を用いて出力画像を更新します。今回は顔の中心の x 座標がカメラ画像の幅に対してどのあたりの位置にあるかを3パターン(右、中央、左)で分け、キャラクターを背景に重ねる位置を変更しています。こうすることで顔がカメラに映る位置を変えるたびに、生成された画像内でキャラの立ち位置が変化します。


5. 表情分析

次は「カメラに映る表情によってキャラクターの表情を決定」の部分です。これもやはりface.pyあたりにまとまっています。ここでは、DeepFaceというライブラリを使います。Facebook製らしい。

from deepface import DeepFace
# 入力画像はinput_frameを縮小したもの
result_emotion = DeepFace.analyze(入力画像, actions=['emotion'])
dominant_emotion = result_emotion['dominant_emotion']

これで dominant_emotion に'happy'や'angry'など表情分析の結果が得られます。そしてこの結果をもとに、どの表情のキャラクターを描画するかを選べばいいわけです。めちゃくちゃ簡単ですね。

ただこの表情分析、全ての入力フレームに対して実行すると重いです。諸事情(後述)あって並列化をまともにしていないのはあるにしても、リアルタイムというには遅延がありすぎました。そのため、今回のコードでは表情分析は 11 フレームに 1 回だけ行っています(Haar-cancade と DeepFace を併用しているのはこれが理由)。なお、公式の README には「リアルタイム分析なら DeepFace.stream()を使え」と書いてあるんですが、これカメラからフレーム取ってきてその上に判定結果を重ねるところまで一気にやってそうなんですよね。他の諸々と競合しそうなのでやめました。

ちなみに、表情認識で一番難しいのは「そう認識されそうな顔をする」という点です。上の動画で変顔大会みたいになっていることからも分かるように、普通に生活してたら「明確に怒っていると判定できる顔」とかそうそうしないので、かなり意識的に表情を作る必要があります。もし毎フレーム表情分析にかけていたら常にそういう表情を継続しなくてはならないので、かなり顔が疲れる事態になっていた気がします。怪我の功名?


6. 手の認識

「カメラに映る手が立てている指の本数によって背景が変化」「カメラに映る手の裏表でキャラクター変更を制御」の部分です。これはhand.pyにまとまってます。ここで使うのはGoogleが開発したMediaPipeです。これを使えば、わけがわからないくらい色々できます。さすがGoogleって感じ。公式から出てる資料が充実しているのもありがたい。このサイトのコード中に書いてある、"To improve performance, optionally mark the image as not writeable to pass by reference." というコメントがPythonらしくなさを感じて好き。

import mediapipe as mp
mp_hands = mp.solutions.hands
hands = mp_hands.Hands(max_num_hands=1, min_detection_confidence=0.7, min_tracking_confidence=0.5)
# 入力画像はinput_frameを縮小・左右反転したもの
results = hands.process(入力画像)

これで results に、画像から検出された手の位置情報などが格納されます。この時、例えば「人差し指の先端は画面内のこの位置」というように、手の各部分に対して位置情報が得られます。そのため、ここから手の各部の座標を比較すれば「指は立てられているか」「手の表裏どちらをカメラに向けているか」を判定できます。あとは「指が 1 本だけ立っていればこの背景」など、自分の設定した条件に従って、キャラや背景を変化させていけばいいです。

手の認識も、表情と同じく 11 フレームに 1 回だけ行っています。これは、全フレームで認識すると、手の表裏によるキャラの切り替えがうまく働かなくなってしまうからです(手の裏を向けた途端にキャラが延々と切り替わり続ける)。


7. 音声認識

「マイクに入力された音声をセリフとして表示」の部分です。voice.pyあたりにまとまっています。音声認識系のライブラリにも色々ありますが、今回はVOSKを選びました。他の候補としては「SpeechRecognitionGoogleAPIを叩く」というのもありましたが、ノベルゲーム風を名乗るならオフラインで動いて欲しいですし、また、言葉の切れ目ごとに認識結果を得ていたのではリアルタイム性も損なわれます。VOSKになると認識精度は多少落ちますが、オフラインで動き、しかも言葉の途中でも認識結果のテキストを取得できるため、よりリアルタイムに近いです。公式のGitHubにサンプルコードがあるのでそれを参考にやっていきます。

from vosk import Model, KaldiRecognizer
import queue
import sounddevice as sd
import json

q = queue.Queue()

# オーディオのコールバック関数
def callback(self, indata, frames, time, status):
    """This is called (from a separate thread) for each audio block."""
    if status:
        print(status, file=sys.stderr)
    q.put(bytes(indata))

samplerate = 44100
model = Model(lang="ja")
rec = KaldiRecognizer(model, samplerate)
message = ""
with sd.RawInputStream(
        samplerate=samplerate,
        blocksize=8000, device=None,
        dtype="int16", channels=1,
        callback=callback
        ):
     while True:
        # 音声認識結果を取得
        data = q.get()
        translate_flag = rec.AcceptWaveform(data)
        # 発話の区切れ目かどうかで結果取得方法が異なる
        if(translate_flag):
            json_dict = json.loads(rec.Result())
            message = json_dict["text"]
        else:
            json_dict = json.loads(rec.PartialResult())
            message = json_dict["partial"]

これで、マイクからの入力を音声認識した結果が message に得られます。あとはこの message を整形(余分な空白の除去や適切な改行の付与)して出力に反映させれば良いです。


8. 音声合成

「『さようなら』『さよなら』を認識すると別れの挨拶を発声」の部分です。終了時にキャラが何か喋るのはノベルゲームあるあるですね。これはutils.pyあたりに書いてあります。先ほどの音声認識の結果、messageに入っているのが「さようなら」あるいは「さよなら」だった場合、音声合成で別れの挨拶を発話させます。この音声合成には、pyttsx3というライブラリを用います。これもオフラインで動作し、ここに使い方が大体書いてあります。

import pyttsx3
ph = "さよなら。またね。" # 発話させたいフレーズ
engine = pyttsx3.init()
engine.setProperty("rate",100) # 発話速度の設定
engine.say(ph)
engine.runAndWait()



ここまででしてようやく、リアルタイムで得た情報を出力に反映させることができます。タイトルだけは一丁前にカッコつけましたが、「実際やっていることはライブラリを使いまくっただけじゃないか」というのは禁句です。ライブラリを組み合わせて使うだけで誰でもこういうものが作れてしまうのは、かなり時代の進歩を感じますね。


改善すべき点

今はフレームごとに全ての処理を直列に行っていますが、特に画像認識系は普通に並列でやるべきな気がしますね。入力画像に処理結果を重ねる部分の制御が面倒だったのと、そこまでやってる時間がなかったので単純に直列にしてます。あと、背景とキャラ切り替えの制御はもっとスマートにできる気がするので、時間ができた時にでも調整したいです。



ここまで読んでいただきありがとうございました。OpenCV/GL実験は自由に色々作ることができ、みんなの発表を聞くだけでも楽しいので来年以降受けられる人はぜひ受けましょう!


参考資料

https://blanktar.jp/blog/2015/02/python-opencv-overlay https://qiita.com/mo256man/items/b6e17b5a66d1ea13b5e3 https://qiita.com/FukuharaYohei/items/ec6dce7cc5ea21a51a82 https://github.com/serengil/deepface https://laboratory.kazuuu.net/detecting-facial-expressions-using-pythons-deepface-module/ https://google.github.io/mediapipe/solutions/hands.html#python-solution-api https://alphacephei.com/vosk/ https://pypi.org/project/SpeechRecognition/ https://github.com/alphacep/vosk-api/blob/master/python/example/test_microphone.py