13

画像(干し草の山)の中から画像()を見つけたいのですが。

簡単にするために、デスクトップのスクリーンショットを2枚撮ります。1つのフルサイズ(干し草の山)と小さなもの()。次に、干し草の山の画像をループして、針の画像を見つけようとします。

  1. 針と干し草の山のスクリーンショットをキャプチャします
  2. 干し草の山をループし、干し草の山を探します[i]==針の最初のピクセル
  3. [2.がtrueの場合:]針の最後から2番目のピクセルをループして、それをhaystack[i]と比較します。

期待される結果:針の画像が正しい位置にあります。

私はすでにいくつかの座標/幅/高さ(A)でそれを動作させました。

ただし、ビットが「オフ」になっているように見えることがあるため、一致するものが見つかりません(B)。

何が間違っているのでしょうか?どんな提案でも大歓迎です。ありがとう。


var needle_height = 25;
var needle_width = 25;
var haystack_height = 400;
var haystack_width = 500;

A.入力例-一致

var needle = screenshot(5, 3, needle_width, needle_height); 
var haystack = screenshot(0, 0, haystack_width, haystack_height);
var result = findmatch(haystack, needle);

B.入力例-一致なし

var needle = screenshot(5, 5, needle_width, needle_height); 
var haystack = screenshot(0, 0, haystack_width, haystack_height);
var result = findmatch(haystack, needle);

1.針と干し草の山の画像をキャプチャします

private int[] screenshot(int x, int y, int width, int height)
{
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Graphics.FromImage(bmp).CopyFromScreen(x, y, 0, 0, bmp.Size);

var bmd = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 
  ImageLockMode.ReadOnly, bmp.PixelFormat);
var ptr = bmd.Scan0;

var bytes = bmd.Stride * bmp.Height / 4;
var result = new int[bytes];

Marshal.Copy(ptr, result, 0, bytes);
bmp.UnlockBits(bmd);

return result;
}

2.一致するものを見つけてください

public Point findmatch(int[] haystack, int[] needle)
{
var firstpixel = needle[0];

for (int i = 0; i < haystack.Length; i++)
{
    if (haystack[i] == firstpixel)
    {
    var y = i / haystack_height;
    var x = i % haystack_width;

    var matched = checkmatch(haystack, needle, x, y);
    if (matched)
        return (new Point(x,y));
    }
}    
return new Point();
}

3.完全一致を確認します

public bool checkmatch(int[] haystack, int[] needle, int startx, int starty)
{
    for (int y = starty; y < starty + needle_height; y++)
    {
        for (int x = startx; x < startx + needle_width; x++)
        {
            int haystack_index = y * haystack_width + x;
            int needle_index = (y - starty) * needle_width + x - startx;
            if (haystack[haystack_index] != needle[needle_index])
                return false;
        }
    }
    return true;
}
4

4 に答える 4

3

デスクトップの 2 つのスクリーンショットを間隔を空けて作成する代わりに、スクリーンショットを 1 回撮って、同じビットマップ ソースから「針」と「干し草の山」を切り取ります。そうしないと、スクリーンショットが撮られる 2 つの瞬間の間にデスクトップの内容が変更される危険性があります。

編集:その後も問題が発生する場合は、画像をファイルに保存し、デバッガーを使用してそのファイルで再試行し、再現可能な状況を提供します。

于 2011-10-11T17:18:50.190 に答える
2

まず、findmatchループに問題があります。針の幅と高さをそれぞれ右と下から差し引く必要があるため、干し草の山イメージを配列として使用するだけではいけません。

public Point? findmatch(int[] haystack, int[] needle)
{
    var firstpixel = needle[0];

    for (int y = 0; y < haystack_height - needle_height; y++)
        for (int x = 0; x < haystack_width - needle_width; x++)
        {
            if (haystack[y * haystack_width + x] == firstpixel)
            {
                var matched = checkmatch(haystack, needle, x, y);
                if (matched)
                    return (new Point(x, y));
            }
        }

    return null;
}

それはおそらく問題を解決するはずです。また、複数の一致が存在する可能性があることに注意してください。たとえば、"needle" がウィンドウの完全に白い四角形の部分である場合、画面全体に多くの一致がある可能性が高くなります。この可能性がある場合は、findmatchメソッドを変更して、最初の結果が見つかった後も結果の検索を続行します。

public IEnumerable<Point> FindMatches(int[] haystack, int[] needle)
{
    var firstpixel = needle[0];
    for (int y = 0; y < haystack_height - needle_height; y++)
        for (int x = 0; x < haystack_width - needle_width; x++)
        {
            if (haystack[y * haystack_width + x] == firstpixel)
            {
                if (checkmatch(haystack, needle, x, y))
                    yield return (new Point(x, y));
            }
        }
}

次に、自分で作成した を実装するすべてのオブジェクトを手動で破棄する習慣を維持する必要がありIDisposableます。BitmapGraphicsはそのようなオブジェクトです。つまり、これらのオブジェクトをステートメントscreenshotでラップするには、メソッドを変更する必要があります。using

private int[] screenshot(int x, int y, int width, int height)
{
    // dispose 'bmp' after use
    using (var bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb))
    {
        // dispose 'g' after use
        using (var g = Graphics.FromImage(bmp))
        {
            g.CopyFromScreen(x, y, 0, 0, bmp.Size);

            var bmd = bmp.LockBits(
                new Rectangle(0, 0, bmp.Width, bmp.Height),
                ImageLockMode.ReadOnly,
                bmp.PixelFormat);

            var ptr = bmd.Scan0;

            // as David pointed out, "bytes" might be
            // a bit misleading name for a length of
            // a 32-bit int array (so I've changed it to "len")

            var len = bmd.Stride * bmp.Height / 4;
            var result = new int[len];
            Marshal.Copy(ptr, result, 0, len);

            bmp.UnlockBits(bmd);

            return result;
        }
    }
}

コードの残りの部分は問題ないように見えますが、特定の入力に対してはあまり効率的ではないという指摘があります。たとえば、デスクトップの背景として大きな単色を使用すると、多くのcheckmatch呼び出しが発生する可能性があります。

パフォーマンスに関心がある場合は、検索を高速化するさまざまな方法を確認することをお勧めします (変更されたRabin-Karpのようなものが思い浮かびますが、無効な候補がすぐにスキップされるようにする既存のアルゴリズムがいくつかあると確信しています)。 .

于 2011-10-11T19:06:48.467 に答える
2

あなたの方程式が正しいhaystack_indexとは思いません。ビットマップデータをコピーするときにオフセットを考慮してneedle_indexいるように見えますが、バイト位置を計算するときにビットマップを使用する必要があります。Scan0Stride

また、Format32bppArgbフォーマットはピクセルあたり 4 バイトを使用します。ピクセルあたり1バイトを想定しているようです。

これらの方程式を支援するために私が使用したサイトは次のとおりです

Format32BppArgb: X 座標と Y 座標を指定すると、ピクセルの最初の要素のアドレスは Scan0+(y * ストライド)+(x*4) になります。これは青いバイトを指します。次の 3 バイトには、緑、赤、およびアルファ バイトが含まれます。

于 2011-10-11T18:04:53.660 に答える
0

これは、2018 年に、USB カメラからの各フレームの干し草の山で針を見つけるための私の C# アプリケーションでうまく機能する コード例を含むクラス リファレンスです... Accord は、ほとんどが高速な C++ コード用の C# ラッパーの束であると思います。

また、USB カメラからの各フレーム内で針を検索するために使用するMicrosoft C++ DirectShowの C# ラッパーも確認してください。

于 2018-04-11T16:43:08.620 に答える