月を眺める孤島

多分デレステとポケモンの話がメインのブログです

続・Pythonでデレステのガシャのスクショからアイドルの名前を読み取ろうと頑張った

むぅんです。お久しぶりです。
最近若干デレステのモチベが下がっていたのですが、久々にモチベが戻ってきたので今回は以前作った名前を読み取るプログラムの改良を行ったものを紹介しようと思います。

前回の記事はこちらになります。
shinemoon227.hatenablog.jp


さて、こちらのプログラムですが、主に2つの問題点がありました。

・名前が正しく読み取れることのほうが少ない
・長い名前のアイドルの名前が読み取れない

まず1つ目に関してですが、これは文字を認識していく上で仕方のないことです。勿論文字が違うこともありますし、除ききれなかったゴミが認識されてしまい記号が含まれてしまうこともあります。特に背景のあるSR以上のカードはかなり精度が悪いです。
2つ目に、少しでもゴミを減らすために読み取る幅を更に狭めてしまうと、「イヴ・サンタクロース」や「メアリー・コクラン」といった長い名前のアイドルがまるで読み取れないといった問題点も挙げられます。

difflibの利用

そこで今回はPythondifflibというライブラリを利用し、認識できた文字列と最も名前の近いアイドルを出力結果とすることで、少なくとも実在しないアイドルの名前になることを避ける方針でプログラムを組んでみました。
文字列str1とstr2の類似度  d (ただし  0 \leq d \leq 1)はdifflib.SequenceMatcher(None, str1, str2).ratio()で求めることができます。完全に一致していれば1、全く一致していなければ0が返ってきます。

a = '関裕美'
b = '間裕美'
c = '積裕美'
print(difflib.SequenceMatcher(None, a, b).ratio())
print(difflib.SequenceMatcher(None, a, c).ratio())
$ python3 main.py
0.6666666666666666
0.6666666666666666


類似度とは言ってもあくまで文字がどれくらいの割合で一致しているかというだけで、見た目が似ている漢字であるとか読みが同じ漢字であるかということは関係なく計算されるようです。上記の例ではどちらも文字列の $2/3$ が一致しているということがわかるわけですね。カタカナなどは全角と半角でも区別されるそうです。


アイドルは全員合わせても200人未満なので、それぞれの名前をリストに格納しておき、その中で最も類似していた名前を解としました。ただし、稀に1文字も認識できないことがあり、その場合は比較しようがないので適当な文字列を返します。

def searchCorrectName(txt, nameList):
	if txt == '':
		return '???'
	mem = {}
	for name in nameList:
		d = difflib.SequenceMatcher(None, txt, name).ratio()
		if d > 0:
			mem[name] = d
	return sorted(mem.items(), key=lambda x:x[1], reverse=True)[0]

やっていることはこれだけです。txtが読み取った文字列、nameListが全アイドルの名前が格納されたリストです。
一応簡単に解説しておくと、txtが空のとき、すなわち名前が認識できていなかったときは'???'を返します。そうでなかったときは類似度が0より大きい名前とその類似度をディクショナリmemに格納します(かすってすらいない名前が大半なのでそれらは弾く)。最後にそれをソートし、reverseにTrueを指定し降順にしてから先頭の要素(最大値)を持つキーを返すという関数になっています。

txt = re.sub(r'[ -~]', '', txt)
txt = txt.replace(' ', '')

書き忘れていましたが、読み取った文字列は事前に上記の処理を行って余計な文字を除去しています。前回の記事では半角および全角スペースのみを除去していましたが、今回は正規表現で半角文字を一括で除去してしまいます。

実験

それでは以下の画像に写っているアイドルの名前を実際に読み取ってみましょう。

f:id:shinemoon227:20190525191344p:plain

白菊ほたる
前川みく
結城晴
野々村そら
若林智香
???
佐久間まゆ
海老原菜帆
伊集院惠
メアリー・コクラン
西川保奈美
難波笑美


もともと読み取れていなかったアイドル以外は完璧です!しぶりんはどうやっても名前を読めなかったのでこればかりは仕方ないですね…。

ただ、プログラムを書いてたときは名前がほぼ一致している城ヶ崎姉妹の処理を工夫した気がするんですがその痕跡が見当たらなかったのは何故でしょうね…(プログラムを書いたのが去年のことなのに記事にするのが遅すぎる
もし工夫するとするなら、プログラム作成時にはいなかった久川姉妹の名前も合わせて処理してやる必要がありそうです(Rは存在しないので現状滅多にスクショを撮れる機会はありませんが)。



blog.mudatobunka.org

今回参考にさせていただいた記事です。

2019年1月のデレステ宝くじシミュレータを作ってみた

f:id:shinemoon227:20181226000542p:plain

ターレッスンチケットが新たな目玉


今回も作りました。
報酬に何も変更がなかった9月の宝くじと異なり、今回は報酬にいくつか変更が見られます。
中でも最も大きいのはスターレッスントレーナーチケットの追加でしょう。通常プラチナメダルが3枚も必要なアイテムが4等以上で確実に手に入ります。
また、魔法の時計が6等でも手に入るのは驚きです。ここまで大盤振る舞いだと、しばらくは時計に困ることはなさそうです。


当選本数一覧 本数
1等 1本(変更なし)
2等 1000本(変更なし)
3等 3000本(変更なし)
4等 25000本(変更なし)
5等 750000本(変更なし)
6等 2500000本(変更なし)
7等 5000000本(変更なし)
8等 7500000本(変更なし)
9等(※) 無制限(変更なし)

(※) PLvが20に達しておらず、2ndパネルミッション未開放のプロデューサーは必ず当選


※実際の宝くじはシミュレータによって得られた通りの結果になるとは限りません。使用は自己責任でお願いします。


下のテキストボックスに全プロデューサーおよび自分が所持しているチケットの総数を入力してください。ただし、他人が持っているチケットの総数は分からないので入力するのは推定値となります。参考までに、デフォルトで入力されている15779001枚が8等までの総数です。それより多く設定すれば9等が当選するようになり、それより少なく設定すれば上位の当選確率が上がります。極端な話、上も下も39枚に設定すれば自分で39枚しかないチケットを全て独占している状態になるため1等が確実に当選します。
なお、ここで入力した値には9等しか当選しない(8等以上が当選する条件を満たしていない)チケットは含まれません。


また、今回貰えるチケットの総数はデフォルトで35枚に設定してあります。これは、次回Grooveイベントが来年の1/1から始まると仮定し、イベント期間中は3枚、期間外は2枚貰えるという前提での枚数となっています。昨年同様、大晦日限定のイベントなどにより変動する場合がありますので、チケットの枚数が確定次第変更したいと思います。
枚数が39枚でほぼ確定したため、デフォルト値を39枚に変更しました。


【1/1追記】「1回戻る」「1回進む」ボタンを追加しました。宝くじの抽選結果を10回まで巻き戻すことができます。ただし、チケットの枚数を変えてしまった場合は正しく巻き戻せません。
「プラチナ宝くじを引く」ボタンを連打している際に2等などの良い結果をスルーしてしまった際の保険としてお使いください。



全プロデューサーが所持しているチケットの総数:
      自分が所持しているチケットの総数:











【更新内容】
・35連から39連に変更
・「1回戻る」「1回進む」ボタンの追加に伴う巻き戻し機能の実装

続・アタポンイベントで50位以内(2桁前半)を目指す人へ向けて

f:id:shinemoon227:20181222180213p:plain

今回は順位調整メインでした




第2弾です。前回の記事はこちらからどうぞ。

shinemoon227.hatenablog.jp




僕の担当アイドルである姫川友紀と依田芳乃の内、依田芳乃が上位報酬となったイベントが開催されたため今回はだいぶ気合を入れて走りました。
放置編成が広く使われるようになった今、前回と比較にならないほどボーダーは上がってしまいました。こちらも放置編成を組むことができたのは幸運でしたが、もし組めなかった場合間違いなくここまで高い順位は取れなかったでしょう。


用意したもの(あくまで参考までに)


・毎日最低17~20時間以上の余裕
最重要です。イベント期間中、毎日24時間全ての時間を使うことができたとしても1週間以上に渡って4時間から6時間程度の睡眠しかとることができません。オールできる日が1日でもあれば多少は1日あたりの睡眠時間を余分に確保することができますが、オールすることで翌日の睡眠時間が大幅に伸びてしまっては元も子もないので最終日のみ寝ないなど無理をしすぎないほうが良いです。
ちなみにスキブ睡眠という後述の放置編成を使ってLIVE中に2分間睡眠をとる方法も存在しますが、1桁を狙わない限り行う必要はありません。相当熟練したスキルがなければ寝落ちして痛い目を見るだけです。


・最低30000個以上のスタージュエル
相当ドリンクに余裕があればもう少し少なくても大丈夫かもしれませんが、最後の方はスタージュエルを買いに行く暇すらなくなると思うのでイベント開始前になるべく余裕を持って所持しておきましょう(放置編成が組めるならその限りではありませんが、外出先のプレイは周囲の迷惑にならないよう気をつけましょう)。


・手首用のサポーター
大きい端末を使用する場合手首にかなりの負担がかかるため、手首をよく使う人はあったほうがいいかと思います。親指勢はどうか無理をしないように。
放置編成が組める場合は不要です。


ジップロック
風呂でイベントを回せます。必須です。


・気合
大事です。最終日までひたすら休みなく同じことを繰り返してると頭がおかしくなるので通話相手を探したりすることをオススメします。


・担当への強い思い
2桁を狙うほどなら、そのイベントに担当アイドルが参加している場合が殆どでしょう。イベント期間中、コミュやMVを観る余裕は全くありませんが、担当のために頑張る気持ちがあれば走り続けられます。


・放置編成
アタポン形式イベントではかなり重要なものです。詳細はこの後すぐ。


放置編成について

f:id:shinemoon227:20181222180649p:plain

人 権



放置編成とは、スキルブーストの特技を持つSSRアイドルと判定強化の特技を持つSSRアイドルを同時にユニットに組み込むことでMISSを含む全ての判定をPERFECTに変えつつ、更にそれらで補えない場所をダメージガードで耐えつつ画面をほぼ触ることなくクリアするためのユニットのことです。イベントで2桁前半を目指すにはひたすら毎日10時間以上プレイしなければならないため、放置編成があるかないかで周回しやすさは大きく変わってきます。
現在は放置編成が普及したため、周回しやすいかどうか以前にこれがないと周回はかなり厳しいものとなってしまいました。

また、1年前の記事で挙げたデメリットもいくつか改善されています。順番に放置編成のデメリットを見ていきましょう。


・ユニットの編成難易度が非常に高い
これは改善されるどころか更に編成難易度が上昇しています。周期が8高のスキブに加えシナジーも必要となったため、スカチケで迎えられないSSRは2枚に増えてしまいました。
シナジーはスコアを出すための特技でクリアだけなら必須ではありませんが、「シナジーを含む編成のほうが強い」ならそれを組める人たちは当然使ってくるでしょう。2桁前半を狙う人ともなるとデレステをそれなりにやっている人が多いでしょうし、理想編成を組めないと苦戦を強いられることになるはずです。


・完全放置でスコアSはまず狙えない → 改善
恐らくイベラン最大の革命でしょう。かつては命燃やして恋せよ乙女しか放置でスコアSを取ることはできませんでしたが、シナジーの登場及び「特技発動率」のポテンシャル解放実装により全曲中最速のキミとボクのミライ(以下キミボク)を完全放置でSランククリアすることが可能になりました(このイベント直後に実装されたHOT LIMITに最速曲の座を譲ることとなりました)。これに伴い、イベント期間の内の大半は2分おきにスマホを操作するだけのゲームと化してしまいました(だけ、とは言いましたが1日の3/4以上休憩なしでこの単純作業を繰り返すのはかなり辛い作業です)。


・ゲスト選択が面倒 → やや改善
ゲストの表示順をソートできるようになりました。ゲストはVoDaViのいずれかを上昇させるトリコロール(最初に実装された9枚)でなければなりませんが、トリコロールは全てひとまとめにされるので上手いことスキブ持ち(センター効果が「トリコロール・アビリティ」のアイドル)を弾いていく必要はあります。


アイドルマスター(プロデューサーランクSSS)を並行して狙うには不向き → 勝手にSSSになる
放置編成の台頭によりボーダーが上昇してしまったので、もはや放置するだけで自動的にアイドルマスターになれます。その月のボーダーにもよりますが、アタポンイベまでに殆どファン数を稼げていなくても、600000ptを超えたあたりでアイドルマスターがほぼ確定します。勿論それまでに貯金があればもう少し少ないptでも狙うことは十分可能です。


このように、放置編成を組めさえすればデメリットなしでイベントを回せます。いっそのこと断言してしまうと放置編成は必須です。狙うのが2000位程度なら不要ですが、2桁前半はないととんでもなく厳しいです。


イベントptについて


今回も1年前と同じiPad Pro 9.7インチで走りましたが、今回は期間中に一度も1位を取ることができませんでした。その原因はやはり機種の差でしょう。当時はiPad Pro第2世代が出て間もない頃でしたが、今は第3世代すら出ている時代です。当然時速はそれほど出ませんし、休憩なしで何時間走ろうが追いつけないものは追いつけません。
仮にもiPad Proでこれなので、Androidの特に古い機種で走る方は苦戦を強いられることとなるでしょう。


イベント期間中の出来事


基本的にやっていることがあまりに代わり映えしないので今回は相当適当です。だって書くことないし。
桜の頃のイベランレポは結構ボリューム濃かったんですけどね。

【11/11(日) 開始8日前】

今回は異例中の異例となるイベント開始より大幅に前のイベント開催予告。
流れ的に次のアタポンイベであることが確定し、その後1週間以上に渡って胃の痛い日々を送ることとなる。

f:id:shinemoon227:20181222182603j:plain

発表当日の現地の写真です

【11/19(月) 1日目・16時間48分】0pt→62689pt (+62689pt) 11位

イベントptの目標はよしのんの誕生日に合わせ730000ptに設定。
数日前に発売されたばかりのポケモン最新作を遊びながらイベントを回し始める。100000pt稼ぐまで寝ない予定だったが流石に眠いので8時前に就寝。

f:id:shinemoon227:20181222183545p:plain

この日に早くも金トロは確定

【11/20(火) 2日目・13時間44分】62689pt→107765pt (+45076pt) 35位

食欲がなさすぎる上心臓の痛みで苦しむ。この日はモバマスPMF最終日だったので同時に回す。

f:id:shinemoon227:20181222184341j:plain

何をやっているんだろうという気分になる

【11/21(水) 3日目・19時間8分】107765pt→175430pt (+67665pt) 36位

起きてから寝るまでノンストップで回す。

f:id:shinemoon227:20181222234333p:plain

何も貼れるスクショがない

【11/22(木) 4日目・17時間5分】175430pt→236011pt (+60581pt) 39位

この日も睡魔と戦いながらノンストップで回す。

f:id:shinemoon227:20181222234838p:plain

森久保は可愛い

【11/23(金) 5日目・19時間46分】236011pt→307341pt (+71330pt) 34位

ビール摂取で生き返る。翌日に後半戦を控えテンションが上がる。
寝る前にエンブレムを一瞬カンストさせてしまい一瞬で眠気が吹き飛ぶ。

f:id:shinemoon227:20181222235055p:plain

心臓が止まるかと思った

【11/24(土) 6日目・17時間57分】307341pt→398913pt (+91572pt) 36位

この日より後半戦が始まる。ペース的に730000ptは不可能と判断し、前回走った冬空プレシャスと同順位である27位を目標に再設定。
とはいえ100位より上のペースは全体的に過去最高で推移しており、歴代最高ボーダーだったPretty Liar以上となると700000ptを超える可能性は十分にあったので気合を入れて走る。

f:id:shinemoon227:20181222235457p:plain

この日からイベントを走る負担は一気に減る

【11/25(日) 7日目・18時間54分】398913pt→520136pt (+121223pt) 28位

虚無と戦いながらひたすら走る。15時のガシャ更新で限定ガシャが復刻するが、何と響子ちゃんを2種類も引いてしまう。実質響子フェス。

f:id:shinemoon227:20181222235640p:plain
f:id:shinemoon227:20181222235727p:plain

響子フェス

【11/26(月) 8日目・34時間19分】520136pt→702657pt (+182521pt) 26位

ぼーっと走っていると、ふと「あれ、これこのペースで700000ptに間に合うか?」という疑問が湧く。計算してみると、これまでのように4時間寝ると確実に死ぬことがわかったので完徹することにする。
走っているうちにとうとう無意識にイベントを回せる境地に達するが、眠いもんは眠いので魔剤をがぶ飲みしたりガムを噛んだりシャワーを浴びたり自分自身をビンタしまくることで強引に起きる。
昼の12時頃(注:すでに火曜日)、とうとう我慢できなくなったので20分だけ寝るつもりが目覚ましが4日後の設定になっていたので40分も寝てしまう。40分で済んで本当に良かった。
眠気により目覚ましの設定すらままならない状況の中、湯船に浸かって疲れを取ろうとするも栓をし忘れ何十分もお湯を垂れ流す。
そして最後の2時間、ここから地獄の順位調整が始まる。この順位帯の人たちがどこまで走るか分からず、イベントptの推移をExcelに記録しながら微調整を繰り返す。途中何度も27位になり、「この瞬間イベントが終わってくれれば…。」という気分になる。
そうこうしている内にイベントが終了、ptを調整していると時間が一瞬で過ぎることが分かった。

f:id:shinemoon227:20181223000155p:plain

イベント終了後に初めて見ることができたMV

【11/28(水) 結果発表】702657pt 27位

1人上、1人下はそれぞれ1000ptも離れていない激戦区の中で無事に目標だった27位の称号を獲得。もう1曲余分にイベント曲を回していただけで26位だったので本当にギリギリだったが無事に取れて満足。
前回の記事で「次回は400000ptを目標に」と言ったのは何だったのかという爆走っぷりだったが、流石にもう次はないです。

f:id:shinemoon227:20181223000802p:plain

今回も27位称号と金トロを獲得


記録

獲得pt 楽曲プレイ回数 合計 プレイ時間 PLv
キミボク イベMAS イベMAS+
1日目 62689 669 297 138 1104 16時間48分 267
2日目 45076 13時間44分 269
3日目 67665 19時間08分 271
4日目 60581 214 141 22 377 17時間05分 272
5日目 71330 238 156 18 412 19時間46分 274
6日目 91572 321 58 31 410 17時間57分 276
7日目 121223 359 72 8 439 18時間54分 278
8日目 182521 635 80 36 751 34時間19分 281
合計 702657 2436 804 253 3493 157時間41分 15

※PLvは266でスタート
※1日目~3日目は各楽曲何回プレイしたかの記録を取り忘れていたため合計値を記載


イベ前 イベ後 獲得数
キュートイヤリング 3069 3935 866
キュートペンダント 1334 1666 332
キュートティアラ 126 210 84
クールイヤリング 1984 2842 858
クールペンダント 879 1256 377
クールティアラ 75 153 78
パッションイヤリング 1301 2901 1600
パッションペンダント 611 1368 757
パッションティアラ 71 234 163
魔法の靴 8180 9999 1819
魔法のドレス 3202 5972 2770
ルーキートレーナーチケット 6508 8307 1799
トレーナーチケット 4637 4812 175
ベテラントレーナーチケット 198 268 70
マスタートレーナーチケット 165 170 5
マニー 5300701 9999999 7474298
スタージュエル 20835 1791 -35064
ファン数 108229335 117652215 9422880

※スタージュエルは途中何度か購入
※マニーは途中何度か使用
※魔法の靴及びマニーは何度かカンストしているため正確な値ではない

まとめ

今回は環境の変化に伴い非常に辛い戦いを強いられましたが、無事に目標だった前回と同順位の称号を獲得することができました。
疲労感自体は過去に走ったイベントほどではなく、とにかく睡眠時間を削ることによる不快感との戦いだったように思えます。放置編成を組むことができ、走る時間があれば後はひたすら寝ないだけなので、それこそ徹夜が余裕な方であればもっと上の順位も目指せると思います。
ここまでボーダーが伸びた今回は27位でしたが、イベントによってはここからさらに1日あたりの睡眠時間を1~2時間ほど削れば1桁に入ることも不可能ではありません。色段を目指すためにはスキブ睡眠を会得しなければならないので流石に一筋縄ではいかないと思いますし、このレポ程度のプレイ時間だと参考にもならないレベルですがもし目指す方がいれば頑張ってください。
今回一緒にイベントを走ったプロデューサーの方々、お疲れ様でした。


f:id:shinemoon227:20181223010047p:plain

この名刺のために頑張りました


おまけ

f:id:shinemoon227:20181223010407p:plain

形が綺麗だと見ていて飽きないです


今回もイベントptと順位の推移を記録してみました。文字が読みづらくて申し訳ないのですが、左側の縦軸がイベントpt(緑色)、右側の縦軸が順位(青色)、横軸が時間となっています。
このグラフの通り、前半戦はエンブレムが貯まったら適宜イベント曲をプレイし、後半戦はひたすらカンスト砲を打ち続けていたことが分かります。カンスト砲の形は綺麗で大好きです。

6th1日目を終えた姫川友紀Pの感想

こんばんは。姫川友紀と依田芳乃を担当しているむぅんといいます。

語彙力が大変低くなっていますが、先程西武ドームを離脱しホテルに到着したので簡単ではありますが今日のことを思い出しながら感想を書いていきたいと思います。





僕がシンデレラガールズに出会ったのは一昨年の2月です。その頃はまだライブに興味はなく、4thも「中の人の顔も名前もよく分からないし、こんな自分が行くくらいなら本気で行きたい人が行くべきだ」と思い行くことはありませんでした。
しかし、4thを終えたPの感想を見ているうちにライブに興味が沸き始め、結果として5thに行く決断をしたのが去年のことです。
それでも、当時はあまりライブにお金をかけたくなく、全国ツアーだったのもあり大阪と福岡、SSAにしか応募しなかったため担当を現地で見ることはありませんでした(福岡やSSA1日目は普通に外した上一般販売のことも知らないという今では考えられないことをしていました)。

そして今回の6thでようやく担当を現地で見る機会に恵まれました。アソビストア先行で唯一友紀が出る今日の公演だけ当たらなかったのですがSS3A2日目との交換で連れて行って頂くことができたのです。




正直、僕としてはDear My Dreamersを聴くことができればそれで十分と思っていたのですが、待ち受けていたのはあまりにすごいものでした(ごめんなさい、語彙力を失いすぎてて上手く表現できないです)。

公演後、最初に始まったのはまさかの始球式でした。そこにいたのは「野球が好きなアイドルを演じている声優さん」ではなく、「野球のことが好きな姫川友紀」と「野球のことが好きな杜野まこさん」でした。
これは友紀でなければ絶対にできなかったことです。他のアイドルではなく、姫川友紀だからこそできたことです。
勿論ドームでなければ見ることはできなかったですし、まこさんだったからこそあのような演出ができたんだと思います。



そして、その後に流れた「気持ちいいよね一等賞!」。

正直、Dear My Dreamersが発表されたとき、嬉しさの影で少しもやもやした気持ちもありました。
それは一等賞を現地で一度も聴くことがないままソロ2曲目が来てしまったという後悔から来たものです。最近のライブは完全に2曲目を中心に構成されていますし、1曲目が出たばかり、もしくは何度も2曲目を披露したアイドルでなければ最初のソロ曲を披露することはないものと思っていました。

それがまさか、こんなことになるとは。
友紀が出演するのが今日しかない以上、Dear My Dreamersも披露するのは明らかでした。1日にソロ曲を2曲披露する。こんなことがあるなんて、ライブが始まる前の自分は思ってもいませんでしたし、日本中の友紀Pが同じだったことでしょう。



ただ1つだけ心残りだったのが、披露されないだろうと思い一等賞のコールを完璧にしてこなかったことです。
フルは何度も聴いていましたが、それどころかデレステでフル以上に聴いた1番のコールすらままなりませんでした。

もし、もし次があるなら次こそは完璧にコールをしたいです。

今回は記憶をかなり失っており、申し訳ないことにDear My Dreamersの間奏でまこさんが何を喋っていたかすらあまり覚えていないほどです。
なので、曲の感想は他の人に任せたいと思います。
きっと僕よりずっと素敵な感想を書いてくれるでしょう。





姫川友紀Pとして、今日現地でライブを観ることができたのは一生の思い出です。本当に、本当にありがとうございました。

Pythonでデレステのガシャのスクショからアイドルの名前を読み取ろうと頑張った

こんばんは、むぅんです。
僕はこれまで引いたガシャの記録を全て取っており、定期的にExcelにデータを入力しては眺めて楽しんでいたのですが最近サボりまくったせいでスクショが溜まりに溜まってえらいことになってしまいました。
特におはガシャはその場ですぐに記録すればいいものをあろうことか毎日スクショで済ませてしまったが故に、今からこれを手動で打ち込むのはExcelの入力補完を使っても流石にしんどいものがあります。

そこで、Pythonで画像を頑張って加工して文字を自動で読み取れるようにしたら楽なんじゃね?という結論に至ったのでプログラムを組んでみました。
今回は、TesseractというGoogleによって開発されているOCR光学文字認識)エンジンを用いて画像から名前を出力するのを目標とします。画像の加工は皆大好きOpenCVです。
なお、Pythonバージョンは3.6です。

f:id:shinemoon227:20181017204950p:plain


これから、このひたすらコンビニと家を往復し天井をオーバーした末に拝むことができたスクショから「姫川友紀」という文字列を出力していくまでの流れを見ていきます。

f:id:shinemoon227:20181017195249p:plain



from PIL import Image
import sys
import cv2
import numpy as np
import math
import pyocr
import pyocr.builders

まずは必要なものをimportしていきます。OpenCV以外にもTesseractを動かすためのpyocrが必要となるのでそちらも合わせてインストールしておきます(日本語を読むことになるので、Tesseractは日本語のライブラリも導入しておく必要があります)。pip installで簡単に導入できるので詳細は各自で調べてください。
また、行列の計算を行うのでnumpyも必要です。

def contrast(image, a):
	lut = [np.uint8(255.0 / (1 + math.exp(-a * (i - 128.) / 255.))) for i in range(256)] 
	result_image = np.array([lut[value] for value in image.flat], dtype=np.uint8)
	result_image = result_image.reshape(image.shape)
	return result_image

OpenCVにはコントラストを調整するための関数がないようなのでここで作っておきます。こちらのコードをお借りしました。

path_input = 'IMG_0181.png'
path_output = 'IMG_0181_P.png'

画像のパスをここで指定します。inputは読み込むスクショ、outputは加工した後の画像の保存先です。間違って既に存在する画像のパスをoutputに指定してしまうと容赦なく上書きされてしまうので気をつけてください。


ss = cv2.imread(path_input, 0)
size = tuple([ss.shape[1], ss.shape[0]])
center = tuple([int(size[0]/2), int(size[1]/2)])

f:id:shinemoon227:20181017195355p:plain

画像を読み込みます。ここで、cv2.imreadの第2引数に0を指定することでグレースケールで画像を読み込むことができます。画像を加工するにあたって、このようにグレースケールで読み込むと後から大変都合が良いので忘れずに指定しておきましょう。

angle = -6
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
img_rot = cv2.warpAffine(ss, rotation_matrix, size, flags=cv2.INTER_CUBIC)

f:id:shinemoon227:20181017195534p:plain

続いて画像を6°だけ時計回りに回転させます。cv2.getRotationMatrix2Dで回転行列を作成でき、アフィン変換を容易に行うことができます。

pos = [[902, 1178], [1942, 1316]] # 切り取る矩形の左上と右下のx座標とy座標の組
img_cut = img_rot[pos[0][1]:pos[1][1], pos[0][0]:pos[1][0]]

f:id:shinemoon227:20181017195757p:plain

ここではスクショから名前の部分だけを切り取る作業を行います。左上の座標を \((x_1, y_1)\) 、右下の座標を \((x_2, y_2)\) とすると、リストposは \([[x_1, y_1], [x_2, y_2]]\) となります。勿論この通りである必要はないので各自分かりやすいように書き換えてもらっても大丈夫です。
なお、このまま実行するとiPadで撮影した画像に対応した箇所が切り取られるので、各自の機種の座標を自分で調べて入力しておきます。ここでの座標は回転後の座標になるので、アフィン変換を行った直後に一度cv2.imwriteで画像を書き出してみて、その画像を元に座標を調べておくのが良いかもしれません。
ちなみに、横にかなり長く切り取っているのはイヴちゃんのためです。ただし、残念ながらTesseractではロダンNTLGで書かれた「ン」を認識できないらしく、「イヴ・サ〕タクロ一ス」となってしまいます…。

con = 10
img_con = contrast(img_cut, con)

f:id:shinemoon227:20181017195819p:plain

ここから画像の加工に入っていきます。まずは先程定義した関数を用いてコントラストを調整します。パラメータは自分で何度も試行錯誤を行った結果10に落ち着きましたが、よりよい値を見つけた方はコメントで教えてください。

gamma = 0.1
imax = img_con.max()
img_gam = imax * (img_con / imax)**(1/gamma)

f:id:shinemoon227:20181017195834p:plain

続いてガンマ補正を加えます。この段階で既に文字だけ非常にくっきり表示できていることが分かります。CuとCoはここまで行ったコントラストの調整とガンマ補正だけで既に文字を読み取ることが可能です(con=50、gamma=0.01で動作確認済)が、Paは文字が薄く同じように調整すると上半分が欠けて読み取れなくなるのでここから更に調整を加えていきます。CuとCoの画像だと無駄な処理となってしまいますが、今回は属性による場合分けは考えないことにします。

thresh = 80
max_pixel = 255
ret, img_dst = cv2.threshold(img_gam, thresh, max_pixel, cv2.THRESH_BINARY_INV)

f:id:shinemoon227:20181017195847p:plain

ここで画像を2値化します。その際、後に行う処理のために白黒を反転しておきます。cv2.thresholdでcv2.THRESH_BINARY_INVを指定するとネガポジ反転した状態で2値化してくれます。この指定方法を知る前は普通に2値化したあとcv2.bitwise_notで反転しようと考えていたのですが、この関数で2値化したときRGB値がちょうど255と0でなくわずかにずれた値となり、NOTをかけると負数とnanになる(→画像が真っ黒になる。もう一度NOTをかけると復元は可)という不可解な現象が起きたため断念しました。2値化直後に一度png画像として出力してしまえば恐らくこの問題は解決できますが、それよりは引数で指定するほうがわかりやすく処理も高速なのでこちらの方法をオススメします。

m = 3
move_matrix = np.float32([[1, 0, m],[0, 1, m]])
size2 = tuple([img_dst.shape[1], img_dst.shape[0]])
img_mov = cv2.warpAffine(img_dst, move_matrix, size2, flags=cv2.INTER_CUBIC)

f:id:shinemoon227:20181017195902p:plain

ここまで加工した画像を複製し、3pxだけ右下にずらしたものを用意します。2×3の行列を用意して平行移動を行います。

img_and = cv2.bitwise_and(img_dst, img_mov)
cv2.imwrite(path_output, img_and)

f:id:shinemoon227:20181017195931p:plain

最後にずらす前とずらした後の画像のANDをとります。すると、元々文字の影だった部分が見事に消えてなくなります。先程2値化したときにネガポジ反転を行ったのは白い文字(1)のみを残して、影だった部分を黒い文字の縁(0)と重ねて消去するためです。この影だった部分が残っていると正しく文字を読み取れません(先程con=50、gamma=0.01でCuとCoは2値化せずとも検出可能と書きましたが、これはその補正をかけた段階で影が消えてなくなるためです。ただ、Paの文字の上半分は影と同程度の明度しかないので影と一緒に消えてしまいます。それを防ぐため、コントラストの調整やガンマ補正は弱めにかけて後からずらして重ねる方法で影だけを除去しました)。
これで見事に文字のみを真っ白な状態で抜き出せたので、これを画像として出力します。

tools = pyocr.get_available_tools()
if len(tools) == 0:
	print("No OCR tool found")
	sys.exit(1)

tool = tools[0]

txt = tool.image_to_string(
	Image.open(path_output),
	lang='jpn',
	builder=pyocr.builders.TextBuilder()
)
txt = txt.replace(' ', '')
txt = txt.replace(' ', '')
print(txt)

最後に文字をpyocrで読み取ります。関数1つで読み取れるのはすごいですよね。
返ってきた文字列は空白を含んでいることがあるのでreplaceで除去してから出力するようにします。


それでは早速実行してみましょう。

$ python3 idolName_OCR.py
姫川友紀

いけました!こんな感じでアイドルの名前を出力できます。
他の属性のアイドルも見ていきましょう。



f:id:shinemoon227:20181017201636p:plain
f:id:shinemoon227:20181017201838p:plain

$ python3 idolName_OCR.py
小早丿ーー紗枝

姫川の「川」がいけて小早川の「川」がいけないのはなんで???非常に惜しいんですけどね…



f:id:shinemoon227:20181017201817p:plain
f:id:shinemoon227:20181017201854p:plain

$ python3 idolName_OCR.py
、森久保乃々_}_`

こっちはこっちですごいことになってます。ただ、これは半角文字を全て取り除けばいいだけなのでまだ何とかなりそうです。



というわけで、OCRで完璧に名前を読むことが如何に難しいかということがよく分かる結果となりました。183人全員試すのは難しいですが、1文字も間違えずに出力できるアイドルはそこまで多くないかと思われます。
勿論スクショを撮ったタイミングが悪かったりする(キラキラしてたり白みがかってたり)とこれより更に精度は落ちますが、そうでなくてもこの結果なのでテンプレートマッチングを使って一致率が一定以上だった文字列を採用する方法のほうが確実でしょう。

{
\displaystyle
\begin{equation}
\end{equation}
}

結局ポテンシャル解放でプロデュースptはどう振り分けるのが正解なのか?(1)

こんばんは、むぅんです。
先月のデレステのアップデートにより、プロデュースptの上限が30から35に上がり、新たに特技発動率にもプロデュースptを割り振れるようになりました。記事を公開するのが遅すぎて先月どころの話ではなくなってしまいました。
それに伴い、これまでのVoDaViそれぞれに10ずつ振る振り方が最適解でなくなってしまったため、どのように振り分ければよいのかが分かりづらくなってしまいました。
そこで、ポテンシャル解放の最適解を求めるためにC++でプログラムを組んでみました。最初はPythonで書いていたのですが、シミュレートに日単位でかかることが判明したのでC++で1から書き直しています。
今回はひとまずパッションプリンセスの強そうなユニットで、各アイドルのVoDaViで一番低いステータスと特技発動率に余った15ptのうちそれぞれ何ptずつ振り分ければよいかを総当たりで調べてみたいと思います。スパークルに関しては未実装なので次回改めて調べてみます。

検証例

f:id:shinemoon227:20180927134857p:plain


後から気付いたのですが、ステータスの振り方はセンター効果依存(基礎スコアは合計アピール値によって決定され、ポテンシャル解放によるステータスの上昇幅はどこに振っても同じなため)でプリンセスの場合どこに振ろうがスコアには影響を与えないんですよね。ですが、例えばVoが一番高いのにVoにプロデュースptを振らないのは違和感がありますし、こう決めておくことで後々トリコロールのスコアを計算するときにも同じ方法が使えるため今回はこのまま進めていきたいと思います。

技術的なお話


ただ書きたかっただけなので読むのが面倒な方は適宜読み飛ばしてください。

Simulation simu(idol);
for(int p1=5; p1<=10; p1++){
	for(int p2=5; p2<=10; p2++){
		for(int p3=5; p3<=10; p3++){
			for(int p4=5; p4<=10; p4++){
				for(int p5=5; p5<=10; p5++){
					vector<int> p{p1, p2, p3, p4, p5};
					simu = simulateOneSet(simu, idol, p);
				}
			}
		}
	}
}


今回はこんな感じに頭の悪い総当たりで調べていきます。p1からp5までのカウンタをvectorとして渡していますが、これが特技発動率のプロデュースptの組となります。例えばp1=8のとき、1人目のアイドルの特技発動率に8pt振るのに対し、一番低いステータスに7pt振ることになります。
このプロデュースptの組を元にステータスを計算し、指定した曲を何回かAPしたときのスコア(例えば最大値や平均値など)をプロデュースptとのpairとしてvectorに格納したあと、スコアを元にソートをかけてプロデュースptの組を取り出します。
今回はこのプロデュースptの組をvectorで渡しましたが、整数で渡すことも可能です。例えば、2次元配列のあるインデックス \([i][j]\) は、 \(i\) が取りうる値が \((0 \leq i < n)\) のとき \([i*n+j]\) と計算することで1次元配列に変換できます。これを \(N\) 次元に拡張することを考えます。
インデックスが \(i_1, i_2, ... , i_N\) (ただし要素数はいずれも \(n\) )のとき

\begin{eqnarray}
j=\sum^N_{k=1}i_k n ^{N-k}
\end{eqnarray}

とすれば1次元に変換できます。また、 \(d\) 次元目のインデックス \(i_d\) は

\[
i_d = j \left( \frac{1}{n} \right) ^{N-k+1} \bmod n
\]

と求めることができます。これを用いて、例えば3次元のインデックス \([3][4][2] (n=5)\) は \([3*5*5 + 4*5 + 2]\) 、すなわち \([97]\) と変換でき、逆に \([97/5/5\%5][97/5\%5][97\%5]\) を計算することで \([3][4][2]\) と復元することも可能です。実際に計算するときは5人分のアイドルのプロデュースptを5から10まで総当たりで調べることになるので、 \( n=6, N=5 \) のデータを格納することになります。今回は要素数が一定だったのでこの変換を用いたソートも可能でしたが、もっと複雑なループを行うと途端にわけがわからなくなるので今回は素直にvectorで表現することにしました。

void sortOthers(vector<pair<vector<int>, int>> &vec){
	sort(
		vec.begin(),
		vec.end(),
		[](const pair<vector<int>, int>& x, const pair<vector<int>, int>& y){return x.second > y.second;}
	);
}


地味に苦労したソートの部分です。前述したようにデータはpairで管理しており、ここではsecondの値をもとにソートしています。

void setTable(vector<Idol> idol){
	// 5人分(スキブを最優先して計算するためデクリメント)
	for(int i=4; i>=0; i--){
		int cScore, cCombo, mem = 0;
		double t;
		// 全ノーツ分
		for(int j=0; j<totalNotes; j++){
			t = sec[j];
			if(idol[i].begin[mem] > t){
				continue;
			}
			else if(idol[i].end[count[i]-1] < t){
				break;
			}
			else{
				for(int k=mem; k<count[i]; k++){
					if(idol[i].end[k] < t){
						mem++;
					}
				}
				if(idol[i].begin[mem] <= t && idol[i].end[mem] >= t){
					if(i == 4) isBoost[j] = true; // スキブの場合
					else{
						cScore = skillBonus[isBoost[j]][idol[i].skillName][0];
						cCombo = skillBonus[isBoost[j]][idol[i].skillName][1];
						bonusTable[j][0] = bonusTable[j][0] < cScore ? cScore : bonusTable[j][0];
						bonusTable[j][1] = bonusTable[j][1] < cCombo ? cCombo : bonusTable[j][1];
					}
				}
			}
		}
	}
}


そして今回一番苦労した特技倍率をセットする関数です。bonusTableという2×ノーツ数分の要素数の2次元配列を用意し、そこに各ノーツのスコアアップとコンボナの倍率を埋めていく関数ですが(例えば100ノーツ目のスコアアップが17%、コンボナが18%だった場合 bonusTable[99][0] = 17, bonusTable[99][1] = 18 といった具合です)、スキブの扱いが非常に難しく適当に組むとプログラムの実行にとてつもない時間がかかる始末でした。そこで特技がスキブのアイドルを4番目に配置している前提のもとスキブが発動しているかどうかを最優先で調べ、その結果に応じスコアアップ等の倍率を場合分けするという処理を1回のループ中で行うことにしました。スキブの確認とスコア倍率の計算という2つの異なる処理を同一関数の同ループ内で行うあまりに汚いプログラムを書くのは正直気が引けましたが、結果として17倍ほど高速化できたので個人的には大満足です。どうせ自分しか見ないプログラムだし多少はね?

検証結果


今回検証に用いたユニットはこちらになります。


センター Pa 限定 本田未央 9高 フォーカス
Pa 限定 片桐早苗 6中 フォーカス
Pa 限定 堀裕子 7中 オバロ
Pa 限定 高森藍子 11中 コンボナ
Pa フェス限 十時愛梨 8高 スキブ
ゲスト Pa 限定 本田未央


このユニットで生存本能ヴァルキュリアのMASTER+を1000回APしたときの理論値、最大値、平均値、上位1% / 4% / 15% / 30% / 50%(中央値)スコアをMatplotlibでプロットした結果を以下に示します。縦軸が特技に振ったプロデュースptの5人分の合計、横軸が順位です。ただし、ゲストは特技発動率に振っても意味はないためVoDaViに10ずつ振った状態で固定しています。

まずは平均値から見てみましょう。

f:id:shinemoon227:20180927010651p:plain

見事なまでに特技発動率に振ったほうが平均的に高スコアを叩き出せていることが分かります。相関係数は約-0.887、p値は0.001未満という綺麗な負の相関関係がある結果です。


続いて1000回APした内の最大値、すなわち特技の引きが最も良かったときの結果です。

f:id:shinemoon227:20180927010719p:plain

流石に1000回もAPしただけあり、必ずしも特技発動率に振ったほうが良いとは言えない結果です。発動率の低さを試行回数で稼ぐ形となり、ステータスを伸ばすような振り方をしていてもひたすら繋ぎ続ければやがて高スコアを叩き出すことができるようですね。ただ、Lv30の楽曲を1000回もAPできるかどうかを考えるとやはり特技発動率に振らないのは良い選択とは思えません。
ちなみに、相関係数はおよそ0.034で殆ど相関は見られませんでした。


次に上位1% / 4% / 15% / 30% / 50%スコアを見ていきます。これらのスコアはデレステ計算機さんに準じてみました。

f:id:shinemoon227:20180927010738p:plain
f:id:shinemoon227:20180927010747p:plain
f:id:shinemoon227:20180927010758p:plain
f:id:shinemoon227:20180927010815p:plain
f:id:shinemoon227:20180927010827p:plain

下位のスコアになるほどより強い負の相関があることが分かります。しかし、いずれも特技発動率に振ったほうがより高スコアを叩き出しやすいようです。


最後に理論値を見てみましょう。

f:id:shinemoon227:20180927010842p:plain

流石に理論値だけあって非常に綺麗なグラフになっています。当然全員の特技発動率に10振ったときに最もスコアが低く出ています。

ここまでのまとめです。


相関係数 p値
平均値 -0.887 <0.001
理論値 0.529 0.000
最大値 0.034 0.002
上位1% -0.629 <0.001
上位4% -0.750 <0.001
上位15% -0.817 <0.001
上位30% -0.846 <0.001
上位50% -0.870 <0.001



しかし、この結果だけではまだ特技発動率に振るべきとは言えません。ステータス重視の振り方をした場合、確かに理論値以外は大きく順位を落としていましたがそれだけではスコアがどれだけ落ちたか分からないためです。
というわけで、次はスコアの実数値を見ていきたいと思います。情報量が予想より遥かに多くなってしまったため次回に続きます。

おまけ


プログラムの実行にかかった時間の変遷です。先程の検証と同じく生存本能ヴァルキュリアのMASTER+を1000回シミュレートした場合の実行時間ですが、プロデュースptの組はVoDaViに10ずつ振った場合で固定してあります。実際の検証では7776通りの計算を行ったので、おおよそこれらの実行時間を7776倍すれば実際にかかる時間となります。

なお、下記に示す時間はtimeコマンドのrealの値となります。


完成直後(Python3) 15.759s
ファイル読み込みをループ外で行うよう変更 13.953s
特技倍率を調べる関数の見直し 12.043s
更に変更 10.316s
上記プログラムをC++で書き直す 1.333s
-O3オプションありでコンパイル 0.955s
特技に関する関数の最適化 0.056s


というわけでPythonが如何に遅いかがよく分かる結果となりました。書きやすさは圧倒的にあちらの方が上だったんですけどね…。
ちなみに、0.056秒という記録を叩き出した最後のプログラムで7776通りのシミュレートを行うと約7分12秒ほどかかりました。最初のプログラムで同じ回数だけ回すと1日以上かかる計算になるので頑張って書き直して良かったです。

{
\displaystyle
\begin{equation}
\end{equation}
}

2018年9月のデレステ宝くじシミュレータを作ってみた

【12/26追記】最新のシミュレータを作成しました。2019年1月の宝くじはこちらからどうぞ。
【1/16追記】間違えてこのページそのもののリンクを貼ったまま3週間も放置してました(馬鹿なの?)、申し訳ありません。現在は正しいリンクに修正されています。
shinemoon227.hatenablog.jp







f:id:shinemoon227:20180822000603p:plain

3周年のイメージカラーは紺色でしょうか



いつものやつです。今回は9等の当選枚数が99999999枚から無制限へと変更されましたが、実質前回と全く同一の宝くじと見て問題ないでしょう。
前回作ったシミュレータを流用しているため、入手できる宝くじの枚数はデフォルトで38枚になっています。
正確な枚数が分かり次第更新したいと思います。34枚に修正しました。


当選本数一覧 本数
1等 1本(変更なし)
2等 1000本(変更なし)
3等 3000本(変更なし)
4等 25000本(変更なし)
5等 750000本(変更なし)
6等 2500000本(変更なし)
7等 5000000本(変更なし)
8等 7500000本(変更なし)
9等(※) 無制限(実質変更なし)

(※) PLvが20に達しておらず、2ndパネルミッション未開放のプロデューサーは必ず当選



※実際の宝くじはシミュレータによって得られた通りの結果になるとは限りません。使用は自己責任でお願いします。

下のテキストボックスに全プロデューサーおよび自分が所持しているチケットの総数を入力してください。ただし、他人が持っているチケットの総数は分からないので入力するのは推定値となります。参考までに、デフォルトで入力されている15779001枚が8等までの総数です。それより多く設定すれば9等が当選するようになり、それより少なく設定すれば上位の当選確率が上がります。極端な話、上も下も34枚に設定すれば自分で34枚しかないチケットを全て独占している状態になるため1等が確実に当選します。
なお、ここで入力した値には9等しか当選しない(8等以上が当選する条件を満たしていない)チケットは含まれません。


全プロデューサーが所持しているチケットの総数:
      自分が所持しているチケットの総数: