開発記録 / 画像認識 × 将棋のルール
盤の材質も、駒の書体も、撮影の角度も、画面に占める大きさも違います。それでも「実物用」「ゲーム用」と処理を分けることなく、ひとつの仕組みで棋譜になるものを作りたいと思いました。
盤を真上から撮影した動画。指す人の手が盤に写り込みます。投了まで通しで読み取りました。
画面録画。投了までの全手を読み取りました。
斜め上からの撮影。盤は画面の一部にしか写りません。
ブラウザの録画。罫線がはっきり描かれた、最も条件の良い盤です。
実物の対局を録画した動画の例です。盤を真上から撮影した1コマです。
指す人の手が盤に入り込み、いくつものマスが隠れています。動画の大半はこの状態で、盤全体がきれいに見える瞬間は限られています。
写真の駒はプラスチック、盤は将棋マットです。実物の対局で使う道具はさまざまで、木の盤に木の駒なら、駒と盤はほとんど同じ色になります。道具によって色が変わる以上、色を手がかりにはできません。駒も盤も常に同じ色で描かれる画面上の対局とは、条件がまるで違います。
こちらは将棋ゲームの画面です。
将棋ゲームの画面を記事や配信に使うときは、そのゲームの利用規約に従ってください。
これはオンライン将棋lishogiの対局をしている画面を録画したものです。
作成したアプリの画面です。
指し終えた将棋を、あとから並べ直したくなることがあります。ところが実物の盤で指した将棋には、棋譜が残りません。対局しながら手を書き留めるのは大変ですし、終わってから思い出そうとしても、途中で分からなくなります。
盤を撮っておくことはできます。ただ、その動画から棋譜を起こすには、映像を見返しながら1手ずつ手で入力するしかありません。数十手ぶんとなると、それだけでかなりの時間がかかります。
撮った動画を渡すだけで棋譜ができれば、そのまま将棋ソフトで開いて検討にかけられます。それを作った記録です。
最初の方針は素直なものでした。次の4段階で処理します。
素直に作ったものは、動きませんでした。フレームの抽出から駒の認識まで、3段階目まではどうにか動きました。つまずいたのは最後の「変わったマスから指し手を求める」段階です。
画像認識だけの判定 : △22ka(22) 画像認識だけの判定 : ▲28hi(28) 画像認識だけの判定 : 変化を特定できず
1行目は「2二の角が2二に動いた」という意味です。移動元と移動先が同じで、将棋の手として成立していません。試した手はすべて失敗でした。
原因ははっきりしています。照明、影、駒の書体、盤の木目、カメラのわずかな揺れ。条件が少し変わるだけで認識は崩れます。そして「変化したマスを2つ選ぶ」という方式は、認識が1マスでも誤った瞬間に破綻します。閾値を調整しても、あるフレームで直せば別のフレームで壊れました。
行き詰まって気づいたのは、将棋にはルールがあるという点でした。ある局面で指せる手は限られています。平手の初手なら30通り。中盤でも100通り前後です。
画像から答えを作るのではなく、先に候補を用意して画像で選ぶ。順序を入れ替えたわけです。ルール上ありえない手はそもそも候補に入らないため、先ほどのような答えは原理的に出なくなります。
局面の管理には python-shogi を使いました。確定した手を1手ずつ進めていくので、ルール上ありえない盤面には決してなりません。一度の誤認識が後々まで尾を引くこともありません。
ルールと組み合わせてもなお、画像認識の精度に引きずられる部分が残っていました。候補が複数あるとき、最後は「移動先のマスが本当にその駒に見えるか」を採点して選んでいたからです。
実際に判定させると、正解の手と次点の手の差はごくわずかでした。採点項目のひとつに「移動元のマスが空になっているか」があり、この判定が反転するだけで、その差は消えてしまいます。照明や撮影角度が少し変われば、順位は簡単に入れ替わるということです。
そこで、画像に任せる役割をさらに減らすことにしました。
駒の種類はルールが決めてくれます。7六に動ける先手の駒が歩しかないなら、そこにあるのは歩です。画像で確かめる必要がありません。
各合法手について「指したら配置がどう変わるか」を計算し、画像から読み取った配置と完全に一致する手だけを残します。
| 手の種類 | 盤の変化 |
|---|---|
| 通常の移動 | 駒が1マス消え、1マス現れる |
| 駒を取る | 駒が1マス消える |
| 持ち駒を打つ | 駒が1マス現れる |
| 変化なし | どこも変わらない |
この方式に変えたところ、実測ではほとんどの手が候補1つに絞られました。見た目の微妙な差で決める必要がなくなったのです。
小さく写った駒の文字を読むのは困難ですが、「そこに何かあるか」なら誤りにくい。難易度の高い判断を機械に負わせず、確実にできることだけを任せて、残りはルールに解かせる。 今回もっとも効いた設計判断でした。
ここで別の問題が出ます。初期の実装は「1手指された」ことを前提に最良の手を選ぶだけで、どんな画像を渡しても必ず何かの手を返していました。局面が変わっていない画像を並べたところ、19手ぶんの誤った棋譜が生成されました。
そこで、画像1枚を読んだ結果を5つに分けるようにしました。
| 状態 | 意味 | 動作 |
|---|---|---|
| move | 1手(または2手)進んだ | 棋譜に追加 |
| no_change | 配置が前と同じ | 読み飛ばす |
| stale | 少し前の局面が映っている | 読み飛ばす |
| occluded | 手などが盤に被っている | 読み飛ばす |
| unexplained | 手順で説明できない | 読み飛ばす |
これでフレームを多めに抽出しても構わなくなりました。余分な画像は判定側が弾きます。抽出側で完璧を目指す必要がなくなったのは大きな利点でした。
ここまでの組み替えを重ねた結果、画像1枚を読む手順は次のようになりました。最初に考えた4段階のうち、そのまま残ったのは前半だけです。
ここからは、この図の順に見ていきます。まずは盤を見つけるところからです。盤がどこにあるかは、色ではなく罫線の本数で決めます。盤を正方形に補正するには四隅の座標が必要です。画面に格子状に49個の種点を置いて領域を広げ、盤らしい候補をいくつも作ります。そのすべてについて「いったん正方形に補正して、罫線が縦横10本ずつ等間隔で取れるか」を確かめます。将棋盤なら必ず10本ずつあるはずで、これは盤の色と無関係です。
歪んだ元画像から罫線を直接探すのは不可能でした。 横線は取れるのに縦線が取れない。原因は透視歪みではなく駒の並びです。横方向は駒9個の縁が同じ座標に揃って信号を補強しますが、4・5・6段が空なので縦方向にはその補強が効きません。縦で拾えていたのは、罫線ではなく駒の輪郭でした。
つまり「歪みを取るには盤の位置が必要で、盤を見つけるには罫線が必要」という循環があります。粗い候補を作ってから正方形化して検証するという順序でなければ解けません。
ゲーム画面では罫線が薄すぎてこの方式が通らず、別のやり方(罫線の周期からマスの大きさを求め、駒の並びが平手と一致する位置を探す)を追加しました。ただし「実物かゲームか」で切り替えてはいません。 まず方式1を試し、失敗したときだけ方式2を試すという順序です。
この仕組みは、思わぬところでつまずきました。罫線がはっきり描かれた盤ほど、盤が見つからなくなるのです。
候補を作る最初の段階で、種点から色の似た範囲へ塗り広げています。罫線が濃くはっきりしていると、この塗りが線を越えられません。オンライン対局の画面では、駒の無い中央3段の帯だけが埋まって止まり、画面のわずか2.4パーセントにしかなりませんでした。候補として扱う最低の大きさに届かず、盤の候補にすら入らなかったのです。条件が良いほど失敗するという逆転が起きていました。
対処は単純でした。盤の輪郭を探す段階では罫線も駒も邪魔なだけなので、先に潰してから塗ります。 暗い罫線は埋め、明るい駒は削る。どちらもマスより小さい構造なので、この操作で盤が平らな一枚の領域になります。候補は足すだけなので、それまで見つかっていた候補は失われません。
もうひとつ問題がありました。塗り広げで得られる四角形は盤の内側に寄ります。 一回り小さいため、いちばん外側の罫線が枠の外に出てしまい、10本のうち8本しか取れません。そこで、通らなかったときだけ枠を1マス分広げて切り出し直します。広げると盤の外の輪郭も混ざるので、本数はちょうど10本になりません。ここでも「9マス等間隔」という将棋盤の条件が使えます。拾った線の中から、等間隔に最も近い10本の組を選びます。
結果として、四隅の検出は3段構えになりました。先に通った段の結果を使うので、すでに動いていた映像の挙動は変わりません。
| 段 | やり方 | これで決まる映像 |
|---|---|---|
| 1 | 候補をそのまま正方形化して、罫線が10本ずつ取れるか | 実物の対局、中継 |
| 2 | 1マス分広げて、等間隔に並ぶ10本を選ぶ | オンライン対局 |
| 3 | 罫線の周期からマスの大きさを求め、駒の並びが平手と一致する位置を探す | ゲーム画面 |
3段目は最後の手段です。駒はマスの大部分を占めるので、半マスずれても並びは一致してしまいます。 同点の候補が数百個並び、そこから正しいものを選ぶ方法は8種類試してどれも当たりませんでした。罫線が読める盤は、1段目か2段目で決めるべきです。
図の「各マスを『空』『駒』『どちらでもない』に読む」段です。「どのマスが埋まっているか」だけを読む方式にしたので、その判定精度がすべてになりました。4つの局面のすべてのマスで試しました。明るさの平均では多くのマスを誤りますが、彩度なら誤りは出ません。これで解決したと思いました。しかし、盤は木目が入ることがあり、色が安定しない場合があります。
彩度で判定できたのは、たまたま手元の盤が布製のベージュで、駒が白かった時でした。木製の盤に木製の駒なら、盤と駒の色はほとんど同じになります。誤動作を防ぐために、色を使う判定はすべて止めることにしました。
代わりに使ったのが「ざらつき」です。駒には文字と輪郭があるのでマスの中の明るさがばらつき、空のマスは比較的のっぺりしています。測っているのは、明るさのばらつきと、輪郭の多さ(エッジの割合)の2つです。これは色に依存せず、誤りも彩度と同じく出ませんでした。
ただし絶対値では判定できません。木目の強い盤なら空のマスもざらつくからです。そこで次のようにしました。
どのマスが空かは局面から分かります。ルールが与えてくれる情報です。初期配置なら4・5・6段の27マスは必ず空。そこを測って基準を作り、その2倍を超えたら駒とみなします。木目が強い盤なら基準も自動的に上がります。
実物とゲーム画面は見た目がまるで違うので、動画がどちらかを判別して処理を切り替える案も考えました。採用していません。分ければ片方の不具合がもう片方に隠れますし、きれいに撮った実物の盤と写実的なゲーム画面は見分けが付きにくいので、判別を誤れば全体が止まります。そもそも目標が「等しく扱えること」なので、分けた時点で遠ざかります。
代わりに徹底したのが、この節の方針です。実際に測って決めている量を挙げます。
| 量 | 何から決めているか |
|---|---|
| 空きマスの見え方 | その盤の空きマスを測った値 |
| カーソルの大きさ | その盤の駒の輪郭の広がりとの比 |
| 使えるフレームの条件 | その動画の「読めないマス数」の分布 |
動画から使えるフレームを選ぶとき、最初は「読めないマスが少ないもの」という一本の基準で選んでいました。これが中継の映像で行き詰まります。
盤が小さく写る映像では、端の駒が潰れて読めません。 しかもそれは一瞬ではなく、その局面が続く数秒のあいだずっとです。基準をどこに置いてもその局面だけが丸ごと抜け落ち、手順のつながりが切れて、そこから先が総崩れになります。
基準を緩めれば拾えるかというと、そう単純ではありませんでした。少し緩めるだけで拾える枚数はわずかしか増えないのに、読み取れる手数は0手から全手まで変わります。枚数の問題ではなく、特定の局面が拾えているかどうかで決まっているからです。この性質のため、分布から一本の基準を決める方法はどれも成功しませんでした。
解決したのは、基準を増やすことでした。
どちらかを満たせば使います。後者は何マス読めないかを問いません。指した後の局面は次の手まで盤に残りますが、駒が動いている最中や画面が切り替わる瞬間は一瞬しか映らないので、時間で分けられます。
秒数を選んだのには理由があります。「読めないマスの数」は盤の大きさや画質で意味が変わりますが、「指した後の局面はしばらく盤に残る」は、対局という行為の性質で、映像の作りに左右されません。
ただし後者だけでも足りません。実物の対局は指す人の手が絶えず動いていて、0.5秒そのまま映る瞬間がないからです。2つの条件は、性質の違う映像をそれぞれ受け持っています。どちらを使うかも動画が決めます。ほとんどのフレームが完璧に読める映像では、時間の条件を加えません。加えると、演出や選択画面の静止した状態まで局面として拾ってしまうからです。
実物の対局を長く通して読ませたとき、途中から棋譜が崩れました。存在しない手が2つ挿入され、そこから先が別の手順に化けています。
原因は、画面上の対局には無い現象でした。実物の盤で駒を取るとき、人は「相手の駒を外す」「自分の駒を進める」の2つの動作をします。 その間に、取られた駒だけが消えて、取る側の駒はまだ元の位置にある、という状態が生まれます。手が盤から離れているので、盤はきれいに写ります。
これが厄介なのは、駒の配置だけを見ると本物の「取る」手と区別が付かないことです。どちらも「あるマスが空になり、新しく駒が現れたマスは無い」という同じ形をしています。区別するにはルールの知識が必要で、それはフレームを選ぶ段階にはありません。
選ぶ段階が持っている情報は、時間だけでした。本物の局面は数秒から10秒あまり盤に残るのに対し、駒を持ち替える間は1秒足らずです。
ただし秒数をそのまま条件にはできません。中継の映像ではカメラが棋士に切り替わるため、正しい局面でも0.3秒しか映らず、この中間状態より短くなります。同じ「時間」でも、映像の作りによって意味が変わってしまいます。
そこで、前後の局面と比べることにしました。前の局面も次の局面も自分より何倍も長く映っているなら、それは指し手の途中である、と判断します。比であれば、切り替わりの多い映像でも意味が保たれます。
もうひとつ、実際に手こずった点があります。中間状態は続けて2つ現れることがあります。 相手の駒を外した瞬間と、自分の駒を置いている途中です。1つずつ見ると、隣にもう一方の中間状態がいるせいで「前後とも十分長い」を満たせず、素通りしてしまいます。中間状態どうしが互いをかばう形です。そこで、続けて現れた短い状態はひとまとまりとして見ることにしました。前後の本物の局面が数秒ずつ映るのに対し、あいだの2つはどちらも1秒足らずです。まとめれば、前後との差はふたたびはっきりします。
これで、実物の対局が投了まで通して読めるようになりました。
駒でないものが、駒に見えることがあります。ゲーム画面の動画では、実際には指されていない手が棋譜に混ざりました。原因は3種類で、いずれも盤に何かが重なって「そこに駒がある」ように見せていたものです。
いちばん手強かったのは成り/不成の選択ダイアログです。ダイアログには本物の駒の絵が、ほぼマスと同じ大きさで描かれています。見分けられる特徴を2つ実測しましたが、どちらも失敗しました。
1マスだけを見る特徴では、原理的に区別できないと分かりました。
3つの誤りを並べたところ、共通点がありました。マウスの矢印も、演出の表示も、選択ダイアログも、作り出した誤った手はすべて「持ち駒を打った」形だったのです。理由を考えると当然でした。
通常の移動も駒を取る手も、移動元のマスが空になっていなければ成立しません。重なっただけの要素では、移動元の変化を作れない。だから盤に重なるものは、必ず「打ち」の形でしか入り込めないのです。
ここで前提をひとつ。アプリは動画をそのまま見ているのではなく、盤がよく見えている瞬間だけを静止画として抜き出し、それを1枚ずつ順に読んでいます。少しあとの盤を確かめたければ、次に抜き出された画像を見ればよいわけです。
対策は一点に絞れます。打ったと判断したら、その駒があとの画像にも残っているか確かめる。
| 打った直後 | あとの画像 | 判定 | |
|---|---|---|---|
| 本物の打った駒 | ある | ある | 採用 |
| カーソル / 演出 / ダイアログ | ある | ない | 却下 |
重なる要素の見た目を一切知らずに済むので、別の将棋ゲームで演出の形が変わっても通用します。
ここだけは、画像の見た目に頼るしかありません。図の絞り込みの1段目、打った駒の種類を決めるところです。持ち駒に歩と銀があるとき、どちらを打っても「そのマスに駒が現れる」だけです。盤の変化はまったく同じで、配置を見ている限り原理的に区別できません。
しかもこの誤りは、その場では表面化しません。打った駒が数手後に動くとき、歩なら真っすぐ前へ、銀なら斜め前へ進みます。行き先は違いますが、どちらも「打ったマスが空になる」だけで、盤の変化は一致します。誤ったまま何手も進み、ずっと後になって破綻します。
さらに、打った直後の駒はもっとも読みにくい状態です。ゲーム画面では白く光る演出が入るためです。
ここでもあとの画像が効いてきます。「1枚あと」とは、次に抜き出された画像、つまり対局が少し進んだ時点の盤のことです。光が消えたあとなら、駒の絵をはっきり照合できます。歩を打った場面では、打った直後こそ歩と銀がほとんど並びますが、1枚あとの画像では歩がはっきり勝ちました。
ただし、あとの画像を見れば済むという単純な話ではありませんでした。別の場面では、どの画像を見るかで答えが変わります。桂を打った場面では、1枚あとでも2枚あとでも香が1位で、正解の桂が勝つのは3枚あとの画像だけでした。
そこで、4つの規則を採用しました。
打った駒は、いずれ動くか取られます。そのあとのマスはどの駒にも似ておらず、駒の種類について何も語りません。ところが候補どうしの差だけは、偶然大きくなることがあります。
ある場面では、駒が残っている4枚すべてで正解の香が1位でした。ところが差がもっとも開いたのは、駒が消えたあとの画像です。そこだけを見て角と誤りました。差が大きいことと、判断材料があることは別です。 1位でさえ駒に見えない画像は、使いません。
残った画像のうち、1位の一致度がもっとも高い1枚を選びます。差が最大の1枚ではありません。 画像ごとの一致度を平均すると候補どうしが打ち消し合ってしまうので1枚に絞りますが、選ぶ基準は「駒がはっきり写っていること」です。
差の測り方にも注意が要ります。当初は「最高値と最低値の差」で判断していました。しかしこれでは、明らかに異なる駒が候補に混じっているだけで差が水増しされます。ごく僅差でしかない1位が、はっきり勝ったものとして選ばれていました。比較するのは1位と2位です。
もっとも鮮明な画像でも差が小さいことがあります。そのときは、使える画像すべてで同じ駒が1位になっている場合にかぎって決めます。過半数では足りません。実際に過半数で判定させたところ、本当に見分けが付かない場面で誤りました。決めないことも、正しい判断のひとつです。
判定を見送ると、処理は採点に回されます。ところが採点は打った直後の画像を参照します。そこでは光の影響で誤った駒が1位になっていました。「決められないときに、より信頼性の低い証拠に戻る」という誤った構造でした。
あとの画像は、決めきれなくても「明らかに異なる駒を除外する」ことはできます。これが効いた理由は後になって判明しました。香を使ったことにすると持ち駒が1枚減り、その先の局面で正しい手が候補に入らなくなってしまうのです。
画面はPython標準のtkinterだけで作りました。画像処理はOpenCV、数値の計算はNumPy、局面の管理はpython-shogiを使っています。
盤をうまく認識できているか不安なときは、「盤の検出結果を見る」を押すと、四隅と9×9の格子を描き込んだ画像が開きます。ここがずれていれば、以降の判定もすべてずれます。最初に確かめておくと確実です。
入力で迷いやすいのは対局の開始時刻です。駒がまだ初期配置に並んでいて、指す手が盤に写っていない瞬間を指定してください。アプリはその1枚から駒の見た目を覚えるので、ここを外すとうまく動きません。
勝敗を手で入力してもらう理由もあります。投了・時間切れ・反則負けは駒が動かないため、画像から判定できないからです。詰みだけは機械的に判定できますが、実際の対局は投了後そのまま感想戦に入って盤面が動き続けるので、自動判定は見送りました。
出力は将棋ソフトでそのまま開けるKIF形式です。
#KIF version=2.0 encoding=Shift_JIS 手合割:平手 先手: 後手: 手数----指手---------消費時間-- 1 7六歩(77) ( 0:00/00:00:00) 27 同 歩(87) ( 0:00/00:00:00) 85 3六香打 ( 0:00/00:00:00) 86 投了 ( 0:00/00:00:00) まで85手で先手の勝ち
KIFには細かい決まりがあります。手番記号(▲△)は書かない、直前と同じマスに指したときは「同 」と全角空白を入れる、文字コードはShift_JIS、消費時間の欄は省くと読めないソフトがある。このあたりを踏まえて実装しました。
処理中は、盤を切り分けたマス画像が大量に生成されます。そのままでは溜まり続けるので、処理の前と後の2回、片付けるようにしました。前に消すのは、動画を切り替えたときに前回のファイルが混ざるのを防ぐため。後に消すのは容量のためです。処理を終えると、棋譜と確認用の画像だけが残ります。
処理は2段階です。まず動画から盤がよく見えているフレームを抜き出し、次にそれを1枚ずつ読んで棋譜にします。
かかる時間を決めるのは動画全体の長さではなく、対局部分の長さです。開始と終了の時刻を指定すると、その範囲だけを調べるためです。
ゲーム実況の動画で測った実例です。
| 時間 | ||
|---|---|---|
| 動画全体 | 41分57秒 | 解説や複数の対局を含む |
| うち対局部分 | 15分34秒 | 開始・終了の時刻で指定した範囲 |
| 処理時間 | 9分52秒 | フレーム抽出6分38秒 + 解析3分14秒 |
対局時間の6割ほどで終わる計算です。15分半の対局なら10分弱。動画を見返しながら手で棋譜を起こすことを考えれば、待っていられる範囲だと思います。
時間の3分の2はフレームの抽出に使われています。動画を細かく調べて「盤がよく見えている瞬間」を探す工程で、ここが処理の主役です。読み取り自体は、抽出さえ済んでいれば速く終わります。
なお、盤が画面に大きく写っている動画ほど1マスあたりの画素が多くなり、照合に時間がかかります。上の数字はあくまで目安です。
| 理由 | |
|---|---|
| カット編集された動画 | 指し手が映っていない |
| 駒落ち・途中局面から | 平手を前提に駒を学習している |
| 1本に複数局(自動分割) | 時間指定で足りると判断 |
| 対局中に盤やカメラが動く | 四隅を1枚目で確定して使い回す設計 |
いちばん上の行について補足します。中継やダイジェストでは、対局の一部が編集で切り落とされ、数手ぶんの映像がまるごと無くなっていることがあります。
これはアプリの精度とは関係のない問題です。指されていない手は画像のどこにも写っていないので、どれだけ読み取りを工夫しても復元できません。情報がそもそも存在しないからです。なおカットとカットの間は、それぞれ正しく読めます。通しの棋譜にはなりませんが、区間ごとの記録としてなら使えます。
編集されているかどうかは、隣り合うフレームで「見た目が大きく変わったマスの数」を数えれば分かります。1手で変わるのは2マス前後で、10マスを超える変化は盤の上で起きたことでは説明が付きません。対局時計が映っていれば、切れ目の前後で時計が何分も進んでいることでも分かります。
2行目の平手を前提にしているのは、1枚目の画像から駒の見た目を覚えるためです。中継の映像では、盤に切り替わってから最初の手が指されるまでの窓が1秒未満しかなく、そこを外すと解析そのものが成立しませんでした。
この記事で説明した仕組みは、GitHubで公開しています。
github.com/onipoo/shogi-kifu-from-video-and-pc-screen
いまのところ閲覧のみで、複製や改変は許可していません。内容が固まった段階で、あらためて決めるつもりです。
本記事で使用した将棋ゲームの画面は、とつげき東北氏にご提供いただきました。仕組みを検討する過程でも、技術的な助言をいただいています。記して感謝いたします。
本文で「オンライン対局サイト」としている画面は lishogi.org のものです。lishogi は AGPLv3 以降のフリーソフトウェアで、盤の画像は the lishogi authors・nexxogen・CouchTomato87 による AGPLv3+、駒の画像(Ryoko_1Kanji)は nexxogen 氏による CC BY-SA 4.0 です。画面は筆者自身のアカウントで対局したものです。
実物の盤の写真、アプリの画面、図表はいずれも筆者が撮影・作成したものです。