プログラマーが違いを探す方法





多くの場合、プログラム、ゲーム、またはWebサイトを見ると、奇妙な考えがあることに気づきます。 そして、これらの考えは私を怖がらせます。 そして、このプログラム/サイト/ゲームがどのように接続され、ハッキングされ、保護をバイパスされ、自動化され、機能が拡張されるかを考えるたびに。 おそらく、プロの変形はそれ自体を感じさせます。 または、仕事で使用されていない蓄積された知識を使用したいという潜在意識の欲求です。 原則として、これらの欲求は思考レベルにとどまりますが、例外があります。 今日はそのようなケースについてお話します...



ずいぶん前のことです。 2008年のコマーシャル。これは普通の冬の日でした。 眠れぬ夜を予感させるものはありませんでした。 しかし、その後、私は将来の妻が1つのゲームでコンピューターでどのようにプレーするかに気付きました...





それがゲームでした(「5つの違いを見つける」)(元の「5つのスポット」)。 ゲームのユーザーインターフェースを見たとき、すぐに前述の要望がありました-「違いを探して、マウスでクリックする場所をプレイヤーに伝える、または自分で動かして自分で押すプログラムを作成することは可能ですか?」 結局のところ、何でも可能です。



ゲーム自体は非常に古く、原始的です。 スクリーンショットからわかるように、違いのある2つの写真が表示され、ユーザーがマウスでクリックするまで待機します。 すべてがシンプルです。 私はまた、私の決定でこのアプローチを選択しました。

1.ユーザーがヘルププログラム(PP)を起動します

2.ターゲットゲームを起動します

3.マジックキーの組み合わせを押す

4.写真の適切な場所で、PPは違いを強調します。



プログラムが私に話すとき、私はそれが好きです:彼らはログを書いて、彼らの行動について報告して、エラーを報告します。 それから、プログラムはその仕事をする魂のない乾いたアルゴリズムではなく、生きている有機体であるようです。 彼は静かに、ときどきメッセージを表示したり、おしゃべりで積極的にコンソールにフィガチャしたりすることができます...





一般的に、PPの基盤としてコンソールアプリケーションを選択しました。 ホットキーの組み合わせCtrl + F1(タイプ、「ヘルプ」)を登録し、ハンドラーを切断しました。 しかし、ゲームの2つの写真の違いを見つける方法は? まず第一に、写真はプログラムで「見られる」必要がありました。 ここでも、すべてが単純です。ホットキーを押すことで、フォーカスされているウィンドウをメモリに「写真」します。



画面キャプチャ
HWND targetWindow = ::GetForegroundWindow(); HDC targetWindowDC = ::GetWindowDC(targetWindow); if (targetWindowDC != NULL) { HDC memoryDC = ::CreateCompatibleDC(targetWindowDC); if (memoryDC != NULL) { CRect targetWindowRectangle; ::GetWindowRect(targetWindow, &targetWindowRectangle); HBITMAP memoryBitmap = ::CreateCompatibleBitmap(targetWindowDC, targetWindowRectangle.Width(), targetWindowRectangle.Height()); if (memoryBitmap != NULL) { ::SelectObject(memoryDC, memoryBitmap); ::BitBlt(memoryDC, 0, 0, targetWindowRectangle.Width(), targetWindowRectangle.Height(), targetWindowDC, 0, 0, SRCCOPY);
      
      









ゲーム内で違いのある画像の位置は一定で、ゲームウィンドウのサイズも一定です。したがって、オフセットとサイズのハードコードを解決します(結局、このソフトウェアはこのゲームでのみ動作します)。 メモリ内で2枚の写真を撮り、それらを重ねて「Xorim」します。

XOR 2つの半分
  #define BITMAP_WIDTH 375 #define BITMAP_HEIGHT 292 #define COORD_X_LEFT_IMAGE_UPPER_LEFT 19 #define COORD_Y_LEFT_IMAGE_UPPER_LEFT 152 #define COORD_X_RIGHT_IMAGE_UPPER_LEFT 405 #define COORD_Y_RIGHT_IMAGE_UPPER_LEFT COORD_Y_LEFT_IMAGE_UPPER_LEFT ::BitBlt( memoryDC, COORD_X_LEFT_IMAGE_UPPER_LEFT, COORD_Y_LEFT_IMAGE_UPPER_LEFT, BITMAP_WIDTH, BITMAP_HEIGHT, memoryDC, COORD_X_RIGHT_IMAGE_UPPER_LEFT, COORD_Y_RIGHT_IMAGE_UPPER_LEFT, SRCINVERT );
      
      









次の図が表示されます。





そして、違いの検索が始まります。



さて、私がこの記事を書いているとき、大学でこのトピックに関するある種の研究室または期間プロジェクトを持っていたことを思い出します。 同様の画像の処理に関するトピック。 そして、私はこのアルゴリズムを書きました。 私は新しいものを発明しなかったことを理解しています-おそらく、このアルゴリズムには特別な名前さえあります。 そして、彼はまったくイメージに執着していません。 一般に、それが何であるかを誰が知っているか、教えてください。



そのため、違いがあった場所に黒以外のピクセルを持つ黒の画像があります。 さらに、これらのピクセルは互いに近くに配置されていませんが、一般的な場合、いくつかのギャップがあります。 しかし、スクリーンショットからわかるように、違いはかなり局所化されています。 これらの領域の検索アルゴリズムは次のとおりです。

1.写真を確認する

2.黒でないピクセルを見つける

3.その周辺を見て、黒でない隣人を探します-見つかった領域にすべてを置きます(問題のピクセルが以前に処理されなかった場合)



ここで調整可能なパラメーターは、ピクセル近傍の「サイズ」です-そこからどれだけ遠くまで見ることができますか。 これにより、差異のより多くの「不鮮明な」領域を検索できます。 これはすべて不完全であり、一般的な場合、写真の違いよりも多くの領域があることは明らかです-結局のところ、写真自体が圧縮、ぶら下がりのマウスカーソル、またはプログラムレベルで違いのように見える何かからノイズを引き起こす可能性があります。しかし、プレーヤーの観点からは感知できません。 したがって、検出された差異はエリアごとにソートする必要があります。エリアに含まれる黒以外のピクセルが多いほど、ノイズではない可能性、つまり差異が大きくなります。



後で、OpenCVを見つけて試しました(おそらくそれについての記事があるでしょう)。 より高速で最適化されたアルゴリズムがあると思います。 しかし、その後、私はちょうどそのようなオプションがありました。





違いの検索のソース(コードは古いため、変更なしで公開しています):

違いを探す
 #include "StdAfx.h" #include ".\bitmapinfo.h" #include <stack> const CPixel CBitmapInfo::m_defaultPixel; CBitmapInfo::CBitmapInfo(void) { m_uWidth = 0; m_uHeight = 0; } CBitmapInfo::~CBitmapInfo(void) { Clear(); } HRESULT CBitmapInfo::Clear() { m_uWidth = 0; m_uHeight = 0; // Pixel clearing for (CPixelAreaIterator pixelAreaIterator = m_arPixels.begin(); pixelAreaIterator != m_arPixels.end(); ++pixelAreaIterator) { delete (*pixelAreaIterator); } m_arPixels.clear(); return S_OK; } HRESULT CBitmapInfo::LoadBitmap(HDC hDC, const CRect &bitmapRect) { Clear(); m_uWidth = bitmapRect.Width(); m_uHeight = bitmapRect.Height(); m_arPixels.assign(m_uHeight * m_uWidth, NULL); for (INT nPixelY = 0; nPixelY < m_uHeight; ++nPixelY) { for (INT nPixelX = 0; nPixelX < m_uWidth; ++nPixelX) { CPixel *pPixel = new CPixel(nPixelX, nPixelY, ::GetPixel(hDC, nPixelX + bitmapRect.left, nPixelY + bitmapRect.top)); SetPixel(nPixelX, nPixelY, pPixel); } } return S_OK; } HRESULT CBitmapInfo::GetPixelAreas(INT nPixelVicinityWidth, CPixelAreaList &arPixelAreaList) { arPixelAreaList.clear(); if (m_uHeight > 0) { // Reinitialize all pixel reserved values (if needed) const CPixel *pFirstPixel = GetPixel(0, 0); if (pFirstPixel->IsValid() != FALSE && pFirstPixel->GetReserved() != CBitmapInfo::m_defaultPixel.GetReserved()) { for (INT nPixelY = 0; nPixelY < m_uHeight; ++nPixelY) { for (INT nPixelX = 0; nPixelX < m_uWidth; ++nPixelX) { CPixel *pPixel = GetPixel(nPixelX, nPixelY); pPixel->SetReserved(-1); } } } // Process pixels typedef stack<CPixel*> CPixelStack; // Look through all bitmap pixels const UINT uPixelCount = m_uWidth * m_uHeight; UINT uPixelAreaIndex = 0; for (INT nPixelY = 0; nPixelY < (INT)m_uHeight; ++nPixelY) { for (INT nPixelX = 0; nPixelX < (INT)m_uWidth; ++nPixelX) { CPixel *pPixel = GetPixel(nPixelX, nPixelY); // If this pixel is valid (belongs to bitmap) if (pPixel->IsValid() != FALSE) { // If this current pixel is not already processed if (pPixel->GetReserved() == CBitmapInfo::m_defaultPixel.GetReserved()) { // Set this pixel as processed pPixel->SetReserved(uPixelAreaIndex); // If this pixel matches localization criteria if (pPixel->GetColor() != COLOR_BITMAP_BACKGROUND) { // Add pixel to its area CPixelArea *pPixelArea = new CPixelArea(); pPixelArea->push_back(pPixel); // Push pixel to its stack CPixelStack pixelStack; pixelStack.push(pPixel); do { CPixel *pVicinityPixel = pixelStack.top(); pixelStack.pop(); INT nStartingX = pVicinityPixel->GetX(); INT nStartingY = pVicinityPixel->GetY(); for (INT nVicinityY = nStartingY - nPixelVicinityWidth; nVicinityY <= nStartingY + nPixelVicinityWidth; ++nVicinityY) { for (INT nVicinityX = nStartingX - nPixelVicinityWidth; nVicinityX <= nStartingX + nPixelVicinityWidth; ++nVicinityX) { pVicinityPixel = GetPixel(nVicinityX, nVicinityY); // If this pixel is valid (belongs to bitmap) if (pVicinityPixel->IsValid() != FALSE) { // If this current pixel is not already processed if (pVicinityPixel->GetReserved() == CBitmapInfo::m_defaultPixel.GetReserved()) { // Set this pixel as processed pVicinityPixel->SetReserved(uPixelAreaIndex); // If this pixel matches localization criteria if (pVicinityPixel->GetColor() != COLOR_BITMAP_BACKGROUND) { pPixelArea->push_back(pVicinityPixel); pixelStack.push(pVicinityPixel); } } } } } } while (pixelStack.size() > 0); arPixelAreaList.push_back(pPixelArea); ++uPixelAreaIndex; } } } } } } return S_OK; }
      
      









さらに簡単なのは、画面上の見つかった領域を強調表示することです。 ゲームプログラムはDirectX'ovを使用していないので(私が知る限り)、ゲームウィンドウでのグラフィックスの単純な表示が役立ちました。 一般に、DirectXがあった場合、ゲームの上部の違いを強調することは言うまでもなく、画面の「写真を撮る」ことはそれほど簡単ではなかったでしょう。 しかし、ここではWinAPIルール(関数:: Rectangle())。 バックライト結果:







私は完全にプログラムされたゲームを放棄しなければなりませんでした-PPはすでにゲームをあまりにも簡単にしました。それもあなたのためにプレイした場合、それは完全に面白くないでしょう。 しかし、ソフトウェアをボットにねじ込むのはこれまで以上に簡単です-差分領域の座標を知っているので、マウスでそれらをクリックし、次のレベルを待ち、差分を認識します...



これはすべて可能ですが、どうやら、私は眠れぬ夜を一回だけ過ごしたようです。



All Articles