『SPY×FAMILY謎解き』のらくえンをPythonで解いてみた（光学文字認識）

# やること
　こんにちは、Suzuka22です。前回はスパイファミリーのたぬき暗号の記事を書きました。引き続き、今回も2020年９月に開催されたキャンペーンの「SPY x FAMILY謎解き」に出てくる謎解き問題を解いてみました。今回は謎解き問題の上級３の一部を光学文字認識で解いてみました

 # 参考文献
　光学文字認識とはテキストの画像を機械で読み取り可能なテキストに変換するプロセスのことを言います
 
https://aws.amazon.com/jp/what-is/ocr/#:~:text=%E5%85%89%E5%AD%A6%E6%96%87%E5%AD%97%E8%AA%8D%E8%AD%98%20(OCR)%20%E3%81%AF,%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%81%AF%E3%81%A7%E3%81%8D%E3%81%BE%E3%81%9B%E3%82%93%E3%80%82

 以下は光学文字認識を使う際に参考した記事です。環境構築を行うときに参考してください

 https://gammasoft.jp/blog/tesseract-ocr-install-on-windows/

 https://qiita.com/ryuka0610/items/20fbea83fa2b193e2d84

 https://openillumi.com/pytesseract-tesseract-not-found-error-solution-guide/

 以下は画像処理で文字認識を行う際に参考した記事です

 https://takaaki-hobby-blog.com/python/python_ocr/
 

 https://takaaki-hobby-blog.com/python/get_word_position/
 

 https://shikaku-mafia.com/opencv-line/
 
# 問題

　こちらの問題です。問題としては新聞と広告の画像から何かのキーワードを抽出するとのことです。 (ソース：SPY×FAMILY SPECIAL MISSION)
フォージャー一家が写っている広告と新聞の画像があります。
　広告と新聞を照らし合わせて見る必要がありそうですね。ここで早速ヒントですが広告に写っているロイドに注目してください

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/964975/1418bc81-8586-4c2d-b7e2-74903cbad8e3.png)

<br>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/964975/0c2e1baf-af07-452a-970a-1ae19a6e50aa.png)


# 答え
　　読者の皆さんは解けましたか？？ちなみに筆者は解けませんでした(汗　
答えは**らくえン**です。なぜらくえンなのか解説していきます

# 解説
　まず、広告のロイドの近くに写っているORDERという文字に注目します。このORDERという文字が存在する箇所を新聞から探します。すると縦並びの文章にO,R,D,Eという文字が存在することが分かります。そして、ORDERの順番で辿っていくと、矢印を一筆書きしたような筆跡が出力されます。出力された筆跡の矢印が指し示す延長線上の文字を読むと「らくえン(楽園)」という単語になります。この単語は意味が通る単語なので正解である確率が高そうです。そのため、答えは「らくえン(楽園)」です

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/964975/060850d6-b502-4b1a-9641-dad83c872c93.png)


 # Pythonで問題を解くための手順
 　問題の解き方が分かりました。ここからはPythonで問題を解いていきます。下記はPythonで問題を解く手順です
  
 1\. 光学文字認識でORDEという４文字を特定
 2\. 1で特定したそれぞれの４文字の座標値を縦並びの文字が写っている画像から取得
 3\. O→R→D→E→Rの順番で線を結ぶ

 # 使用した画像
下記は使用した画像です
新聞から縦並びの文章のみを切り取った画像を使用しました

 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/964975/8337ef21-e6cd-4262-b99a-9b0c283f9b94.png)


# １．光学文字認識でORDEという４文字を特定
　光学文字認識で縦並びの文章を抽出します。新聞から縦並びの文章が写っている箇所を切り取ります。

 ## ４文字を特定するソースコード
　このソースコードを実行したら果たして文字が抽出されるでしょうか
(ドキドキ)

```OCR.py
#システムの利用を宣言する
import sys
import os

#PyOCRを読み込む    
from PIL import Image
import pyocr

#OpenCVの利用を宣言する(画像に四角を書き込む際に使用)
import cv2

#Tesseractのインストール場所をOSに教える
tesseract_path = "C:\Program Files\Tesseract-OCR" 
if tesseract_path not in os.environ["PATH"].split(os.pathsep):
    os.environ["PATH"] += os.pathsep + tesseract_path

#OCRエンジンを取得する
tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("OCRエンジンが指定されていません")
    sys.exit(1)
else:
    tool = tools[0]

#画像の読み込み
file_path = "ここに文字を抽出したい画像のパスを入力"
img = Image.open(file_path)
img2 = cv2.imread(file_path)

box_builder = pyocr.builders.WordBoxBuilder(tesseract_layout=6)
text_position = tool.image_to_string(img, lang="jpn", builder=box_builder)

# 取得した座標と文字を出力し、画像に枠を書き込む
order_dict = {"O": None, "R": None, "D": None, "E": None, "R": None}  # ORDER の順番
for res in text_position:
    if res.content in order_dict:
        print(res.content)
```
## 実行結果
```
D
R
O
```
文字が抽出されました！　しかし、Eが抽出されていません。なぜでしょう
Eが抽出されていない原因を探るために全ての文字を抽出します

## 画像から全ての文字を抽出するソースコード
```OCR_Extraction.py
#システムの利用を宣言する
import sys
import os

#PyOCRを読み込む    
from PIL import Image
import pyocr

#OpenCVの利用を宣言する(画像に四角を書き込む際に使用)
import cv2

#Tesseractのインストール場所をOSに教える
tesseract_path = "C:\Program Files\Tesseract-OCR" 
if tesseract_path not in os.environ["PATH"].split(os.pathsep):
    os.environ["PATH"] += os.pathsep + tesseract_path

#OCRエンジンを取得する
tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("OCRエンジンが指定されていません")
    sys.exit(1)
else:
    tool = tools[0]

#画像の読み込み
file_path = "ここに文字を抽出したい画像のパスを入力"
img = Image.open(file_path)
img2 = cv2.imread(file_path)

box_builder = pyocr.builders.WordBoxBuilder(tesseract_layout=6)
text_position = tool.image_to_string(img, lang="jpn", builder=box_builder)

print(" ".join([res.content for res in text_position]))
```
<br>
縦並びの文章の文字が縦に表示されました

```
P
ア
暮
どす
る
で
一
後
と
政
る
計
中
下
走
1
ん
ら
東
真
る
状
は
東
の
い
東
で
と
4
だ
へ
行
西
し
に
当
仙
し
ー
プ
|
)」。、、
|
し
下
ん
か
態
な
西
情
う
西
あい
名
っ
と
中
国
王
且
軒
較
、
生
届
ロト
、』
に
問
中
)
?
い
間
有
夫
こ
と
間
り
車
が
た
だ
落
に
外
還
用
た
|
有
l
も
に
に
座
何
が
の
悪
と
の
ヽ
の
死
外
下
交
月
記紀
同
/
B
.
人
PE
影
お
いも
が
い
確
化
ち
緊
事
プ
選
交
し
屋
官
全車
出
昨
叫
/
ブリ
償
寺
主計
紋
け
る
分
き
常
執
へ
あ
迫
件
レレ
官
だ
た
外
が
中層
較
剛
骨
胡
紳
<
ヾ
軸
凍る
まめ
っ
に
は
の
り
が
が
仁
1
た
と
用
乗
中
則
販
ン
克
M
の
AND
た
作
財
う
ら
か
火
今
不
い
高
は
キ
?
運
大
防
る
導電
クソ
NN
】
え
着
だ
な
け
種
に
安政
まな
の
警
転
使
音
リ
ンジ
。
で
て
状
?
い
で
が
始
を
府
る
い
改
家
手
館
壁
ム
凍
4
条
。
クリン
リ
似
い
態
二
?
そ
ど
ま
漏
関
中
と
陣
の
ヽ
いま
を
ジ
の
j
ペ
ニ
ジ
ラ
「
。
(AN)
る
は
火
れ
と
こっ
ら
係
で
い
に
発
通
で
超
ン
2
の
う
ル
ン
た
:
N
爆
に
と
?
?
る
に
高
に
フン
生
こ
プー
し
川
NNNA
|
澤
の
の
発
あ
と
今
件
事
よら
途
梨
が
は
き
国
込
る
に
向
化
化
で
寄
そ
声
人
成
や
ら
二
へ
国
D
更
よ
と
わ
で
の
に
R
と
く
は
の
いい
ヽ
民
ま
?
お
は
を
し
の
り
の
今
が
か
長
錦
\
今
の
の
影
氏
に
うと
どれ
あ
が
お
8
語
際
一
首
つ
両
で
れい
け
続
標
た
攻
が
動
や
上
ら
を
易
世
後
影
大
響
の
そ
に
は
る
る
いい
る
に
近
都
訪
国
ある
ずる
き
構
ど
防
全
向
両
が
や
妨
の
界
も
才
手
は
雇
の
ヽ
稀
課
<
両
て
だ
“も
頃
バ
れ
を
る
の
れ
探
いす
言
戦
くに
国
っ
経
げ
閉
か
両
に
財
市
が
奥
を
で
報
両国
も
や
け
信
周
で
!
る
と
?
は
に
り
東
る
え
が
見
注
民
で
て
済
る
塞
ら
国
つ
内
民
透
の
の
は
員
陣
恋
よ
で
気
囲
は
リ
の
り
両
いせ
合
西
外
る
今
ら
目
の
お
活
も
化
よ
間
い
系
生け
東
影
あ
の
営
報
く
な
ア
の
秘
ン
だ
な
国
つ
よい
間
交
回
れ
す
み
り
動
の
な
り
の
て
で
て
銀
活
て
で
西
は
る
活
が
機
引く
ニ
こ
視
密
ト
ろ
す
政
で
ヽ
ー
で
政
他
の
ずる
な
`
い
の
と
ど
孤
緊
次
行
に
見
関
見
が
動
派
関
きつ
メ
線
警
に
うー
府
も
国
を
の
策
方
事
い
中
ら
無
合
な
ヽ
立
迫
の
の
劉
え
係
え
い
が
萌
の
合
N
一
が
察
暮
か
絆
が
その
際
一
の
で
件
む
ヽ
ず
視
滞る
各
・
状
よ
O
ま
る
の
隠
今
明
し
活
い
ES
気
を
ら
?
一
そ
と
思
立
テ
表
いで
し
互
世
で
を
は
国外
態
う
B
ら
よ
全
れ
回
る
て
動
に
WP
に
恐
す
を
れ
に
員
た
1
面
国
よ
ろ
い
界
き
懸
ず
に
貨
が
に
は
な
う
て
レ
の
みい
の
出
S8Y
な
れ
女
育
に
暮
に
せ
プ
的
交
り
水
の
各
な
念
?
お
の
続
語
^
い
で
を
て
事
に
る
過ぎ
る
`
條
お気
ら
巻
て
ル
な
正
表
面
歩
国
い
す
財
け
流
く
す
経
?
あ
握
め
件
出
と
激
れ
8W
“
出
人
目
づ
すぎ
い
下
動
常
画
下
み
が
ご
〇
る
界
る
入
な
?
済
東
る
る
へ
の
る
思
化
る
ー
ム
ー
歩
ざ
```

文字が区切られて表示されています。しかし、表示された文字数が多すぎます。そこで、Eを検索します

```
ES
```
検索に引っ掛かりました！ しかし、抽出された文字が**E**ではなくて**ES**になっています。はっきりとした原因は分かりませんが縦並びの文章が関係しているのかもしれません。仕方がないので１のソースコードの

```
order_dict = {"O": None, "R": None, "D": None, "E": None, "R": None} 
```
のEから

```
order_dict = {"O": None, "R": None, "D": None, "ES": None, "R": None}
```
ESに変更してみます。変更した場合は

```
D
R
O
ES
```
Sも表示されていますが特定したい文字の１つであるEが抽出されました！

# 1で特定したそれぞれの４文字の位置を新聞から取得

とりあえず、ORDEの4文字が抽出されました。次は新聞の画像からORDEの位置を取得します

## 新聞の画像から文字の位置を取得するソースコード
```OCR_position_get.py
#システムの利用を宣言する
import sys
import os

#PyOCRを読み込む    
from PIL import Image
import pyocr

#OpenCVの利用を宣言する(画像に四角を書き込む際に使用)
import cv2

#Tesseractのインストール場所をOSに教える
tesseract_path = "C:\Program Files\Tesseract-OCR" 
if tesseract_path not in os.environ["PATH"].split(os.pathsep):
    os.environ["PATH"] += os.pathsep + tesseract_path

#OCRエンジンを取得する
tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("OCRエンジンが指定されていません")
    sys.exit(1)
else:
    tool = tools[0]

#画像の読み込み
file_path = "C:/Users/homur/Downloads/mission.jpg"
img = Image.open(file_path)
img2 = cv2.imread(file_path)

box_builder = pyocr.builders.WordBoxBuilder(tesseract_layout=6)
text_position = tool.image_to_string(img, lang="jpn", builder=box_builder)

# 取得した座標と文字を出力し、画像に枠を書き込む
order_dict = {"O": None, "R": None, "D": None, "ES": None, "R": None}  # ORDER の順番
for res in text_position:
    #print(res.content)    #  画像から抽出した文字を表示
    if res.content in order_dict:
        print(res.position)
        order_dict[res.content] = res.position
        cv2.rectangle(img2, res.position[0], res.position[1], (0, 0, 255), 2)
```

## 実行結果
```
((1002, 496), (1052, 518))
((1328, 497), (1347, 517))
((935, 753), (962, 807))
((1331, 772), (1386, 792))
```
文字の座標値が出力されました。次は出力された座標値がそれぞれの文字と合致しているのかopencvを使って確認します

## 文字の座標値を可視化するソースコード
```OCR_Position_visualize.py
#システムの利用を宣言する
import sys
import os

#PyOCRを読み込む    
from PIL import Image
import pyocr

#OpenCVの利用を宣言する(画像に四角を書き込む際に使用)
import cv2

#Tesseractのインストール場所をOSに教える
tesseract_path = "C:\Program Files\Tesseract-OCR" 
if tesseract_path not in os.environ["PATH"].split(os.pathsep):
    os.environ["PATH"] += os.pathsep + tesseract_path

#OCRエンジンを取得する
tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("OCRエンジンが指定されていません")
    sys.exit(1)
else:
    tool = tools[0]

#画像の読み込み
file_path = "文字を抽出したい画像のパスを入力"
img = Image.open(file_path)
img2 = cv2.imread(file_path)

box_builder = pyocr.builders.WordBoxBuilder(tesseract_layout=6)
text_position = tool.image_to_string(img, lang="jpn", builder=box_builder)

# 取得した座標と文字を出力し、画像に枠を書き込む
order_dict = {"O": None, "R": None, "D": None, "ES": None, "R": None}  # ORDER の順番
for res in text_position:
    #print(res.content)    #  画像から抽出した文字を表示
    if res.content in order_dict:
        print(res.position)
        order_dict[res.content] = res.position
        cv2.rectangle(img2, res.position[0], res.position[1], (0, 0, 255), 2)


#四角を書き込んだ画像を表示する
cv2.imshow("image",img2)
cv2.imwrite("出力させたい画像のパスを入力",img2)
cv2.waitKey(0)
```
## 座標値を可視化した結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/964975/0bcefb41-1932-4ccd-85bb-40fcbda276dc.png)

４文字の位置を特定することがほぼ出来ています。しかし、Oを囲んでいる赤枠の位置が少々ずれています。また、Dを囲む赤枠が右隣に存在する文字の更まで囲んでいます。縦並びの文章が写っている画像を使用しているのが影響しているのかもしれません

# 3 O→R→D→E→Rの順番で線を結ぶ
とりあえず、文字の座標値の件は大目に見ます。(え)
４文字の座標値を特定することが出来ているとしてORDERの順番で線を結んでいきます。
opencvを使います

## 特定した文字を線で結ぶソースコード
```OCR_line.py
#システムの利用を宣言する
import sys
import os

#PyOCRを読み込む    
from PIL import Image
import pyocr

#OpenCVの利用を宣言する(画像に四角を書き込む際に使用)
import cv2

#Tesseractのインストール場所をOSに教える
tesseract_path = "C:\Program Files\Tesseract-OCR" 
if tesseract_path not in os.environ["PATH"].split(os.pathsep):
    os.environ["PATH"] += os.pathsep + tesseract_path

#OCRエンジンを取得する
tools = pyocr.get_available_tools()
if len(tools) == 0:
    print("OCRエンジンが指定されていません")
    sys.exit(1)
else:
    tool = tools[0]

#画像の読み込み
file_path = "文字を抽出したい画像のパスを入力"
img = Image.open(file_path)
img2 = cv2.imread(file_path)

box_builder = pyocr.builders.WordBoxBuilder(tesseract_layout=6)
text_position = tool.image_to_string(img, lang="jpn", builder=box_builder)

# 取得した座標と文字を出力し、画像に枠を書き込む
order_dict = {"O": None, "R": None, "D": None, "ES": None, "R": None}  # ORDER の順番
for res in text_position:
    #print(res.content)    #  画像から抽出した文字を表示
    if res.content in order_dict:
        print(res.position)
        order_dict[res.content] = res.position
        cv2.rectangle(img2, res.position[0], res.position[1], (0, 0, 255), 2)

# ORDERの順番で線を引く
order_keys = ["O", "R", "D", "ES", "R"]
prev_pos = None
for key in order_keys:
    if key in order_dict and order_dict[key] is not None:
        center_pos = ((order_dict[key][0][0] + order_dict[key][1][0]) // 2, 
                      (order_dict[key][0][1] + order_dict[key][1][1]) // 2)
        if prev_pos is not None:
            cv2.line(img2, prev_pos, center_pos, (255, 0, 0), 2)
        prev_pos = center_pos


#四角を書き込んだ画像を表示する
cv2.imshow("image",img2)
cv2.imwrite("出力させたい画像のパスを入力",img2)
cv2.waitKey(0)
```

## 特定した文字を線で結ぶソースコードの実行結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/964975/aa7c120e-bcd0-4e93-9b26-dbf8eefe8e2b.png)


矢印を一筆書きしたような結果が出力されました！！！！！矢印の延長線上の文字を読むとらくえンであることが確認できました

# さいごに
　今回は謎解き問題の上級３の問題の一部をPythonで解きました。本記事では新聞を画像として扱いました。そして、光学文字認識を使って画像から文字を抽出する手法で問題を解きました。新聞をテキストと扱ってOREDという４文字を探索する方法でも解けそうです。
　今後の課題としては光学文字認識の精度を向上させる、新聞をテキストと扱って解くことが挙げられます

ここまで記事を読んでいただきありがとうございました！！！！！！！！

# [宣伝] 発信中のアカウントについて
※ウマ娘の声をフーリエ変換した記事や水栓の開閉判定をLineに通知する記事も書いています．suzuが私です．興味がある方はぜひご覧ください！

https://vigne-cla.com/31-1/#toc5

※ noteもやっています

https://note.com/madoka235


アニメを題材にテック記事を書いています。
UnityやITなどの備忘録も発信しています。


**ぜひフォローよろしくお願いします!**