やること
正式名称は分かりませんが「この中に一つだけ違う文字があります」という間違い探しがあります。例えばこういうものです↓
こちらのサイトにもたくさんの問題があります。
このような間違い探しを見るたびに「X線結晶構造解析で解けないかな」と考えています。今回は光の回折の原理を応用して「この中に一つだけ違う文字があります」を解いてみます。
実行環境
WinPython3.6をおすすめしています。
Google Colaboratoryが利用可能です。
問題1
自作の問題です。「あ」の中に違うひらがなを混ぜました。「何が」「何個」隠れているのかも問題に含まれますので、ヒントなしの上級問題と言えます。
光の回折とX線結晶構造解析
光の回折については、ヤングの二重スリット実験で学んだ通りです。
光には物の後ろに回り込むように進む「回析」という現象がある。光は波の性質を持つため、回折光も波のように位相を持つ。規則的に並んでいる物体に光を当てると、その後ろに規則的な回折光が生じ、それらは位相により部分的に強め合ったり弱め合ったりする(=干渉する)ことで規則的な模様を生じる。これを回折像と呼ぶ。
この原理を用いて物質の結晶構造を解析するのが「X線結晶構造解析」です。
考え方
「この中に一つだけ違う文字があります」の間違い探しでは規則的に文字が並んでいます。規則的に並んでいる文字に光を当てると、規則的な回折像ができると思います。しかし、一つだけ違う文字があった場合、その近くの回折像が乱れることが予想されます。
この回折像の乱れを見て、隠れている文字の場所を特定しようという発想です。
import、パラメータ
必要なパッケージをインポートします。足りないと怒られたらpip installしてください。
import cv2
import numpy as np
from scipy import signal
import matplotlib.pyplot as plt
#============================
# 設定
#============================
#画像パス
img_path = '14-13_1.jpg'
#波を作る半径
r_set = [0, 2, 4, 6, 8, 10]
#波の高さ
wave_set = [+1, -0.8, +0.6, -0.4, +0.2] # 半径の間の数だけ
表示関数、準備
画像をグレースケールで表示する関数、波紋フィルターを表示する関数を用意します。
#============================
# 表示関数
#============================
def show(img, n=0):
plt.figure(figsize=(15, 15))
plt.imshow(img, cmap='gray')
plt.title(n)
plt.show()
plt.close()
print()
def show_filter(img):
plt.figure(figsize=(10, 10))
plt.imshow(img, vmin = -1, vmax = 1, cmap='bwr')
plt.show()
plt.close()
print()
#============================
# 準備
#============================
#パラメータ表示
print('r_set:\n{}'.format(r_set))
print('wave_set:\n{}'.format(wave_set))
#波紋フィルターの作成
r_max = r_set[-1]
f = np.zeros((r_max*2 - 1, r_max*2 - 1))
for i in range(len(f)):
for j in range(len(f[0])):
#注目するピクセルから中心までの距離
r = ((i + 1 - r_max)**2 + (j + 1 - r_max)**2)**0.5
#どの範囲に属するか調べて波の高さを更新
for k in range(len(r_set) - 1):
if r_set[k] <= r < r_set[k+1]:
f[i, j] = wave_set[k]
break
show_filter(f)
r_set:
[0, 2, 4, 6, 8, 10]
wave_set:
[1, -0.8, 0.6, -0.4, 0.2]
波紋フィルターは回折光を表現しています。波の高さは中心から+1.0, -0.8, +0.6, -0.4, +0.2と振動しながら減衰していく感じです。
メイン処理
ビックリするくらい短いです。畳み込みでおなじみの signal.convolve2d() 関数を用いて、画像を1ピクセルずつ波紋フィルターで畳み込んでいきます。畳込み処理で回折像を作り、回折像に対してまた繰り返し回折処理を行います。
#============================
# メイン処理
#============================
#画像を読み込んで、幅500のグレースケールに変換
img = cv2.imread(img_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
show(img, 0)
#画像に繰り返し回折処理を適用
for i in range(1, 40):
img = signal.convolve2d(img, f, mode='same', boundary='wrap')
show(img, i)
どうでしょう。歪みが見えたでしょうか?心眼で見てください。4箇所歪んでいるところがあります。
隠れている文字は上から順に「ぬ」「お」「め」「ね」でした。
問題2
冒頭のツイートから問題を拝借しました。
結果
こちらも歪みが検出されました。「6」が隠れていました。
問題3
こちらのサイトから問題を拝借しました。
結果
一瞬「どこかな?」と思いましたが、左下にわずかに乱れが生じています。「8」が隠れていました。
まとめ
この手法は文字が規則的にぎっしりと並んでいる場合に有効です。文字の間隔が開いている場合は波紋フィルターを大きくすることで対応できますが、計算時間は2乗に比例して伸びます。また、文字が規則的に並んでいない場合はお手上げです。
しかしながら、「何が」「いくつ」隠れているかがわからなくても使用できるという点では使い所はあると言えるでしょう。長年気になっていた検証ができたので及第とします。