役立つ情報 | 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に削減しました。

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

ブログ    

推薦する

...

私はパニックになりました。上司はこう言いました。「AIはフロントエンドを100%置き換えるだろう」

この記事では、フロントエンド開発と人工知能の関係、そして将来 AI がフロントエンド開発の仕事に取っ...

人工知能は前例のないキャリア革命をもたらすだろう

最近、サンフランシスコでEatsaというアメリカンレストランが人気になっています! [[203610...

...

OpenAIはMicrosoftに対し、Bingチャットボットのリリースを急がないよう警告したと報じられている

6月14日のニュース:最近、人工知能の新興企業OpenAIとMicrosoftが人工知能の分野で協力...

データ保護にはAIベースのセキュリティ戦略が必要

回答者の半数だけが、自社のデータセキュリティ戦略が AI の発展に追いついていると答えました。さらに...

百度副社長の尹世明氏:人工知能のプライバシー問題は技術で解決できる

[[260878]] 「当社は、個人データへのアクセスを必要としないマルチパーティデータコンピューテ...

...

2023年ゴードン・ベル賞発表:最先端のスーパーコンピューターによる「量子レベルの精度」の材料シミュレーションが受賞

ACM ゴードン・ベル賞は 1987 年に設立され、計算機協会によって授与されます。スーパーコンピュ...

株式取引における人工知能の応用

1 か月以上の努力の末、私たちはついに、単純な完全接続ニューラル ネットワークを使用して翌日の株価の...

口コミの逆転、Pika 1.0の試用効果は多くの人々を納得させ、「最高のビデオジェネレーター」と呼んだ

先月末、Pika 1.0と呼ばれる動画生成AIモデルがソーシャルメディア上で話題になった。3Dアニメ...

2021年中国の人工知能産業市場規模とサブ産業の市場予測分析

人工知能は、人間による情報の統合、データの分析、機械の助けを借りた洞察の獲得のプロセスを再構築し、人...

自律型ドローン技術の長所と短所を探る

自律型ドローン技術は、業界全体に変革をもたらす力として登場し、比類のない効率性と革新性を約束していま...

Googleを超えろ!世界最大の時間結晶が記録を破り、量子コンピューターが新たな奇跡を起こす

今日、タイムクリスタルは再び新しいスターを迎え、Sceinceサブマガジンに登場しました。 タイムク...