Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 12 Next capture
2021 2022 2023
1 capture
12 Jun 22 - 12 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
  • Help Room /
avatar image
1
Question by z3nth10n · Jan 07, 2017 at 05:10 PM · c#pointspixelsconnected

Order points from array based on 8-connectivity (Moore neighborhood)

Well, I'm working with pixels right now in Unity... I was wondering if anyone could help me with this.

For example, I created an image like this:

https://i.gyazo.com/4beb60124cab41d31500968c6b4833e1.png

And now I store every black pixel in a List<Point> like this:

 //Think that I can get all pixels as an Color[,] array (actually, I can)

 List<Point> points = new List<Point>();

 for(int i = 0; i < width; ++i)
     for(int j = 0; j < height; ++j)
         if(colorMap[i, j] == Color.black)
             points.Add(new Point(i, j));

Now I have stored all the points in a List, but with the problem that they are ordered as the image was looped out. So, now I have the question, how can I order it?

I have tried something like that:

 public Shape SortDistance()
 {
         List<Point> vlist = vertices.ToList(), //Get all the vertices in a list
                     orderedList = new List<Point>(); //This will be the final list
         Point fp = vlist.Find(x => x == position), //Get the first pixel that was detected before... (This is )
               curPos = fp;
         int maxLoops = vertices.Length * 10, //I tried to avoid freezes without luck
             lp = 0;
         while(vlist.Count > 0 && lp < maxLoops)
         {
             var clockwiseDirs = new Point[] { Point.upperLeft, Point.up, Point.upperRight, Point.right, Point.downRight, Point.down, Point.downLeft, Point.left }; //8-connectivity array

             List<Point>[] neighs = new [] { new List<Point>(), new List<Point>() }; //This is a little bit confusimg, but here, the only trhing I do, is store in the first array the 4-connectivity (up, left, bottom and right) points and in the seconds the corners
             foreach (Point p in clockwiseDirs)
             {
                 int j = p.x,
                     k = p.y;
                 if (j != 0 && k != 0)
                 { //If this position isn't the center...
                     Point nPoint = vertices.FirstOrDefault(x => x == curPos + new Point(j, k)); //Check if there is any pixel, if not return null
                     if (nPoint == null || nPoint == Point.zero)
                         continue; //And skip...
                     List<Point> mergedNeighs = neighs[0].Union(neighs[1]).ToList(); //If yes, merge the two arrays
                     if (!mergedNeighs.Contains(nPoint)) //Only for check if this points wasn't already added
                         if ((nPoint - curPos).sqrMagnitude == 1)
                             neighs[0].Add(nPoint); //If the distance is 1 it means that the position is top or left or bottom or right...
                         else if ((nPoint - curPos).sqrMagnitude == 2)
                             neighs[1].Add(nPoint); //If it's 2, it means the corners 
                 }
             }

             bool n1 = neighs[0].Count > 0, //Sides
                  n2 = neighs[1].Count > 0; //Corners

             if (n1 || n2)
             { //If it has neighbors
                 List<Point> mergedNeighs = n1 && n2 ? neighs[0].Union(neighs[1]).ToList() : neighs[n1 && !n2 ? 0 : 1]; //There we have to sleect what we want, depending on the neighbors found before
                 foreach (Point ne in mergedNeighs) //Now we loop over them
                 { //Comprobamos cada uno de los vecinos
                     List<Point> fPoints = new List<Point>();
                     if (!orderedList.Contains(ne)) //Check if the final list doesn't has this point
                         fPoints.Add(ne);
                     if (fPoints.Count == 1)
                     { //If there is only one neighbor
                         orderedList.Add(fPoints[0]); //Add it to the fional list
                         vlist.Remove(curPos); //Delete it from the other list
                         curPos = fPoints[0]; //And set the new pos to revise
                     }
                     else
                     { //If there are several neighbors
                         Point np = fPoints.FirstOrDefault(x => x.sqrMagnitude == 1); //We select the nearest one
                         foreach(Point vp in fPoints)
                             orderedList.Add(vp); //They are orderer, so we have to add them
                         vlist.Remove(curPos);
                         curPos = np;
                     }
                 }
             }
             else //If the pixel is alone remove it, impossible case??
                 vlist.Remove(curPos);
             ++lp;
         }
         if (lp > vertices.Length * 5)
             Debug.Log("Mmmm..."); //If this goes up something is going wrong, but I have noticed it because my pc freezes
         vertices = orderedList.ToArray();
         return this;
 }

I'm aware this is better to be in Code Review, but I don't know, I have to optimize it, but first fix it, because I think that the while freezes, but I have small RAM size and a lot of "Shapes" to check, so I cannot know exactly where it fails, because, my PC freezes.

One option could be to make a coroutine, but my problem there is that my script is a messm and If I do this I could cause a null object reference.

If anyone could help me I would be so happy! Thanks in advance.

Comment
Add comment · Show 2
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Glurth · Jan 07, 2017 at 06:40 PM 0
Share

I'm not quite clear on HOW you are deter$$anonymous$$ing the sort order: but rather than perform the sort yourself, I recommend you use List(T).Sort https://msdn.microsoft.com/en-us/library/w56d4y5z(v=vs.110).aspx

Create a single comparison function, between two points, that will define how points should be ordered. Pass this function to your list's Sort function, as in the example in the link above.

But, from your code, it looks like you might be doing a bit more than simply Sorting the list: is that correct?

avatar image z3nth10n Glurth · Jan 08, 2017 at 01:59 AM 0
Share

Actually, I use Sort to order them according to their position to the center and their angle within it. But, in very enclosed points the algorithm fails, and it order following their angle not their real position... So I was wondering If it was possible to achieve what I need by using 8-connectivity.

1 Reply

· Add your reply
  • Sort: 
avatar image
1

Answer by Bunny83 · Jan 08, 2017 at 12:28 PM

Your shape is a concave shape so using the angle to some sort of center point won't work. That only works for convex shapes.

Your problem can be solved easier when you work directly on the grid. Do you really need all pixels or is it enough to get the boundary pixels? Because keeping those extra pixels just creates ambiguity where the path continues.

So there are two solutions depending on if you want all pixels or the boundary pixels:

Getting the boundary pixels is the easiest and most straight forward. You simply pick a point that is for sure on the outer boundary. If you simply scan the image row by row the first pixel you find does met that criteria.

Now you simply go ahead in the same direction on the x axis. You know that in the row below the current is empty since in the current row you found the first pixel. So the pixel you've found is one of the lowest one on the y axis. (just in case you didn't know, images are stored left to right and bottem to top. So the first pixel is the bottom left pixel in an image).

For this algorithm you have to store / carry over to the next step:

  • the current position

  • the current direction.

The direction can be an enum / int with 8 values or a direction vector. The logic is now this:

  • Check the pixel that is 90° clockwise from the current direction.

  • If there is a pixel, store it and set the new direction to this direction

  • if there is no pixel rotate the direction 45° counter clockwise until you find a pixel

  • repeat

Note that if the shape is a closed shape you should end at the starting pixel. Though it's possible in some cases that you get stuck somewhere. If might be a good idea to build in some safe-guards. Either remember which pixels you already processed and terminate when you reach the same pixel again, or simply restrict the amount of iterations. (you never need more iterations than there are pixels in the image ^^).

The resulting list is already sorted since you walked the boundary in counter clockwise order.

If you want to process those extra pixels as well it get's a bit more complicated. You can use the same procedure as above, but in addition each step you would also check the "next" 45° rotation and if there's a pixel you add it in between the current and the last.

So it's basically like this:

  • Check the pixel that is 90° clockwise from the current direction.

  • If there is a pixel, check 45° counter clockwise and if there is another pixel add this first. Now add the current pixel

  • if there is no pixel rotate the direction 45° counter clockwise.

  • repeat

Note that this will also only process a "connected" path. If there are additional pixels inside the path that aren't part of the outer path, they will be ignored.

If you plan to have multiply pathes in one image it can get tricky. The easiest solution is as you process the pixels you actually remove them from the image data. So once one path is done you can simply continue the search for the first pixel from the last starting position. A safe way to remove a completed path would be to iterate through the path and remove the 9 pixel area around each path point. This ensures everything that belongs to this path is gone.

I haven't tested this approach but it should work as described. The "rotation" can be easily done with adding / subtracting from the int / enum and wrap the value properly, or when using a direction vector by some simply vector math. Rotating a 2d vector by 90° you simply flip x and y and invert one of them. Depending which one you invert it's +90° or -90°. 45° can be easily done by adding the current vector and the 90° rotated version together and "normalize" the vector. "normalize" here means ensure each component is either -1, 0 or 1. So a 45° vector would be (1, 1) so it can be used as offset into the image grid.

edit
In case you don't know what boundary pixels i mean, see this image:

BoundaryPixels


boundarypixels.png (5.6 kB)
Comment
Add comment · Show 8 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image z3nth10n · Jan 10, 2017 at 08:23 AM 0
Share

Thanks for your long answer! Well, I did this as you stated, but for some reason it failed (it gets less shapes than the number I wanted *), and I prefer using another algorithm that is called CCL (Connected component Labelling), but now I've realized that I can use this to process several shapes at the same time.

(*) I have an image that's 7000x5000 with 2877 shapes, I believe that only 2300 were detected incorrectly, but I can now process the 2877 shapes in less time.

avatar image z3nth10n · Jan 14, 2017 at 03:54 PM 0
Share

I'm very busy this week, so I only could to do something right now... And well, this is my unlucky result:

http://pastebin.com/a$$anonymous$$Ut6ZXD

I actually did what you said... But, nothing works, I have to debug to know where it fails. As I know, it doesn't not detect any point by the moment.

avatar image Bunny83 · Jan 16, 2017 at 05:05 PM 0
Share

@Ikillnukes:

I haven't tested your code, but your GetDir method has several "bugs".

 private static Direction GetDir(Direction dir, int n)
 {
     if (n == 0) return dir;
     return (int)dir + n < dirLength ? (n >= 0 ? dir : (Direction)dirLength + n) : dir + n - dirLength;
 }

I guess this method should actually "rotate" a direction, right? Let's have a look at some cases.

  • The usual case is that "dir + n" is still in the valid range so we just should return this. However in none of your cases you return "dir + n", even that's the most likely case.

  • If n >= 0 you always just return "dir". I guess you wanted to return dir+n

  • if n <0 you don't use dir at all but return the "-n th" element from the back. This makes no sense ^^.

  • You don't check the lower bounds at all.

It's by far easier to simply offset the current value and then rotate it around:

 private static Direction GetDir(Direction dir, int n)
 {
     int v = (int)dir + n;
     if (v < 0)
         v += dirLength;
     else if (v >= dirLength)
         v -=dirLength;
     return (Direction)v;
 }

Personally i wouldn't use an enum but simply a vector / point but that shouldn't matter.

Can you, by any chance, share your image? Or is it a secret? It would simplify testing my own code and we could see if there are any problems with some shapes. I haven't mentioned it explicitly but of course it only detects the "outer bounds" of a shape. So for example and "8" shape would come out as something like a "0" as the middle line is never traversed (with the exception of an "8" with 1 or 2 pixels in the center).

avatar image Bunny83 · Jan 16, 2017 at 05:56 PM 0
Share

I just implemented what i explained in my answer myself. Here's my solution.
I used my own "Vector2i" struct which was easier to use in my case. Note that the "drawing" code in OnRenderObject is not very optimised. It simply draws a line from point to point and "closes" the line loop by drawing a line from start to end.

I used this image as source. The result is this.

Note that i have placed all problematic shapes at the bottom. Here are the reasons for the wrongly detected shapes from left to right:

  • The top of the star consists of a single line. So it can't find any pixel to continue except the one it came from so it finishes the shape. That's why the top left part of the start is a seperate shape.

  • The second star has the same problem. The right tip of the star is also only one line thick. So it breaks here. Note that the remaining shape is now iterated on the "inside" since we start with the bottom right pixel. That's why there is a tiny fragment at the top left as seperate shape.

  • The two inverse U shapes are included to show that the big outer "U" is traversed from the bottom right on the "outside" of the shape, while the small inner "U" is traversed on the inside. So the outer shape is ordered counter clockwise and the inner shape is ordered clockwise. Shapes should always be "closed" so they are detected correctly.

  • The 4th shape is just a single line so it's not a closed shape.

  • The next two "U" shapes are identical but the top one is closed. As you can see the bottom shape is splitted into two shapes. Since we start at the bottom left the blue / cyan shape is found first. When the algorithm reaches the top right it stops.

  • The last shape is that cloudy speach bubble thing from $$anonymous$$S paint. Since it's closed the boundary is traversed correctly. Though since the shapes has some pixels going inside those are detected later after the boundary shape as been removed.

avatar image Bunny83 · Jan 17, 2017 at 04:14 AM 0
Share

I've played around a bit and created a WebGL live demo(located on free hoster). It allows you to view the parsed shapes as line loops interactively. You can zoom (scroll wheel or right mouse button) and pan the view around (left mouse button). It also views the source texture in the background.

I added a "rendering animation". It will always highlight a single line segment of each shape in "white" which will traverse through all segments over time. This helps to see which order they actually have been detected. I also show some stats like: how many shapes it has detected, how many points / pixels all those shapes have in total and how long it took (on your PC, in web javascript ^^) to parse the source image. On my PC it took about 240ms in the WebGL build. Strangely when i run it inside the editor it takes 1100ms ^^.

Sometimes my browser (FireFox) said that the webGL application run out of memory, however the last few times i tested it just works as intended.

So i think that's enough for a "prove of concept" :) I also modified the actual detection code and inlined most methods which greatly improved the speed. If you want to see the latest version that the WebGL build uses, just have a look here. The two materials have to be supplied to the script. Line$$anonymous$$at just uses the "Particles / Alpha Blended Premultiply" shader and the "tex$$anonymous$$at" uses "Particles / Alpha Blended" with the texture assign and a small alpha value as tint color.

The scene uses an orthographic camera which is located at (0,0,-10). The script is attached to an empty gameobject at (0,0,0).

Show more comments

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

267 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Help with coin pick up script 1 Answer

Don't trust "Vector3.Distance"? 1 Answer

Coin Pickup system carry int into void 1 Answer

Finding Boundaries of a Cover Object 1 Answer

How do i Instantiate sub-Points with in Multiple Points?? 0 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges