VOOZH about

URL: https://qiita.com/eigs/items/5058a5b70ffbac65a5b3

⇱ 数独の楽しみが台無し!写真を撮って解けるようにしてみた話 #JavaScript - Qiita


👁 Image
69

Go to list of users who liked

30

Share on X(Twitter)

Share on Facebook

Add to Hatena Bookmark

More than 3 years have passed since last update.

@eigs(michio)

数独の楽しみが台無し!写真を撮って解けるようにしてみた話

69
Last updated at Posted at 2021-12-12

なにしたの?

GitHub Pages で動く数独ソルバーを作ってみました。

ソース:GitHub: Sudoku Solver via Wasm

端末側ですべての処理は行われて撮った画像がどこかに送られることはないのでご安心ください。

👁 image_0.png

ここでは以下の 3 点について簡単に紹介します。

  • そもそもの動機
  • 画像が処理される過程の解説
  • CNN での予測を安定化させるための工夫

なんでいまさらこんなことを・・

数独を解く例題はたくさんあるが、、何かソフトを立ち上げないと遊べないのはつまらない!ということで誰でも遊べるようにしたかったんです。手軽に公開出来て・手軽にアクセスできるように GitHub Pages に上げました。

数独をプログラムで解かせる例は MATLAB だと

や、File Exchange で "Sudoku" で検索すると 63 件(2021/12/11 時点)もヒットします。中には画像から数独を認識させて解く例もあります。例えばこの 2 つ。前者はセミナーのつかみによく使ってました(笑)

後者はセマンティックセグメンテーションを使って Sudoku を画像から見つけて・・とかなり気合の入った力作です。2018 年に MATLAB Expo UK で ”Computer Vision and Image processing with MATLAB” として講演されました。英語ですが結構丁寧に解説されているので興味のある方は是非こちらチェックしてみてください。(動画ブログ

具体的にどうやったのか

手順は以下の通り

  1. Teja さんのコードをベースに C コード生成できるように書き換え
  2. 数字認識部分は CNN を使うように変更
  3. WebAssembly 化して JavaScript で呼び出し

ポイント1:できるだけ軽く・・

Deep Sudoku Solver by Justin Pinkney のように CNN にいろいろお任せするのも 1 案ではありますが、今回はブラウザで動かすことを考えてできるだけ重い処理は避けたい。なので Deep Learning を使う部分は数字の認識だけにとどめて、更に軽いネットワークでも動くように、数値を取り出すところまでを画像処理で何とかすることにします。画像処理関係の関数は多くが C コード生成に対応しているので WebAssembly への変換も大丈夫です。数字認識(CNN での予測)も手軽に精度が安定するようにちょこっと工夫しました。

ポイント2:WebAssembly 化

MATLAB Coder で C コード化して WebAssembly 化する部分は GenerateJavaScriptUsingMATLABCoder, Geoff McVittie (2021) を使います。このツールは以前も Qiita で紹介しています。

C コード生成に対応している処理であれば MATLAB の処理がそのまま Javascript に統合できるので便利。これは MathWorks 製品に含まれてはいませんが、MathWorks の社員が業務外で作って公開してくれたものです。

最近のアップデートで MATLAB Coder アプリを使ってポチポチと変換できるようになったので手軽です。

実際に変換する際に wasm-ld: error: initial memory too small に遭遇しましたが、これは Emscripten からのメッセージで

-s INITIAL_MEMORY=205914112

というフラグを付けることで回避。背景は @goccy さんの「1年間本番環境で WebAssembly ( by Emscripten )を使ってきた中で生じた問題とその解決策」が参考になります。

主要な関数の概要

必要なソースコードは GitHub: Sudoku Solver via Wasm に置いてありますが、肝になる関数・スクリプトは以下の通りです。

エントリー関数である solveSudokuImage_codegen.m は数独の画像(実装時は 750 x 750 で固定)を入力として受けて、解を上書きした画像を返します。

JavaScript 側から受けるデータ形式に合わせて 1 次元のベクトルとして受ける点に注意です。

使用した Toolbox

  • MATLAB R2021b
  • MATLAB Coder
  • Image Processing Toolbox
  • Deep Learning Toolbox

画像処理ステップ

Deep Sudoku Solver by Justin Pinkney のようにセマンティックセグメンテーションを使って数独を認識させるのも1案ではありますが、今回はブラウザで動かすことを考えてできるだけ重い処理は避けたい。Deep Learning を使う部分は数字の認識だけにとどめ、数値を取り出すところは数独を大きく撮るという撮影方法と+画像処理で何とかすることにします。

ここでは入力画像がどのように処理されていくかの流れを紹介します。

入力画像の 2 値化 + ノイズ除去

まず携帯で撮影した画像を読み込んで 2 値化します。RGB イメージからバイナリ イメージを生成するには、まず rgb2gray でグレースケールに変換して imbinarize で。rgb2grayimbinarize も C コード生成対応関数ですね。

撮影状況も様々だろうと想定し、サンプルにある適応しきい値処理を使います。

Code
Icam = imread('photoEx5.jpg');
figure
imshow(Icam)
👁 attach:cat
Code
Igray = rgb2gray(Icam);
Ibw = ~imbinarize(Igray,'adaptive', ...
 'ForegroundPolarity','dark', ...
 'Sensitivity',0.4); % codegen ok
tmp = Ibw; % 比較表示用

枠内に小さいゴミが見られるので画像全体の 0.01 % (サイズで 1%)より小さいものは bwareaopen を使って取り除きます。

Code
thNoise = numel(Ibw)*0.0001; % 0.01%
Ibw = bwareaopen(Ibw,floor(thNoise)); % code gen ok

さらに今回はあまり気になりませんが、imclearborder で外とつながっているピクセルを除去しておきます。しっかり画像に写っているものだけを気にします。画像を並べて比較するには montage ですね。

Code
Ibw = imclearborder(Ibw); % code gen ok
figure
montage({tmp,Ibw},BackgroundColor='white',BorderSize=[10,10])

👁 figure_1.png

数独の認識

regionprops を使って一番大きな物体(ピクセルの集まり)を探します。数独の他に大きな物体が写り込んでいると上手くいかないので要注意。この辺は画像の撮り方で対処することにします。

Code
R = regionprops(Ibw,'Area','BoundingBox','PixelList') % code gen ok
フィールド Area BoundingBox PixelList
1 44310 [62.5000,100.5000,58... 44310x2 double
2 53 [80.5000,692.5000,14... 53x2 double
3 51 [80.5000,701.5000,9,... 51x2 double
4 65 [84.5000,45.5000,22,... 65x2 double
5 255 [89.5000,117.5000,22... 255x2 double
6 324 [89.5000,170.5000,24... 324x2 double
7 382 [90.5000,284.5000,25... 382x2 double
8 311 [90.5000,344.5000,26... 311x2 double
9 235 [93.5000,406.5000,23... 235x2 double
10 180 [114.5000,4.5000,24,... 180x2 double
11 204 [116.5000,45.5000,23... 204x2 double
12 221 [156.5000,119.5000,2... 221x2 double
13 309 [157.5000,226.5000,2... 309x2 double
14 363 [166.5000,463.5000,2... 363x2 double

結果は構造体ベクトルとして出てきますので、C コード生成対応している範囲で、一番 Area (面積) が大きい物体を探します。

Code
areaMax = 0;
kmax = 0;
for ii=1:length(R)
 if areaMax < R(ii).Area
 areaMax = R(ii).Area;
 kmax = ii;
 end
end

% C コード生成しないならこんな書き方も。。
% [~,kmax] = max(vertcat(R.Area));
% tR = struct2table(R);
% [~,kmax] = max(tR.Area);

一番大きな物体の四隅のピクセル位置を探して、プロット。

Code
tmpR = R(kmax);
tmp = tmpR.PixelList;
DIAG1 = sum(tmp,2);
DIAG2 = diff(tmp,[],2);

[~,dUL] = min(DIAG1); [~,dDR] = max(DIAG1);
[~,dDL] = min(DIAG2); [~,dUR] = max(DIAG2);

pts = tmpR.PixelList([dUL dDL dDR dUR dUL],:);

figure
imshow(Ibw)
hold on 
plot(pts(:,1),pts(:,2),'m','linewidth',3);
👁 attach:cat

幾何学変換

大枠が捉えられたのでここから枠内の数字を抽出します。ただ、今回の画像は数独が斜めに写っていますので、まずは数独を正面に捉えた画像に幾何学的変換。fitgeotrans を使います。その後は 9x9 の升に等分してその中身を数字のピクセルして取り出します。

Code

fixedPoints = [1 1; 9 1; 9 9; 1 9]/10.*size(Ibw); % 変換後の数独の四隅の位置(正方形)
movingPoints = pts(1:4,:); % 変換前の数独の四隅

とその前に、数独がうまく捉えられていない場合は入力をそのまま返すようにエラー処理を入れておきます。数独として捉えただろう枠の面積が全体の 10 % に満たない場合はエラーとしておきます。もう少し大きく映してね。

Code
boxarea = polyarea(movingPoints(:,1),movingPoints(:,2));

if boxarea/(1e6) < 0.1
 tmp = cat(3,Icam,zeros(N,N,1,'uint8'));
 tmp = permute(tmp,[3,2,1]);
 Imask = tmp(:);
 return
else
 transformationType = "projective";
 tform = fitgeotrans(movingPoints,fixedPoints, ...
 transformationType); % code gen ok % error occurs here for bad images
end
Ibw_warp = imwarp(Ibw,tform); % code gen ok
Icam_warp = imwarp(Icam,tform); % code gen ok

figure
montage({Ibw,Ibw_warp},BackgroundColor='white',BorderSize=[10,10])

👁 figure_3.png

再度大きい枠(数独)の探索+中の数字を抽出

変換後の画像内の四隅のピクセル位置を同じ処理で見つけ、今度はその枠を 9x9 に等分します。

👁 image_1.png

69

Go to list of users who liked

30
0

Go to list of comments

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69

Go to list of users who liked

30