C#

2011-10-11 3 views
13

이미지의 이미지 인식 (haystack) 이미지 (바늘)를 찾으려면 여기를 클릭하십시오.C#

일을 간단하게하기 위해 내 데스크톱의 스크린 샷 두 장을 가져옵니다. 하나의 풀 사이즈 (건초 더미)와 작은 하나 (바늘). 그런 다음 건초 더미 이미지를 반복하여 바늘 이미지를 찾습니다.

  1. 캡처 니들 건초 스크린 샷
  2. 루프 덤 통해 덤 대해 찾아 [I] == 바늘의 제 1 픽셀
  3. [2 사실 인 경우]의 마지막 픽셀에 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. 예의 입력 - NO 매치

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; 
} 

답변

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 구현하는 모든 오브젝트를 배치하는 습관을 유지할 필요 당신 자신. BitmapGraphicsscreenshot 방법은 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과 같은 것이 있지만 잘못된 후보자를 건너 뛰는 몇 가지 기존 알고리즘이 있습니다. 바로).

+0

그건 속임수 였어. 너 정말 천재 야. 코딩 개선 방법에 대한 귀하의 도움과 노력에 진심으로 감사드립니다. – fanti

3

대신 의 그 사이의 시간 간격으로 바탕 화면의 두 스크린 샷을 만들고, 나는 한 번 스크린 샷을 찍어 동일한 비트 맵 소스에서 "바늘"과 "건초 더미"를 자른다. 그렇지 않으면 스크린 샷을 찍은 순간 사이에 데스크탑 컨텐츠가 변경 될 위험이 있습니다.

편집 : 그리고 그 후에도 문제가 발생하면 이미지를 파일에 저장하고 디버거를 사용하여 해당 파일을 다시 시도하여 재현 할 수있는 상황을 만들어보십시오.

+0

그가 찾고자하는 이미지에서 물건을 잘라 내고자하는 것은 어리석은 것처럼 보입니다. 닭/계란? – Druegor

+1

@Druegor : 스크린 샷 예제는 OP와 일치하는 것으로 예상되는 OP의 테스트 사례 일뿐입니다. –

+0

안녕하세요, 닥 브라운. 그것은 유효한 제안입니다. 나는 그것도 생각했고, 스크린의이 영역을 변화시키는 것이 없다는 것을 확신 할 수있다. 그럼에도 불구하고 나는 시도 할 것이다. – fanti

2

귀하의 방정식이 haystack_index 또는 needle_index이라고 생각하지 않습니다.비트 맵 데이터를 복사 할 때 Scan0 오프셋을 고려한 것처럼 보이지만 바이트 위치를 계산할 때는 비트 맵 Stride을 사용해야합니다.

또한 Format32bppArgb 형식은 픽셀 당 4 바이트를 사용합니다. 1 픽셀 당 1 바이트라고 가정합니다. http://www.bobpowell.net/lockingbits.htm

Format32BppArgb :

여기서 I는 해당 수학 식을 돕기 위해 사용 된 위치의 X 감안할 및 Y는 화소에서의 첫번째 요소의 어드레스는 Scan0 + (y를 * 보폭) + (x는 좌표 * 4). 파랑 바이트를 가리 킵니다. 다음 세 바이트는 녹색, 빨간색 및 알파 바이트를 포함합니다.

+1

변수가'bytes'라는 이름으로 혼란스러워서 4로 나누기가 있습니다. 그러나'bytes'는 실제로'int' 배열의 길이입니다. 따라서 알고리즘은 개별 구성 요소가 아닌 전체 RGBA 픽셀을 비교합니다. – Groo