役立つ情報 | 115 行のコードで数独パーサーを作成する方法を段階的に説明します。

役立つ情報 | 115 行のコードで数独パーサーを作成する方法を段階的に説明します。

あなたも数独愛好家ですか?

Aakash Jhawar さんは、多くの人と同じように、新しい困難な課題に取り組むことを楽しんでいます。彼は学生時代、毎朝数独をやっていました。私たちが成長し、テクノロジーが進歩すれば、コンピューターを使って数独を解くことができるようになります。数独の画像をクリックするだけで、9 つのマス目がすべて埋められます。

ディン~ここに数独分析チュートリアルがありますので、チェックしてください~ハードコアな乾物を集めるのが好きな友達は、ぜひ見てください~

数独は 9×9 のグリッドで構成され、各行、列、宮殿には 1 から 9 までの数字を入力する必要があり、各行、列、宮殿の数字は重複してはならないことは誰もが知っています。

数独を解くプロセス全体は、3 つのステップに分けられます。

ステップ1: 画像から数独を抽出する

ステップ2: 画像に表示される各数字を抽出する

ステップ3: アルゴリズムを使用して数独の解を計算する

ステップ1: 画像から数独を抽出する

まず、画像処理が必要です。

1. 画像を前処理する

まず、カーネル サイズ (高さ、幅) が 9 の画像にガウス ブラーを適用します。カーネル サイズは正の奇数でなければならず、カーネルは正方形でなければならないことに注意してください。次に、最も近い 11 個のピクセルを使用して適応しきい値が適用されます。

  1. proc = cv2.GaussianBlur (img.copy(), (9, 9), 0)  
  2. proc = cv2.adaptiveThreshold (proc, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)

グリッド線のピクセル値がゼロにならないようにするには、色を反転します。また、画像を拡大すると、グリッド線のサイズが大きくなります。

  1. proc = cv2.bitwise_not (proc, proc)  
  2. カーネル= np.array ([[0., 1., 0.], [1., 1., 1.], [0., 1., 0.]], np.uint8)  
  3. proc = cv2.dilate (proc、カーネル)

閾値処理後の数独画像

2. 最大の多角形の角度を求める

次のステップは、画像内の最大の輪郭の 4 つの角を見つけることです。したがって、すべての等高線を見つけて、面積の降順で並べ替え、面積が最大のものを選択する必要があります。

  1. _、輪郭、 h = cv2.findContours (img.copy()、cv2.RETR_EXTERNAL、cv2.CHAIN_APPROX_SIMPLE)  
  2. 輪郭=ソート済み(輪郭、キー= cv2.contourArea 逆順= True )  
  3. ポリゴン=輪郭[0]

使用する演算子。 max と min を持つ itemgetter を使用すると、そのポイントのインデックスを取得できます。各ポイントは 1 つの座標を持つ配列であり、[0] と [1] はそれぞれ x と y を取得するために使用されます。

右下隅には最大 (x + y) 値があり、左上隅には最小 (x + y) 値があり、左下隅には最小 (x - y) 値があり、右上隅には最大 (x - y) 値があります。

  1. bottom_right、 _ = max (enumerate([pt[0][0] + pt[0][1] ptの場合 
  2. ポリゴン])、キー= operator.itemgetter (1))  
  3. top_left、 _ = min (enumerate([pt[0][0] + pt[0][1] ptの場合 
  4. ポリゴン])、キー= operator.itemgetter (1))  
  5. bottom_left、 _ = min (enumerate([pt[0][0] - pt[0][1] ptの場合 
  6. ポリゴン])、キー= operator.itemgetter (1))  
  7. top_right、 _ = max (enumerate([pt[0][0] - pt[0][1] ptの場合 
  8. ポリゴン])、キー= operator.itemgetter (1))

4 つのポイントの座標がわかったので、インデックスを使用して 4 つのポイントの配列を返す必要があります。各ポイントは独自の座標配列内にあります。

  1. [ポリゴン[左上][0]、ポリゴン[右上][0]、ポリゴン[右下][0]、ポリゴン[左下][0]]

最大の多角形の4つの角

3. 画像の切り抜きと変形

数独パズルの 4 つの座標がわかったので、1 つの画像から長方形の部分を切り取って、同様のサイズの正方形に曲げる必要があります。左上、右上、右下、左下の点によって表される長方形。

注意: データ型を明示的に float32 または 'getPerspectiveTransform' に設定すると、エラーが発生します。

  1. 左上、右上、右下、左下=クロップ矩形[0]、クロップ矩形 [1]、クロップ矩形 [2]、クロップ矩形 [3]  
  2. src = np .array([左上、右上、右下、左下]、 dtype = float32 )  
  3. サイド=最大([ 距離_between(bottom_right, top_right),  
  4. 距離(左上、左下)、  
  5. 距離(右下、左下)、  
  6. distance_between(top_left, top_right) ])

正方形をその辺の長さで表現することは、新たな視点です。次に行う必要があるのは、前後の 4 つのポイントを比較して、画像を傾斜させるための変換行列を取得することです。最後に、元の画像が変換されます。

  1. dst = np .array([[0, 0], [side - 1, 0], [side - 1, side - 1], [0, side - 1]], dtype = float32 )  
  2. m = cv2.getPerspectiveTransform (ソース、dst)  
  3. cv2.warpPerspective(画像, m, (int(サイド), int(サイド)))

切り取られて変形された数独画像

4. 正方形の画像からグリッドを推測する

正方形の画像から 81 個のセルが推測されました。ここで j と i を入れ替えて、四角形が上から下ではなく左から右に読み取られるリストに格納されるようにします。

  1. 正方形= []  
  2. サイド= img .shape[:1]  
  3. サイドサイド= サイド[0] / 9
  4. jが範囲内(9)の場合:  
  5. iが範囲内(9)の場合:  
  6. p1 = (i * 辺、j * 辺) #ボックスの左上隅 
  7. p2 = ((i + 1) * 辺、(j + 1) * 辺) #右下隅
  8.   squares.append((p1, p2))は正方形を返す

5. すべての数字を取得する

次のステップは、セルから数字を抽出し、配列を構築することです。

  1. 数字= []  
  2. img = pre_process_image (img.copy(), skip_dilate = True )  
  3. 正方形の正方形の場合:  
  4. digits.append(extract_digit(画像、正方形、サイズ))

extract_digit は、数独のマス目から数字(ある場合)を抽出する関数です。ボックス全体から数字ボックスを取得し、塗りつぶし特徴検索を使用してボックスの中央の最大の特徴を取得し、数字に属するエッジのピクセルを見つけて中央の領域を定義します。次に、数値をスケーリングしてパディングし、機械学習での使用に適した平方にする必要があります。同時に、小さな境界は無視する必要があります。

  1. def extract_digit(画像、矩形、サイズ):  
  2. 数字= cut_from_rect (画像、rect)  
  3. h, w =数字の形状[:2]  
  4. マージン= int (np.mean([h, w]) / 2.5)  
  5. _, bbox, seed = find_largest_feature (digit, [margin, margin], [w  
  6. - マージン、h - マージン])  
  7. 数字= cut_from_rect (数字、ボックス)  
  8. w = bbox [1][0] - bbox[0][0]  
  9. h = bbox [1][1] - bbox[0][1]  
  10. w > 0 かつ h > 0 かつ (w * h) > 100 かつ len(digit) > 0 の場合:  
  11. scale_and_centre(数字、サイズ、4) を返します 
  12. それ以外:  
  13. np.zeros((size, size), np.uint8) を返します

最後の数独の画像

これで、最終的な前処理済みの数独画像ができました。次のタスクは、画像内の各数字を抽出してマトリックスに保存し、何らかのアルゴリズムを使用して数独の解を計算することです。

ステップ2: 画像に表示される各数字を抽出する

数字認識では、0 から 9 までの数字の画像 60,000 枚を含む MNIST データセットでニューラル ネットワークをトレーニングします。まず、すべてのライブラリをインポートします。

  1. numpyをインポートする 
  2. keras.datasetsからcv2をインポートする 
  3. keras.modelsからmnistをインポートする 
  4. keras.layersからSequentialをインポートする 
  5. keras.layersからDenseをインポートする 
  6. keras.layersからDropoutをインポートする 
  7. keras.layers.convolutional から Flatten をインポートします 
  8. keras.layers.convolutional から Conv2D をインポートします 
  9. keras.utilsからMaxPooling2Dをインポートします 
  10. kerasからnp_utilsをインポートする 
  11. バックエンドをKとしてインポートする 
  12. matplotlib.pyplot を plt としてインポートします。

再現性を確保するには、ランダム シードを固定する必要があります。

  1. K.set_image_dim_ordering( 番目 )  
  2. シード= 7numpy.random.seed (シード)  
  3. (X_train, y_train)、(X_test, y_test) = mnist.load_data()

次に、画像はサンプル*ピクセル*幅*高さに再形成され、入力は 0 ~ 255 から 0 ~ 1 に正規化されます。この後、出力はワンホットエンコードされます。

  1. X_train X_train = X_train.reshape(X_train.shape[0], 1, 28,  
  2. 28).astype( float32 )  
  3. X_test X_test = X_test.reshape(X_test.shape[0], 1, 28,  
  4. 28).astype( float32 )  
  5. X_train X_train = X_train / 255  
  6. X_テストX_テスト= X_テスト / 255
  7. y_train = np_utils.to_categorical (y_train)  
  8. y_test = np_utils.to_categorical (y_test)  
  9. num_classes = y_test.shape [1]

次に、手書きの数字を予測するモデルを作成します。

  1. モデル=シーケンシャル()  
  2. モデルを追加します(Conv2D(32, (5, 5), input_shape =(1, 28, 28),  
  3. アクティベーション= relu ))  
  4. model.add(MaxPooling2D( pool_size =(2, 2)))model.add(Conv2D(16, (3,  
  5. 3)、活性化= relu ))  
  6. モデルを追加します(MaxPooling2D(プールサイズ=(2, 2)))
  7. モデルを追加します(ドロップアウト(0.2))  
  8. モデルを追加します(フラット化())  
  9. model.add(Dense(128, activation = relu ))  
  10. model.add(Dense(64, activation = relu ))  
  11. model.add(Dense(num_classes, activation = softmax ))

モデルの概要

モデルを作成したら、それをコンパイルし、データセットに適合させて評価する必要があります。

  1. model.compile(損失= categorical_crossentropy オプティマイザー= adam  
  2. メトリクス=[精度])  
  3. モデル.fit(X_train, y_train,検証データ=(X_test, y_test),  
  4. エポック= 10 バッチサイズ= 200 )  
  5. スコア=モデル.evaluate(X_test, y_test, verbose = 0 )  
  6. print("大きなCNNエラー: %.2f%%" % (100-スコア[1]*100))

ここで、上記で作成したモデルをテストします。

  1. テスト画像= X_テスト[1:5]  
  2. test_images test_images = test_images.reshape(test_images.shape[0], 28, 28)  
  3. print ("テスト画像の形状: {}".format(test_images.shape))  
  4. i、test_image を enumerate(test_images、 start = 1 ) します:  
  5. org_image =テストイメージ   
  6. test_image test_image = test_image.reshape(1,1,28,28)  
  7. 予測=モデル.predict_classes(test_image, verbose = 0 )  
  8. print ("予測された数字: {}".format(prediction[0]))  
  9. plt.サブプロット(220+i)  
  10. plt.axis( オフ )  
  11. plt.title("予測された数字: {}".format(prediction[0]))  
  12. plt.imshow(org_image, cmap = plt .get_cmap( グレー ))  
  13. plt.show()

手書き数字分類モデルから予測された数字

ニューラルネットワークの精度は 98.314% です!最後に、シーケンス モデルを保存して、使用する必要があるときに何度もトレーニングする必要がないようにしておきます。

  1. # モデルをJSONにシリアル化する 
  2. モデルmodel_json = model.to_json()  
  3. open("model.json", "w") を json_file として実行します:  
  4. json_file.write(モデルjson)
  5. # 重みを HDF5 にシリアル化します 
  6. モデル.save_weights("model.h5")  
  7. print("モデルをディスクに保存しました")

手書き数字認識に関する詳細情報:

https://github.com/aakashjhawar/手書き数字認識

次のステップは、事前トレーニング済みのモデルをロードすることです。

  1. json_file =開く(model.json, r)  
  2. loaded_model_json = json_file .read()  
  3. json_file.close()  
  4. loaded_model = model_from_json (読み込まれたモデルjson)  
  5. loaded_model.load_weights("model.h5")

画像のサイズを変更し、9x9 の画像に分割します。それぞれの小さな画像には 1 から 9 までの番号が付いています。

  1. 数独= cv2.resize (数独, (450,450))  
  2. グリッド= np.zeros ([9,9])  
  3. iが範囲内(9)の場合:  
  4. jが範囲内(9)の場合:  
  5. 画像=数独[i*50:(i+1)*50,j*50:(j+1)*50]  
  6. image.sum() > 25000の場合:  
  7. grid[i][j] = 識別番号(画像)  
  8. それ以外:  
  9. グリッド[i][j] = 0  
  10. グリッドグリッド= grid.astype(int)

identify_number 関数は、数字の画像を取得し、画像内の数字を予測します。

  1. def identify_number(画像):  
  2. image_resize = cv2.resize (image, (28,28)) # plt.imshowの場合 
  3. image_resize image_resize_2 = image_resize.reshape(1,1,28,28) # model.predict_classesへの入力用 
  4. # cv2.imshow( 数値 , image_test_1)  
  5. loaded_model loaded_model_pred = loaded_model.predict_classes(image_resize_2  
  6. 詳細= 0 )  
  7. loaded_model_pred[0]を返す

上記の手順を完了すると、数独グリッドは次のようになります。

抽出された数独

ステップ3: バックトラッキングアルゴリズムを使用して数独の解を計算する

バックトラッキング アルゴリズムを使用して、数独の解を計算します。

グリッドでまだ割り当てられていないエントリを検索します。参照パラメータ row が見つかった場合、col は未割り当ての位置にセットされ、true が返されます。割り当てられていないエントリが残っていない場合は false を返します。 「l」は、増加する行と列を追跡するためにsolve_sudoku関数に渡されるリスト変数です。

  1. def find_empty_location(arr,l):  
  2. 範囲(9)内の行の場合:  
  3. 範囲(9)内の列の場合:  
  4. (arr[行][列]==0の場合)  
  5. l[0]=行 
  6. l[1]=列 
  7. Trueを返す 
  8. Falseを返す

指定された行に割り当てられた項目のいずれかが指定された番号と一致するかどうかを示すブール値を返します。

  1. 使用中の行を定義します(arr,行,数値):  
  2. iが範囲内(9)の場合:  
  3. if(arr[行][i] == num):  
  4. Trueを返す 
  5. Falseを返す

指定された列に割り当てられた項目のいずれかが指定された番号と一致するかどうかを示すブール値を返します。

  1. 使用される列を定義します(arr、列、数値):  
  2. iが範囲内(9)の場合:  
  3. if(arr[i][col] == num):  
  4. Trueを返す 
  5. Falseを返す

指定された 3x3 ボックス内の割り当てられた項目のいずれかが指定された番号と一致するかどうかを示すブール値を返します。

  1. def used_in_box(arr,row,col,num):  
  2. iが範囲(3)内にある場合:  
  3. jが範囲(3)内にある場合:  
  4. if(arr[i+行][j+列] == 数値):  
  5. Trueを返す
  6.   Falseを返す

指定された (行、列) に num を割り当てることが正当かどうかを確認します。 'num' が現在の行、現在の列、現在の 3x3 ボックスにまだ配置されていないかどうかを確認します。

  1. def check_location_is_safe(arr,row,col,num):  
  2. used_in_row(arr,row,num)を返さず、  
  3. used_in_col(arr,col,num) ではなく、  
  4. not used_in_box(arr,row - row%3,col - col%3,num)

部分的に埋められたグリッドを取得し、数独の解答要件(行、列、ボックス間で重複しない)を満たすすべての未割り当ての位置に値を割り当てようとします。 「l」は、find_empty_location 関数の行レコードと列レコードを保持するリスト変数です。上記の関数から取得した行と列をリストの値に割り当てます。

  1. 数独を解く定義(arr):  
  2. l = [0,0]  
  3. find_empty_location(arr,l) が存在しない場合には:  
  4. Trueを返す 
  5. = l [0]  
  6. =l[1]  
  7. numが範囲(1,10)の場合:  
  8. check_location_is_safe(arr,row,col,num) の場合:  
  9. arr[行][列]=数値 
  10. (solve_sudoku(arr))の場合:  
  11. Trueを返す 
  12. # 失敗、元に戻して再試行 
  13. arr[行][列] = 0  
  14. Falseを返す

最後にグリッドを印刷します。

  1. def print_grid(arr):  
  2. iが範囲内(9)の場合:  
  3. jが範囲内(9)の場合:  
  4. 印刷 (arr[i][j])
  5.   印刷 ( )

最後に、すべての関数をメイン関数に統合します。

  1. 定義 sudoku_solver(グリッド):  
  2. (数独を解く(グリッド))の場合:  
  3. 印刷(---)  
  4. それ以外:  
  5. print ("解決策は存在しません")  
  6. グリッドグリッド= grid.astype(int)
  7.   リターングリッド

この関数の出力は、最終的に解かれた数独になります。

最終解決策

もちろん、このソリューションは決して完璧というわけではなく、解析できない画像や、誤って解析されて処理できない画像を処理するときに、依然としていくつかの問題が発生します。しかし、私たちの目標は新しい技術を探求することであり、この観点からすると、このプロジェクトは依然として価値があります。

<<:  マイクロソフトは、兆パラメータのAIモデルのトレーニングに必要なGPUを4,000から800に削減しました。

>>:  デザイナーのための人工知能ガイド: 基本概念

ブログ    
ブログ    

推薦する

ChatGPT が突然大きなバグを発見しました!フル機能のGPT-4は無料で使用でき、ネットユーザーは大喜びしている

11月15日、OpenAIは突然、ChatGPT Plusの新規ユーザー登録を停止すると発表しました...

配達員に代わるドローン配達は、人々に「嫌われるのではなく愛される」ようになる

現在、人々の生活や仕事のペースはますます加速し、インターネット電子商取引プラットフォームは急速に発展...

危険な環境を恐れず、人工知能配信ネットワークライブ操作ロボットが重い責任を勇敢に引き受けます

現在、産業、農業、住民の電力消費は急速に増加しており、風力発電、水力発電などの電源が電力供給の主な手...

AIが人間社会に与える影響

今後 25 年間は、既存の制御可能かつプログラム可能ないわゆる「人工知能」を活用して、人類が生物学の...

機械学習に必須: TensorFlow を使用するための 11 のヒント

[[326623]] TensorFlow 2.x は、モデルの構築と全体的な使用において多くの利便...

自然言語処理(NLP)はソーシャルエンジニアリング攻撃の解決に役立ちます

新しいツールは、件名や URL に基づいてソーシャル エンジニアリング攻撃を検出するのではなく、テキ...

飛んでくる花穂は人々を不安にさせますが、人と機械の組み合わせで不安を防ぐことができます!

「霧深い春の朝、緑の枝に雪の結晶が舞い散る。」さあ、また雪のように雪の結晶が舞い散る季節がやってき...

このAIは、監視カメラを素早く検索し、重要なシーンを見つけ、24時間のビデオを10分で処理するのに役立ちます。

1月23日のニュース、今日では、ビデオ監視の存在により、過去には検証が困難だった多くの事実を記録す...

2019年の世界人工知能チップ産業の市場競争状況の分析

1. 世界の人工知能チップ産業の企業概要の分析近年、さまざまな勢力が AIチップに注目しています。参...

データベースセキュリティとテーブル検索攻撃における MD5 暗号化アルゴリズムの応用

MD5 は最も広く使用されているハッシュ アルゴリズムの 1 つです。1992 年に MIT の R...

自動化された機械学習: よく使われる 5 つの AutoML フレームワークの紹介

AutoML フレームワークによって実行されるタスクは、次のように要約できます。データを前処理して...

清華大学の博士が「チップレット・アクチュアリー」サミットを提案!ムーアの法則に近づくほど、マルチチップ統合のコスト効率は向上する。

Chiplet は、製品の歩留まり、パッケージの歩留まり、さまざまなコストなどを考慮しながら、大規...

毎日のアルゴリズム: 文字の繰り返しのない最長の部分文字列

[[421075]]この記事はWeChatの公開アカウント「3分でフロントエンドを学ぶ」から転載した...

...

レースをしながら「機械学習」を学ぶ? 380万人が観ていなかったら、信じられなかったでしょう。

[[440972]] 「秋名山には人が少なく、ドライバー同士が競争することが多い。今は自動運転車が...