를 찾는 가장 가까운 검은 색이 아닌 픽셀에서 이미지를 빠르
-
08-07-2019 - |
문제
I have a2D 이미지로 그 때때로 흩어져있으로 픽셀이 있습니다.
주어진 시점에 이미지,나를 찾을 필요가 거리를 가장 가까운 픽셀에 없는 배경 색상(블랙)입니다.
가장 빠른 방법은 무엇입니까?
유일한 방법으로 올 수 있는 건물 kd-트리에 대한 픽셀이 있습니다.하지만 내가 정말로 원하는 것을 방지하는 비싼 선택하였다.또한,그것은 보인다 kd-트리에게 더 이상 필요합니다.나만의 거리는 뭔가가 나에 대해 걱정하지 않은 무엇이 뭔가.
해결책
으로 파이 말한 검색 사각형의 경계는 이동을 유지 하나의 픽셀은 시간에서의 원점(i.e증가 너비와 높이에 의해 두 개의 픽셀은 시간).를 쳤을 때 검은 색이 아닌 픽셀,당신은 거리를 계산합(이것은 당신의 첫 번째 비싼 계산)및 그 계속 중 바깥쪽으로까지의 너비자는 두번까지의 거리를 처음 발견된 포인트(모든 점 이상 할 수 없습니다 보다 가깝게 원본을 발견 픽셀).을 저장하는 검은 색이 아닌 포인트를 찾는 동안 이 부분을,그리고 다음 계산의 각각 자신의 거리를 볼 어떤 경우에는 그들의 보다 가까운 원본점이다.
에 이상적 찾을,당신은 단지 하나의 비싼 거리가 계산이 됩니다.
업데이트:기 때문에 당신이 계산하는 픽셀 거리를 여기서(대신 임의의 정밀 부동 소수점 위치),가속화할 수 있 이 알고리즘을 실질적으로 사용하여 미리 계산 lookup table(단 높이에 의해 폭 배)을 줄 당신은 거리의 기능으로 x 고 y.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에서 수행하는 방법).
간단한 조회 테이블을 수행합니다. 모든 픽셀에 대해 가장 가까운 비 블랙 픽셀까지의 거리를 미리 계산하고 해당 픽셀과 동일한 오프셋에 값을 저장합니다. 물론, 이런 식으로 더 많은 메모리가 필요합니다.