すごいですね、Python を使用して世界記録を破りました!
皆さんこんにちは、
Python OpenCV を使用して自動地雷除去を実装し、世界記録を破りました。まずその効果を見てみましょう。
中級 - 0.74 秒 3BV/S=60.81
マインスイーパは、昔から古典的なゲーム (グラフィック カード テスト) として知られている人も多いと思います。タイム(ソフトウェア)で、多くの人は中国の雷聖、郭偉佳のことを聞いたことがあるでしょう。彼は中国でナンバーワン、そして世界で第二位の掃海艇でもあります。 Windows 9x 時代に誕生した古典ゲームであるマインスイーパには、テンポの速い高精度なマウス操作、素早い応答性、記録を樹立する爽快感など、独特の魅力が昔から現在まで色濃く残っています。マインスイーパがもたらすすべてのこと マインスイーパの仲間たちがもたらす独特の興奮は、マインスイーパにしかありません。
1. 準備
自動地雷除去ソフトウェアのセットを作成する準備をする前に、次のツール/ソフトウェア/環境を準備する必要があります。
- 開発環境
- Python3 環境 - 3.6 以降を推奨 [Anaconda3 の方が推奨されます。以下の依存ライブラリの多くはインストールする必要がありません]
- numpy 依存ライブラリ [Anaconda をお持ちの場合は不要ですインストールする]
- PIL 依存ライブラリ [Anaconda をお持ちの場合はインストールする必要はありません]
- opencv-python
- win32gui、win32api 依存ライブラリ
- Python をサポートする IDE [テキスト エディターを使用してプログラムを作成できる場合はオプション]
- フォーム コンテンツのインターセプト パートを完了します
- サンダー ブロック セグメンテーション パートを完了します
- 地雷ブロックの種類の識別パートを完了します。
- 地雷除去アルゴリズムを完了します。
class_name = "TMain" title_name = "Minesweeper Arbiter "
- ms_arbiter.exe のメイン ウィンドウ カテゴリは "TMain" ms_arbiter のメイン ウィンドウ.exe 本体名は「マインスイーパーアービター」
hwnd = win32gui.FindWindow(class_name, title_name) if hwnd: left, top, right, bottom = win32gui.GetWindowRect(hwnd)
from PIL import ImageGrab
left += 15 top += 101 right -= 15 bottom -= 43 rect = (left, top, right, bottom) img = ImageGrab.grab().crop(rect)
よし、チェス盤の画像ができました。次のステップは、各地雷ブロックの画像をセグメント化することです~
2. 地雷ブロックの分割
#地雷ブロックを分割する前に、地雷ブロックのサイズとその境界線のサイズを事前に知る必要があります。筆者の計測によれば、ms_arbiter における各地雷ブロックのサイズは 16px*16px です。
block_width, block_height = 16, 16 blocks_x = int((right - left) / block_width) blocks_y = int((bottom - top) / block_height)
def crop_block(hole_img, x, y): x1, y1 = x * block_width, y * block_height x2, y2 = x1 + block_width, y1 + block_height return hole_img.crop((x1, y1, x2, y2)) blocks_img = [[0 for i in range(blocks_y)] for i in range(blocks_x)] for y in range(blocks_y): for x in range(blocks_x): blocks_img[x][y] = crop_block(img, x, y)
3. 雷块识别
这一部分可能是整个项目里除了扫雷算法本身之外最重要的部分了。笔者在进行雷块检测的时候采用了比较简单的特征,高效并且可以满足要求。
def analyze_block(self, block, location): block = imageProcess.pil_to_cv(block) block_color = block[8, 8] x, y = location[0], location[1] # -1:Not opened # -2:Opened but blank # -3:Un initialized # Opened if self.equal(block_color, self.rgb_to_bgr((192, 192, 192))): if not self.equal(block[8, 1], self.rgb_to_bgr((255, 255, 255))): self.blocks_num[x][y] = -2 self.is_started = True else: self.blocks_num[x][y] = -1 elif self.equal(block_color, self.rgb_to_bgr((0, 0, 255))): self.blocks_num[x][y] = 1 elif self.equal(block_color, self.rgb_to_bgr((0, 128, 0))): self.blocks_num[x][y] = 2 elif self.equal(block_color, self.rgb_to_bgr((255, 0, 0))): self.blocks_num[x][y] = 3 elif self.equal(block_color, self.rgb_to_bgr((0, 0, 128))): self.blocks_num[x][y] = 4 elif self.equal(block_color, self.rgb_to_bgr((128, 0, 0))): self.blocks_num[x][y] = 5 elif self.equal(block_color, self.rgb_to_bgr((0, 128, 128))): self.blocks_num[x][y] = 6 elif self.equal(block_color, self.rgb_to_bgr((0, 0, 0))): if self.equal(block[6, 6], self.rgb_to_bgr((255, 255, 255))): # Is mine self.blocks_num[x][y] = 9 elif self.equal(block[5, 8], self.rgb_to_bgr((255, 0, 0))): # Is flag self.blocks_num[x][y] = 0 else: self.blocks_num[x][y] = 7 elif self.equal(block_color, self.rgb_to_bgr((128, 128, 128))): self.blocks_num[x][y] = 8 else: self.blocks_num[x][y] = -3 self.is_mine_form = False if self.blocks_num[x][y] == -3 or not self.blocks_num[x][y] == -1: self.is_new_start = False
可以看到,我们采用了读取每个雷块的中心点像素的方式来判断雷块的类别,并且针对插旗、未点开、已点开但是空白等情况进行了进一步判断。具体色值是笔者直接取色得到的,并且屏幕截图的色彩也没有经过压缩,所以通过中心像素结合其他特征点来判断类别已经足够了,并且做到了高效率。
在本项目中,我们实现的时候采用了如下标注方式:
- 1-8:表示数字1到8
- 9:表示是地雷
- 0:表示插旗
- -1:表示未打开
- -2:表示打开但是空白
- -3:表示不是扫雷游戏中的任何方块类型
通过这种简单快速又有效的方式,我们成功实现了高效率的图像识别。
4. 扫雷算法实现
这可能是本篇文章最激动人心的部分了。在这里我们需要先说明一下具体的扫雷算法思路:
- 遍历每一个已经有数字的雷块,判断在它周围的九宫格内未被打开的雷块数量是否和本身数字相同,如果相同则表明周围九宫格内全部都是地雷,进行标记。
- 再次遍历每一个有数字的雷块,取九宫格范围内所有未被打开的雷块,去除已经被上一次遍历标记为地雷的雷块,记录并且点开。
- 如果以上方式无法继续进行,那么说明遇到了死局,选择在当前所有未打开的雷块中随机点击。(当然这个方法不是最优的,有更加优秀的解决方案,但是实现相对麻烦)
基本的扫雷流程就是这样,那么让我们来亲手实现它吧~
首先我们需要一个能够找出一个雷块的九宫格范围的所有方块位置的方法。因为扫雷游戏的特殊性,在棋盘的四边是没有九宫格的边缘部分的,所以我们需要筛选来排除掉可能超过边界的访问。
def generate_kernel(k, k_width, k_height, block_location): ls = [] loc_x, loc_y = block_location[0], block_location[1] for now_y in range(k_height): for now_x in range(k_width): if k[now_y][now_x]: rel_x, rel_y = now_x - 1, now_y - 1 ls.append((loc_y + rel_y, loc_x + rel_x)) return ls kernel_width, kernel_height = 3, 3 # Kernel mode:[Row][Col] kernel = [[1, 1, 1], [1, 1, 1], [1, 1, 1]] # Left border if x == 0: for i in range(kernel_height): kernel[i][0] = 0 # Right border if x == self.blocks_x - 1: for i in range(kernel_height): kernel[i][kernel_width - 1] = 0 # Top border if y == 0: for i in range(kernel_width): kernel[0][i] = 0 # Bottom border if y == self.blocks_y - 1: for i in range(kernel_width): kernel[kernel_height - 1][i] = 0 # Generate the search map to_visit = generate_kernel(kernel, kernel_width, kernel_height, location)
我们在这一部分通过检测当前雷块是否在棋盘的各个边缘来进行核的删除(在核中,1为保留,0为舍弃),之后通过generate_kernel函数来进行最终坐标的生成。
def count_unopen_blocks(blocks): count = 0 for single_block in blocks: if self.blocks_num[single_block[1]][single_block[0]] == -1: count += 1 return count def mark_as_mine(blocks): for single_block in blocks: if self.blocks_num[single_block[1]][single_block[0]] == -1: self.blocks_is_mine[single_block[1]][single_block[0]] = 1 unopen_blocks = count_unopen_blocks(to_visit) if unopen_blocks == self.blocks_num[x][y]: mark_as_mine(to_visit)
在完成核的生成之后,我们有了一个需要去检测的雷块“地址簿”:to_visit。之后,我们通过count_unopen_blocks函数来统计周围九宫格范围的未打开数量,并且和当前雷块的数字进行比对,如果相等则将所有九宫格内雷块通过mark_as_mine函数来标注为地雷。
def mark_to_click_block(blocks): for single_block in blocks: # Not Mine if not self.blocks_is_mine[single_block[1]][single_block[0]] == 1: # Click-able if self.blocks_num[single_block[1]][single_block[0]] == -1: # Source Syntax: [y][x] - Converted if not (single_block[1], single_block[0]) in self.next_steps: self.next_steps.append((single_block[1], single_block[0])) def count_mines(blocks): count = 0 for single_block in blocks: if self.blocks_is_mine[single_block[1]][single_block[0]] == 1: count += 1 return count mines_count = count_mines(to_visit) if mines_count == block: mark_to_click_block(to_visit)
扫雷流程中的第二步我们也采用了和第一步相近的方法来实现。先用和第一步完全一样的方法来生成需要访问的雷块的核,之后生成具体的雷块位置,通过count_mines函数来获取九宫格范围内所有雷块的数量,并且判断当前九宫格内所有雷块是否已经被检测出来。
如果是,则通过mark_to_click_block函数来排除九宫格内已经被标记为地雷的雷块,并且将剩余的安全雷块加入next_steps数组内。
# Analyze the number of blocks self.iterate_blocks_image(BoomMine.analyze_block) # Mark all mines self.iterate_blocks_number(BoomMine.detect_mine) # Calculate where to click self.iterate_blocks_number(BoomMine.detect_to_click_block) if self.is_in_form(mouseOperation.get_mouse_point()): for to_click in self.next_steps: on_screen_location = self.rel_loc_to_real(to_click) mouseOperation.mouse_move(on_screen_location[0], on_screen_location[1]) mouseOperation.mouse_click()
在最终的实现内,笔者将几个过程都封装成为了函数,并且可以通过iterate_blocks_number方法来对所有雷块都使用传入的函数来进行处理,这有点类似Python中Filter的作用。
之后笔者做的工作就是判断当前鼠标位置是否在棋盘之内,如果是,就会自动开始识别并且点击。具体的点击部分,笔者采用了作者为"wp"的一份代码(从互联网搜集而得),里面实现了基于win32api的窗体消息发送工作,进而完成了鼠标移动和点击的操作。具体实现封装在mouseOperation.py中,可以在查看完整代码:
https://www.php.cn/link/b8a6550662b363eb34145965d64d0cfb
以上がすごいですね、Python を使用して世界記録を破りました!の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











PHPは主に手順プログラミングですが、オブジェクト指向プログラミング(OOP)もサポートしています。 Pythonは、OOP、機能、手続き上のプログラミングなど、さまざまなパラダイムをサポートしています。 PHPはWeb開発に適しており、Pythonはデータ分析や機械学習などのさまざまなアプリケーションに適しています。

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。

PHPはWeb開発と迅速なプロトタイピングに適しており、Pythonはデータサイエンスと機械学習に適しています。 1.PHPは、単純な構文と迅速な開発に適した動的なWeb開発に使用されます。 2。Pythonには簡潔な構文があり、複数のフィールドに適しており、強力なライブラリエコシステムがあります。

VSコードはWindows 8で実行できますが、エクスペリエンスは大きくない場合があります。まず、システムが最新のパッチに更新されていることを確認してから、システムアーキテクチャに一致するVSコードインストールパッケージをダウンロードして、プロンプトとしてインストールします。インストール後、一部の拡張機能はWindows 8と互換性があり、代替拡張機能を探すか、仮想マシンで新しいWindowsシステムを使用する必要があることに注意してください。必要な拡張機能をインストールして、適切に動作するかどうかを確認します。 Windows 8ではVSコードは実行可能ですが、開発エクスペリエンスとセキュリティを向上させるために、新しいWindowsシステムにアップグレードすることをお勧めします。

VSコードはPythonの書き込みに使用でき、Pythonアプリケーションを開発するための理想的なツールになる多くの機能を提供できます。ユーザーは以下を可能にします。Python拡張機能をインストールして、コードの完了、構文の強調表示、デバッグなどの関数を取得できます。デバッガーを使用して、コードを段階的に追跡し、エラーを見つけて修正します。バージョンコントロールのためにGitを統合します。コードフォーマットツールを使用して、コードの一貫性を維持します。糸くずツールを使用して、事前に潜在的な問題を発見します。

PHPは1994年に発信され、Rasmuslerdorfによって開発されました。もともとはウェブサイトの訪問者を追跡するために使用され、サーバー側のスクリプト言語に徐々に進化し、Web開発で広く使用されていました。 Pythonは、1980年代後半にGuidovan Rossumによって開発され、1991年に最初にリリースされました。コードの読みやすさとシンプルさを強調し、科学的コンピューティング、データ分析、その他の分野に適しています。

VSコードでは、次の手順を通じて端末でプログラムを実行できます。コードを準備し、統合端子を開き、コードディレクトリが端末作業ディレクトリと一致していることを確認します。プログラミング言語(pythonのpython your_file_name.pyなど)に従って実行コマンドを選択して、それが正常に実行されるかどうかを確認し、エラーを解決します。デバッガーを使用して、デバッグ効率を向上させます。

VSコード拡張機能は、悪意のあるコードの隠れ、脆弱性の活用、合法的な拡張機能としての自慰行為など、悪意のあるリスクを引き起こします。悪意のある拡張機能を識別する方法には、パブリッシャーのチェック、コメントの読み取り、コードのチェック、およびインストールに注意してください。セキュリティ対策には、セキュリティ認識、良好な習慣、定期的な更新、ウイルス対策ソフトウェアも含まれます。
