I am attempting to create an adjacency matrix from a 2D array of nodes. The adjacency matrix will be passed to a program that will cluster the nodes either through
Spectral clustering algorithm
Kmeans clustering algorithm
**Node class **
public class Node{
public int _id;
public bool _isWalkable;
public int _positionX;
public int _positionY;
public Vector3 _worldPosition;
}
Grid Class
public class Grid : MonoBehaviour
{
void CreateGrid()
{
grid = new Node[_gridSizeX, _gridSizeY];
Vector3 worldBottomLeft = transform.position -
Vector3.right * worldSize.x / 2 - Vector3.forward * worldSize.y / 2;
//set the grid
int id = 0;
for (int x = 0; x < _gridSizeX; x++)
{
for (int y = 0; y < _gridSizeY; y++)
{
Vector3 worldPosition = worldBottomLeft + Vector3.right *
(x * _nodeDiameter + _nodeRadius) +
Vector3.forward * (y * _nodeDiameter + _nodeRadius);
//check to see if current position is walkable
bool isWalkable =
!Physics.CheckSphere(worldPosition, _nodeRadius, UnwalkableMask);
grid[x, y] = new Node(isWalkable, worldPosition, x, y);
grid[x, y].Id = id ++;
}
}
totalNodes = id;
}
}
Nodes are stored inside a 2D array called grid and represent a walkable path for a character to move on. I have succesfully implemented an A* algorithm with a euclidean distance heuristic. What I would like to do is cluster these nodes using the aforementioned clustering algorithms, but first I need to create an adjacency algorithm for them. This is the best pseudocode I could come up with
int[][] _adjacencyMatrix = new int[gridSizeX*gridSizeY][gridSizeX*gridSizeY];
for(int x = 0; x < gridSize;x< XgridSize; i++)
{
for(int y = 0; y < gridSize;y< YgridSize; i++)
{
if( !Grid[x][y]._isWalkable)
continue;
Node n = Grid[x][y];
List<Node> neighbors = GetNeighbors(n);
for(int k; k<neighbors.Count(); k++)
{
_adjacencyMatrix[n._id][neighbors[k]._id]=1;
}
}
}
public List<Node> GetNeighbours(Node n)
{
//where is this node in the grid?
List<Node> neighbours = new List<Node>();
//this will search in a 3X3 block
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
if (x == 0 && y == 0)
continue; //we're at the current node
int checkX = n._positionX + x;
int checkY = n._positionY + y;
if (checkX >= 0 && checkX < _gridSizeX && checkY >= 0
&& checkY < _gridSizeY)
{
if(grid[checkX, checkY]._isWalkable)
neighbours.Add(grid[checkX, checkY]);
else
continue;
}
}
}
return neighbours;
}
My main concern
My main concern with this is the total complexity of the above algorithm. It feels like it's going to be heavy and I have a total of (75^2 = 5625) nodes in a adjacency matrix that will be 5625X5625 in size! There must be a better way to find the neighbors than this, is there?
The matrix is symmetric, so you only need to save half of it, see (How to store a symmetric matrix?) for an example. The matrix values are binary, so saving them as booleans or in a bit vector will cut down memory by a factor of 4 or 32, respectively.
Alternatively, since the check for two adjacent nodes takes constant time (abs(n1.x - n2.x) <= 1 && abs(n1.y - n1.y) <= 1 && grid[n1.x, n2.x].isWalkable() && grid[n2.x, n2.y]), you could just pass the clustering algorithm a function which checks for adjacency on-the-fly.
5k by 5k is not very large. 100 MB is something you can keep in memory. If you want to avoid this cost, do not use algorithms based on distance matrixes!
However, since your similarity appears to be
d(x,y) = 1 if adjacent and both nodes walkable else 0
your results will degenerate. If you are lucky, you get something like connected components (which you could have gotten much easier).
Pairwise shortest paths would be more useful, but also more expensive to build. Maybe consider solving this first, though. Having a full adjacency matrix is a good starting point I guess.
k-means cannot work with pairwise distances at all. It needs distances point-to-mean only, for arbitrary means.
I suggest to look at graph algorithms, and spend some more time understanding your objective, before trying to squeeze the data into clustering algorithms that may be solving a different problem.
Related
So i have a cellular automaton, where i can place pixels on an image and they just move down one pixel each "tick". Now the problem is since the for loop is like this:
for(int x = 0; x < 100; x++){
for(int y = 0; y < 100; y++){
//Check if nothing below (x,y) pixel and move it down if so
}
}
Then the pixels get teleported to the bottom because they get moved down every iteration of the y loop. I solved it by making the y loop go from 100 down to 0 instead of 0 to 100, so its iterating upwards but it wont work if i want to make my pixels move upwards in certain situations.
Maybe a double loop where it makes a list of which pixels to move and where in the first one and actually do it in the second but that seems quite performance heavy and im sure there is a better solution
PS: if you have a better title for the question, let me know
You need two copies of the cells. In pseudo code:
int[] currentCells = new int[...];
int[] nextCells = new int[...];
Initialize(currentCells);
while (true) {
Draw(currentCells);
Calculate next state by using currentCells as source and store result into nextCells;
// exchange (this copies only references and is fast).
var temp = currentCells;
currentCells = nextCells;
nextCells = temp;
}
Note that we loop through each cell of the destination (nextCells) to get a new value for it. Throughout this process we never look at the cells in nextCells, because these could be moved ones already. Our source is strictly currentCells which now represents the previous (frozen) state.
// Calculate next state.
for(int x = 0; x < 100; x++){
for(int y = 0; y < 100; y++){
if(currentCells[x, y] == 0 && y > 0) { // Nothing here
// Take value from above
nextCells[x, y] = currentCells[x, y - 1];
} else {
// Just copy
nextCells[x, y] = currentCells[x, y];
}
}
}
In Conway's Game of Life, for instance, you calculate the state of a cell by analyzing the values of the surrounding cells. This means that neither working upwards nor downwards will work. By having 2 buffers, you always have a source buffer that is not changed during the calculation of the next state.
Would something like this work, assuming you've got what you want to do inside the inner for loop correct?
static void MovePixels(bool moveUp)
{
for (int x = 0; x < 100; x++)
{
if (moveUp)
{
for (int y = 0; y < 100; y++)
{
}
}
else
{
for (int y = 100; y > 0; y--)
{
}
}
}
}
Basically, I am creating a voxel-based game. I have an object Chunk with a 3 dimensional array of the object Voxel. In the array of Voxels, the Voxel currently has an isAir bool on it that signifies no voxel being there.
I am trying to add a property to Chunk which returns a 2 dimensional array of Voxel's that are on the surface of the chunk. Currently, this array will always have Voxel.isAir == false in the lower Y values and Voxel.isAir == true in the higher Y values, there is never a clear run on the "Y" axis of the array that is either all "air"s or all Voxel's, and there is never a "air" below a Voxel in the array.
To get only the "Surface" Voxel's, I have added this code to the Chunk:
public Voxel[,,] Voxels { get; set; }
private Voxel[,] _surfaceVoxels = null;
public Voxel[,] SurfaceVoxels
{
get
{
if (_surfaceVoxels == null)
{
_surfaceVoxels = new Voxel[this.Voxels.GetLength(0), this.Voxels.GetLength(2)];
for (var x = 0; x < this.Voxels.GetLength(0); x++)
{
for (var z = 0; z < this.Voxels.GetLength(2); z++)
{
for (var y = 0; y < this.Voxels.GetLength(1); y++)
{
Voxel v = this.Voxels[x, y, z];
var solidAbove = false;
var solidBelow = false;
if (y - 1 >= 0)
{
Voxel vBelow = this.Voxels[x, y - 1, z];
solidBelow = !vBelow.isAir;
}
if (y + 1 < this.Voxels.GetLength(1))
{
Voxel vAbove = this.Voxels[x, y + 1, z];
solidAbove = !vAbove.isAir;
}
if (!v.isAir && !solidAbove && solidBelow)
{
_surfaceVoxels[x, z] = v;
}
}
}
}
}
return _surfaceVoxels;
}
}
Calculating the surface voxels this way is computationally expensive, and i cannot see a faster way of doing this. Because of this I effectively "cache" the surface array, which is fine until the underlying 3 dimensional array is changed, as then the surface voxels will obviously need to be recalculated.
Is there some way on the get of public Voxel[,,] it only returns a clone of the array and not the array itself, and on the set set the entire array and set _surfaceVoxels to NULL, or even better is there some inexpensive way to execute code when values in an array are changed?
Is there some a more efficient way to accomplish what I have explained here that I have overlooked?
I'm trying to randomly generate blocks on a flat map and make it so that they don't overlap each other.
I have made a matrix (c# array) of the size of the map (500x500), the blocks have a scale between 1 and 5.
The code works but if a generated block overlaps another one, it is destroyed and not regenerated somewhere else.
Only around 80 of the 1000 blocks I try to generate don't overlap another block.
Here is a picture of the map with around 80 blocks generated, the green squares are blocks
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
bool elementFound = false;
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el;
// Randomly generate block size and position
int size = Random.Range(minScale, maxScale + 1);
int x = Random.Range(0, mapSizex + 1 - size);
int y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
if (elementFound)
continue;
else {
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
}
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
}
}
I thought of 3 possible fixes
I should set the size of the block depending of the place it has.
I should use another randomization algorithm.
I'm not doing this right.
What do you think is the best idea ?
UPDATE
I got the code working much better. I now try to instantiate the blocks multiple times if needed (maximum 5 for the moment) and I fixed the bugs. If there are already many elements on the map, they will not always be instantiated and that's what I wanted, I just have to find the right amount of times it will try to instantiate the block.
I tried instantiating 1280 elements on a 500x500 map. It takes only about 1.5 second and it instantiated 1278/1280 blocks (99.843%).
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
bool elementFound = false;
int cnt = 0;
// Generate every block
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el = null;
// Randomly generate block size and position
int size, x, y, tryCnt = 0;
// Try maximum 5 times to generate the block
do {
elementFound = false;
// Randomly set block size and position
size = Random.Range(minScale, maxScale + 1);
x = Random.Range(0, mapSizex + 1 - size);
y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
tryCnt++;
} while (elementFound && tryCnt < 5);
if (tryCnt >= 5 && elementFound) continue;
// Instantiate the block
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
cnt++;
}
print("Instantiated " + cnt + "/" + ratio * generationDefault);
}
This is incredibly difficult to do well.
Here's a quick solution you'll maybe like ... depending on your scene.
actualWidth = 500 //or whatever. assume here is square
// your blocks are up to 5 size
chunkWidth = actualWidth / 5
// it goes without saying, everything here is an int
kChunks = chunkWidth*chunkWidth
List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();
howManyWanted = 1000
shuf = shuf.Take(howManyWanted)
foreach( i in shuf )
x = i % actualWidth
y = i / actualWidth
make block at x y
put block in list allBlocks
HOWEVER ............
...... you'll see that this looks kind of "regular", so do this:
Just randomly perturb all the blocks. Remember, video game programming is about clever tricks!
Ideally, you have to start from the middle and work your way out; in any event you can't just do them in a line. Shuffling is OK. So, do this ..
harmonic = 3 //for example. TRY DIFFERENT VALUES
function rh = Random.Range(1,harmonic) (that's 1 not 0)
function rhPosNeg
n = rh
n = either +n or -n
return n
function onePerturbation
{
allBlocks = allBlocks.OrderBy(r => Random.value) //essential
foreach b in allBlocks
newPotentialPosition = Vector2(rhPosNeg,rhPosNeg)
possible = your function to check if it is possible
to have a block at newPotentialPosition,
however be careful not to check "yourself"
if possible, move block to newPotentialPosition
}
The simplest approach is just run onePerturbation, say, three times. Have a look at it between each run. Also try different values of the harmonic tuning factor.
There are many ways to perturb fields of differently-sized blocks, above is a KISS solution that hopefully looks good for your situation.
Coding note...
How to get sets of unique random numbers.
Just to explain this line of code...
List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();
If you are new to coding: say you want to do this: "get a hundred random numbers, from 1 to million, but with no repeats".
Fortunately, this is a very well known problem with a very simple solution.
The way you get numbers with no repeats, is simply shuffle all the numbers, and then take how many you want off the top.
For example, say you need a random couple of numbers from 1-10 but with no repeats.
So, here's the numbers 1-10 shuffled: 3,8,6,1,2,7,10,9,4,5
Simply take what you need off the front: so, 3, 8, 6 etc.
So to make an example let's say you want twelve numbers, no repeats, from 1 through 75. So the first problem is, you want a List with all the numbers up to 75, but shuffled. In fact you do that like this ..
List<int> shuf = Enumerable.Range(1,75).OrderBy(r=>Random.value).ToList();
So that list is 75 items long. You can check it by saying foreach(int r in shuf) Debug.Log(r);. Next in the example you only want 12 of those numbers. Fortunately there's a List call that does this:
shuf = shuf.Take(12)
So, that's it - you now have 12 numbers, no repeats, all random between 1 and 75. Again you can check with foreach(int r in shuf) Debug.Log(r);
In short, when you want "n" numbers, no repeats, between 1 and Max, all you have to so is this:
List<int> shuf = Enumerable.Range(1,Max).OrderBy(r=>Random.value).ToList();
shuf = shuf.Take(n);
et voilà, you can check the result with foreach(int r in shuf) Debug.Log(r);
I just explain this at length because the question is often asked "how to get random numbers that are unique". This is an "age-old" programming trick and the answer is simply that you shuffle an array of all the integers involved.
Interestingly, if you google this question ("how to get random numbers that are unique") it's one of those rare occasions where google is not much help, because: whenever this question is asked, you get a plethora of keen new programmers (who have not heard the simple trick to do it properly!!) writing out huge long complicated ideas, leading to further confusion and complication.
So that's how you make random numbers with no repeats, fortunately it is trivial.
if (elementFound) continue; will skip out this current loop iteration. You need to wrap the int x=Random..; int y=Random()..; part in a while loop with the condition being while(/* position x/y already occupued*/) { /* generate new valid point */} like this for example:
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el;
// Randomly generate block size and position
bool elementFound = false;
int size, x, y;
do
{
elementFound = false;
size = Random.Range(minScale, maxScale + 1);
x = Random.Range(0, mapSizex + 1 - size);
y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
} while(elementFound);
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
}
}
You shouldn't be getting that many collisions.
Assuming your blocks were ALL 5 units wide and you're trying to fit them into a grid of 500,500 you would have 100*100 spaces for them at minimum, which gives 10,000 spaces into which to fit 1,000 blocks.
Try playing around with this code:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
var result = PlaceNonOverlappingBlocks(1000, 5, 500, 500);
}
static List<Block> PlaceNonOverlappingBlocks(int count, int maxBlockSize, int mapX, int mapY)
{
var map = new bool[mapY, mapX];
var rng = new Random();
var result = new List<Block>(count);
int collisions = 0;
while (count > 0)
{
int size = rng.Next(1, maxBlockSize + 1);
int x = rng.Next(0, mapX - size);
int y = rng.Next(0, mapY - size);
if (fits(map, x, y, size))
{
result.Add(new Block(x, y, size));
addToMap(map, x, y, size);
--count;
}
else
{
if (++collisions> 100000)
throw new InvalidOperationException("Hell has frozen over");
}
}
// This is just for diagnostics, and can be removed.
Console.WriteLine($"There were {collisions} collisions.");
return result;
}
static void addToMap(bool[,] map, int px, int py, int size)
{
for (int x = px; x < px+size; ++x)
for (int y = py; y < py + size; ++y)
map[y, x] = true;
}
static bool fits(bool[,] map, int px, int py, int size)
{
for (int x = px; x < px + size; ++x)
for (int y = py; y < py + size; ++y)
if (map[y, x])
return false;
return true;
}
internal class Block
{
public int X { get; }
public int Y { get; }
public int Size { get; }
public Block(int x, int y, int size)
{
X = x;
Y = y;
Size = size;
}
}
}
}
I'm stuck with a college project and I wonder if you can help me have a hint on how to do this, I have to do it on c#.
Using an 80x80 matrix I have to go through it only from left to right and from up to down so I can find the path that gives me the lowest number when sum all the values from top left corner to bottom right corner.
As an example on this case the numbers that should be picked up are:
131,201,96,342,746,422,121,37,331 = 2427 the lowest number
It does not matter how many times you move to the right or down but what matters is to get the lowest number.
This is an interesting project in that it illustrates an important technique called dynamic programming: a solution to the entire problem can be constructed from a solution to a smaller sub-problem with a simple computation step.
Start with a recursive solution that wouldn't work for large matrix:
// m is the matrix
// R (uppercase) is the number of rows; C is the number of columns
// r (lowercase) and c are starting row/column
int minSum(int[,] m, int R, int C, int r, int c) {
int res;
if (r == R-1 && c == C-1) {
// Bottom-right corner - one answer
res = m[r,c];
} else if (r == R-1) {
// Bottom row - go right
res = m[r,c] + minSum(m, R, C, r, c+1);
} else if (c == C-1) {
// Rightmost column - go down
res = m[r,c] + minSum(m, R, C, r+1, c);
} else {
// In the middle - try going right, then try going down
int goRight = m[r,c] + minSum(m, R, C, r, c+1);
int goDown = m[r,c] + minSum(m, R, C, r+1, c);
res = Math.Min(goRight, goDown);
}
return res;
}
This will work for a 10×10 matrix, but it would take too long for a 80×80 matrix. However, it provides a template for a working solution: if you add a separate matrix of results you obtained at earlier steps, you would transform it into a faster solution:
// m is the matrix
// R (uppercase) is the number of rows; C is the number of columns
// known is the matrix of solutions you already know
// r (lowercase) and c are starting row/column
int minSum(int[,] m, int R, int C, int?[,] known, int r, int c) {
if (known[r,c].HasValue) {
return known[r,c];
}
int res;
... // Computation of the result goes here
known[r,c] = res;
return res;
}
This particular technique of implementing dynamic programming solutions is called memoization.
First step is always analysis, in particular to try to figure out the scale of the problem.
Ok assuming you can only ever step down or to the right, you will have 79 steps down and 79 steps to the right. 158 steps total of the form 011100101001 (1=move right, 0=move down) etc. Note that the solution space is not as much as 2^158 since not all binary numbers are possible... you must have exactly 79 downs and 79 rights. From combinatorics, this limits the number of possible correct answers to 158!/79!79!, which evaluates to still a very large number, something like 10^46.
You should realize is that this is quite large to brute-force, which methodology otherwise should definitely be a consideration for you if the project does not specifically rule it out, since it invariably makes the algorithm simpler (e.g. by simply iterating all the solution possibilities). I imagine the question has been designed this way in order to require you to use an algorithm that does not brute-force the correct answer.
The way to solve this problem without iterating the whole solution space is to realize that the best path to the lower right corner is the better of the two best paths to the squares immediately to the left of, and above, the lower right corner, and the best path to those is the best path to the next diagonal (numbers 524, 121, 111 in your diagram), and so on.
What you need to do is to treat each cell as a node in a graph and implement shortest path algorithm.
Dijkstra algorithm is one of them. You can find more information here https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
It is really simple, because You can divide the problem into solved and unsolved part and move items from unsolved into solved one by one. Start on top left and move through all "/" diagonals towards bottom right.
int size = 5;
int[,] matrix = new int[,] {
{131,673,234,103,18},
{201,96,342,965,150},
{630,803,746,422,111},
{537,699,497,121,956},
{805,732,524,37,331}
};
//Random rand = new Random();
//for (int y = 0; y < size; ++y)
//{
// for (int x = 0; x < size; ++x)
// {
// matrix[y, x] = rand.Next(10);
// }
//}
int[,] distance = new int[size, size];
distance[0, 0] = matrix[0, 0];
for (int i = 1; i < size * 2 - 1; ++i)
{
int y = Math.Min(i, size - 1);
int x = i - y;
while (x < size && y >= 0)
{
distance[y, x] = Math.Min(
x > 0 ? distance[y, x - 1] + matrix[y, x] : int.MaxValue,
y > 0 ? distance[y - 1, x] + matrix[y, x] : int.MaxValue);
x++;
y--;
}
}
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x)
{
Console.Write(matrix[y, x].ToString().PadLeft(5, ' '));
}
Console.WriteLine();
}
Console.WriteLine();
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x)
{
Console.Write(distance[y, x].ToString().PadLeft(5, ' '));
}
Console.WriteLine();
}
Console.WriteLine();
I'm having my thesis "Multiple Choice Examination Checker" and I'm having a big issue about what to do with my problem. I got a picture image (a bitmap specifically) here it is, so you can see:
This is the image with the detectedbox, I will describe this:
This is an examination paper, 1-50 items. each number has a corresponding box(right side of the number, that serves as a container for the answer)
This pictures is just a sample, the number of detected boxes may vary. My approximation is it contains 150-200 detected boxes.
Each detectedboxes are stored in a List(MCvBOX2D) which holds the detectedboxes' size, center, etc.
I transferred those center coordinates in a new list List(PointF) center;
Each box from the image, may have 3-5 detectedboxes. as you can see there are more than one detectedboxes in each of the boxes from the image.
I sorted all the detectedboxes in ascending order, so I would know which will possibly be the number1, number2, and so on..
Here is some of my code, which contains the sorting of boxes.
List<PointF> center = new List<PointF>();
List<PointF> centernew = new List<PointF>();
foreach (MCvBox2D box in lstRectangles)
{
// this code transfers every center-coordinates of detected boxes
// to a new list which is center
center.Add(new PointF(box.center.X, box.center.Y));
}
// and this one sorts the coordinates in ascending order.
centernew = center.OrderBy(p => p.Y).ThenBy(p => p.X).ToList();
I'm done with the sorting part, now my problem is, since there many detected boxes in every box from the image, I would like to group the sortedlist of center-coordinates, so I could eliminate the other detectedboxes and get only one detectedbox for each number.
I know it's hard to understand so I'll explain more.
Let's say my sortedlist of detectedboxes contains first five center-coordinates which are:
let's say this are the center-coordinates of each of the detectedboxes from first box of the image.
center[0] = [ 45.39, 47.6]
center[1] = [ 65.39, 47.6]
center[2] = [ 45.40, 47.10]
center[3] = [ 65.45, 47.25]
center[4] = [ 46.01, 47.50]
and the 2nd are:
center[5] = [ 200.39, 47.2]
center[6] = [ 45.39, 47.2]
center[7] = [ 45.39, 47.3]
center[8] = [ 45.39, 47.55]
My goal is to organize all the sorted detectedboxes inside the list, I must be able to group all the center-coordinates that have close value with the other center, specifically their Y-coordinates.
var rand = new Random();
var threshold = 1;
var points = new List<PointF>();
for (int i = 0; i < 20; i++)
{
points.Add(new PointF((float) rand.NextDouble()*10, (float) rand.NextDouble()*10));
}
Console.WriteLine(points.Count);
for (int i = 0; i < points.Count(); i++)
{
for (int j = i + 1; j < points.Count(); )
{
var pointHere = points[i];
var pointThere = points[j];
var vectorX = pointThere.X - pointHere.X;
var vectorY = pointThere.Y - pointHere.Y;
var length = Math.Sqrt(Math.Pow(vectorX, 2) + Math.Pow(vectorY, 2));
if (length <= threshold)
{
points.RemoveAt(j);
}
else
{
j += 1;
}
}
}
Console.WriteLine(points.Count);
You can calculate the distance between a given point and any other point in the list. If the distance is less than half the width of a box, you can be pretty sure that it's part of the same box.
double threshold = 3.0; // Make this whatever is appropriate
for (int i = center.Count - 1; i >= 0; --i)
if (center.Any(p => p != center[i] && Distance(center[i], p) < threshold))
center.Remove(center[i]);
And you could use this for your Distance() method:
private double Distance(PointF p1, PointF p2)
{
double deltaX = Math.Abs(p1.X - p2.X);
double deltaY = Math.Abs(p1.Y - p2.Y);
return Math.Sqrt((deltaX * deltaX) + (deltaY * deltaY));
}
You could use Distinct with an custom IEqualityComparer (see MSDN).
As an example, define a class:
class BoxEqualityComparer : IEqualityComparer<MCvBox2D>
{
private static Double Tolerance = 0.01; //set your tolerance here
public Boolean Equals(MCvBox2D b1, MCvBox2D b2)
{
if (CentersAreCloseEnough(b1.Center, b2.Center))
{
return true;
}
else
{
return false;
}
}
private Boolean CentersAreCloseEnough(PointF c1, PointF c2)
{
return Math.Abs(c1.X - c2.X) < Tolerance && Math.Abs(c1.Y - c2.Y) < Tolerance;
}
}
then use the method in your code like so:
var distinctRectangles = lstRectangles.Distinct(new BoxEqualityComparer());
You are free to implement CentersAreCloseEnough(PointF c1, PointF c2) however you would like; you could use vector distance, absolute distance in x and y, etc.
If you are just concerned about positions with Y coordinates, just sort by that number. If you want to sort both you can add both X and Y and use that number to sort them. Here is an example of what I meen.
for(int i = 0; i < points.length - 1; i++)
{
int temp = points[i].x + points[i].y;
for(int j = i+1; j < points.length; i++)
{
int temp2 = point[j].x + points[j].y;
if(temp2 < temp)
{
Point jugglePoint = points[i];
points[i] = points[j];
points[j] = jugglePoint;
}
}
}