見出し画像

サイゼリヤで間違い探しをしていると延々と滞在してしまう件は、プログラムで解決できるのか。

ツキシマでーす。

サイゼリヤのラムのランプステーキって美味しいですよね。

また羊の話か。


ラムを食べてワインを飲みながら、間違い探しにハマる(時間的に)

毎回、新作の間違い探しが出るたびに全部見つけるまで帰れない縛りをするんですが、今回のは特に難しい!

問題の間違い探し(上の鉄板がラムだけど見切れてる)

結局、いろいろつまみながら全部見つかるまで2時間近くいて、2人でワインの 500ml デカンタ3本で済みましたが、飲めば飲むほど見つからなくなるので危うく帰れなくなるところでした。


本題:回答を見ると負けた気がするので見ないけど、自分でプログラムを書いて見つければ負けではない。

「自分でプログラムを書く」「プログラムが間違いを探す」ができれば、「ちゃんと自分で間違いを探しました」とも言えますね!(推移律)

ということで、上の写真から間違いを見つけましょう。プログラムとか気にしないアングルで写真を撮ってしまったので、ゴリゴリの MATLAB プログラムを書いて切り取りや角度補正からやりましょう。

またプログラムの話か。

注意:間違いが見つかっちゃうので、サイゼリヤの間違い探しを楽しみにしてる人は、ここから下は見ちゃダメですよ!


手順1:テーブルと紙を画像的に分離したいので、明るさで分類してみる。

すごいカラフルな絵でたくさん色を含むので、RGB の色空間でテーブルと紙を分離するのは難しいですね。HSVに変換して V を見たほうが良さそう。

I = imread("saize.jpg");
HSV = rgb2hsv(I);
V = HSV(:,:,3);
明度 V(白いほど明るい)


手順2:紙とテーブルでは明らかにエントロピーが違うので、エントロピーフィルターを適用する。

小さい領域に区切って見てみると、間違い探しの絵の部分は細かい線とか色変化が少ない(エントロピーが低い)のと、テーブルは木目でガサガサしてる感じがする(エントロピーが高い)ので、エントロピーの低い箇所を取り出してみます。

R = entropyfilt(V);
BW = R < 2.718;
まあまあ取れてる(エントロピーが低いのが白)


手順3:モルフォロジー処理をして、凸包でくくってみる。

小さい物体と隙間が多いので、モルフォロジーのオープン・クローズ処理なんかを駆使すればよさそうですね。

BW = bwareaopen(BW,1000);
st = strel('rectangle',[10,200]);
BW = imclose(BW,st);
BW = imclearborder(BW);
BW = bwconvhull(BW);

figure(1)
imshow(I)
hold on
visboundaries(BW,'LineWidth',5)

ちゃんと紙の部分だけ取れてるか確認のため、元の画像に結果を重ねて書いてみます。

ちょっとズレてるけど取れてます!(ちょっとくらいズレても問題ない。)


手順4:エッジ検出してハフ変換すれば、紙の4つの頂点座標が取れるはず。

傾いたままだと間違い探しが難しいので、長方形の紙の4つの頂点を空間変換するために取得したいと思いますが、どうやら紙が丸くカットされております。ハフ変換で直線の数式は求まるので、$${y = a_1x + b_1}$$ と $${y = a_2x + b_2}$$ であれば、以下の式でカットされている頂点座標 (x,y) が求まります。

$$
\begin{bmatrix}
x \\ y
\end{bmatrix}

\begin{bmatrix}
-a_1 & 1 \\ -a_2 & 1
\end{bmatrix}
\backslash
\begin{bmatrix}
b_1 \\  b_2
\end{bmatrix}
$$

E = edge(BW);
[H,t,r] = hough(E);
pks = houghpeaks(H,4,"Threshold",100,'NHoodSize',[777 33]);
lines = houghlines(E,t,r,pks,"FillGap",1000,"MinLength",200);

ab = cell(1,length(lines));
for n = 1:length(lines)
    ab{n} = polyfit([lines(n).point1(1) lines(n).point2(1)],[lines(n).point1(2) lines(n).point2(2)],1);
end

xpoint = zeros(length(lines),2);
for n = 1:2
    for m = 3:4
        xpoint(m-2+(n-1)*2,:) = ([-ab{n}(1)  1; -ab{m}(1)  1] \ [ab{n}(2); ab{m}(2)])';
    end
end

figure(1)
imshow(I)
hold on

for n = 1:length(lines)
    plot([lines(n).point1(1) lines(n).point2(1)], [lines(n).point1(2) lines(n).point2(2)],'m:','LineWidth',5)
end

for n = 1:length(lines)
    plot(xpoint(n,1),xpoint(n,2),'gx','LineWidth',6,'MarkerSize',20)
end
角が欠けてないとして、緑のバツが頂点座標


手順5:紙のサイズは分かりませんが、比率が 1:2 っぽいので、それに合わせる。

上の緑のバツ座標を、長方形 [縦2000 x 横4000 (px)] の頂点に射影変換すれば傾きが補正できますね。

basepoint = [4000 2000;0 2000;4000 0;0 0];
tform = fitgeotform2d(xpoint,basepoint,"projective");
P = imwarp(I,tform,OutputView=imref2d([2000 4000]));
傾き補正をして、やっとスタート地点になりました!


手順6:真ん中から左右2つに切って、強度ベースで相関の高いところに合わせる。

適当に半分になるように左右の画像に分離します。その後に右側の画像にレジストレーション推定をしてアフィン変換をするとズレ補正ができます。

I_Left = P(:,1:2000,:);
I_Right = P(:,2001:end,:);

[optimizer,metric] = imregconfig("monomodal");
optimizer.MaximumIterations = 300;

R = imregtform(rgb2gray(I_Right),rgb2gray(I_Left),'affine',optimizer,metric);

Rfixed = imref2d(size(I_Left));
I_Right_Corr = imwarp(I_Right,R,OutputView=Rfixed);
figure(1)
imshowpair(I_Left,I_Right_Corr)

補正して左右の画像を重ね書き!

緑が間違いのある場所

※ 右上の雲と空はラム肉の鉄板で押さえてるので歪んで違いが出てますが、違わない部分です。


手順7:色の違いは出てこないので、差分を L*a*b*色空間の a*値で見てみる。

上の絵で大きめに出てる緑の箇所を数えれば9個くらい見つかると思いますが、サイゼの間違い探しは色だけが違ったりします。2つの画像の差分を取って、色が変わった所を探してみます。

X = imabsdiff(I_Left,I_Right_Corr);
LAB = rgb2lab(X);
ColorD = LAB(:,:,2) > 30; 
ColorD = bwareaopen(ColorD,1000);
figure(1)
imshowpair(I_Left,ColorD)
赤い部分が L*a*b* の a* 空間(緑赤空間)で差分が大きい=間違いの箇所。

右下の子の服の色で10個ですね!


まとめ:プログラム任せじゃなくて、自分で解いてからプログラムでも解けば二回楽しい。

ちょうど今、こんな画像処理を駆使する仕事が1つ入ってるので、プログラムの練習がてらちょうどいい問題でしたが、自力で間違いを探すのも楽しいので、結局は今後も新しい間違い探しが出るたびにワインを飲みながら解いてると思います。

あと、ラムのランプステーキは近畿以西しか販売されてないみたいなので、西の方でサイゼリヤに行く人は食べてみてね。(イチオシ)

ランプステーキの写真はないのか。


この記事が参加している募集

私のイチオシ

この記事が気に入ったらサポートをしてみませんか?