문제

I have a2D 이미지로 그 때때로 흩어져있으로 픽셀이 있습니다.
주어진 시점에 이미지,나를 찾을 필요가 거리를 가장 가까운 픽셀에 없는 배경 색상(블랙)입니다.
가장 빠른 방법은 무엇입니까?

유일한 방법으로 올 수 있는 건물 kd-트리에 대한 픽셀이 있습니다.하지만 내가 정말로 원하는 것을 방지하는 비싼 선택하였다.또한,그것은 보인다 kd-트리에게 더 이상 필요합니다.나만의 거리는 뭔가가 나에 대해 걱정하지 않은 무엇이 뭔가.

도움이 되었습니까?

해결책

으로 파이 말한 검색 사각형의 경계는 이동을 유지 하나의 픽셀은 시간에서의 원점(i.e증가 너비와 높이에 의해 두 개의 픽셀은 시간).를 쳤을 때 검은 색이 아닌 픽셀,당신은 거리를 계산합(이것은 당신의 첫 번째 비싼 계산)및 그 계속 중 바깥쪽으로까지의 너비자는 두번까지의 거리를 처음 발견된 포인트(모든 점 이상 할 수 없습니다 보다 가깝게 원본을 발견 픽셀).을 저장하는 검은 색이 아닌 포인트를 찾는 동안 이 부분을,그리고 다음 계산의 각각 자신의 거리를 볼 어떤 경우에는 그들의 보다 가까운 원본점이다.

에 이상적 찾을,당신은 단지 하나의 비싼 거리가 계산이 됩니다.

업데이트:기 때문에 당신이 계산하는 픽셀 거리를 여기서(대신 임의의 정밀 부동 소수점 위치),가속화할 수 있 이 알고리즘을 실질적으로 사용하여 미리 계산 lookup table(단 높이에 의해 폭 배)을 줄 당신은 거리의 기능으로 xy.100x100 배열은 비용이 기본적으로 40K 의 메모리고 200x200 광장의 주위에 원래 포인트,그리고 당신이 여분의 비용을 하는 고가의 거리 계산(는지 여부를 피타고라스나 매트릭스 대수학)대한 모든 컬러 픽셀을 찾을 수 있습니다.이 배열 수도 미리 계산에 포함된 앱을 리소스로를 당신의 초기 계산 시간(이것은 아마도 심각한 과잉).

업데이트 2:또한을 최적화하는 방법을 찾는 광장기 바랍니다.귀하의 검색을 시작해야에서 교차하는 이 축 이동 하나의 픽셀은 시간에 모서리(을 8 개 이 검색 포인트는 쉽게 이보다 더 큰 가치,에 따라서 응용 프로그램의 요구 사항).로 당신을 찾는 컬러 픽셀,할 필요가 없으로 계속 모로 남아있는 포인트는 모두에서 기원합니다.

후 처음 발견된 픽셀을 추가로 제한할 수 있습니다 추가 검색 영역에 필요한 최소한 사용하여 조회 테이블을 보장하기 위해 각 지점 검색이 가까이 보다 발트 포인트(다시 시작에서 축 및 중지할 때 거리 한계에 도달).이 두번째 최적화할 수 있을 것 너무 비싸게 사용하는 경우 당신이 계산하는 각 거리에 있습니다.

는 경우 가장 가까운 픽셀에서는 200x200 상자(또는 원하는 크기의 작품에 대한 데이터),당신은 단지 내에서 검색을 원에 의해 제한된 픽셀의 일만 조회 <>비교할 수 있습니다.

다른 팁

개인적으로, 나는 조회 테이블에 대한 musigenesis의 제안을 무시합니다.

픽셀 사이의 거리를 계산합니다 ~ 아니다 특히이 초기 테스트의 경우 실제 거리가 필요하지 않으므로 제곱근을 취할 필요가 없습니다. 거리^2로 작업 할 수 있습니다.

r^2 = dx^2 + dy^2

또한 한 번에 하나의 픽셀을 바깥으로 가면 다음을 기억하십시오.

(n + 1)^2 = n^2 + 2n + 1

또는 만약 NX 현재 값이고 황소 이전 값입니다.

    nx^2  = ox^2 + 2ox + 1
          = ox^2 + 2(nx - 1) + 1
          = ox^2 + 2nx - 1
=>  nx^2 += 2nx - 1 

이것이 어떻게 작동하는지 쉽게 알 수 있습니다.

1^2 =  0 + 2*1 - 1 =  1
2^2 =  1 + 2*2 - 1 =  4
3^2 =  4 + 2*3 - 1 =  9
4^2 =  9 + 2*4 - 1 = 16
5^2 = 16 + 2*5 - 1 = 25
etc...

따라서 각 반복에서는 일부 중간 변수 만 유지하면됩니다.

int dx2 = 0, dy2, r2;
for (dx = 1; dx < w; ++dx) {  // ignoring bounds checks
   dx2 += (dx << 1) - 1;
   dy2 = 0;
   for (dy = 1; dy < h; ++dy) {
       dy2 += (dy << 1) - 1;
       r2 = dx2 + dy2;
       // do tests here
   }
}

타다! r^2 비트 교대, 추가 및 빼기만으로 계산 :)

물론, 괜찮은 현대 CPU에서 R^2 = dx*dx + dy*dy는 이것만큼 빠를 수 있습니다 ...

거리를 측정하는 방법을 지정하지 않았습니다. 더 쉽기 때문에 L1 (rectilinear)을 가정 할 것입니다. 아마도 이러한 아이디어는 L2 (유클리드)에 대해 수정 될 수 있습니다.

상대적으로 적은 픽셀에 대해서만이 작업을 수행하는 경우, 비 흑인을 때릴 때까지 소스 픽셀에서 나선형으로 바깥쪽으로 검색하십시오.

많은/모두를 위해이 작업을 수행하는 경우,이 방법은 어떻습니까 : 각 셀이 가장 가까운 비 흑인 픽셀까지의 거리를 저장하는 이미지의 크기를 2D 배열로 구축하십시오 (필요한 경우 해당 픽셀의 좌표 ). 왼쪽에서 오른쪽, 오른쪽에서 왼쪽에서 왼쪽에서 왼쪽에서 위쪽까지, 위에서 아래로 4 개의 라인 스윕을 수행하십시오. 왼쪽에서 오른쪽 스윕을 고려하십시오. 청소할 때 각 행에 보이는 마지막 비 흑백 픽셀을 포함하는 1D 열을 유지하고 각 셀을 2 차원 배열로 표시 해당 픽셀의 거리 및/또는 좌표로 표시하십시오. o (n^2).

또는 KD 트리는 과잉입니다. 쿼드 트리를 사용할 수 있습니다. 내 라인 스윕보다 코딩하기가 조금 더 어렵고, 메모리가 조금 더 (그러나 두 배 미만) 더 빠릅니다.

"가장 가까운 이웃 검색"을 검색하고 Google의 첫 두 링크가 도움이됩니다.

이미지 당 1 픽셀에 대해서만이 작업을 수행한다면 최선의 방법은 단지 선형 검색, 1 픽셀 너비 상자라고 생각합니다. 검색 창이 정사각형 인 경우 찾은 첫 번째 지점을 가져갈 수 없습니다. 조심해야합니다

예, 가장 가까운 이웃 검색은 좋지만 '가장 가까운'것을 찾을 수는 없습니다. 매번 하나의 픽셀을 옮기면 사각형 검색이 생성됩니다. 대각선은 수평 / 수직보다 멀어집니다. 이것이 중요하다면, 절대 수평이 '발견 된'픽셀보다 더 큰 거리를 가질 때까지 계속 확장 한 다음 위치한 모든 비 흑백 픽셀에서 거리를 계산할 때까지 계속 확장해야합니다.

좋아, 이것은 흥미로운 것 같다. 나는 C ++ 버전의 영혼을 만들었습니다. 이것이 당신에게 도움이 될지 모르겠습니다. 나는 그것이 800*600 행렬에서 거의 즉각적이기 때문에 충분히 빠르게 작동한다고 생각합니다. 궁금한 점이 있으면 물어보십시오.

내가 한 실수에 대해 죄송합니다. 10 분 코드입니다 ... 이것은 반복적 인 버전입니다 (나는 재귀적인 것을 만들기 위해 계획하고 있었지만 마음이 바뀌 었습니다). 출발점에서 가장 먼 거리에있는 포인트 배열에 포인트 배열에 어떤 점을 추가하지 않음으로써 알고리즘을 개선 할 수 있지만, 여기에는 각 픽셀 (색상에도 불구하고)에 대해 시작점과의 거리를 계산하는 것이 포함됩니다.

도움이되기를 바랍니다

//(c++ version)
#include<iostream>
#include<cmath>
#include<ctime>
using namespace std;
//ITERATIVE VERSION

//picture witdh&height
#define width 800
#define height 600
//indexex
int i,j;

//initial point coordinates
int x,y;
//variables to work with the array
int p,u;
//minimum dist
double min_dist=2000000000;
//array for memorising the points added
struct point{
  int x;
  int y;
} points[width*height];
double dist;
bool viz[width][height];
// direction vectors, used for adding adjacent points in the "points" array.
int dx[8]={1,1,0,-1,-1,-1,0,1};
int dy[8]={0,1,1,1,0,-1,-1,-1};
int k,nX,nY;
//we will generate an image with white&black pixels (0&1)
bool image[width-1][height-1];
int main(){
    srand(time(0));
    //generate the random pic
    for(i=1;i<=width-1;i++)
        for(j=1;j<=height-1;j++)
            if(rand()%10001<=9999) //9999/10000 chances of generating a black pixel
            image[i][j]=0;
            else image[i][j]=1;
    //random coordinates for starting x&y
    x=rand()%width;
    y=rand()%height;
    p=1;u=1;
    points[1].x=x;
    points[1].y=y;
    while(p<=u){
        for(k=0;k<=7;k++){
          nX=points[p].x+dx[k];
          nY=points[p].y+dy[k];
          //nX&nY are the coordinates for the next point
          //if we haven't added the point yet
          //also check if the point is valid
          if(nX>0&&nY>0&&nX<width&&nY<height)
          if(viz[nX][nY] == 0 ){
              //mark it as added
              viz[nX][nY]=1;
              //add it in the array
              u++;
              points[u].x=nX;
              points[u].y=nY;
              //if it's not black
              if(image[nX][nY]!=0){
              //calculate the distance
              dist=(x-nX)*(x-nX) + (y-nY)*(y-nY);
              dist=sqrt(dist);
              //if the dist is shorter than the minimum, we save it
              if(dist<min_dist)
                  min_dist=dist;
                  //you could save the coordinates of the point that has
                  //the minimum distance too, like sX=nX;, sY=nY;
              }
            }
        }
        p++;
}
    cout<<"Minimum dist:"<<min_dist<<"\n";
return 0;
}

나는 이것이 더 잘 이루어질 수 있다고 확신하지만 여기에 중앙 픽셀 주변의 정사각형 주변을 검색하고 중심을 먼저 검사하고 모서리쪽으로 이동하는 코드가 있습니다. 픽셀을 찾을 수없는 경우 반경 한계에 도달하거나 픽셀이 발견 될 때까지 주변 (반경)이 확장됩니다. 첫 번째 구현은 중심 지점 주위에 간단한 나선형을하는 루프 였지만 언급했듯이 절대 가장 가까운 픽셀을 찾지 못했습니다. 루프 내부의 SomeBigoBJCStruct의 생성은 매우 느 렸습니다. 루프에서 제거하면 충분히 좋게 만들었고 나선형 접근 방식이 사용되었습니다. 그러나 어쨌든이 구현은 다음과 같습니다. 테스트를 거의 또는 전혀 조심하지 않아도됩니다.

그것은 모두 정수 추가 및 뺄셈으로 이루어집니다.

- (SomeBigObjCStruct *)nearestWalkablePoint:(SomeBigObjCStruct)point {    

typedef struct _testPoint { // using the IYMapPoint object here is very slow
    int x;
    int y;
} testPoint;

// see if the point supplied is walkable
testPoint centre;
centre.x = point.x;
centre.y = point.y;

NSMutableData *map = [self getWalkingMapDataForLevelId:point.levelId];

// check point for walkable (case radius = 0)
if(testThePoint(centre.x, centre.y, map) != 0) // bullseye
    return point;

// radius is the distance from the location of point. A square is checked on each iteration, radius units from point.
// The point with y=0 or x=0 distance is checked first, i.e. the centre of the side of the square. A cursor variable
// is used to move along the side of the square looking for a walkable point. This proceeds until a walkable point
// is found or the side is exhausted. Sides are checked until radius is exhausted at which point the search fails.
int radius = 1;

BOOL leftWithinMap = YES, rightWithinMap = YES, upWithinMap = YES, downWithinMap = YES;

testPoint leftCentre, upCentre, rightCentre, downCentre;
testPoint leftUp, leftDown, rightUp, rightDown;
testPoint upLeft, upRight, downLeft, downRight;

leftCentre = rightCentre = upCentre = downCentre = centre;

int foundX = -1;
int foundY = -1;

while(radius < 1000) {

    // radius increases. move centres outward
    if(leftWithinMap == YES) {

        leftCentre.x -= 1; // move left

        if(leftCentre.x < 0) {

            leftWithinMap = NO;
        }
    }

    if(rightWithinMap == YES) {

        rightCentre.x += 1; // move right

        if(!(rightCentre.x < kIYMapWidth)) {

            rightWithinMap = NO;
        }
    }

    if(upWithinMap == YES) {

        upCentre.y -= 1; // move up

        if(upCentre.y < 0) {

            upWithinMap = NO;
        }
    }

    if(downWithinMap == YES) {

        downCentre.y += 1; // move down

        if(!(downCentre.y < kIYMapHeight)) {

            downWithinMap = NO;
        }
    }

    // set up cursor values for checking along the sides of the square
    leftUp = leftDown = leftCentre;
    leftUp.y -= 1;
    leftDown.y += 1;
    rightUp = rightDown = rightCentre;
    rightUp.y -= 1;
    rightDown.y += 1;
    upRight = upLeft = upCentre;
    upRight.x += 1;
    upLeft.x -= 1;
    downRight = downLeft = downCentre;
    downRight.x += 1;
    downLeft.x -= 1;

    // check centres
    if(testThePoint(leftCentre.x, leftCentre.y, map) != 0) {

        foundX = leftCentre.x;
        foundY = leftCentre.y;
        break;
    }
    if(testThePoint(rightCentre.x, rightCentre.y, map) != 0) {

        foundX = rightCentre.x;
        foundY = rightCentre.y;
        break;
    }
    if(testThePoint(upCentre.x, upCentre.y, map) != 0) {

        foundX = upCentre.x;
        foundY = upCentre.y;
        break;
    }
    if(testThePoint(downCentre.x, downCentre.y, map) != 0) {

        foundX = downCentre.x;
        foundY = downCentre.y;
        break;
    }

    int i;

    for(i = 0; i < radius; i++) {

        if(leftWithinMap == YES) {
            // LEFT Side - stop short of top/bottom rows because up/down horizontal cursors check that line
            // if cursor position is within map
            if(i < radius - 1) {

                if(leftUp.y > 0) {
                    // check it
                    if(testThePoint(leftUp.x, leftUp.y, map) != 0) {
                        foundX = leftUp.x;
                        foundY = leftUp.y;
                        break;
                    }
                    leftUp.y -= 1; // moving up
                }
                if(leftDown.y < kIYMapHeight) {
                    // check it
                    if(testThePoint(leftDown.x, leftDown.y, map) != 0) {
                        foundX = leftDown.x;
                        foundY = leftDown.y;
                        break;
                    }
                    leftDown.y += 1; // moving down
                }
            }
        }

        if(rightWithinMap == YES) {
            // RIGHT Side
            if(i < radius - 1) {

                if(rightUp.y > 0) {

                    if(testThePoint(rightUp.x, rightUp.y, map) != 0) {
                        foundX = rightUp.x;
                        foundY = rightUp.y;
                        break;
                    }
                    rightUp.y -= 1; // moving up
                }
                if(rightDown.y < kIYMapHeight) {

                    if(testThePoint(rightDown.x, rightDown.y, map) != 0) {
                        foundX = rightDown.x;
                        foundY = rightDown.y;
                        break;
                    }
                    rightDown.y += 1; // moving down
                }
            }
        }

        if(upWithinMap == YES) {
            // UP Side
            if(upRight.x < kIYMapWidth) {

                if(testThePoint(upRight.x, upRight.y, map) != 0) {
                    foundX = upRight.x;
                    foundY = upRight.y;
                    break;
                }
                upRight.x += 1; // moving right
            }
            if(upLeft.x > 0) {

                if(testThePoint(upLeft.x, upLeft.y, map) != 0) {
                    foundX = upLeft.x;
                    foundY = upLeft.y;
                    break;
                }
                upLeft.y -= 1; // moving left
            }
        }

        if(downWithinMap == YES) {
            // DOWN Side
            if(downRight.x < kIYMapWidth) {

                if(testThePoint(downRight.x, downRight.y, map) != 0) {
                    foundX = downRight.x;
                    foundY = downRight.y;
                    break;
                }
                downRight.x += 1; // moving right
            }
            if(downLeft.x > 0) {

                if(testThePoint(upLeft.x, upLeft.y, map) != 0) {
                    foundX = downLeft.x;
                    foundY = downLeft.y;
                    break;
                }
                downLeft.y -= 1; // moving left
            }
        }
    }

    if(foundX != -1 && foundY != -1) {
        break;
    }

    radius++;
}

// build the return object
if(foundX != -1 && foundY != -1) {

    SomeBigObjCStruct *foundPoint = [SomeBigObjCStruct mapPointWithX:foundX Y:foundY levelId:point.levelId];
    foundPoint.z = [self zWithLevelId:point.levelId];
    return foundPoint;
}
return nil;

}

여러 가지 방법을 결합하여 속도를 높일 수 있습니다.

  • 픽셀 조회를 가속화하는 방법은 내가 공간 조회 맵이라고 부르는 것을 사용하는 것입니다. 기본적으로 해당 블록의 픽셀의 다운 샘플링 된 맵 (예 : 8x8 픽셀, 트레이드 오프)입니다. 값은 "픽셀 없음" "부분 픽셀 세트" "모든 픽셀 세트"일 수 있습니다. 이런 식으로 읽은 것은 블록/셀이 가득 차 있는지, 부분적으로 가득 차 있는지 또는 비어 있는지 알 수 있습니다.
  • 멀리 떨어진 픽셀/셀이 많기 때문에 중앙 주변의 상자/사각형을 스캔하는 것은 이상적이지 않을 수 있습니다. 오버 헤드를 줄이기 위해 원 그리기 알고리즘 (Bresenham)을 사용합니다.
  • 원시 픽셀 값을 읽는 것은 수평 배치, 예를 들어 바이트 (8x8 또는 배수의 셀 크기의 경우), dword 또는 long에서 발생할 수 있습니다. 이것은 당신에게 다시 심각한 속도를 제공해야합니다.
  • 여러 레벨의 "공간 조회 맵"을 사용할 수도 있습니다.

거리 계산의 경우 언급 된 조회 테이블을 사용할 수 있지만 (캐시) 대역폭 대 계산 속도 트레이드 오프 (예 : GPU에서 수행하는 방법).

간단한 조회 테이블을 수행합니다. 모든 픽셀에 대해 가장 가까운 비 블랙 픽셀까지의 거리를 미리 계산하고 해당 픽셀과 동일한 오프셋에 값을 저장합니다. 물론, 이런 식으로 더 많은 메모리가 필요합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top